ファイルトリガーでAWS Elastic Beanstalkのデプロイを自動実行する

2015.07.30

超おはようございます、暑さにはめっきり弱い城内です。 冬の寒さも苦手ですが、夏の暑さは汗をかくのが嫌ですね。

はじめに

さて、今回は以下の記事に出てきたBeanstalkのアプリケーションデプロイを自動化するスクリプトを紹介したいと思います。

上記で紹介されているものと何が違うかというと、ファイルをトリガーにデプロイを実行するという点です。 今回のスクリプトをEC2上で定期実行しておけば、SSHログインすることなく、SFTP等でトリガーファイルをアップロードするだけでアプリケーションをデプロイできるようになります。

構成図

今回は、以前記事にしたGravを使ったCMSシステムを使用します。もちろん、WordPressでも実装することは可能です。

grab_web

セットアップ

手順

セットアップ手順は、以下のステップになります。

  1. MasterサーバにIAMロールを付与する
  2. BashスクリプトをMasterサーバに配置する
  3. BashスクリプトをCronに設定する(ここでトリガーファイルの格納先とファイル名が決まる)
  4. トリガーファイルを作成する

IAMロール

IAMロールのポリシーは、以下の感じになります。 (もっと制限することも可能ですが、Beanstalkの内部処理に関わってきますので、必要なものを洗い出すのは結構大変です)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "elasticbeanstalk:CreateApplicationVersion",
                "elasticbeanstalk:UpdateEnvironment"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:elasticbeanstalk:ap-northeast-1:123456789012:applicationversion/grav-test/*",
                "arn:aws:elasticbeanstalk:ap-northeast-1:123456789012:environment/grav-test/grav-test-prod"
            ],
            "Condition": {
                "StringEquals": {
                    "elasticbeanstalk:InApplication": [
                        "arn:aws:elasticbeanstalk:ap-northeast-1:123456789012:application/grav-test"
                    ]
                }
            }
        },
        {
            "Action": [
                "s3:*",
                "autoscaling:*",
                "cloudformation:*",
                "ec2:*",
                "elasticloadbalancing:*"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}

スクリプト

スクリプトは、以下のBashスクリプトになります。前提として、AWS CLIが使用できる(リージョン指定をお忘れなく)ようになってさえいれば、特に変数定義等の必要もなくそのまま使用できるはずです。 これをMasterサーバの任意のディレクトリに配置してください。

eb_auto_deploy.sh

#!/bin/bash

### Variables
label_prefix="grav_"

### Function
function usage {
	echo "Usage: ${0##*/} trigger_file"
	exit 0
}

function myecho {
	echo "${0##*/}: $1"
}

