トレタ開発者ブログ

飲食店向け予約/顧客台帳サービス「トレタ」、モバイルオーダー「トレタO/X」などを運営するトレタの開発メンバーによるブログです。

テストデータ生成に欠かせない便利な◯◯kitの勧め

iOSを担当している高です。

開発しているとそれっぽいテストデータが欲しいってことが結構あります。今までは各々がローカルで都度スクリプト書いてるような状態で、僕の場合はトレタアプリの中に直接書いて都度書き捨ててる感じでした。 これは相当効率が悪いですし、自分用に書いたものは人にも共有しづらいという問題があったのでこれを機にツールを作ってみることにしました。

開発上の課題

iOS開発をする上でこの様な課題がありました。

  • パフォーマンス確認のために大量データが欲しい
  • テーブル数などの設定をかんたんに変えたい
  • 外部連携が必要なテストデータが欲しい。けれどE2Eで作るのはちょっと大変

大量データは文字のごとくです。通信時間やアプリ上での描画パフォーマンスを確認するために想定される最大のデータを用意したい。データ作成が1回だけで良ければ手で作ってもいいのですが、トレタの場合日付が軸になるので次の日には過去データになってしまいます。そうならないように未来データとして作ると今度は毎回そこまで行くのが面倒という本末転倒なことになります。

テーブル数も現状アプリからは1テーブルずつでしか追加と削除が出来ません。これはユーザが使う分には問題無いのですが、テストで多様なデータを用意する場合には結構手間な作業になります。消すのも同様です。

外部連携が必要なテストデータに関してはアプリ上ではまずは表示の確認がしたいだけのことが多いです。charlesでレスポンスを書き換えたりしていたのですが、結構手間なこともあり、あまり実用的ではありませんでした。

octokit

言わずと知れたGitHubのAPIツールです。 https://octokit.github.io

コマンドラインからちょっとしたことをやりたい時なんかにoctokit.rbを使ったりしますよね。こんな感じでトレタのAPIも触れるといいなぁと思いました。

toretakit

ということでtoretakit inspired by octokitを作ってみました。 f:id:y_koh:20160428113221p:plain

octokit.rbと同じようにgemとして作っています。

予約データを作る

トレタで予約を作るために必要な最低限の情報はこれらになります。

  • 予約日時
  • 氏名(漢字)
  • 氏名(よみがな)
  • 電話番号
  • 人数

氏名や電話番号にはfakerというgemを使いました。結構有名なgemなのでご存じの方も多いと思います。ただ、氏名に関してはfakerはふりがなに対応していないので、gimeiというgemを使いました。 gimeiの説明をREADMEから引用します。

gimei は、日本人の名前や、日本の住所をランダムに返すライブラリです。テストの時などに使います。似たようなライブラリにfakerがあります。fakerはとても優れたライブラリで、多言語対応もしていますが、ふりがな(フリガナ)は流石に対応していません。gimei はふりがな(及びフリガナ)に対応しています。

出現率の異なるランダム値が欲しい

テストデータを作るときに同じデータばかり作ってもしかたがないのである程度ランダムで作りたいことがあります。

例えばトレタの場合だと予約するときの人数はランダムで決めたい、みたいな感じです。ただこの時に本当に単純なランダムにしてしまうと2人の予約と10人の予約が同じ回数だけ出てくるという現実離れしたデータになってしまいます。

実際のデータではところどころ山があると思いますが、そこまで忠実に再現するのは既存データを洗ったりしないといけなくてちょっと大変なので簡単に大きい数字の方が出にくいようにしたいと思いました。

なんか良いアルゴリズムが無いかなと探したのですが見つからず、とりいそぎベタにこんな感じで対応してみました。数字の間隔はなんとなくです。

def rand_count()
  r = rand(100)
  case r
  when 99..100 then 10
  when 96..99 then 9
  when 93..96 then 8
  when 90..93 then 7
  when 85..90 then 6
  when 80..85 then 5
  when 70..80 then 4
  when 60..70 then 3
  when 50..60 then 2
  when 0..50 then 1
  end
