はじめに
AWS CodeDeployを使ってBlue/Greenデプロイを行うサンプルです。
ネット上に情報はちらほらあるのですが(大変参考にさせていただきました)、実際のWebアプリケーションデプロイ手順に近いサンプルがなかったので、こちらにまとめてみました。
「Blue-Green Deploymentに興味はあるんだけど、めんどくさそうだしなあ…」という方の助けになれば幸いです。
1.「普通に」デプロイ
まずはWebアプリケーションを「通常のデプロイ手順どおり」デプロイします。CodeDeployを動かしながらWebアプリのデプロイをトライアンドエラーしていくのは大変しんどいです。Webアプリ側は**「手順書通りにやれば、確実にアプリケーションが動かせる」**状態にしておきましょう。
今回はこちらのサンプルWebアプリケーションを利用させていただきました。uzulla/Tinitter: PHP sample web application
Slim+Eloquentで作られているシンプルなWebアプリで、PHPのWebアプリ経験者ならだいたい何をやってるのか想像つくかと思いますので、細かい説明は省いてます。ご容赦ください。1
- t2.micro
- Amazon Linux AMI 2017.09.1 | 64bit
- t2.micro
- セキュリティグループで
HTTP 80, SSH 22
を開放
インスタンス作成後、SSHログインして進めていきます。
// OS設定
$ sudo yum -y update
$ sudo cp /etc/localtime /etc/localtime.org && \
sudo cp -p /usr/share/zoneinfo/Japan /etc/localtime
// PHP(5.6)の導入
// 4年前の書籍のソースコードなので、楽に入れられて互換性問題が起きにくそうな、5.6系を入れる
$ sudo yum install php56 php56-pdo php56-mbstring
$ php -v
PHP 5.6.35 (cli) (built: Apr 3 2018 21:27:01)
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies
// DBにはsqliteを使うので確認
$ php -i | grep pdo_sqlite
/etc/php-5.6.d/30-pdo_sqlite.ini,
PHP Warning: Unknown: It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in Unknown on line 0
pdo_sqlite
// php.iniの設定
$ sudo cp /etc/php.ini /etc/php.ini.orig
$ sudo sed -i 's/;internal_encoding =/internal_encoding = UTF-8/g' /etc/php.ini
$ sudo sed -i 's@;date.timezone =@date.timezone = "Asia/Tokyo"@g' /etc/php.ini
// ソースコードの配置
// ここではgit clone(ssh agent利用)でソースコードを取得していますが、wget等でも可
$ cd ~/
$ sudo yum -y install git
$ git clone git@github.com:uzulla/Tinitter.git
$ sudo mv ~/Tinitter /
$ ls /Tinitter
LICENSE README.md composer.json composer.lock config.php.sample htdocs lib schema.mysql.sql schema.sqlite3.sql templates test
ビルトインのWebサーバを使い、疎通を確認。
// Composer配置、ライブラリ導入
$ cd /Tinitter
$ php -r "eval('?>'.file_get_contents('https://getcomposer.org/installer'));"
$ php composer.phar install
// アプリケーションのセットアップ
$ cp config.php.sample config.php
$ sqlite3 sqlite.db < schema.sqlite3.sql
// 動作確認
$ cd /Tinitter/htdocs
$ sudo php -S 0.0.0.0:80
PHP 5.6.35 Development Server started at Wed Apr 11 09:04:30 2018
Listening on http://0.0.0.0:80
Document root is /home/ec2-user/Tinitter
Press Ctrl-C to quit.
http://{IPアドレス}/ にアクセスして、画面が表示されるのを確認。単体テストもあるので確認しておきます。
$ cd /Tinitter/test/
$ ../vendor/bin/phpunit
PHPUnit 3.7.28 by Sebastian Bergmann.
Configuration read from /home/ec2-user/Tinitter/test/phpunit.xml
..........
Time: 635 ms, Memory: 17.00Mb
OK (10 tests, 41 assertions)
続いてMW設定を行います。ビルトインサーバでも問題ないんですが、サンプルに丁度いいのでApache(httpd)で動かします。
// .htaccess(rewrite系)を設定
$ cd /Tinitter/htdocs
$ cp .htaccess.sample .htaccess
// バーチャルホストを設定
$ sudo rm -rf /etc/httpd/conf.d/welcome.conf
$ cat <<EOL >> tinitter.conf
<VirtualHost *:80>
DocumentRoot "/Tinitter/htdocs"
<Directory "/Tinitter/htdocs">
AllowOverride All
Options All
Require all granted
</Directory>
</VirtualHost>
EOL
$ sudo mv tinitter.conf /etc/httpd/conf.d/tinitter.conf
// httpdからアクセスできるように権限周りを設定
$ sudo chown -R apache:apache /Tinitter
$ sudo chmod 777 /Tinitter/sqlite.db
// httpd起動・自動起動設定
$ sudo service httpd start
Starting httpd: [ OK ]
$ sudo chkconfig httpd on

