くらしのマーケット開発ブログ

「くらしのマーケット」を運営する、みんなのマーケット株式会社のテックブログ。積極採用中です。

ansibleで 「NameError: name 'temp_path' is not defined」 と言われた話

バックエンドエンジニア 兼 SRE のまのめです。

くらしのマーケットでは、デプロイの仕組みとして ansible を採用しており、テスト環境の構築でも同じ仕組みを利用しています。
テスト環境は対応する git のブランチ別に構築することができるようにしています。
この環境を社内では tako 環境 と呼んでおり、テスト環境を構築することを tako を焼く と呼んでいます。

今回は、その tako 環境で遭遇したエラーに関して、備忘録的な意味で書いていこうと思います。
なお、ちょっとした対応の失敗も含んだ内容なので、あしからず。

事の発端

ある日、tako を焼いたメンバーから「tako 焼きに失敗する」と言われました。
エラーを見てみると、

NameError: name 'temp_path' is not defined

と書かれていますが、 temp_path という変数を register している箇所は ansible-playbook の task にありません。
はて…ということで、調査開始です。

調査

まずはググる、ということで該当のエラーで検索したところ、GitHub issue 上で

Out of disk space condition gives error

と言われていました。(リンク)

ということで、ディスクスペースを調べたのですが、

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs         16G   52K   16G   1% /dev
tmpfs            16G  112K   16G   1% /dev/shm
/dev/xxx         99G   46G   53G   7% /

(一部名前など改変しています)
といったように、ディスクスペースには問題がありません。

次に、そもそもコケている ansible-playbook の task の内容を確認したところ、

-   name: install latest version of epel repository
    yum:
        name: epel-release
        state: latest

となっています。

試しにこのコマンドを実行してみようと思い、tako 環境に入って epel-release のインストールを試したら、以下のように出ました。

$ sudo yum install epel-release
Loaded plugins: priorities, update-motd, upgrade-helper
Cannot open logfile /var/log/yum.log
Could not create lock at /var/run/yum.pid: [Errno 30] Read-only file system: '/var/run/yum.pid'

Can't create lock file; exiting

ほう…どうやらなにかしらが原因で yum の pid ファイルが lock されてしまったようです。

lock の解除のため、メンバーに許可を取って EC2 インスタンスを再起動してみたところ、問題が発生し、原因もわかりました。

原因

再起動をかけたところ、インスタンスステータスのチェックが失敗し続け、ssh もできなくなりました。
もはや中身を見ることが叶わなくなったので、EC2 のコンソールから「システムログの取得」をしたところ、こんなエラーが出ていました。

[/sbin/fsck.ext4 (1) -- /] fsck.ext4 -a /dev/xxx
/ contains a file system with errors, check forced.
/: Inodes that were part of a corrupted orphan linked list found.  

/: UNEXPECTED INCONSISTENCY; RUN fsck MANUALLY.
  (i.e., without -a or -p options)
[FAILED]

*** An error occurred during the file system check.
***/dev/fd/9: line 2: plymouth: command not found
Give root password for maintenance

\(^o^)/
どうやら EBS の問題で、 ファイルシステムに不良が出てしまった ようです。
ファイルロックがかかっていたのも関係していそうですね。

対処

EBS の差し替え目的で、インスタンスを立て直しました。
tako 環境用にいろいろ設定するのが手間ですが、仕方ないです。

最後に

tako 環境は無事復旧できました。
パニックになっていたのでインスタンスの再起動という手を取りましたが、それまでは ssh できていたので、普通に fsck をやればもっと早く原因が分かって対処もできたはずですね。
あと、epel-release のインストールにコケた際に、もう少し詳細に調べたほうが良かったです。 そもそも lock ファイルが作れないというエラーなので、もうその時点でおかしいですよね。

私たちテックチームでは「くらしのマーケット」を一緒に作る仲間を募集しています。
今回のようなケースをスマートに解決できる SRE の方も大募集中です。
くらしのマーケットのシステムに興味がある方はコーポレートサイト https://www.minma.jp/ までお気軽にご連絡ください!

第1回 みんなでボードゲーム会@みんなのマーケット不動前オフィス