end

何か良いアルゴリズムがあったら是非教えてほしいです!

どんなことが出来るようになったか

こんな感じでテーブル数が100の設定で、2回転しているデータ(かなりの繁盛店!)をそれっぽく作る、みたいなことが簡単にできるようになりました。 f:id:y_koh:20160428113031p:plain

(人数が固定になってますが、先ほどのランダムロジックはここではまだ使ってなくて他のところで使ってます)

また、他のPJでも使ってもらえるようになりました。 f:id:y_koh:20160428113112p:plain

今後の展望

現状toretakitはAPI通信とデータ生成が一緒くたになっているのでのちのち別gemとして切り出したいなと思ってます。

将来的には複数店舗さんのシミュレーションデータ生成ロジックを用意して、コマンド一発でそれっぽいデータが用意できるようにしたいなと思っています。

まとめ

僕は普段はSwift、たまにObjective-C(既存コード)という生活を送ってるのですが、こういったツール系で普段と違う言語・環境で開発するのも楽しいものですね。

引き続きエンジニアは募集中ですので興味のある方はチェックしてみてください!

メールの配信状況を可視化、追跡する

週1でスープカレー食ってる佐野です。仕事ではトレタのインフラをあれこれしています。今回はメール配信の異変にいち早く気づき、カスタマーサポートのレスポンスを向上する取り組みについてです。 スマートフォンの普及、メッセンジャーの台頭などによって個人間でのメールでのやりとりは減っているかもしれませんが、通知の仕組みとしてまだまだメールは現役です。弊社ではお店への予約確定の通知、お店への予約一覧のPDF送信、お客様への来店日のリマインド...などにメールを活用しています。メールを使っていると、たまにお客様から弊社カスタマーサポートに「メールが届かない」「突然届かなくなった」という問い合わせをいただくことがあります。担当者は原因(トレタの障害?メール配信システムの障害?お客様のメールアドレス間違い?...etc)を即座に調べて回答する必要があります。今日はその仕組みについて。技術的には簡単な話です。

メール配信

トレタではSendGridを使っています。SendGrid自体の障害に備えて、コンフィグの変更/デプロイでAWS SESに切り替えられるようにもしてあります。SendGridには処理したメールの状況を通知できるEvent Notificationという仕組みが備わっていて、各種イベント(Processed(メール送信リクエスト受け付け)、Delivered(受信側メールサーバまで到達)、Deferred(遅延)、Bounces(拒否)、Drops...etc)とその理由を通知できるので、これを利用してメールの状況を取得/可視化します(SendGridのコンソールでも配信状況は見れるのですが直近数日分しか見ることができません)。ちなみに、SESでもSNSに通知が可能なので、SESでも似たような仕組みは簡単に作れるはずです。

仕組み

その図です。

f:id:hiroakis:20160420144125p:plain

AWS上で稼働するFluentdでEvent Notificationを受け付け、それをBigQueryに投入することでSQLで検索できるようにしてあります。またMackerelでグラフを描いて可視化しています。

https+basic認証でイベントを通知する(Event Notification)

こちらはSendGridのコンソールから簡単にできます。宛先のURLを入力するだけです。

SendGridからのEventの受信

拙著fluent-plugin-sendgrid-eventを利用しています。SendGridから飛んできたHTTP POSTをパースします。すでにプラグインがありそうだったけどなかったので書いてみました。SendGridのEvent NotificationはHTTP 503を返せばしばらく時間をおいてから同じイベントを再送してくれるので、このプラグインでは予期せぬエラーが発生した際は503を返すようにしてあります。

BigQueryへの投入

fluent-plugin-anonymizerfluent-plugin-bigqueryを利用しています。データにはお客様のメールアドレスが含まれているためそれをfluent-plugin-anonymizerでハッシュしたうえで、BigQueryに転送します。お客様から問い合わせのあったら、そのメールアドレスをsaltとともにハッシュ関数に通してSQLを叩くことで確認できます(管理画面作らんと...)。