2. CodeDeployの動作確認
次に、AWS CodeDeploy(以下CodeDeploy)の動作確認を行いますが、長くなったので別記事に分けました。
AWS CodeDeployで公式サンプルを動かす(入門者向け・WebUIベース・インプレースデプロイ) - Qiita
慣れている人は省略してしまっても良いですが、IAM等が絡んでめんどくさいので、単体で動作確認をしておくのがオススメです。
(なお、今回作成したEC2に公式のCodeDeployサンプルアプリケーションを載せようとすると、php5.6が利用しているapacheと、公式が入れようとしているApacheのバージョン不整合でエラーになります。ご注意ください)
3. CodeDeployで既存インスタンスにインプレースデプロイ
ここから本編です。まずは作成した既存のインスタンスに、インプレースデプロイでリポジトリからソースコードを配備して動かします。
appspec.ymlの追加
forkしてappspec.yml
を追加します。terukizm/Tinitter: PHP sample web application
version: 0.0
os: linux
files:
- source: /
destination: /Tinitter
hooks:
BeoreInstall:
- location: script/before_install.sh
timeout: 300
runas: root
AfterInstall:
- location: script/after_install.sh
timeout: 300
runas: root
ソースコードを/Tinitter
に配備する前、配備した後の処理もスクリプト化しておきましょう。ここではMW関係のインストールや設定は省略しています。(そちらはAMI化するところで入れます)
# !/bin/sh
rm -rf /Tinitter
# !/bin/sh
cd /Tinitter
php -r "eval('?>'.file_get_contents('https://getcomposer.org/installer'));"
php composer.phar install
cp config.php.sample config.php
sqlite3 sqlite.db < schema.sqlite3.sql
cd /Tinitter/htdocs
cp .htaccess.sample .htaccess
chown -R apache:apache /Tinitter
chmod 777 /Tinitter/sqlite.db
参考: CodedeployのLifeCycleに関するメモ - Qiita
ソースコードの配備
リポジトリ内のソースコードをデプロイする方法ですが、二通りあります。
- CodeDeployとGitHubを連携させ、ソースコードを直接デプロイ
- GitHubのソースコードをzipに固めてS3に格納し、S3経由でデプロイ
GitHubを利用する前提なら1.が楽ですが2、今回は汎用性が高い(S3にzipで固めて置ければVCS等の種類を問わない)2.の方法で進めます。
デプロイ用のS3バケット(code-deploy-s3-sample
)を作成し、リポジトリをgit archive
コマンドでzipに固めたものを、適当な名前でUploadします。
$ git clone git@github.com:terukizm/Tinitter.git
$ git archive HEAD --output=/tmp/tinitter-1.0.0.zip

ここではhttps://s3-ap-northeast-1.amazonaws.com/code-deploy-s3-sample/tinitter-1.0.0.zip
としました。
WebUI経由でのデプロイ
まずはWebUIからデプロイを実施します。「リビジョンの場所」に先程のS3のURLを入力してください。



