AWS CodeDeployでパッケージをデプロイしてbootstrapの処理を走らせる

佐々木海氏(以下、佐々木):次にあるのが、インスタンスのbootstrapの時間がかかるという話と、あとコンフィグレーションの話。コンフィグレーションの設定が難しいというのと、あとオートスケールをどうさせるかというお話。これらを全部解決するために作った仕組みが、このAmazonのCodeDeployとAuto Scaling Groupを使ってクラスタを立ち上げるという仕組みです。

仕組みとしては、EC2のAuto Scaling Groupというものを使います。こいつはあるグループを定義してあげると、そのグループに所属するEC2インスタンスを指定された数だけ立ち上げて、指定されたbootstrap処理を走らせてくれる。それを基本的に自動でやってくれるものです。私たちがやらなきゃいけないのは何台立ち上げるかというのを指定してあげるだけです。

このAuto Scaling Groupに、例えば「40台のPrestoクラスタを立ち上げて」と言うと、EC2に処理が渡ってEC2が適切な台数のEC2インスタンスを立ち上げてくれます。そのあとbootstrap処理としてAWSのCodeDeployにフックが走ってパッケージをデプロイします。

こうすることでEC2インスタンスの立ち上げの時間をかなり短縮させることができたのと、そのトラッキングをする必要がなくなります。EC2とCodeDeployが勝手にそのインスタンスのbootstrapのマネージをしてくれるので、こっちからマニュアルで見る必要はなくなります。

CodeDeployがどういうサービスかというと、あるパッケージ、単純なZIPファイルなんですけど、そこにコンフィグレーションやアプリケーションのビルト済みのコードと、あとhook scriptがあります。あとで説明しますが、hook scriptのコンフィグレーションを突っ込んで固めたZIPファイルをCodeDeployパッケージとしてCodeDeployシステムの中に入れます。

こいつを特定のインスタンスグループたちにデプロイしてと言うと、そのパッケージをデプロイして指定されたbootstrap処理を走らせてくれます。

マルチクラウド環境できれいにデプロイするために必要な仕組み

佐々木:大事なのは、このCodeDeployパッケージは単なるZIPファイルなので基本的に中になにを入れても大丈夫です。あとはバージョニングがきちんとできるので、各インスタンスグループ、今回の場合は1つのクラスタなんですけど、それに対してどのバージョンがデプロイされたかというのが適切に管理できます。

つまり、クラスタとそこにデプロイされたパッケージのバージョンが1対1に対応して、CodeDeployが管理してくれるので、僕たちは管理する必要がない。どのグループにどのバージョンがデプロイされたかはCodeDeployが知っている。いちいち一つひとつのインスタンスにそれを確認する必要はないというのが大きなメリットです。

さっき言ったことですが、1クラスタに対して1つのパッケージが対応するのはいいんですけど、これがマルチクラウドな状態、つまりAWS以外にもクラウドがあったり、あるいはリージョン、ほかのロケーションでクラスタがあったりした場合に、どのクラスタにどのパッケージのバージョンがデプロイされたかというのをうまく管理する必要があります。

そのために作ったというか定義したのが、このDeployment Targetという仕組みです。これは、デプロイの定義をDeployment Targetという4つ組の値とパッケージのバージョンで定義します。つまり、このパッケージをこのsite・stage・service・clusterという4つ組の値に対してデプロイをするという様に定義します。

site・stage・service・clusterとはなにか、siteというのはクラウドの名前ですね。AWSとか。あとはTreasure Dataの場合だとIDCF Cloudでサービスを、IDCフロンティアのクラウドサービスでサービスを提供しているのでIDCFだったりします。

あとは東京リージョンでもあるので、そこでもこのsiteという名前が別の名前が定義されて、AWS Tokyoという別のsite名が定義されています。siteでクラウド環境を1つ指定します。

stageがよくあるプロダクション環境やステージングデベロップメント環境です。serviceが、今回の場合だとPrestoのCoordinatorロール、PrestoのWorkerのロール、コンポーネントの名前を指定するものです。clusterがクラスタの名前です。グループの名前と言ってもいいかもしれません。

これを指定することで1つのターゲットが定義できます。ここに対して1つのCodeDeployパッケージをデプロイすることになります。ここに例が書いてあるのですが、「presto-coordinator-default-production-aws」って書いてあるのは、これが1つのグループを定義。これが1つのDeployment Targetになります。

この名前はCodeDeployやAuto Scaling Groupと1対1に対応していて、こいつにある1つのCodeDeployパッケージをデプロイすると、そのグループに所属しているインスタンスにCodeDeployがパッケージをデプロイして展開してくれます。

こうすることでデプロイの対象とCodeDeploy・Auto Scalingを1対1対応させて、そこに所属しているインスタンスに関してはこっちで細かく気にしなくていいことになります。何台あるかどうかだけ気にしていれば、そこに所属するインスタンスが誰なのかということは、開発者、デベロッパー側としては、運用者としては気にする必要がなくなります。

