packer
CodeDeploy

Step by StepでBlue/Greenデプロイ(by AWS CodeDeploy)

はじめに

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

スクリーンショット 2018-04-11 18.19.15.png

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

appspec.yml
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化するところで入れます)

script/before_install.sh
#!/bin/sh

rm -rf /Tinitter
script/after_install.sh
#!/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

ソースコードの配備

リポジトリ内のソースコードをデプロイする方法ですが、二通りあります。

  1. CodeDeployとGitHubを連携させ、ソースコードを直接デプロイ
  2. 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

スクリーンショット 2018-04-13 21.02.41.png

ここではhttps://s3-ap-northeast-1.amazonaws.com/code-deploy-s3-sample/tinitter-1.0.0.zipとしました。

WebUI経由でのデプロイ

まずはWebUIからデプロイを実施します。「リビジョンの場所」に先程のS3のURLを入力してください。

スクリーンショット 2018-04-13 21.13.10.png
スクリーンショット 2018-04-13 21.13.24.png

スクリーンショット 2018-04-14 13.05.04.png

http://{EC2のIP}/にもアクセスし、Webアプリがきちんと動作していることも確認しておきましょう。

awscli経由でのデプロイ

毎回WebUI経由でS3にzipをUploadしたり、デプロイを作成したりするのはしんどいです。後々のCIツールとの連携を考えればawscliを使うのがオススメです。

CodeDeploy用のIAMユーザ払い出し

CIツール等から利用することを考慮して、CodeDeploy用のIAMユーザを払い出しておきましょう。まずはCodeDeployへのアクセス権限と、S3への書き込み権限を付与したポリシー(ここではCodeDeployUserPolicy)を作成します。3「IAM」→「ポリシー」→「ポリシーの作成」→「JSON」で作成してください。

スクリーンショット 2018-04-14 17.18.06.png

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」→「ユーザー」→「ユーザーを追加」からユーザを作成し、アクセスキー、シークレットキーを払い出してください。

スクリーンショット 2018-04-14 17.19.48.png
スクリーンショット 2018-04-14 17.27.46.png
スクリーンショット 2018-04-14 17.20.19.png
スクリーンショット 2018-04-14 17.26.10.png

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が払い出されるので、きちんとデプロイできていることを確認しましょう。

スクリーンショット 2018-04-14 17.58.26.png

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

スクリーンショット 2018-04-15 19.01.35.png

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

スクリーンショット 2018-04-15 14.49.04.png
スクリーンショット 2018-04-15 14.49.48.png
スクリーンショット 2018-04-15 14.50.22.png

Auto Scalingの作成

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

スクリーンショット 2018-04-15 15.45.35.png

あとは概ねインプレースデプロイと同じです。

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

スクリーンショット 2018-04-15 15.46.54.png

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

スクリーンショット 2018-04-15 15.47.33.png

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

スクリーンショット 2018-04-15 15.48.07.png
スクリーンショット 2018-04-15 15.48.30.png

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

スクリーンショット 2018-04-15 15.54.36.png

デプロイタイプ変更

「AWS CodeDeploy」→「アプリケーション」→「作成したアプリケーション(CodeDeploySampleApplication)」→「作成したデプロイグループ(CodeDeploySampleDeployGroup)」と選択。「アクション」→「編集」から、「Auto Scalingグループ」に、先程作成したAuto Scaling グループを設定します。

以下の設定も一緒にやっておきましょう。

  • ロードバランシング
    • 作成したELB(ALB)を指定
  • デプロイ方式
    • 一番わかりやすいAllAtOnceに

スクリーンショット 2018-04-15 16.05.02.png
スクリーンショット 2018-04-15 16.05.19.png

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

スクリーンショット 2018-04-15 16.12.14.png

あとはELBに設定されているDNS名でアクセスし、

  • アプリケーションがデプロイされていること
  • 2インスタンスにロードバランシングされていること

が確認できればOKです。

スクリーンショット 2018-04-15 16.48.47.png

5. CodeDeployでBlue/Green デプロイ

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

スクリーンショット 2018-04-15 16.48.47.png
スクリーンショット 2018-04-15 16.49.09.png

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

スクリーンショット 2018-04-15 18.17.16.png
スクリーンショット 2018-04-15 18.19.12.png
スクリーンショット 2018-04-15 18.19.12.png

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

スクリーンショット 2018-04-15 17.13.25.png

終わりに

CodeDeployを使ったBlue/GreenデプロイはIAMやELB、AutoScalingなど考慮する部分が多くて複雑に見えますが、一歩ずつ進めていけば意外とシンプルなのかなと思いました。是非お試しください。

■備考(サンプルアプリケーションについて)

単体やインプレースデプロイで動かしている分には問題なかったのですが、やっていくうちに以下の問題が出てきました。

  • sqliteでやっているのでオートスケールできない
    • DBの内容も吹っ飛ぶ、migrationとかもない
    • プログラム的にはmysqlにも対応しているので、RDSを使うようにしてやればいいはず
      • ただもうCodeDeployと戯れているだけでめんどくさくなり…
  • ELB上で動かしたときに"CSRF token"エラーになる
    • SlimのSession周りとか、csrfプラグインくさい?
      • めんどくさくなって以下のようにスティッキーセッションにしてしまいました…

スクリーンショット 2018-04-15 16.59.13.png

当たり前ですが、Blue/Greenデプロイで動かすWebアプリケーションは、スケーラブルな環境で動くようにしておかないといけないですね…

参考


  1. デプロイのサンプルにいい感じのWebアプリないかなー…とGitHubを検索していて見つけました。フルスタックなPHP製WebAppのエッセンスをうまくチュートリアルレベルに落とし込んである印象で、覚えることが多い初学者には良いサンプルなんじゃないかなと思いました。本の中味は読んでないんですが、この記事書き終わったら買ってみます… 

  2. GitHub決めうちでやる場合は、こちらが参考になります。 GitHubへのpush時にCodeDeployを使ってEC2にアプリケーションをデプロイする - Qiita 

  3. サンプル用にResourceをかなり緩く設定しています。実運用の際はapplication, deploymentgroup, s3のbacketなどをきちんと設定するようにしてください。