CodeDeployによるデプロイ環境
なぜCodeDeployにしたか
既存環境はPacker + Terraform + Ansibleのみでした。
デプロイはpackerで生成したAMIを使い、開発環境と緊急デプロイ用にファイルのみデプロイを行う二通りの方法を用意していました。
下記がその環境について記事にしたものです。(ちょっと書き換えちゃったのでファイルデプロイの話がCodeDeployに修正されていますが)
アプリケーションの更新に対していちいちAMIを作るのが面倒となってファイルのみデプロイが主になってしまったので、デプロイしつつAMIを作っておくという二度手間が発生して大変よろしくないので切り替えしなきゃということで
- autoscalingによるインスタンスの起動時にアプリケーションが配備されること
- 過去バージョンのデプロイが可能なこと
あたりを条件としてみたところ、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は下記です。
つまり、成果物と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でデプロイされるには成功したリビジョンが必要
- 既存の環境にデプロイすることはできない
どうするかというと、
- 先にTerraform上にCodeDeployのアプリケーションとデプロイグループを作る
- packerでAMIを作る
- 作ったAMIから手動でインスタンスを作ってユニークなタグをつける
- デプロイグループがデプロイ先を、上のタグに書き換える
- CodeDeployによるデプロイ
- インスタンスを消す
- デプロイグループのデプロイする先を戻す
これで成功したリビジョンが出来る上に、CodeDeployが成功するAMIかどうかも確認できます。
あとは順に差し替えていくだけです。