f:id:hiroakis:20160420151840p:plain

※ メアドはハッシュしているので問題はないはずですが、画像では一応マスクもしてあります。

Mackerelでの可視化

fluent-plugin-datacounterfluent-plugin-mackerelを利用しています。次の画像は、ちょうど先日、SendGridで発生したメール配信遅延障害時の状況です。ある時間帯にdeffered(遅延:紫のグラフ)が大量に発生していることが確認できます。status.sendgrid.comに情報が出るよりも早く、遅延が発生していることに気づくことができました。

f:id:hiroakis:20160420150129p:plain

簡単ですが以上です。

おわり

トレタのインフラ運用、支えている道具(Packer, Terraform, Serverspec, Ansible, Roadworker, Circle CI)、考え方

インフラをアレしてる佐野です。トレタのコア部分はEngineyardで運用していますが、事業拡大に伴いサブシステムも増えてきました。新しいサブシステムは主にAWSで運用しています。そこで今回は事例として弊社の新規部分のインフラ運用のやり方、そこで使われている道具(Packer, Terraform, Serverspec, Ansible, Roadworker, Circle CI)、考え方などについて書きます。これらの道具はもはやよく知られたものであり、あまり真新しくはないとは思っています。しかしながら弊社に遊びに来た方や採用の応募者の方などからトレタのシステム運用に関する質問をいただくことがあり、その説明資料のかわりになるかな、という目的もあって書かせていただきます。これ以外にも道具はあるのですが、なんとなく興味をもってくれそうなワードをタイトルに羅列させていただきました。以下、目次になります。

  1. AMIの作成
  2. サーバ/ネットワーク
  3. サーバプロビジョニング
  4. DNS
  5. 監視
  6. 運用の方針と考え方
  7. 今後やりたいこと

1. 図

EC2インスタンスをはじめ、種々のマネージドサービスを利用しています。これらの管理に使っている主な道具は件のとおりPacker, Terraform, Ansible, Roadworkerです。図中において矢印が刺さっている箇所が管理対象です。矢印が刺さっていない、例えばRDSなどは手作業です。これらをなぜTerraformの管理下に置かないかというと、ちょっと怖くて日和りました...。また、図を見ていただければわかるように、Circle CIを中心にインフラオペレーションを行っており、特定のブランチにpushもしくはマージされたタイミングでインフラオペレーションが走るようにしてあります。以下、それぞれ道具の使い方やポイントについて書きます。

f:id:hiroakis:20160302172604p:plain

2. AMIの作成: Packer(緑の線)

PackerはOSの基本設定と、Rubyなどコンパイルに時間のかかるもののランタイムがあらかじめインストールされたAMIを用意するために使っています(Rubyなど実行環境のランタイムについてはRPMやdebにして固めてあとからyum install / apt-get installする方式でも良いかもしれない)。masterブランチにプッシュもしくはマージされたタイミングでCircle CIからpacker buildが走るようにしてあります。packer build後にServerspecが流れ、成功したらAMIが作成されるようにしています。次の図のようなイメージです。Serverspecのテストケースがパスされ、AMIが作成されていることがわかります。

f:id:hiroakis:20160302172618p:plain

なお、packer buildにはそこそこ時間がかかるというのと、Circle CIのビルドには制限時間があるので、あまりに時間がかかるようになったら別の手段にするなど工夫する必要がでてきます。

3. サーバ/ネットワーク: Terraform(青い線)

VPC(サブネット、ルーティング、セキュリティグループ..etc)とEC2のみを管理対象としています。インスタンスを増やしたり減らしたり、新しいサブネットを切ったりするにはTerraformで行います。インスタンスの種には先ほど言及したPackerで作成したAMIを使います。TerraformのapplyはCircle CIで行うのですが、ここでポイントになるのがtfstateファイルです。terraformはapplyを行うとtfstateというファイルが生成され、次のapplyではそのtfstateとの差分を適用する動きになります。各個人のローカルマシンから実行する場合はこのtfstateファイルをgithubにプッシュし、そこで一元管理をすることになると思いますが、Circle CIにapplyをさせた場合、apply後のtfstateファイルをどこかに置く必要があります。 もっとも手っ取り早いのがS3に格納する方法だと思っています。S3にtfstate格納用のバケット(たとえばfoo-tf-state-bucket)を掘っておき、Circle CIからは下記のような流れでapplyを行うようにします。S3からtfstateを取得する -> apply -> apply後のtfstateをS3にアップロードという動きをしてくれます。