http://{EC2のIP}/にもアクセスし、Webアプリがきちんと動作していることも確認しておきましょう。
awscli経由でのデプロイ
毎回WebUI経由でS3にzipをUploadしたり、デプロイを作成したりするのはしんどいです。後々のCIツールとの連携を考えればawscliを使うのがオススメです。
CodeDeploy用のIAMユーザ払い出し
CIツール等から利用することを考慮して、CodeDeploy用のIAMユーザを払い出しておきましょう。まずはCodeDeployへのアクセス権限と、S3への書き込み権限を付与したポリシー(ここではCodeDeployUserPolicy
)を作成します。3「IAM」→「ポリシー」→「ポリシーの作成」→「JSON」で作成してください。

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"codedeploy:RegisterApplicationRevision",
"codedeploy:GetApplicationRevision"
],
"Resource": [
"arn:aws:codedeploy:*:*:application:*"
]
},
{
"Effect": "Allow",
"Action": [
"codedeploy:CreateDeployment",
"codedeploy:GetDeployment"
],
"Resource": [
"arn:aws:codedeploy:*:*:deploymentgroup:*"
]
},
{
"Effect": "Allow",
"Action": [
"codedeploy:GetDeploymentConfig"
],
"Resource": [
"arn:aws:codedeploy:*:*:deploymentconfig:CodeDeployDefault.OneAtATime",
"arn:aws:codedeploy:*:*:deploymentconfig:CodeDeployDefault.HalfAtATime",
"arn:aws:codedeploy:*:*:deploymentconfig:CodeDeployDefault.AllAtOnce"
]
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::*/*"
]
}
]
}
このポリシーが設定されているユーザ(ここではCodeDeploySampleUser
としました)を作成します。「IAM」→「ユーザー」→「ユーザーを追加」からユーザを作成し、アクセスキー、シークレットキーを払い出してください。




awscli経由での動作確認
awscliに作成したユーザのアクセスキー、シークレットキーを設定します。
参照: AWS CLI で S3 にファイルをアップロード - Qiita
// MacOSから実行
$ brew install awscli
// アクセスキー、シークレットキーを設定(profile = code-deploy-sample)
$ aws configure --profile code-deploy-sample
AWS Access Key ID [None]: XXXXXXXXXXXXX
AWS Secret Access Key [None]: XXXXXXXXXXXXXXXXXXXX
Default region name [None]: ap-northeast-1
Default output format [None]: json
あとはWebUIから手動で行ったものと同じです。慣れればこっちの方が断然楽ですね。
// zipで固める
$ git archive HEAD --output=/tmp/tinitter-1.0.1.zip
// S3にアップロード
$ aws s3 cp /tmp/tinitter-1.0.1.zip s3://code-deploy-s3-sample/ --profile code-deploy-sample
upload: ../../../../tmp/tinitter-1.0.1.zip to s3://code-deploy-s3-sample/tinitter-1.0.1.zip
// CodeDeployでインプレースデプロイ
$ aws deploy create-deployment \
--region ap-northeast-1 \
--application-name CodeDeploySampleApplication \
--deployment-group-name CodeDeploySampleDeployGroup \
--s3-location bucket=code-deploy-s3-sample,key=tinitter-1.0.1.zip,bundleType=zip \
--profile code-deploy-sample
{
"deploymentId": "d-YXO73HUSR"
}
deploymentIdが払い出されるので、きちんとデプロイできていることを確認しましょう。

4. CodeDeployでELB+AutoScaling環境にインプレースデプロイ
ELB+AutoScalingの環境にデプロイできることを確認します。なお、この環境が初回Blue/Greenデプロイでの「Blue」環境に相当するものになります。
AMI作成
Blue/Greenデプロイは、デプロイの度に新しいインスタンスを生成します。元となるAMIは既存インスタンスから手動で作っても良いのですが、packerでコード化しておくと後々のメンテナンスが楽です。
// MacOSから実行
$ brew install packer
$ cd ~/Tinitter/packer
$ cp credential.json.example credential.json
$ vi credential.json
---
(AWSKEY, SECRETKEYを設定)
---
$ packer build --force -var-file=credential.json ami.json
==> amazon-ebs: Force Deregister flag found, skipping prevalidating AMI Name
amazon-ebs: Found Image ID: ami-df455ba3
(略)
==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs: AMIs were created:
ap-northeast-1: ami-df455ba3

Tinitterアプリケーションへの修正
Blue/Greenデプロイとは無関係ですが、TinitterアプリケーションにインスタンスIDを表示させるようにしておきます。
参考: EC2インスタンス内から情報を取得する方法 - Qiita
これで、新しいインスタンスで稼働していること、ロードバランシングされていることがわかりやすくなります。
ELB(ALB)の作成
「AWS EC2」→「ロードバランシング」→「ロードバランサー」→「ロードバランサーの作成」→「Application Load Balancer」としてELB(ALB)を作成します。
ここでは以下の設定で作成しました。
- CodeDeploySampleELB
- ターゲットグループ
- CodeDeploySampleTargetGroup
- セキュリティグループ
- 80番(HTTP)のみ開放
- アベイラビリティゾーン
- ap-northeast-1a
- ap-northeast-1c
- ターゲットグループ



Auto Scalingの作成
続いてAuto Scaling、 AutoScalingグループを作成します。「AWS EC2」→「AUTO SCALING」→「起動設定」として、先程作成したAMIを指定。

あとは概ねインプレースデプロイと同じです。
- ec2.nano (2つ起動するので一番安いのにしました)
- IAMロール: EC2->S3のアクセス権限ロール(EC2User)
- セキュリティグループ: 80番と22番を開放

そのままAuto Scalingグループも作成。2インスタンスで起動します。

AutoScalingを利用する場合、タグでインスタンスを区別する必要はないのですが、設定しておくとわかりやすいので、適宜設定しておくと良いかもしれません。


作成完了後、2インスタンスが起動していればOKです。

デプロイタイプ変更
「AWS CodeDeploy」→「アプリケーション」→「作成したアプリケーション(CodeDeploySampleApplication)」→「作成したデプロイグループ(CodeDeploySampleDeployGroup)」と選択。「アクション」→「編集」から、「Auto Scalingグループ」に、先程作成したAuto Scaling グループを設定します。
以下の設定も一緒にやっておきましょう。
- ロードバランシング
- 作成したELB(ALB)を指定
- デプロイ方式
- 一番わかりやすいAllAtOnceに


起動している2インスタンスが自動的に選択されているのを確認し、「デプロイ設定が変更されました」にチェックを入れて「保存して今すぐデプロイ」を押下。前回のデプロイ内容で自動的にデプロイが開始されます。

あとはELBに設定されているDNS名でアクセスし、
- アプリケーションがデプロイされていること
- 2インスタンスにロードバランシングされていること
が確認できればOKです。

5. CodeDeployでBlue/Green デプロイ
ここまで来れば、あとは「インプレースデプロイ」から「Blue/Greenデプロイ」に変更するだけです。


オートスケーリングにかかるのと同じくらいの時間で新環境が生成され、そちらにトラフィックが流れれば成功です。お疲れ様でした!



なお、デフォルト設定ではデプロイ成功後もBlue環境が1時間並行稼働するので(切り戻し対策?)、適宜変更してください。

終わりに
CodeDeployを使ったBlue/GreenデプロイはIAMやELB、AutoScalingなど考慮する部分が多くて複雑に見えますが、一歩ずつ進めていけば意外とシンプルなのかなと思いました。是非お試しください。
■備考(サンプルアプリケーションについて)
単体やインプレースデプロイで動かしている分には問題なかったのですが、やっていくうちに以下の問題が出てきました。
- sqliteでやっているのでオートスケールできない
- DBの内容も吹っ飛ぶ、migrationとかもない
- プログラム的にはmysqlにも対応しているので、RDSを使うようにしてやればいいはず
- ただもうCodeDeployと戯れているだけでめんどくさくなり…
- ELB上で動かしたときに"CSRF token"エラーになる
- SlimのSession周りとか、csrfプラグインくさい?
- めんどくさくなって以下のようにスティッキーセッションにしてしまいました…
- SlimのSession周りとか、csrfプラグインくさい?

当たり前ですが、Blue/Greenデプロイで動かすWebアプリケーションは、スケーラブルな環境で動くようにしておかないといけないですね…
参考
- やってみて分かった AWS CodeDeploy の落とし穴 - Qiita
- CircleCI と CodeDeploy のチュートリアル(出来るだけ awscli でやるよ) | cloudpack.media
-
デプロイのサンプルにいい感じのWebアプリないかなー…とGitHubを検索していて見つけました。フルスタックなPHP製WebAppのエッセンスをうまくチュートリアルレベルに落とし込んである印象で、覚えることが多い初学者には良いサンプルなんじゃないかなと思いました。本の中味は読んでないんですが、この記事書き終わったら買ってみます… ↩
-
GitHub決めうちでやる場合は、こちらが参考になります。 GitHubへのpush時にCodeDeployを使ってEC2にアプリケーションをデプロイする - Qiita ↩
-
サンプル用にResourceをかなり緩く設定しています。実運用の際はapplication, deploymentgroup, s3のbacketなどをきちんと設定するようにしてください。 ↩