f:id:curama-tech:20190816130404p:plain

■イベント説明

引越したばかりのみんなのマーケットの綺麗なオフィスで、ボードゲームをする会です。 みんなのマーケットのエンジニア(ボドゲエキスパート)とデザイナー(ボドゲビギナー)が主催するイベントです。 ボードゲームをしたい方なら誰でも参加可能です! 飲み物とお菓子もご用意する予定です!

■イベント概要

【日時】8月24日(土)13:00〜20:00(13:00〜13:30 受付)
※途中参加・退室OK
※終了時刻変更の可能性あり
【場所】みんなのマーケット株式会社(目黒線不動前駅2分)
【住所】東京都品川区西五反田4丁目31−18 目黒テクノビル3F
【参加費】¥0

※途中参加の方へ
当日はビルの入り口が施錠されているため、13:30以降にご参加の場合はあらかじめご連絡いただく必要があります。参加フォームに参加時刻をご入力ください。

■参加方法

下記のフォームより必要事項をご入力ください。
https://forms.gle/eM9hMFhZrXZkf8wa7

※フォーム以外からお申込をご希望の場合
facebookからお申込
https://www.facebook.com/events/1569858529811640/

・connpassからお申込
https://minma.connpass.com/event/141661/

・twiplaからお申込
https://twipla.jp/events/399133

■こんな人向け

誰でも参加OKです!

ボードゲームアナログゲームがお好きな方
ボドゲをやってみたかった方
・みんなのマーケットのオフィスを見たい方
・みんなのマーケットのエンジニアとボドゲでバトルしたい方
・みんなのマーケットのエンジニアとお話したい方
・近くにお住いのかた
・転職をお考え中の方

などなど、お子様・家族連れも大歓迎です!

■タイムテーブル

12:50 受付開始
13:00 ボードゲーム開始
15:00 お知らせ(少しだけ会社のお話をさせてください)
15:20 引き続きゲーム
20:00 終了

■みんなのマーケットにあるボードゲーム

持ち込み大歓迎です!
-スコットランドヤード東京
-インサイダー
-ラブレター
-爆爆バルーン
-ガイスター
-ドンジャラ
-スパイのウインク
-コヨーテ
-犯人は踊る
-ゴモジン
-ゴキブリポーカー
-ナインタイル
-パニックラボ
-ケルベロス
-Kill doctor lucky
-Pralaya
- AVALONレジスタンス:アヴァロン) - タイムボム - ワンナイト新郎 - 知略悪略 - お邪魔者 - お邪魔者2

f:id:curama-tech:20190816130436j:plain
ボードゲーム

■持ち物

・名刺(名刺がない方は受付にて必要事項のご記載のご記載をお願いします)
・持ち込みできるボドゲがある方はぜひご持参下さい!
・飲食の持ち込み可能です(飲み物とお菓子をご用意する予定です)

不動前駅からのアクセス

  1. 目黒線不動前駅を出て左折します。
  2. 商店街を進むと、右手に「BlueNote system」という賃貸管理の会社が見えます。
  3. その左横の小道に入ります。(「いずみ薬局」の前の道)
  4. 小道に入って最初の角を左折します。(左手は「クリーニング竹和」、右手は「ヒロセクリニック」の看板)
  5. 道を進むと大通りにぶつかるので、そこを右折します。
  6. 右手の「銀座よしえクリニック」を過ぎると、目黒テクノビルがあります。(銀座よしえクリニックの隣の建物です)
    会場はこちらの3階です。

■注意事項

・猫がいるので、アレルギー等をお持ちの方は参加をご遠慮ください。
・会場に駐車場、駐輪場はございません。公共交通機関をご利用願います。
・参加された方同士のトラブルは、当事者間での解決をお願い致します。
・社内にはトレーニングルームがあり、たくさんの機器が設置されています。社内での怪我や事故に関しましては、一切の責任を負いかねますのでご注意ください。
・終了時刻が変更になる可能性があります。終了時刻が変更になる場合、みんなのマーケット公式Twitterアカウントにてお知らせいたします。
・イベント中に写真を撮影し、SNSに掲載する予定です。撮影前に撮影可否を確認いたしますので、撮影NGの場合は気兼ねなくお申し出ください。