terraform remote config -backend=S3 -backend-config="bucket=foo-tf-state-bucket" -backend-config="key=terraform.tfstate"
terraform remote pull
terraform apply
terraform remote push

どうしてもローカルマシンから実行したいケース、たとえばCircle CIが死んでいるときなどは、復活を待つか、ローカルからも上記のようにS3上のtfstateを参照し、事後にS3にプッシュしておくようにします。

4. サーバプロビジョニング: Ansible(橙色の線)

Ansibleはミドルウェアやライブラリのインストールを行うために使っています。以前はPackerでミドルウェアの設定まで全部入りのAMIを作って単にそれを使うようにしていました(個人ブログによると約一年前か...)。

https://hiroakis.com/blog/2015/04/24/chef%E3%82%92%E6%8D%A8%E3%81%A6%E3%81%A6%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AB%E3%81%97%E3%81%9F/

しかしながら、シェルスクリプト製の謎フレームワークみたいになってしまったってのと、コンフィグ類の変更が頻繁なときがあるとPacker buildの時間が妙にイラつくというのがあり、Packerからミドルウェア以上のレイヤの部分を切り離してAnsibleでプロビジョニングするように戻しました。また、先日Ansible2系が出ましたが未だ1系です。コンフィグレーションのテストは例によってServerspecを利用しています。テストコードがあればいつでも他のツールに乗り換えることができるし、以前のように全部入りのAMI方式にも戻せる。プロビジョニング後にデプロイを行えばそのサーバはサービスreadyになります。アプリケーションのデプロイにももちろんCircle CIを使っています。

ここで、Circle CIからEC2インスタンスにansible-playbookする簡単なTipsについて紹介します。おおまかな手順は以下になります。

  1. 秘密情報の復号
  2. Circle CIのコンテナのIPを取得する
  3. aws-cliなどで対象のEC2インスタンスのsshポート(from Circle CI)を開放する
  4. プロビジョニング
  5. sshポートを閉じる

まず秘密情報の復号です。各種ファイルをgithubに格納する際、セキュリティ上なるべく格納したくないものもあります。クレデンシャルが書かれたファイルや、秘密鍵、SSL証明書などにです。このようなファイルがない場合は気にしなくても良いですが、ある場合はansible-vaultを使って対象のファイルを暗号化してgit pushしておきます。下記のようにして暗号化できます。

ansible-vault encrypt FILE

そしてCircle CIからgit pullした際、これを次のようにして復号します。ansible_vault_password.txtは復号のパスワードが書かれたファイルです。

ansible-vault decrypt FILE --vault-password-file=ansible_vault_password.txt

Circle CIからやる場合、復号のパスワードはCircle CIの環境変数設定でANSIBLE_VAULT_PASSWORDなどとして格納しておきます。Circle CIには環境変数をマスキングして格納してくれる便利な機能があり、AWS_ACCESS_KEY_IDなども同様にして扱えます。

f:id:hiroakis:20160303160312p:plain

ここで登録されたものはcircle.yml内で$ANSIBLE_VAULT_PASSWORDなどとして使うことができます。次のようにして復号できます。

  pre:
    - pip install -r requirements.txt
    - echo $ANSIBLE_VAULT_PASSWORD > ansible_vault_password.txt
    - ansible-vault decrypt secret_file --vault-password-file=ansible_vault_password.txt

あとの手順3, 4, 5についてはCircle CIからEC2サーバのsshポートを開放してプロビジョニングを実行し、完了後にsshポートを閉じておしましです。

