コネヒト開発者ブログ

コネヒト開発者ブログ

Amazon ECS ScheduleTaskで実現するスマートなDockerベースのバッチ実行環境

こんにちは。インフラエンジニアの永井(shnagai)です。

コネヒトでは、開発環境に続き、続々と本番サービスにもDockerを導入しています。 今回は、中々運用が大変なcronでスケジュール管理するような定期的なバッチ処理を、Amazon ECSのScheduleTaskを使ってDocker駆動な環境で構築した話です。 他の方法との比較やどのように実現しているのかについて紹介したいと思います。

今回対象とするバッチの種類

今回対象とするバッチ処理は、俗に言うスケジュール系のバッチ処理で、毎日00時00分や10分毎にサイクル起動等、事前に定義した時間に正確に動くことが期待されているものです。 ※ジョブキュー形式のバッチだと、AWS BatchやEBのWorkerもしくは、SQS + Cron on EC2で処理するほうがスマートかと思います。

実行方式の選定

上記要件のバッチを実現する基盤として、下記を検討しました。 基本的には、開発環境はDockerで運用しているのでその資産をうまくいかせるシンプルなアーキテクチャを求めていて、バッチの為だけに新規のインフラ環境を用意するみたいな事はしたくないという前提がありました。 ざっくりですが、pros&consをまとめていたので書いておこうと思います。 ※完全に個人の主観が入っていますので、参考程度に。

Cron on EC2

pros:
  • 運用方法が枯れており、安定感はあるので他の方法が取れなかったらこれ
cons:
  • EC2インスタンスの運用が必要
  • crontabの登録が面倒でジョブスケジューラ系のツール入れるとしても運用が。。
  • 実行環境を作らなければならない (開発環境はdockerなのでそれと同じ実行環境をプロビジョニングツールなどで作らないといけない)

Lambda

pros:
  • スケジュール実行は出来る
  • 手軽に実装出来る
cons:
  • 最大の実行時間が、300秒なので不安がある
  • 利用できる実行環境(言語)が制限される。今回はPHPで書かれたプログラムを使いたかったので大変

AWS Batch

pros:
  • バックエンドがECSなので、ECSの資産を流用出来る
  • コンピューティングリソースは全く管理する必要はない (vCpuとメモリをジョブ定義に指定するだけなのでECSでいうクラスタ管理も不要)
cons:
  • 肝心のスケジュール起動は出来ない(あくまでもジョブキュー系の処理をするためのもの)
  • AWS SAにお聞きしたら、大規模データのバッチ処理みたいな使い方を想定されたサービスで、cron代わりといった用途には向かないとの事。

Amazon ECSのサービス起動しているコンテナ内にcronを定義して実行

pros:
  • ECSの資産をそのまま流用出来る
  • CMD句に指定する起動シェルでcrontabの登録をすればバッチ毎にコンテナを作って実行するアーキテクチャで出来そう
cons:
  • デプロイ処理時に、コンテナを破棄して作り直すのでバッチ実行中にデプロイが走るとバッチが強制終了される
  • バッチ全体のスケジュールを一望出来ないので、バッチ管理台帳みたいなものを用意することになりそう

Amazon ECSのScheduleTask

pros:
  • cron記法でスケジュールを定義する事が出来るのでわかりやすい(裏では、cloud watch eventのルールが作られてい る)
  • コンテナ起動時に、指定したDockerイメージをpullするアーキテクチャなので、デプロイ時に実行中のバッチに影響を与えることがない
  • ECSクラスタとしてホストを固定出来るので、docker pull時にキャッシュが効き起動が早い(LambdaやAWS batchは毎回インスタンスが変わるのでpullにそこそこ時間がかかる)
  • 既存のDockerイメージの資産を使える
  • 標準出力をcloud watch logsに送ることで起動処理の失敗等を、AWSコンソールから気軽に確認出来る(デバッグに役立ちます)
cons:

特に減点になるような要素なし。

  • 「枯れてない」「使ったことない」というところは少し気になりましたが、ECSの知見が溜まっておりイケるという感覚があったので採用に至りました。
  • しいていうなら、ScheduleTaskのAPIがなく、手動で登録しなければいけないところくらい(もしかしたら自分が探せてないだけかもしれませんが)

