はじめに
サムザップで SRE チームのエンジニアをやっている ミルクボーズ です。
弊社で運用中のゲームタイトルのインフラ保守・運用が最近の主な業務です。
あるゲームタイトルのリリース前からアーキテクチャの設計・構築を行ってきたのですが、コンセプトとしてできるだけマンパワーが必要な管理コストを削減できるように、マネージドサービスを多く使うように設計してみました。
今回はその中でも AWS のマネージドサービスで行った Blue/Green Deployment でハマったことをつらつらと書いてみたいと思います。
アーキテクチャざっくり
今回とりあげるゲームタイトルのアーキテクチャは図で示すと以下のとおりです。
API サーバは Fargate を採用しており、インスタンスをこちらで管理しない構成をとっています。
また、Aurora の接続先情報を Secrets Manager に、Elasticache の接続先情報を Systems Manager の Parameter Store に格納していて、
API サーバが必要なタイミングでそれぞれを取得し、コンテナ内にキャッシュする仕組みをとっています。
そして、Blue/Green Deployment は CodeDeploy の機能を利用して実現しています。
ハマったこと
今回とりあげるゲームタイトルにおいて Blue/Green Deployment でハマったことを書いていきます。
クォータでハマる
はい、あるあるですね。
リリース前に負荷試験を行う際には、Fargate のタスク数をたくさん用意することになります。
調子に乗ってタスクをたくさん用意しているとデプロイのときに引っかかります。
なぜなら Blue/Green Deployment を行う際は現在用意しているタスクセットの倍のタスク数が必要になるからです。
すぐさまサポートに緩和申請しても、緩和されるまで数日かかるので、それまではデプロイの前にタスク数減らして、デプロイしたらタスク増やしてみたいなことするハメになります。
ついでに、私の場合は ENI の上限にも引っかかり、CodeDeploy で以下のエラーが出力されました。
Unexpected EC2 error while attempting to Create Network Interface in subnet 'subnet-xxxxxxxxxxxx': NetworkInterfaceLimitExceeded
こちらもサポートに上限緩和申請を出しました。
※どうやら現在はリージョンあたりの ENI の上限が大幅にアップしたようなので、よほどじゃない限り緩和申請は必要なさそうです。
デプロイの停止でハマる
CodeDeploy での Blue/Green Deployment では、「トラフィックの再ルーティング」いわばターゲットグループ Blue とターゲットグループ Green のルーティングの切り替えを手動で行う設定ができます。
そうすることで切り替え前のタスクセットをテストリスナーなどを用いてテストすることができます。
ただここでハマったのは、既に切り替え対象のタスクセットが用意できている状態で再ルーティングをせずに「デプロイを停止」を行うと、デプロイ自体がそこで終了し、切り替え対象として用意されたタスクセットが残り続けてしまうということでした。
「デプロイを停止」はあくまで実行中のデプロイを強制停止するだけで、代替のタスクセットの削除までやってくれないんですね。
もう一度デプロイを実行して、「再ルーティング」をかければ問題なくタスクセットは消えてくれるのですが、それまでは不要なタスクセットが残り続けるという罠があるので注意してください。
ちなみに「デプロイを停止してロールバック」を選択すれば、代替のタスクセットは消えてくれます。
再ルーティング前のキャッシュ保存でハマる
前提を話すと長いので、簡潔にやりたかったことを言うと、Blue/Green Deployment の切り替え前のタスクセットの各タスクに必要なデータをキャッシュさせておきたいという要件がありました。
最初は、CodeDeploy の appspec.yml
にhooks
を指定し、アプリケーション内のスクリプトを実行してキャッシュを積もうかなと考えたのですが、どうやら ECS においての hooks
の指定は Lambda 関数のみらしいです。
次にとった策としては、ターゲットグループのヘルスチェックに指定している API にキャッシュを積む処理を用意するということでした。
ここでもハマったのですが、どうやら事前に ALB 側にテストリスナーのポートを用意しておき、 CodeDeploy でそのリスナーを指定していないと、切り替え前のターゲットグループにおいてヘルスチェックは流れませんでした。
確かに落ち着いて考えれば、テストリスナー用のポートが存在していなければ、切り替え前のタスクセットへアクセスできないのは自明の理とは思うのですが、代替タスクセットのターゲットグループの画面を見れば見事に全タスク healthy
となっているので、ヘルスチェックが流れているものだと勘違いしてしまいました。
スケールインでハマる
ECS を使っていることのメリットのひとつでもある AutoScaling ですが、こちらで設定してスケーリングのルールだと Green 用のターゲットグループがアクティブのときだけ自動スケールインが実行されていませんでした。
調べてみると、どうやらまず前提としてスケーリングポリシーを複数設定している場合、スケールアウトはポリシーのいずれかが閾値を超えた場合で実行されるが、スケールインではすべてのポリシーの条件を満たさないと実行されないようです。
また、当ゲームタイトルで設定していたスケーリングポリシーが ECSServiceAverageCPUUtilization
( ECS サービスの平均 CPU 使用率)と ALBRequestCountPerTarget
(タスクあたりのリクエスト量)だったのですが、ALBRequestCountPerTarget
のスケーリングポリシーはあくまで一つのターゲットグループのメトリクスの情報しか参照せず、ターゲットグループ Blue がアクティブなときにはこのターゲットグループの RequestCount
メトリクスを参照するのですが、ターゲットグループ Green がアクティブなときでも Blue の RequestCount
メトリクスを参照し続け、さらに Blue の RequestCount
は「0」でなく、「データなし」の扱いとなります。
指定されたメトリクスに十分なデータがない場合、ターゲットの追跡スケーリングポリシーによってスケールされない状況になり、結果的にスケールインイベントが実行されなくなっていました。
結論言うと、ALBRequestCountPerTarget
のスケーリングポリシーは現時点の AWS の仕様としては、Blue/Green Deployment に適していません。
やむなくですが、ALBRequestCountPerTarget
の方のスケーリングポリシーは削除して、ECSServiceAverageCPUUtilization
のポリシーのみにしたら、スケールインが Green の方でも元気に動いてくれました。
今後 ALB のルーティングの変更をトリガーにスケーリングポリシーの更新を行うようなフック処理を検討しようと思うのですが、自前で作るというよりは今後のアップデートに期待したいところです。
最後に
今回とり上げたゲームタイトルでマネージドサービスを用いての Blue/Green Deployment を検証したときにハマったことを記載しました。
その他にもマネージドサービスを使用していると、その仕様とこちらがやりたい要件の兼ね合いでハマる部分が多々ありますね。
とはいえ、設計・構築段階で想定しうるものを潰しきれば、運用においてマネージドサービスはかなり楽できます。
現に今、今回とりあげたゲームタイトルを運用していて恩恵を受けているところはたくさんあります。
なによりパブリッククラウド及びマネージドサービスを活用することで、サーバーエンジニアもそれらを意識して開発する体制になり、SREとサーバーエンジニアのそれぞれが担当する役割の垣根が以前より低くなったように感じます。
弊社のエンジニア組織の技術力の幅が広がるいいきっかけとなっているのが一番の恩恵かもしれないですね。