このDeployment Targetという仕組みは、こういうマルチクラウド環境で1つのパッケージをきちんときれいにデプロイするために必要な仕組みです。これを定義しました。

コンフィグレーションを各環境ごとに分けてマネージする

佐々木:これを使ってさらにやったことが、コンフィグレーションを各環境で分ける。つまりさっき言ってたように、コンフィグレーションがどのクラスタでどんな値になっているかというのはバラバラになる可能性があるので、それをきちんとマネージする仕組みが必要です。それにこのDeployment Targetを使いました。

どうやっているかというと、Deployment Target、site、stage、service、clusterといくごとに粒度がどんどん細かくなっていくんですね。siteが一番大きくて、stageが次に大きくて、clusterになると一番小さい。

大きいほうからデフォルトの値を定義してあげます。例えば、site=awsでこのJVMオプション、-Xmx4Gと-Xms1Gを定義してあげると、これがAWS環境でのデフォルトの値になります。

こいつをどんどん細かいDeployment Targetにいくにしたがってオーバーライドしていきます。プロダクション環境ではもっと大きなインスタンスがあるので192Gにしたり、クラスタの名前がoneというクラスタに対しては、ワークロードがそれほど高くないのでもうちょっと小さなメモリでよければ127Gに指定する。

これをオーバーライドしていくことで、各環境でどのコンフィグレーションが設定されているか、各環境でデフォルト値がなんなのかというのはきちんとこの粒度にしたがって階層構造になっているので、簡単にわかります。

この仕組みはCodeDeployの中で実現されていて、単なるスクリプトでオーバーライドしていくだけなんですけど、これらのコンフィグレーションがCodeDeployパッケージに含まれて、CodeDeployがそのインスタンスのDeployment Targetを特定して、このコンフィグをこのヒエラルキーにしたがってレンダリングしていってくれます。

こうすることでDeployment Targetとさらにコンフィグレーションを1対1対応させるので、つまりDeployment Targetを1つ指定してあげると、そこにどのパッケージがデプロイされていて、そのパッケージに含まれているコンフィグレーションの値はそれぞれなんなのかというのはユニークに決めることができます。なので、あんまり深く考えることがなくて、一度デプロイされてしまえば、実際どの値が入っているかというのをそんなに気にする必要がなくなります。

Auto Scaling Groupで自動的にインスタンスをシャットダウン

佐々木:ここはAuto Scaling Groupでどれだけインスタンスの立ち上げに時間がかかっているかという話です。

Auto Scaling GroupにさっきEC2のインスタンスをマニュアルで立ち上げるのは、bootstrapのプロセスをトラッキングしなきゃいけないというのと時間がかかるという大きな2つの問題がありました。これは実はCodeDeployとAuto Scaling Groupにmigrateすることでほぼほぼ解決しています。

Auto Scaling Groupが必要なEC2の立ち上げの処理などを勝手にやってくるので、もともと100台ぐらいのクラスタを立ち上げるのに1時間ぐらいかかってたのが、これが10分ぐらいに済むようになりました。なので、CodeDeployとAuto Scaling Groupを使うことでクラスタの立ち上げの時間がだいぶ短縮できました。

分散クエリエンジンのオートスケールインのほうがふつうのWebサービスと大きく違う点としてロングランニングなクエリがある場合があるという話をさっきしたと思うんですけど、それをどう解決したかというのはこのAuto Scaling Groupの Lifecycle Hookという仕組みを使いました。

このインスタンス、各インスタンスにステートがあるんですね。今はinitializeの状態、running、shutdown中、そういうステートがあるのですが、そのステートを動かしていいよ、ほかの状態に遷移していいよっていうのを伝えるために使うのがこのLifecycle Hookです。

これはAuto Scaling Groupで提供されている機能で、それを利用してGraceful Shutdownを実現しました。

Auto Scaling Groupに「あるインスタンスをシャットダウンしてください」ということを言います。そうすると、クエリの実行を待って完了したら、Lifecycle Hookが次の状態に遷移していいよということを言うスクリプトを定期的に走らせています。そうするとAuto Scaling Groupは自動でそのインスタンスをシャットダウンします。

つまり、こっちでクエリの実行を待ってインスタンスをシャットダウンというのを管理することができるので、Graceful Shutdownが実現できることになります。

ほかにも、このhook scriptを使ってアプリケーションがきちんと動いているかというのを確認したりしました。

Target Tracking Scaling Policyでオートスケーリング

佐々木:最後に話したいのがReal Auto Scalingですね。

これは、何台のキャパシティが必要かというのを今まではマニュアルでやらなきゃいけなかったものをもうちょっと機械的にやりたいという話です。