バッチの組み方

Amazon ECSのScheduleTaskは減点ポイントが特になく、webで運用しているECSの資産と知見も活かせるといった理由で採用が決まりました。 ここでは、Amazon ECSのScheduleTaskで1本のバッチを動かすのに必要なものについて書こうと思います。 このバッチ環境を作るには下記の4つの要素を準備する必要があります。 ECSの用語に慣れていないと複雑に感じるかもしれませんが、一つ一つ見ていけばそんなに難しいものではありません。 ※下記のスライドで、ECSの用語について説明しているので参考にしてもらえるといいかもしれません。

Dockerを本番導入するにあたり得た知見 // Speaker Deck

クラスタ
  • Dockerコンテナを動かすEC2インスタンスの集合体。冗長構成を取るためにmultiAZで2台用意しておくのが無難でしょう。
タスク定義
  • バッチ実行コマンドや環境変数を定義します。(dockerコンテナの起動に必要な情報を定義するイメージ)
  • バッチ一つにつき一つのタスク定義を用意します。
  • CMD句にバッチの実行コマンドを記述して、一つの処理だけを実行するコンテナとして定義します。(終了時にコンテナは破棄される)
  • 弊社では、バッチのタスク定義はterraformで管理していて、開発者がPRを投げるような体制を取っています。
Dockerイメージ(ECR)
  • タスク定義にセットする、バッチが実際に動く実行環境のDockerイメージを用意します。(フレームワークやミドルウェアなどの実行に必要な環境とソースコードがパッケージングされたDockerイメージ)
  • dockerhubでも良く、クラスタ上のホストからpull出来ればどのリポジトリでも構いません。
クラスタ内のScheduleTask
  • タスク定義毎に、1つのスケジュールを用意します。

実際の設定方法は、下記の公式ドキュメントがわかりやすいのでご参照下さい。 docs.aws.amazon.com

アーキテクチャ

デプロイを絡めた全体のアーキテクチャは下記のようなイメージになります。

f:id:nagais:20170913161501p:plain

開発フロー

ステージング環境の例ですが、上の図の青い線の部分を説明するとざっくり下記のような流れになります。

開発環境(Docker)でバッチの開発
↓
GithubでPR後に、masterマージで自動的にDockerビルド&ECRへデプロイ
↓
次回のScheduleTask起動時から新規のDockerイメージ(最新のソースコード)でバッチが動く

ScheduleTaskの画面

個人的には、このScheduleTaskの画面は気に入っていてジョブスケジューラ入れずともこの画面が使えるのは中々のメリットだなと思っています。

  • スケジュールがGUIで一望出来る(コメントも記入可能)
  • Enable,Disableも可能

f:id:nagais:20170913153458p:plain

エラー通知

スケジュールバッチは基本的に、必ずその時間だったり間隔だったりで動くことが期待されているので、エラーがあったら受動的に気づける必要があります。(クリティカルなものであればあるほどリアルタイムの検知は必須になります) アプリケーションのエラーは、弊社だとSentryで取得していますが、Dockerコンテナ起動時のエラー等アプリケーション起動以前のエラーが起きた場合にも、Cloud Watch Logs + SNS + Lambdaを使ってslackにリアルタイムに通知が飛ぶようにしています。 こうすることで、バッチが起動しなかった際は確実に気づける体制を作っています。

Lambdaは初めて触ったのですが、BluePrintという便利なテンプレートが用意されていたので比較的苦労なく実現できました。 コンテナで意図しない標準出力/標準エラー出力があると、下記のようにslackに通知が来ます。

f:id:nagais:20170913161719p:plain

※cloud watch logsにログを送るためには、タスク定義に下記のような設定が必要です。(dockerのログドライバを使う) ※事前にロググループを作成しておかないとコンテナ起動に失敗するので注意が必要です。

f:id:nagais:20170913160617p:plain

これは出来ない

  • ジョブスケジューラにあるようなジョブチェーンの実行やリトライは出来ないです。(出来るようになるとうれしい)
  • リトライに関しては、エラー通知トリガでRunTaskを実行させてあげるのが今のところ一番よさそう。(そこまではやってなくて今のところはリランしたい場合は、手動でのタスク実行を行います)

docs.aws.amazon.com

まとめ

