AWS CodeDeploy を使うときに気をつけたいこと

エムティーアイ Advent Calender 2016 の23日目の記事です。

AWS CodeDeploy （以下、CodeDeploy） は AWS の中の開発者ツール（通称 Code シリーズ）の中の1つで、デプロイ作業をコードで記述することで自動化できるサービスです。
開発者ツールや CodeDeploy 自体の説明は以下などを参考にしてください。

* [AWS CodeDeploy Documentation](https://aws.amazon.com/jp/documentation/codedeploy/)
* [AWS Black Belt Tech シリーズ 2015 - AWS CodeCommit & AWS CodePipeline & AWS CodeDeploy](http://www.slideshare.net/AmazonWebServicesJapan/aws-black-belt-tech-2015-aws-codecommit-aws-codepipeline-aws-codedeploy)
* [AWS再入門 AWS CodeDeploy 編 - クラスメソッド株式会社 Developers.IO](http://dev.classmethod.jp/cloud/aws/cm-advent-calendar-2015-aws-re-entering-codedeploy/)

この記事では実際に CodeDeploy を数ヶ月使っていてハマってしまった経験を書いていきます。
どれも経験に基づいた情報であり個人の見解等も入っていますので、正確な情報は公式ドキュメントや、AWS サポート等へご確認ください。

## Hooks スクリプトはどんなときでも動くように

Hooks スクリプトにデプロイ時の様々な付随処理を書くことができます。
スクリプトで目的の処理を実行できるようになったら、いろんな条件でデプロイを試してみてエラーが出ないことを確認します。
例えば、まっさらな状態のインスタンスに対してのデプロイ、アプリケーションが稼働中の状態でのデプロイなどです。

僕の場合はとある C# の Web システムで、開発中は正常にデプロイできてたけど本稼働後にデプロイをさせたら失敗するようになってしまいました。
原因は対象インスタンスへの Web リクエストの停止がうまく行えていなかったために、処理中の dll の置き換えができなくなってしまいました。

また、AutoScaling で新たに生成されたインスタンスに対してのデプロイで失敗してしまうこともありました。
特にこの CodeDeploy のターゲットに AutoScaling グループを指定する場合は注意が必要です。
AutoScaling ではインスタンス生成時に CodeDeploy によるデプロイを実施しますが、デプロイに失敗した場合はインスタンスが正常に起動しなかったものとして、インスタンスを破棄して再生成します。
すると再度失敗して、破棄と生成の無限ループに突入してしまいます。
気づかなければインスタンスがいつまでも InService にならないだけでなく、インスタンスの料金もかかってしまい、目も当てられなくなります・・・。

## PowerShell は 32bit 版が動く

Windows での Hooks スクリプトでは直接 PowerShell を実行することはできず、bat ファイルから ```powershell script.ps1``` のように呼び出して使います。
このとき、たとえ OS が 64bit 版だったとしても 32bit 版の PowerShell が実行されてしまいます。
そのため、一部のコマンドレットが使用できません。

これについて困ってる人は結構いるようで、ググるとそれなりに出てきます。
64bit 版の方を実行させるやり方を書いてる人もいましたが、僕はうまく行きませんでした。
仕方なく通常の Windows コマンドや 32bit 版でなんとか回避しましたが、「こうやると 64bit 版が使えたぜ！」っていう情報があったら教えていただけるとうれしいです。

## CodeDeploy で配置したファイルはいじってはいけない

AppSpec ファイルの files セクションに書くことでファイル群の配置を行うことができます。
その配置されたファイルに CodeDeploy 以外で変更を加えてしまうと、次のデプロイ時に失敗してしまいます。

CodeDeploy では2回目以降のデプロイの際、 files の destination で指定されたファイルやディレクトリ以下を削除して新しいファイルをコピーしています。
配置したファイルの更新日などのメタデータを見ているようで、デプロイ後にファイルが更新されていると「勝手に消したらまずいんじゃね？」と判断（イメージです）してデプロイを中断します。

ファイルを更新してしまった場合は手動で削除するか、CodeDeploy の作業ディレクトリにある元ファイルをコピーして元に戻すかしてから再デプロイしましょう。
もしくはインスタンスごと作り直してもいいと思います。

## インスタンスの時刻がずれてるとデプロイできない

ある日突然、特定のインスタンスだけデプロイに失敗するようになり、しかもマネジメントコンソールで確認するとデプロイライフサイクルにすら入る前にエラーとなっていました。
インスタンスにログインして CodeDeploy エージェントのログを見るとこんなログがありました。

```codedeploy-agent-log.txt
2016-11-30 15:46:26 INFO  [codedeploy-agent(2776)]: [Aws::CodeDeployCommand::Client 400 0.062357 0 retries] poll_host_command(host_identifier:"arn:aws:ec2:ap-northeast-1:NNNNNNNNNNNN:instance/i-XXXXXXXX") Aws::CodeDeployCommand::Errors::InvalidSignatureException Signature expired: 20161130T064626Z is now earlier than 20161130T070808Z (20161130T071308Z - 5 min.)

2016-11-30 15:46:26 ERROR [codedeploy-agent(2776)]: InstanceAgent::Plugins::CodeDeployPlugin::CommandPoller: Cannot reach InstanceService: Aws::CodeDeployCommand::Errors::InvalidSignatureException - Signature expired: 20161130T064626Z is now earlier than 20161130T070808Z (20161130T071308Z - 5 min.)
```

どうやらインスタンスの時刻同期が長いこと失敗し続けていて、インスタンスの時刻が大幅にずれてしまっていたようです。
CodeDeploy ではインスタンスの時刻が5分以上ずれていると、認証に失敗し CodeDeploy エージェントと CodeDeploy との通信が行えません。

[ドキュメント](http://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/troubleshooting-ec2-instances.html#troubleshooting-instance-time-failures)にもちゃんと書いてありました。
> Troubleshooting “InvalidSignatureException – Signature expired: [time] is now earlier than [time]” deployment errors
> 
> AWS CodeDeploy requires accurate time references in order to perform its operations. If your instance's date and time are not set correctly, they may not match the signature date of your deployment request, which AWS CodeDeploy will therefore reject.

## OneAtATime で最後の1台のデプロイ失敗は無視される

インスタンス1台1台のデプロイの成功・失敗とは別に、デプロイ全体（デプロイ ID ごと）の成功・失敗という概念があります。
通常、Deployment config が OneAtATime の場合、対象インスタンスに順次デプロイしていく途中で1台でも失敗するとデプロイが中断され全体として失敗となります。

しかし、最後の1台（4台構成なら4番目にデプロイされるもの）が失敗しても全体として成功と評価されます。
これも[ドキュメント](http://docs.aws.amazon.com/ja_jp/codedeploy/latest/APIReference/API_CreateDeploymentGroup.html#CodeDeploy-CreateDeploymentGroup-request-deploymentConfigName)に書かれているので仕様のようですが、その理由は "1台ずつオフラインになることを前提とした設定だからいいよね" （超意訳。あってるよね？）
・・・そうっすか笑。

> The overall deployment succeeds if the application revision is deployed to all of the instances. The exception to this rule is if deployment to the last instance fails, the overall deployment still succeeds. This is because AWS CodeDeploy allows only one instance at a time to be taken offline with the CodeDeployDefault.OneAtATime configuration.

このため、全体の評価が成功になっていたとしても失敗したインスタンスがないか確認したほうがいいかもしれません。

## 停止しているインスタンスにもデプロイの試行がされる

CodeDeploy のターゲットはインスタンスの任意のタグを指定することができます。
この場合、指定したタグの付いたインスタンスであれば停止しているインスタンスに対してもデプロイの施行が行われてしまいます。
もちろん、停止しているインスタンスにあるエージェントがこの指示を受け取れるわけはないので、しばらくするとタイムアウトで必ず失敗します。

ちなみにマネジメントコンソールで Deployment Group を作成する際にタグを指定するとデプロイ対象のインスタンスが確認できますが、このタイミングで停止しているインスタンスも破棄されたインスタンスもマッチします。
（破棄されたインスタンスへはデプロイの施行はされません）

AWS サポートに問い合わせたところ「現時点ではそういう動作をするようになっているが、ドキュメントへの記載はなく、好ましい動作とは言えないので開発部門へフィードバックする」との旨の返答をいただきました。
（「単純にバグなのでは？」という気もしますが、AWS サポートは丁寧で非常に好感が持てます！）

とりあえず、何かの都合で一部インスタンスを破棄はせず停止しておきたいような場合の対応は、現時点ではインスタンスのタグを一時的に変更するのが手っ取り早くていいと思います。

## 最後に

AWS のサービスは実際に使ってみて初めて知る仕様や、ちょっとしたノウハウが必要なものが多くあり苦戦することもありますが、それもまた面白いところかもしれません。
公式ドキュメントでも日本語化されていないものがあったり、日本語ドキュメントには載ってなくて英語版にだけあるといったことも多々あって英語力の低い僕は大変です・・・。

CodeDeploy もそれに漏れず、いろいろありました。
とは言え、その恩恵は大きく、デプロイのコード化で Excel 方眼紙のデプロイ手順ともおさらばできますね！
AWS CodeBuild が2016年の Re:Invent で発表され、デリバリもどんどん楽になりそうです。