【今週水曜】糖質制限エンジニアのための採用説明会開催

概要

https://user-images.githubusercontent.com/40745943/57514190-d59b2700-734a-11e9-9337-b4cbe04470df.JPG

糖質制限エンジニアのための採用説明会」とは...

「身体と技術を高めたい!」「筋トレできる会社はどこだ!?」というエンジニアを対象に、ゆでたまごやサラダチキンといった低糖質フードを振るまいます。さらに、弊社CTOによる魂を込めた演説が行われる一大イベントとなっております。

  • レーニングジム完備。ぜひご利用ください。
  • 筋トレや低糖質フードに興味がないエンジニアも、もちろん大歓迎です。

開催日時

2019年5月15日(水) 19:00~21:00

開催場所

みんなのマーケット株式会社オフィス
東京都品川区西五反田4丁目31−18目黒テクノビル3F

参加対象

※ ママやパパはぜひ子どもと一緒に!

準備するもの

お名刺1枚

参加方法

下記よりお申し込みください。
https://minma.connpass.com/event/129442/

登壇者情報

みんなのマーケット株式会社CTO 戸澤拓也

1990年7月5日、秋田県生まれ。
成蹊大学理工学部情報科学科にて「HTML5による音声通話ソフトウェアの開発」を研究。2013年3月に同大学を卒業。
2013年4月、みんなのマーケット㈱入社。

タイムテーブル

時間 内容
18:30~19:00 受付
19:00~19:05 開会のあいさつ
19:05~19:50 CTO登壇
19:50~20:00 質疑応答
20:00~20:45 懇親会
20:45~20:50 閉会のあいさつ
20:50~21:00 来場者退場

※ CTO登壇の内容は、会社説明・技術説明・ビジョンについて・福利厚生について

みんなのマーケット株式会社について

みんなのマーケット株式会社では、ハウスクリーニングや引越し、家の修理やリフォームを始めとする生活関連の出張・訪問サービスに特化したインターネット商店街である「くらしのマーケット」を運営しています。くらしのマーケットでは、様々なジャンルの出張・訪問サービスを口コミや料金で比較して、オンラインで予約することが出来ます。

https://user-images.githubusercontent.com/40745943/57514446-886b8500-734b-11e9-811b-bb733d797cd7.JPG

技術コミュニティ支援のために、IT勉強会向けに会場を貸します!(🍻 +🍕 あり)

f:id:curama-tech:20190510122956j:plain

オフィス移転のため、受付停止中です(2020年10月現在)

みんなのマーケットCTOの戸澤です。

みんなのマーケットは、2019年3月18日に東京本社を五反田から不動前に移転し、約2.5倍に増床しました。

不動前の新オフィスでは、セミナールームとオープンスペースを新設しております。 この度、技術への恩返しの意味を込めて技術コミュニティの活性化に貢献するために、IT勉強会向けに会場貸しを行います。

また、要相談ですが、勉強会参加者へのビールとピザの提供を予定しています。

会場

セミナールーム、オープンスペースの利用が可能です。

  • 収容人数: セミナールーム30名, オープンスペース50名〜(応相談)
  • 設備: Wi-Fi, 電源
  • 備品: 50インチモニター2台、プロジェクター, マイク+スピーカーシステム
  • フード: ビール, ピザ(要相談)

参考写真(セミナールーム) f:id:curama-tech:20190510123719j:plainf:id:curama-tech:20190510123730j:plain

アクセス

東急目黒線 不動前駅より徒歩2分 (不動前駅は、JR目黒駅から1駅です)

東京都品川区西五反田4-31-18 目黒テクノビル 3F https://goo.gl/maps/ao2d6Fam4UgeukMk8

問い合わせ/申込み/下見

社内での利用もありますので、まずはtwitterかメールで問い合わせいただければと思います。

皆様のご利用をお待ちしております。

設計の原則の紹介とくらしのマーケットのシステム

f:id:curama-tech:20181220111408p:plain