今回は、運用が辛くなりがちなスケジュールバッチ(cron)をDocker環境で動かす事例を紹介しました。 Dockerベースにすることで、各環境差異でバッチがこけるリスクを回避しつつ、ジョブスケジューラを構築せずとも、AWS ECS Task Schedulerの見やすいビューワーでバッチを一覧して管理出来るというのは中々おすすめな構成です。エラーに関しても、cloud watch logsで拾えるので、ほとんど開発や構築なしにAWSマネージドサービスにどっぷり使ってバッチの実行環境を作ることが出来ます。 今後もっといいものが出てくるかもしれませんが、Dockerでバッチジョブ実行環境としてはかなり有力な選択肢になるのではないでしょうか。 スケジュールジョブの環境をどうしようか悩んでいる方がいたら一度気軽にお試ししてもらえるといいかもしれません。

CakePHP3.5.2にアップデートしました!

こんにちは。先日ついにスプラトゥーン2を手に入れて、 日夜練習を頑張っている結城 (@super_manner)です。 毎日google先生で「ガチマッチ 勝てない 自分のせい」とググって枕を濡らしています。

さて、本日CakePHPのマイナーアップデートがありましたので、いつものごとく、ちょっとした所感、追記を添えてお知らせしたいと思います!! https://cdn-ak.f.st-hatena.com/images/fotolife/i/itosho525/20170430/20170430222524.jpg

bakery.cakephp.org

今回の変更点まとめ

  • Http\Clientは、非標準のプロパティを持つCookieを解析しなくなりました
    • CakePHPのソースのこのあたりに標準プロパティがセットされているので、これ以外のものをセットしても解析ができません
  • CookieComponent設定は、以前のバージョンとの下位互換性があります
    • secureとhttpOnlyの部分の型がキャストされることで互換性を保ったようです
  • poファイルの解析では、複数のメッセージの後に単一メッセージが定義されている場合に、単数と複数の両方に定義された同じメッセージIDを持つpoファイルが処理されるようになりました
  • Helper::addClass()は、クラス属性が配列の場合、警告を出力しなくなりました
    • 今まではクラス属性が配列で来た場合 trim に渡ったタイミングで警告が出ていたようです
  • Viewのsetterはvoidではなく$thisを返却します
  • RedisEngineでキャッシュを使用するケースにおいて、Cake\Cache\Cache::incrementCake\Cache\Cache::decrementを使用する際に、durationを0に設定している場合はtimeoutを設定しないようにしました
    • durationが0の時は確かにexpireを設定する必要はないですね!
  • HtmlHelper::link()は、fullBaseオプションをサポートするようになりました
  • Text::uuid()は、random_intが定義されていればrandom_intを使用するようになりました
    • 定義されていない場合は従来通りのmt_randが使用されます
  • Debugger::highlight()が1行ずれてハイライトされない不具合が修正されました
    • Cookbookのこのあたりのお話ですね。今まではhighlight対象なのがfileの行数-1されていたのでずれてハイライトされる不具合があったようです
  • Http\Client::addCookie()で追加されたCookieには、パスとドメインが必要です

実はコントリビューターになりました

Bakery(CakePHPの公式ブログ)に名前が載りました!やった〜。

修正は先程ご紹介した変更点の1つ目のものです。 実はコネヒトからコントリビューターがでるのは田村(@Utmrer)に引き続き2人目です。 今回は、すごく気合を入れて「よし!なにかOSSに貢献するぞ!!!」と思い立ってIssueを洗いPRを送ったわけではありません。自分が実装している途中に「おや?これはもしかして」と思い、チーム内reviewをしてもらって勇気を出してPRを送ってみました。

いままでOSSにPRを送った経験がなかったので、「こんな些細な事で送っていいのかな?」「英語理解してもらえるかな?」「英語でマサカリとんできそう…」と少し尻込みする気持ちが無かったわけではありません。 ですが、勇気を出しておくってマージされた瞬間に「あ、よかったな。これからも少しでも使っているOSSに貢献していきたいな」という気持ちが湧いてきたので、どんな小さな変更でも、取りあえずPRしてみることをおすすめします! また、意外と拙い英語でも受け入れてもらえるのだなとわかったことも、今後別のものにもチャレンジしてみようと思えたので、私にとって大きい収穫でした。 f:id:supermanner:20170912191339p:plain また、今回の修正箇所はCookie関連の場所だったので、改めて現在のCookieプロパティ関連の知見を深め、復習することができました。