### Main
if [ $# -ne 1 ]; then
	usage
fi

trigger_file=$1
if [ ! -f $trigger_file ]; then
	myecho "$(date '+%Y/%m/%d %H:%M:%S') [INFO] $trigger_file does not exist."
	exit 0
fi

error_file="${trigger_file}.err"
if [ -f $error_file ]; then
	myecho "$(date '+%Y/%m/%d %H:%M:%S') [WARNING] The deployment was canceled because an error file existed."
	exit 1
fi

myecho "$(date '+%Y/%m/%d %H:%M:%S') [INFO] EB auto deploy start."

grep -ve "^#" -e "^$" $trigger_file |
while read source_dir region s3_path eb_app eb_env pre_cmd
do
	export AWS_DEFAULT_REGION=$region

	s3_bucket="${s3_path%%/*}"
	s3_key="${s3_path#*/}"
	if [ "$s3_bucket" = "$s3_key" ]; then
		s3_key=""
	else
		s3_key="${s3_key}/"
	fi

	if [ "$pre_cmd" ]; then
		($pre_cmd)
		if [ $? -ne 0 ]; then
			myecho "[ERROR] PreExec command failed." | tee -a $error_file
			continue
		fi
	fi

	ver_label="${label_prefix}$(date '+%Y%m%d%H%M%S')"
	(cd $source_dir && zip -ry /tmp/${ver_label}.zip . 1>/dev/null && aws s3 cp /tmp/${ver_label}.zip s3://${s3_path}/ && rm -f /tmp/${ver_label}.zip)
	if [ $? -ne 0 ]; then
		myecho "[ERROR] Application upload failed." | tee -a $error_file
		continue
	fi

	aws elasticbeanstalk create-application-version --application-name $eb_app --version-label $ver_label --source-bundle S3Bucket=${s3_bucket},S3Key=${s3_key}${ver_label}.zip
	if [ $? -ne 0 ]; then
		myecho "[ERROR] EB application regist failed." | tee -a $error_file
		continue
	fi

	aws elasticbeanstalk update-environment --environment-name $eb_env --version-label $ver_label
	if [ $? -ne 0 ]; then
		myecho "[ERROR] EB application deploy failed." | tee -a $error_file
		continue
	fi
done

if [ -f $error_file ]; then
	rc=1
else
	rc=0
	rm -f $trigger_file
fi

myecho "$(date '+%Y/%m/%d %H:%M:%S') [INFO] EB auto deploy end.(RC=${rc})"

exit $rc

スクリプトの仕組みとしては、引数に渡されるトリガーファイルを読み込んで、そこに定義されている情報を基に、Beanstalkのデプロイを実行するだけです(トリガーファイルの定義内容は後述します)。 そして、処理が成功した場合は、最後にトリガーファイルが削除されます。もし処理が失敗した場合は、エラーファイルを出力し、次回以降の処理がスキップされるようになっています。トリガーファイルは削除されないため、エラーファイルを削除すれば、次のサイクルから処理が再開されます。

Cron設定

MasterサーバにアップロードしたスクリプトをCronに設定します。 今回はrootユーザに設定しましたが、権限の設定をうまく調整すれば別のユーザでも可能です。

# crontab -e
0,15,30,45 * * * * /root/scripts/eb_auto_deploy.sh /eb_deploy/grav.tsv >> /eb_deploy/logs/eb_auto_deploy_`date "+\%Y\%m\%d"`.log 2>&1

15分毎の実行で、トリガーファイルは/eb_deploy/grav.tsvにしています。あとは、一応ログも出力しておきます。 (今回の設定では、別途ログパージの仕組みが必要になります)

トリガーファイル

トリガーファイルのフォーマットは、タブ区切りで1レコードに以下の項目を記述します。 ※なぜタブ区切りなの!?と思われる方は、スクリプトを修正しちゃってください(笑)。私も気に入ってはいませんので。

  • Beanstalkにデプロイするソースディレクトリ(絶対パス)
  • リージョン名
  • S3バケットの格納先(バケット名を含むパス)
  • Beanstalkのアプリケーション名
  • Beanstalkの環境名
  • 自動デプロイ処理の前に事前実行したいコマンドライン(ワンライナーで)

以下のような内容になります(先頭に「#」でコメントアウトできます)。

# Source directory	Region	S3 path	Elastic Beanstalk application	Elastic Beanstalk environment	PreExec command
/var/www/html	ap-northeast-1	cm-grav-test/resource	grav-test	grav-test-prod	cd /var/www/html && sudo php bin/grav clear-cache --all && aws s3 sync /var/www/html/user/images s3://cm-grav-test/user/images

実行

あとは、grav.tsvファイルを/eb_deployディレクトリにおいて、時を待つだけです。

# ls -l
total 8
-rw-r--r-- 1 root root  291 Jul 29 20:41 grav.tsv
drwxr-xr-x 2 root root 4096 Jul 30 00:00 logs
# date
Thu Jul 30 00:02:40 JST 2015
...
# date
Thu Jul 30 00:18:04 JST 2015
# ls -l
total 4
drwxr-xr-x 2 root root 4096 Jul 30 00:04 logs
# cat logs/eb_auto_deploy_20150730.log
eb_auto_deploy.sh: 2015/07/30 00:00:01 [INFO] /eb_deploy/grav.tsv does not exist.
eb_auto_deploy.sh: 2015/07/30 00:15:01 [INFO] EB auto deploy start.
upload: /tmp/grav_20150730001501.zip to s3://cm-grav-test/resource/grav_20150730001501.zip
{
    "ApplicationVersion": {
        "ApplicationName": "grav-test",
        "VersionLabel": "grav_20150730001501",
        "SourceBundle": {
            "S3Bucket": "cm-grav-test",
            "S3Key": "resource/grav_20150730001501.zip"
        },
        "DateUpdated": "2015-07-29T15:15:03.857Z",
        "DateCreated": "2015-07-29T15:15:03.857Z"
    }
}
{
    "ApplicationName": "grav-test",
    "EnvironmentName": "grav-test-prod",
    "VersionLabel": "grav_20150730001501",
    "Status": "Updating",
    "EnvironmentId": "e-12345abcde",
    "EndpointURL": "11.22.33.44",
    "SolutionStackName": "64bit Amazon Linux 2015.03 v1.4.3 running PHP 5.6",
    "CNAME": "grav-test-prod.elasticbeanstalk.com",
    "Health": "Grey",
    "AbortableOperationInProgress": true,
    "Tier": {
        "Version": " ",
        "Type": "Standard",
        "Name": "WebServer"
    },
    "DateUpdated": "2015-07-29T15:15:04.535Z",
    "DateCreated": "2015-07-26T15:24:02.735Z"
}
eb_auto_deploy.sh: 2015/07/30 00:15:05 [INFO] EB auto deploy end.(RC=0)

上記がサーバ上の実行結果で、以下がマネージメントコンソール上での実行結果です。

eb_01

eb_02

うまくデプロイできていますね。

さいごに

そんなに大した仕組みではありませんが、SFTPだけでデプロイができるというのは、Beanstalk利用の敷居を下げられるものだと思っていますので、もしよかったらぜひ使ってみてください!