自称webエンジニアの高橋です。ソフトウェアを設計するにあたってよく知られている原則に、SOLID原則というものがあります。これは、

  • 単一責任の原則(Single Responsibility Principle)
  • オープン・クローズドの原則(Open-Closed Principle)
  • リスコフの置換原則(Liskov Substitution Principle)
  • インターフェース分離の原則(Interface Segregation Principle)
  • 依存関係逆転の原則(Dependency Inversion Principle)

の5つの原則の頭文字を並べたものです。

本記事では、くらしのマーケットのプロダクションコードの一部を例/反例として、上の原則の紹介をしてみます。なお、以下に紹介するコードは全てTypeScriptです。

単一責任の原則

単一責任の原則はモジュール(クラス)を変更する理由は1つ以上存在してはならないという原則です。 例えば以下のようなコードがAngularのコンポーネントとして定義されているとします。

@Component({
    selector: "terry-ticket-list",
    templateUrl: "./terryTicketList.html",
    styleUrls: ["./terryTicketList.scss"]
})
export class TerryTicketListComponent implements OnInit {
    // 省略

    private getTerryTickets(
        params: GetTerryTicketListParameters,
    ): void {
      // 100行に渡るajax処理
    }
}

Angularのコンポーネントはviewに対する最小限のロジックのみを書くべきですが、このクラスではデータ取得処理を長々とコンポーネントに記述してしまっています。 例えばこの状態で、データ取得処理のモジュールを入れ替えたいという要件が発生した場合(例えば@angular/http@angular/common/httpに変更したいときなど!)、 viewには直接関係ない理由でこのコンポーネントを修正する必要が出てきます。

この場合は以下のように ticketApiService 等の別のモジュールを作成して、処理を委譲してしまうことで解決できそうです。

@Component({
    selector: "terry-ticket-list",
    templateUrl: "./terryTicketList.html",
    styleUrls: ["./terryTicketList.scss"]
})
export class TerryTicketListComponent implements OnInit {
    constructor(
        private ticketApiService: TicketApiService,
    ) {
    }

    // 省略
    private getTerryTickets(
        params: GetTerryTicketListParameters
    ): void {
      return this.ticketApiService(params);
    }
}

これで、viewに関するロジックとデータ取得のロジックが分離されました。

オープン・クローズドの原則

オープン・クローズドの原則は、モジュール(クラス)は拡張に対して開いていなければならず、修正に対して閉じていなければならないという原則です。 ところで、拡張に対して開いているとはどのような状態でしょうか?

export class TerryTicketDetailComponent implements OnInit {
    @ViewChild("placeholder", { read: ViewContainerRef })
    public placeholder: ViewContainerRef;
    private componentRef: ComponentRef<TerryCardComponent>;

   // 省略
    private initChildComponent(): void {
        let currentComponent: typeof TerryCardComponent;
        switch (this.terryTicket.ticket.terryTicketTypeId) {
            case TerryTicketTypes.AlertPostBadReview:
                currentComponent = CardPostBadReviewComponent;
                break;
                // 省略
        }
        const componentFactory = this.compiler.resolveComponentFactory<TerryCardComponent>(currentComponent);
        this.componentRef = this.placeholder.createComponent(componentFactory);
    }
}

例えば上のコンポーネントでは、initChildComponent関数の中で子コンポーネントをswitch文で動的に設定しています。ここで、子コンポーネントをさらに追加したい場合は、case文を新たに追加して修正する必要があります。 このままでもストラテジーパターン的な実装ではありますが、さらに責務を分離するなら、以下のような関数を定義すると良いでしょう。

export class TerryTicketDetailComponent implements OnInit {
    @ViewChild("placeholder", { read: ViewContainerRef })
    public placeholder: ViewContainerRef;
    private componentRef: ComponentRef<TerryCardComponent>;

   // 省略
    private initChildComponent(): void {
        const currentComponent = this.dispatch(terryTicketTypeId)
        const componentFactory = this.compiler.resolveComponentFactory<TerryCardComponent>(currentComponent);
        this.componentRef = this.placeholder.createComponent(componentFactory);
    }

    private dispatch(ticketTypeId: TerryTicketTypeIds): TerryCardComponent {
        const components = this.getComponents();
        for (const component of components) {
          if (ticketTypeId === component.ticketTypeId) {
            return component;
          }
        }
        return defaultComponent;
    }
}