社内にはCakePHPだけでなく、さまざまな言語やOSSへ精力的に貢献している仲間がいるので、今後も良い刺激を受けつつちょっとずつ普段自分が使用しているツール等に恩返しをしていけたらとても素敵だな、と思いました。(ちなみに、はじめて記念としてPRのページはブックマークしました :innocent:) f:id:supermanner:20170912191424p:plain

ありがとうって言ってもらえてとっても嬉しかったです(*´ڡ`●)

おわりに

CakePHPアップデート連載、今回はちょっとしたお気持ちを添えた投稿となりましたがいかがだったでしょうか。 次回は最近CakePHPプラグインを作成していた弊社エンジニアいとしょ(@itosho) がお送りいたします!!

CakePHP3.5.1にアップデートしました!

こんにちは〜。先の投稿では幽☆遊☆白書を読んだと書きました、あれから少し日が空いたのですがその間にONE OUTSを読んだ次第です金城 (o0h_)です。
主人公がエゲツない天才で大活躍〜!って、やっぱり良いもんですね!!

さて、気づいたらCakePHPは3.5時代に突入!!しておりまして、
コネヒトでは3.4.12→3.5.0 →3.5.1へとアップデートを完了させております。
※ to 3.5.0は @itosho がやってくれました 💯

今回のエントリーでは、

  1. 個人的に3.5.0で入った気になるトピック
  2. 3.5.1のアップデート差分のおさらい

をしてみようと思います。!

https://cdn-ak.f.st-hatena.com/images/fotolife/i/itosho525/20170430/20170430222524.jpg

続きを読む

CakePHP3.4.12にアップデートしました!

こんにちは、最近になって幽☆遊☆白書を読了した金城 (o0h_)です。
やはり 危機感 は大事だなと思いました。。それがある人から見ると、それのない人については「分かる」ってことですよね。しっかりと視座を上げて生きていかなければなりません。抜かりのない人間になりたい。

前回のアップデートから少し間が空いてしまいましたが・・>< またまたCakePHPのアップデートを行いました。 https://cdn-ak.f.st-hatena.com/images/fotolife/i/itosho525/20170430/20170430222524.jpg

続きを読む

Kotlin開発Tech Talksで登壇してきました

f:id:tommy_kw:20170727102131j:plain

こんにちは!

こんにちは!富田(@tommykw)です。 先日、株式会社葵さん、ChatWorkさん、m-gramさん、エウレカさんと5社合同のKotlin勉強会があり、Kotlin導入についての発表させていただきましたので、共有させていただきます。

Kotlin開発Tech Talksとは

connpass.com

5社では共通点としてヒトをつなぐサービスを展開しており、5社共にKotlinを導入しています。Kotlin導入度合い、開発リソース、会社規模も異なる環境下で、それぞれのKotlinのノウハウ、知見を共有するための勉強会でした。上記リンクより、登壇者の方のスライドを閲覧できますので、良かったらご覧ください。

発表内容

speakerdeck.com

私はコネヒトにジョインして約10ヶ月間、Kotlinの導入、運用を行い、その中で得たKotlinとJavaの相互運用についての情報を共有させていただきました。当時を振り返ると、エイヤ!と導入していた部分も少なからずあり、反省点も多かったのですが、改めてそれらを振り返る良いきっかけとなりました。

最後に

プロダクトへのKotlin導入からテストへのKotlin導入など、実際の業務に関わるノウハウ、知見の共有が有難かったですし、特にKotlin未経験者に対してレビューを手厚くしたり、Kotlin未経験者のサポートなどチーム全体でKotlin力を上げるお話は興味深かったです。全体を通して、Kotlinが公式サポートされて導入の機運がますます高まってきていると思いますが、一方でKotlinとJavaの相互運用の難しさがあるという印象でした。有意義な時間を過ごせました!みなさんありがとうございました!

【資料公開】コネヒトが考えるサービスづくりに必要な技術とその考え方について

f:id:tatsushim:20170718232036j:plain

こんにちは!

CTOの島田(@tatsushim)です。 先日「【TECH PLAY Career Meetup】活躍しているフルスタックエンジニアが語る!サービスづくり勉強会」に登壇させていただきました。 techplay.jp 当日の発表の後で「スタートアップでサービスをつくる上で大事なことが学べて良かった」というお声と資料公開の要望をいただきましたので、こちらで共有させていただきます。

発表内容

今回の勉強会では「フルスタックエンジニア」がキーワードでしたが、良いサービスを作るための必要条件はすべての技術に精通することではないというお話をさせていただきました。 それは、コネヒトは明確に技術の目的を「ユーザーのために良いサービスをつくること」 としているからです。 目的達成のために、コネヒトでは以下の3つが大事だと考えています。

コネヒトが大事にしている3つのこと

  • 技術を手段として認識していること
  • 知らないことを「知らない」と正しく認識して任せること
  • サービスと共に成長できる柔軟さを持つこと

発表資料

詳細な内容についてはぜひ以下の発表資料をご覧いただければと思います。

終わりに

ついついプログラマとして手を動かしてしまいがちですが、ときには「つくらない」という選択肢を持つことがアウトプットの最大化に繋がるかもしれません。

島田(@tatsushim)がお送りしました!

開発環境改善としてDockerを導入した話

こんにちは。 5月よりコネヒトにjoinしたインフラエンジニアの永井(shnagai)です。

コネヒトにjoinして最初のタスクで、開発環境改善として一部のサービスにDockerを導入しました。 今回は、開発環境改善の道筋とそこから得られたDocker周りの知見について共有します。

開発環境で解決すべき課題

まずは、開発環境改善を行うにあたり、現状の環境の課題を明確にしました。 vagrantで開発環境を組んでいるとありがちな課題がコネヒトの環境にもありました。

1. 開発環境が不安定

vagrant環境が壊れる。。 突然VMが不調になりそれを直すために1~2時間持っていかれたり、 Aさんの環境でしか起きない再現不能の不具合が起きたり、 本来割くべき開発への時間が開発環境を直すことに費やされていました。。

2. アプリケーション増加によるホストOSの負荷問題

macで4,5,6個VM立てると重すぎて。。 サービス数が増える度に、VMの数が増えていきホストマシンのmacのリソースが限界を迎えていました。

3.本番/開発環境の乖離

初回に作成したVagrantのboxファイルを固定して使っていた関係で、 boxの中身はブラックボックスになりメンテナンスもほぼされていない状態でした。 本番との乖離は認識しているけど、追いつかせるのが面倒だし何が正しいかもいまいち不明瞭でした。

理想の開発環境

技術選定するにあたり、開発環境の理想の姿を定義しました。

  • 開発者はコマンド一つくらいのレベルで、さっとローカル環境を立ち上げる事が出来る
  • 誰でも同じ環境が立てられる(開発者/デザイナー関係なく)
  • 設定ファイルがgit管理されておりブラックボックスがない(ミドルウェア,OS,プラグインのバージョンも設定ファイルで管理)
  • 本番も同じ設定ファイルから作られたイメージがデプロイされ、開発環境と本番環境に全く差異のない状態

この理想の状態を実現出来るツールは、今のところDocker一択という話になり、Dockerの導入を前提として開発環境の改善を進める事にしました。

Docker導入の進め方

「新しいツールを入れる時は小さく始める」という原則に基づき、下記のような流れで実際の導入を進めました。

まずは、手元で動く環境を作る
↓
特定のメンバ(1,2名)にパイロット協力してもらい膿を出し切る
※パイロットメンバの協力が絶対不可欠
※いきなり全体展開は絶対ダメ(開発止まる。。。)
↓
ハンズオン形式で全メンバに概要説明した後Docker環境を手元に作ってもらう
↓
Docker関連の問題が出た時はすぐにキャッチアップして潰していく

アーキテクチャ

f:id:nagais:20170705162026p:plain

技術的TIPS

ここからは、Docker for Mac + docker-composerで開発環境を組む上で得られた知見をTIPS集的に書きます。

docker-composeで楽にコンテナ起動

docker-composeというと、複数のコンテナを一括で立ち上げれるという事がよく言われるメリットですが、 今回は、コンテナ操作のコマンドを容易に出来るという点に注目してdocker-composeを導入しました。

例えば、コンテナを立ち上げる時に

docker-composeだとymlに定義することで下記のコマンドでコンテナが簡単に起動出来ます。

※ -dはバックグラウンド起動

$ docker-compose up -d 
version: '3'
services:
  sample1:
    container_name: sample1-web
    build: .
    ports:
      - "8080:80"
    volumes:
      - .:/var/www/html:cached
    environment:
      LANG: ja_JP.UTF-8
      TZ: Asia/Tokyo
    command: bash -c "cd /var/www/html && sh docker-init-setup.sh && /usr/sbin/httpd -DFOREGROUND"
networks:
  default:
    external:
      name: sample_link

Dockerコマンドだと、毎回ポートマッピングやらコンテナ/イメージ名を意識しなければなりません(面倒くさい)

$ docker run -d -p 8080:80 -name sample1-web [イメージ名] 

日々使う開発環境では、コンテナを直感的に操作出来る事は重要だと思うのでこれはメリットだなと感じています。

コンテナ同士の通信

ホスト-コンテナ間の通信はポートマッピングしたポートで通信可能ですが、コンテナ間で直接通信したケースもあります。 そんな時は、docker-networkでユーザ定義NWを作り、そのNWにコンテナを参加させてあげます。

docker-networkの作成

## network作成
$docker network create --driver bridge sample_link

## 確認
$docker network ls|grep mamari_link
54528e6eacf6        sample_link                 bridge              local

コンテナ起動時に、networkを明示的に指定してあげる事で、同一docker-networkに存在するコンテナ同士はコンテナ名で通信する事が可能になります。

docker-composeの場合は、ymlに下記のように定義

networks:
  default:
    external:
      name: sample_link

dockerコマンドの場合は、下記のように –netオプションを使用

docker run -d --net=sample_net

Docker for Mac遅すぎる問題

Docker for Macは、MacのHyperkitという仮想マシンレイヤ(linux)を使って、シームレスにDockerをMac上で動かすものですが、このHyperKit経由でのディレクトリマウントが遅い問題が出ました。

これは、Docker for Mac の17.05以降でサポートされた、VOLUMEのcachedオプションで回避しました。

たいてい開発環境だと、ホストでコードをいじりつつコンテナ上のwebアプリで動作確認をするというパターンかと思います。cachedと聞くと時間差がありそうで少し不安になりますが、今のところ同期が遅いというような声は聞こえてきていませんので問題なく動いています。(Docker for Mac Edgeの17.05でたまに同期されない問題が出たので、17.06 stableに上げたところ解決しました)

    volumes:
      - .:/var/www/html:cached
今だと、Docker for Macの17.06がstableなので、そちらを使うとcachcedオプションが使えます!!

DockerコンテナからVirtualMachineへの通信がたまに途切れる問題

これが、一番頭を悩ませた問題なのですが、コンテナからVM上に立っているredisに対して通信をしていたのですが、たまにredisと通信が出来なくなるような事象が発生しました。

実際に、redis-cliを使って計測した結果が下記です。

アベレージは、0.72sなのにたまに45sの通信が発生。これにあたるとアプリが異常に遅くなるという事象でした。。

$ docker exec redis redis-cli --latency -h 192.168.33.200 -p 6379
min: 0, max: 45, avg: 0.72 (11446 samples)

redisをコンテナで立てて、同じ試験をした結果抜群の安定感を確認。

$ docker exec redis redis-cli --latency -h docker-redis -p 6379
min: 0, max: 3, avg: 0.21 (10061 samples)

最終的な原因まではわからなかったのですが、dockerコンテナからVMへの通信は、ホストマシン間で複数のブリッジNWを経由して行われるので、不安定になることがありそうです。素直にDockerコンテナに集約してあげた方がいいかもしれません。

さいごに

Dockerfileはインフラの持ち物でなく、みんなで育てていきたい

という気持ちが開発者に伝わり、自分で新サービスdockerで作り始めるメンバがいたり、すぐにDockerfileの修正点指摘してくれたりコード化されたメリットを早くも感じ始めています。

Vagrant構成で苦しんでいる方がいれば、そんなに障壁は高くないのでまずは1サービスから小さくDocker導入を進めてみればいかがでしょうか?