なんでマニュアルだとつらいかというと、かなり経験が必要なのとワークロードのタイプが変わったときにまたどうしていいかわからなくなってしまうので、システマチックにやりたい。そのためにやった調査と、トライ&エラーを話します。

使ったのはAWSのAuto Scaling Groupで提供されているTarget Tracking Scaling Policyというものです。

これはCPUのUsageやネットワークのインとかアウトなどのメトリクスに対してtarget value(目標値)を設定して、そこからのギャップをアジャストするために何台追加する必要があるか・何台余分かというのを勝手に計算してくれてアジャストしてくれる機能です。こいつを使ってCPU Usageに対してPrestoクラスタをリアルオートスケールさせてみました。

このグラフがその時のシミュレーションなんですけど、10個〜30クエリを同時実行して、だいたい40WorkerインスタンスのPrestoクラスタに対してシミュレーションしてみました。横軸が時間で縦軸がCPUのUsageですね。ぐにゃぐにゃなっているのがCPU Usageです。

例えば横にピッと入っている線、今40の値に設定されているのですが、ここがtarget value、今回40に設定しました。つまり、このtarget value 40が常になるようにTarget Tracking Policyがクラスタのサイズをアジャストしてくれるはずです。

つまり、ここから上に跳ね上がっている場合にはクラスタのサイズが足りないということなのでインスタンスを追加してくれて、下になっている場合はクラスタがでかすぎるのでインスタンスを減らしてくれます。

理想的にこのクラスタのキャパシティのアジャストをTarget Tracking Policyがしてくれた場合にどれぐらい余分なインスタンスを減らせるかを示したのがこのグラフです。

横軸がtarget valueです。縦軸が平均のクラスタのサイズですね。

なにを意味するかというと、target valueを上げれば上げるほどクラスタをなるべくbusyにしていいということなので、台数を減らすことができます。左に行けば行くほど、target valueが低い、つまり暇にしなきゃいけないのでクラスタのサイズが増えます。

これを見ると、target value=40ぐらいだと平均してクラスタのサイズが今と同じぐらいになります。それより上げるともっと減らせる。それより下げるともっと増やさなきゃいけないので、40以上でtarget tracking valueが完璧に動作するとかなりコストが節約できる可能性があります。

AWSに依存したものではなく、どんなクラウドでも使える汎用性の高いものを

佐々木:これが実際にTarget Tracking Policyで動かしてみたもので、緑色がCPUのUsageです。オレンジ色がクラスタのサイズです。

ここからわかるとおり、赤いところでCPUが跳ね上がると、そのあと20分ぐらい遅れてクラスタのサイズがぐんぐんと上がって、そこからまただんだん減っていきます。

ここからわかるとおり、Target Tracking Policyはあまりよく動かなかったんですね。スケールアウトは約20分遅れで反応してくれて、まぁ20分ぐらいだったらわりといいんですけど、スケールインのほうがぜんぜん遅くて。

Auto Scaling Groupはけっこうスケールインのほうには保守的に機能して、なかなかインスタンスを下げようとしないんですね。ジョブをなるべく安全に動かしたいからなのかそういう挙動になっていて、なかなかインスタンスサイズを減らしてくれません。

それに加えて、ロングランニングなジョブがあるとインスタンスのシャットダウンが利かないので、ずっとインスタンスを維持することになってしまいます。つまり、なかなかこのPolicyをenableするだけだとクラスタの台数が増えてしまうということがわかったので、実現できませんでした。

いずれこういった機能を実現したいのですが、それがなんでできないかというと、僕たち人間はオートスケールしないからです。

もしやりたい人がいたらここをポチってクリックしてもらえるとうれしいです。

ありがとうございました。

(会場拍手)

司会者:ありがとうございます。では質問ある方、申し訳ないですけど1名に限らせていただきますが、ありますでしょうか? ではマイクをお願いします。

質問者:先ほどのReal Auto Scalingなどの点で1点質問がありました。究極的、Auto Scaling Groupたち、AWSの機能に依存したかたちになってしまっていると思うんですけど、これって今はとりあえずAWSの上でやっておいて、それが実現できたらアダプタを定義してほかのクラウドに持っていくといった流れになるという理解でよろしいでしょうか?

佐々木:はい。まったくそのとおりで。今回Real Auto Scalingの話をしてみて、あれはPoC的に。なんであれをやったかというと、すごく簡単にメトリックに基づいてキャパシティのリサイズをしてくれるというのができたのでやってみました。結果として私たちの要件には満たなかったんですけど、あれを基にしてもっとどんなクラウドでも使える汎用的なものを自分たちで作るというのは考えています。

質問者:ありがとうございます。

司会者:佐々木もこのあとの懇親会に参加するので、もしなにか質問がある方がいらっしゃいましたらそちらのほうで聞いていただければと思います。では、ありがとうございました。

(会場拍手)