ここでinitChildComponentメソッドに着目すると、componentを追加(== 拡張)する際に、コードの変更(== 修正)が必要なくなったことがわかります。つまり、initChildComponentレベルで、拡張に対して開いているということになります。なお、クラスレベルでオープン・クローズドの原則を担保する場合は、dispatch関数を別クラスに定義することで、さらに責務を分離できるでしょう。 また、例えばここで getComponentsメソッドを動的にあるディレクトリ配下のcomponentを取得するような処理にするようにすれば、ファイルを追加するだけで上記のコードをオープン・クローズドの原則に従った形で拡張することができます(実際はそこまでせずに、getComponentsの関数の中は追記可とする設計でも、ある程度は結合度を下げられるのでそれで十分という場合もあるかと思います)。

リスコフの置換原則

リスコフの置換原則は、派生型(子クラス)は基本型(親クラス)と置換可能でなければならないという原則です。子クラスで不用意に親クラスのメソッドを上書きしたりすると、子クラスと親クラスが置換不可能になってしまい、クラスのis-A関係が崩れてしまいます。

私達のコードではリスコフの置換原則に反したコードは見つかりませんでしたが、今後も注意してコーディングしていきたいです。

インターフェース分離の原則

インターフェース分離の原則は、クライアントに、クライアントが利用しないメソッドへの依存を強制してはならない。という原則です。 例えばメソッドAとメソッドBがあるインターフェースを利用するクライアントが常にメソッドAしか実行しない場合、メソッドBへの依存は無駄となってしまいます。

export interface IApiPageLogic {
    reverseGeocoding(
        locationIds: string[],
        serviceTypeId?: string,
        pattern?: MasterApiModels.AreaFetchingPatterns
    ): Promise<LocationInfoWithRelaseFlag[]>;
    getStoreAccessToken(
        storeId: string,
        platform: TwilioPlatforms
    ): Promise<CapabilityToken | undefined>;
}

上記のインターフェースは地理情報の処理メソッドと認証情報の取得メソッドが同居しています。そして、これれらのメソッドを必要としているクライアントも別々のクラスです。

この場合は、

export interface IGeocodingLogic {
    reverseGeocoding(
        locationIds: string[],
        serviceTypeId?: string,
        pattern?: MasterApiModels.AreaFetchingPatterns
    ): Promise<LocationInfoWithRelaseFlag[]>;
}


export interface IAuthTokenLogic {
    getStoreAccessToken(
        storeId: string,
        platform: TwilioPlatforms
    ): Promise<CapabilityToken | undefined>;
}

このように、ユースケースを考慮して別々のインターフェースとして分離したほうがより良い設計かと思います。

依存関係逆転の原則

依存関係逆転の原則は、 上位レベルのモジュールは下位レベルのモジュールに依存すべきではない。両方とも抽象に依存すべきである。 という原則です。

くらしのマーケットのNode.jsのコードでは、現在DIの仕組みを取り入れており、上位のモジュールは下位のモジュールのインターフェースに依存するようになっています。

@Dependency.register()
export class CalendarManager implements ICalendarManager {
    static $inject: string[] = ["CalendarApi"];
    constructor(private calendarApi: ICalendarApi) {}
    // メソッド定義
}

例えばこのCalendarManagerクラスは、CalendarApiをインジェクションしていますが、その機能には ICalendarApiインターフェース経由でしかアクセスできません。この場合、コンストラクタ引数のcalendarApiがどのように具体的に実装されているか、CalendarManagerクラスは知ることはできません。このようにして、上位モジュールと下位モジュールの結合度を下げることができます。

まとめ

以上、くらしのマーケットのコードを例にして、SOLID原則の紹介をしてみました。これからも設計を意識しながら、保守しやすいコードを目指して開発していきたいです。 くらしのマーケットのシステムに興味がある方はコーポレートサイト https://www.minma.jp/までお気軽にご連絡ください。

「話だけ聞きたい!」ってことでオフィスに遊びにきていただくことも可能です。 話を聞いてみる / みんなのマーケット株式会社

参考