本記事の内容
AWS CodeDeploy を触ってみて、はまってしまった落とし穴について書いたものです。
ELB + EC2 AutoScaling + blue/green デプロイで既知のバグ: デプロイ後、ELBからのヘルスチェックが効かなくなる
以下、ALB 使用時について説明しますが、CLB でも同様のバグがあるそうです。
blue/green デプロイをすると、新規に作成される green 環境用の AutoScaling グループに、元の blue 環境の AutoScaling グループの設定がコピーされますが、その際にターゲットグループ設定がコピーされません(空になります)。
AutoScaling のヘルスチェック設定を ELB にしていても、ターゲットグループが設定されていないため、ELB からのヘルスチェックが無効になってしまいます(EC2 インスタンスのヘルスチェックのみ有効になります)。
その結果として、ELB からのヘルスチェックが失敗しても、対象インスタンスは healthy と判定され、AutoScaling グループの自動 failover の対象となりません。
このバグは AWS 側も把握しているとのこと。早く修正して欲しいですね・・・。
workaround として以下の方法があります。
AfterAllowTraffic hook で green 環境用の autoScaling グループにターゲットグループを attach します。
autoScaling グループ名は EC2 の Tag から取得できます。
appspec.yml の hooks に指定するスクリプトのパスには、アプリケーションモジュールの root からの相対パスを記述すること
アプリケーションモジュールをインストールするディレクトリを files セクションにて指定しますが、hooks でインストール先のパスを指定すると、ファイルが見つからないエラーになります。
CodeDeploy は hooks に指定したファイルを /opt/codedeploy-agent/deployment-root/9368645e-c79f-4304-9356-21a755b9a421/d-BK4MCFZ0P/deployment-archive/ というようなパスの配下から探します。左記のパスは CodeDeploy が EC2 にアプリケーションモジュールをインストールする際に、取得したモジュールを展開する temporary なディレクトリです。
従って、hooks ではアプリケーションモジュールの root からの相対パスを記述する必要があります。
in-place デプロイで ApplicationStop hook に不備があると、次回以降のデプロイが失敗する
デプロイする際、 lifecycle の最初に ApplicationStop が実行されますが、ここでは最後に成功したデプロイのモジュールが使われます。つまり、直前のデプロイが成功していれば、その設定・モジュールが使われます。
従って、直前のデプロイの ApplicationStop に不備がありエラーになると、次回のデプロイがこけます。デプロイ設定でデプロイ失敗時のロールバックを有効にしていると、再度デプロイしても直前の成功デプロイ(ApplicationStop に不備があるデプロイ)の ApplicationStop が使われるため、やはりデプロイがこけます。
回避策として、デプロイ設定でロールバックを無効にすると、デプロイが失敗してもモジュールが更新されるので、再度デプロイすれば更新後の ApplicationStop が実行されます。
codeDeploy エージェントのインストールは userData の最後で行うこと
CodeDeploy エージェントをインストール → userData の処理が完了
の間に、CodeDeploy によるデプロイ処理が走ってしまう可能性があるためです。
AutoScaling グループへの初回デプロイは少し困難が伴う
初回デプロイ成功後は、AutoScaling により新たに作成されたEC2インスタンスには、CodeDeployにより直前の成功デプロイのリビジョンのアプリが自動でデプロイされます。逆にいうと、初回デプロイ成功前は、直前の成功リビジョンが存在しないため、インスタンスを起動しても、CodeDeploy はアプリをデプロイしません(できません)。しかしながら、CodeDeploy でデプロイするには、デプロイ対象のインスタンスが起動されていなければなりません。つまり、卵が先か鶏が先か、という話になります。
AutoScaling グループのヘルスチェックでは通常、 ELB ヘルスチェックを有効にしますが、アプリケーションが稼働しないとエラーになるでしょう。従って、放っておくと新規インスタンスが起動→ヘルスチェックエラーでインスタンスが停止→新規インスタンスが起動 というループが発生してしまいます。これを防ぐためには、AutoScaling グループで最初のインスタンスを作成した後に、速やかに CodeDeploy のデプロイ処理をキックしなければなりません。その際のデプロイ設定は in-place で AllAtOnce とします。
デプロイ設定は初回は in-place、二回目以降は blue/green とするのが良いかな
初回デプロイは blue 環境がないので in-place で行います。二回目以降は blue/green で行います。
ちなみに、blue/green をすると green 環境として新たな AutoScaling グループが立ち上げられます。AutoScaling グループの設定は blue 環境のものを引きつぎ、名前は CodeDeploy_[デプロイ設定名]_[デプロイID] となります。そのため、blue/green をすると、アプリの稼働している AutoScaling グループが変更になるのですが、そうすると初回デプロイで使用した in-place のデプロイ設定は(AutoScaling グループ名が指定されているため)そのままでは使えなくなります。なので、初回デプロイで使用した in-place のデプロイ設定は削除してしまっていいと思います。
blue/green の場合は blue 環境の終了待ち時間を指定すること
blue/green の場合、デプロイ後に元の環境は不要になるので、デプロイ設定で「デプロイの成功後にデプロイグループの置き換え元インスタンスを終了」を選択するのが通常ですが、インスタンス終了までの時間はデフォルトでは 1時間です。これはもっと短くした方がいいと思います。なぜなら、元の環境のインスタンスが終了するまではデプロイのステータスが完了にならないのですが、完了にならないと次のデプロイを開始できない、つまりは元の環境のインスタンスが終了するまでは次のデプロイを開始できない、ためです。
デプロイ設定は blue/green + AllAtOnce でいいのでは
他にする理由があまりないかな。
まとめ
以上。