CodeDeployによるデプロイ環境

# CodeDeployによるデプロイ環境
 
## なぜCodeDeployにしたか

既存環境はPacker + Terraform + Ansibleのみでした。
デプロイはpackerで生成したAMIを使い、開発環境と緊急デプロイ用にファイルのみデプロイを行う二通りの方法を用意していました。
下記がその環境について記事にしたものです。(ちょっと書き換えちゃったのでファイルデプロイの話がCodeDeployに修正されていますが)

http://qiita.com/kanagi/items/6af87a3a59e70e7e06db

アプリケーションの更新に対していちいちAMIを作るのが面倒となってファイルのみデプロイが主になってしまったので、デプロイしつつAMIを作っておくという二度手間が発生して大変よろしくないので切り替えしなきゃということで

1. autoscalingによるインスタンスの起動時にアプリケーションが配備されること
1. 過去バージョンのデプロイが可能なこと

あたりを条件としてみたところ、autoscalingのlifecycle hookをCodeDeployはそのまま使えるというのもありCodeDeployにしてやろうということになりました。

## CodeDeployの概要

CodeDeployのアプリケーションとビルドしたアプケーションでややこしくなるので、ビルドしたアプリケーションファイルを成果物と呼びます。

* アプリケーション・リビジョン・デプロイグループという枠組み
  * アプリケーション: リビジョン・デプロイグループをまとめる単位
  * リビジョン: デプロイする成果物。具体的にはS3のファイル。
  * デプロイグループ: デプロイ対象。autoscaling名とかAWSのタグとか。
* ファイルを`zip|tar|tar.gz`にかためてS3にアップロードしたものかgithubをデプロイ対象とできる
 * giithubは成果物をコミットする必要があるのでS3を選択。
* デプロイを行うインスタンスは`autoscaling group`またはAWSのタグで選ぶ
  * オンプレミスでも使えるようになったようですが使わないのでそっちは調べてません。

## 環境

* AWSの構成   : Terraform
* サーバの構成 : Packer + Ansible
* アプリケーションデプロイ : CodeDeploy

# CodeDeployを設定してみる

## CodeDeployのアプリケーション・デプロイグループを作る

Terraformの0.6.5からCodeDeployが作れるようになってるので当然利用することにします。

公式のドキュメントのexampleで事足りますが、CodeDeployのアプリケーションが必要とするIAMの権限と、デプロイされるインスタンスが必要とするIAMの権限それぞれがあることは注意が必要です。

## デプロイ先にCodeDeployのagentを入れる

公式に載っているいる下記のスクリプト

```
#!/bin/bash -ex
aws s3 cp s3://aws-codedeploy-us-east-1/latest/install . --region us-east-1
chmod +x ./install
./install auto
```
これを`user_data/codedeploy.sh`として配置して、terraformからuser_dataとして登録して起動時に実行させます。
```
user_data = "${file("user_data/codedeploy.sh")}"
```

ここで、autoscalingをデプロイグループとして登録している場合はautoscalingのlifecycleに勝手に組み込まれ、インスタンスの起動時に必ずデプロイが走ってくれます。

しかし、autoscalingではないインスタンスの場合はやってくれないのでスクリプト内で最新のリビジョンをデプロイさせる処理を入れるか、いっそautoscalingにしてしまいましょう。スケーリングポリシーやELBによるヘルスチェックを設定しないautoscalingで立てたインスタンスは、普通のインスタンスとあまり変わりません。

最新をデプロイ実行するスクリプト例：

```
APPLICATION=batch
REGION=us-east-1

DEPLOY_GROUP=$(aws --region ${REGION} deploy get-deployment-group --application-name ${APPLICATION} --deployment-group-name ${APPLICATION}|jq '.deploymentGroupInfo.targetRevision.s3Location')

if [ -z "${DEPLOY_GROUP}" ]
then
echo "no deployment"
exit 0
fi

S3_BUCKET=$(echo "${DEPLOY_GROUP}" | jq .bucket)
S3_KEY=$(echo "${DEPLOY_GROUP}" | jq .key)
BUNDLE_TYPE=$(echo "${DEPLOY_GROUP}" | jq .bundleType)
ETAG=$(echo "${DEPLOY_GROUP}" | jq .eTag)

if [ ${ETAG} != 'null' ]
then
  ETAG_PARAM=,eTag="${ETAG}"
fi

aws --region "${REGION}" deploy create-deployment --application-name "${APPLICATION}" --deployment-group-name "${APPLICATION}" --s3-location bucket="${S3_BUCKET}",key="${S3_KEY}",bundleType="${BUNDLE_TYPE}""${ETAG_PARAM}"
```

## リビジョンを作る

### `appspec.yml`

`appspec.yml`はデプロイでCodeDeployがどう振る舞うかを記述するものです。このファイルは圧縮ファイルのルートディレクトリに配置すればいいだけです。
詳しくはいい記事がいっぱいあるので省略しますが最低でも

* `files`を使って成果物を配置する
* 特定のlifecycle時に`hooks`を使ってスクリプトを実行 (アプリケーションサービスの再起動など)

が出来れば、デプロイ出来たと言えるでしょう。`hooks`で指定できるlifecycleは下記です。

http://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/app-spec-ref.html#app-spec-ref-hooks

つまり、成果物と`appspec.yml`が置かれた状態になるビルドスクリプトを用意しておけばいいです。

注意点として、lifecycleの`Install`で実行される`files`セクションで指定したファイルの配置は、削除後にコピーを行うものなので、それによってアプリケーションの動作に支障をきたす可能性があります。そのままだとアプリケーション起動中にファイルが消えるということです。
`hooks`をうまく使って必要な処理を追加しましょう。

## デプロイする

CodeDeployに対してデプロイを手動で実行するのは面倒なのでCIツールでやります。スクリプトで実行まではできますが、デプロイが成功したかどうかまで見てくれるので任せた方がいいでしょう。

開発環境のデプロイにはCodeshipを、本番環境はjenkinsを用いていたので、それはそのまま使うことにします。
Codeshipは元からデプロイの手順としてCodeDeployが選択でき、jenkinsはCodeDeploy pluginを使います。

どちらもAWSのcredentialとアプリケーション・デプロイグループ、アップロード先のS3バケットとキーを指定すればいいだけです。

相違点は、Codeshipの場合はCodeDeployの手順に進んだときのカレントディレクトリ、jenkinsの場合はworkspaceまたはその指定したサブディレクトリを成果物とするのでビルドスクリプトの作りで注意が必要です。


# 既存の環境をCodeDeployにダウンタイムなしで置き換える

ではCodeDeploy環境に置き換えましょうということでダウンタイムなしで置き換える方法を考えます。

* CodeDeployでデプロイされるには成功したリビジョンが必要
* 既存の環境にデプロイすることはできない

どうするかというと、

1. 先にTerraform上にCodeDeployのアプリケーションとデプロイグループを作る
1. packerでAMIを作る
1. 作ったAMIから手動でインスタンスを作ってユニークなタグをつける
1. デプロイグループがデプロイ先を、上のタグに書き換える
1. CodeDeployによるデプロイ
1. インスタンスを消す
1. デプロイグループのデプロイする先を戻す

これで成功したリビジョンが出来る上に、CodeDeployが成功するAMIかどうかも確認できます。
あとは順に差し替えていくだけです。