5. DNS: Roadworker(ピンク(///)の線)

DNSのレコード管理にはTerraformではなくRoadworkerを使っています。入社してすぐに導入したのがRoadworkerで、わざわざ乗り換える理由もないのでこれはこのまま回しています。Route53のDNSレコードをRubyのDSLで記述できます。

hosted_zone "toreta.in." do

  #### Toreta corporate site ####
  rrset "toreta.in.", "A" do
    ttl 300
    resource_records(
      "54.64.225.166"
    )
  end
end

言い尽くされていることですが、コード化することによりコメントもつけられますし、gitで変更履歴も管理できるようになります。

6. 監視

監視については自分のブログに以前書いたのですが、このときから大きく変わっていないです。

https://hiroakis.com/blog/2015/10/17/%E3%82%A6%E3%83%81%E3%81%AE%E7%9B%A3%E8%A6%96%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%81%AE%E5%A4%89%E9%81%B7%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E6%9B%B8%E3%81%8F/

7. 運用の方針と考えていること

今回述べたシステムについての運用方針について書きます。これについては賛否あると思いますが、こちらについては私はこのように考えて運用しています。

ホスト名はつけない、ロールだけを気にする

検証時を除き、サーバにsshするケースはほとんどないです。EC2タグやMackerelにサービス名/ロール名をつけ、サービスローンチ後は"ロードバランサの配下にAppロールをもったサーバが何台かある"とだけ認識すれば良いと思っています。サービスとロールだけを気にします。 この言葉はあまり浸透していませんが、PETS or CATTLEというものがあります。オンプレで運用されているサーバは名前をつけ、故障したら直してあげる、あたかもペットのようにケアする。クラウドのサーバ群は家畜の如く雑に扱う。という考え方です。ディスポーザブルではないコンポーネントはなるべくマネージド・サービスに寄せ、EC2で運用するものはなるべく家畜として扱えるアプリケーションサーバだけにします。

異常なインスタンスは問答無用で落とす

EC2で稼働させているMySQLサーバなどがある場合は別として、ロードバランサ配下で均等に負荷分散されているサーバは、異常時は全台で同じ異常が発生するはずです。なので基本的には"appサーバが1台だけおかしい"みたいなケースでは調査自体をしないです(一日一回なぜかN台中の1台が...といった事案が頻発するのであればさすがに調査しますが)。appサーバで丁重に扱わなければならないのはログだけだと思っています。ログはfluendなどを利用してBigQueryやS3に転送しているので、そのサーバのFluentdのファイルバッファがゼロになっていることを確認してから死んでもらいます。

f:id:hiroakis:20160303130843p:plain

自動化しすぎない

インフラをコード化するのは、省力化、人的ミスの軽減あたりが主な目的だと思っています。大抵のことは自動化できると思ってはいますが、私は"自動化で疲弊するくらいならそこはもう手動で良い"という考え方です。ドキュメントと手作業でカバーするのは現実的な落とし所だと考えています。なので適度に諦めます。具体的にどこを諦めたのかというと冒頭で述べたRDS周りとか...。

今後のやりたいこと

  • コア機能のAWS化
  • コンテナの有効活用
  • コスト削減(キャパプラが雑なので...俺の...)
  • 裏側のキメラ化している部分を紐解いていく
  • ちゃんとしたログ基盤も作りてぇな...

お約束

コア部分は早くもレガシー化していますし、ansibleでの実行はダイナミックインベントリを使っていなかったり、重要なクレデンシャルがコードにベタ書きの箇所もあったりと、直したい部分は多々あります。その他、もともと負債になっている箇所、私が生み出してしまった闇もあります。 一緒に整備してくれる仲間がほしいです。ログ基盤やデータ分析基盤も作りたいし、やらなきゃならないことも山のようにあります。興味を持ってくれた方、この記事を見て"貴様のやり方は既に----私が2000年前に通過した道だッッッ"と思った方、またこのネタがわかる方などお待ちしております。

www.wantedly.com

おわり

© Toreta, Inc.

Powered by Hatena Blog