Travis CI + AWS CodeDeploy + Slack
この記事は mediba Advent Calendar 2017 の10日目です。
@mediba-moritake です。au WebポータルやUQライフのサーバーサイド開発をしています。
今回はUQライフの一部で使用しているTravis CI、 AWS CodeDeploy、 Slack を使用した、PHP アプリケーション(Amazon Linux, Nginx, PHP-FPM, PHP5.6)のデプロイを紹介したいと思います。
AWS CodeDeploy とは
- EC2 インスタンスに対するアプリケーションのデプロイを自動化するデプロイサービスです。
概要
- 今回は下記 GitHub ブランチへのマージで、各環境へのデプロイを行います。
- deployment/production
- deployment/staging
- deployment/develop
AWS CodeDeploy
AWS管理コンソール
IAM
- AWS CodeDeploy を使用する IAM ユーザーを作成する。
{
"Version": "2012-10-17",
"Statement" : [
{
"Effect" : "Allow",
"Action" : [
"autoscaling:*",
"codedeploy:*",
"ec2:*",
"elasticloadbalancing:*",
"iam:AddRoleToInstanceProfile",
"iam:CreateInstanceProfile",
"iam:CreateRole",
"iam:DeleteInstanceProfile",
"iam:DeleteRole",
"iam:DeleteRolePolicy",
"iam:GetInstanceProfile",
"iam:GetRole",
"iam:GetRolePolicy",
"iam:ListInstanceProfilesForRole",
"iam:ListRolePolicies",
"iam:ListRoles",
"iam:PassRole",
"iam:PutRolePolicy",
"iam:RemoveRoleFromInstanceProfile",
"s3:*"
],
"Resource" : "*"
}
]
}
CodeDeploy
- AWS CodeDeploy アプリケーションを作成する。
- アプリケーション名に 環境 を定義する。
- uqlife-production
- uqlife-staging
- uqlife-development
- アプリケーション名に 環境 を定義する。
- AWS CodeDeploy デプロイグループ名を作成する。
- デプロイグループ名に サーバーロール を定義する。
- uqlife-api-deployment-group
- uqlife-web-deployment-group
- デプロイグループ名に サーバーロール を定義する。
- デプロイタイプ
-
インプレースデプロイ
を選択する。デプロイタイプの詳細は こちら
-
- 環境設定
-
Amazon EC2 インスタンス
のタググループを指定する。 - タググループに対応したインスタンスを抽出するような キー、値 を選択する。
- 事前にEC2インスタンスにタグを追加しておく必要があります。
-
- デプロイ設定
-
CodeDeployDefault.OneAtATime
を選択する。デプロイ設定の詳細は こちら
-
CodeDeploy Agent インストール/設定
- AWS CodeDeploy を使用できるようになるソフトウェアパッケージ。AWS CodeDeploy でデプロイしたいインスタンスにインストール/設定する。
$ wget https://aws-codedeploy-ap-northeast-1.s3.amazonaws.com/latest/install
$ chmod +x ./install
$ sudo ./install auto
- インストール直後の実行ユーザーは root のため、deploy ユーザーに変更する。
$ sudo service codedeploy-agent stop
$ sed -i 's/""/"deploy"/g' /etc/init.d/codedeploy-agent
$ sudo chown deploy:deploy -R /opt/codedeploy-agent/
$ sudo chown deploy:deploy -R /var/log/aws/
$ sudo service codedeploy-agent start
- 設定ファイル
- デフォルトの設定から変更する必要はなさそうです。設定ファイルの詳細は こちら
/etc/codedeploy-agent/conf/codedeployagent.yml
:log_aws_wire: false
:log_dir: '/var/log/aws/codedeploy-agent/'
:pid_dir: '/opt/codedeploy-agent/state/.pid/'
:program_name: codedeploy-agent
:root_dir: '/opt/codedeploy-agent/deployment-root'
:verbose: false
:wait_between_runs: 1
:proxy_uri:
:max_revisions: 5
Travis CI
-
.travis.yml
を編集します。Travis CI の CodeDeploy 設定は こちら -
Production 環境のみ記述していますが、下記項目が環境毎に異なるように Staging/Develop 環境分も記述します。
- access_key_id
- secret_access_key
- application
- branch
.travis.yml
deploy:
- provider: codedeploy
access_key_id: $prd_codedeploy_access_key_id
secret_access_key: $prd_codedeploy_secret_access_key
application: uqlife-production
deployment_group: uqlife-api-deployment-group
region: ap-northeast-1
on:
repo: mediba/uqlife
branch: deployment/production
- provider: codedeploy
access_key_id: $prd_codedeploy_access_key_id
secret_access_key: $prd_codedeploy_secret_access_key
application: uqlife-production
deployment_group: uqlife-web-deployment-group
region: ap-northeast-1
on:
repo: mediba/uqlife
branch: deployment/production
アプリケーション仕様ファイル(AppSpec ファイル)
-
appspec.yml
ファイルを作成します。アプリケーション仕様ファイル(AppSpec ファイル)の詳細はこちら
appspec.yml
version: 0.0
os: linux
files:
- source: /
destination: /var/www/uqlife-codedeploy/base
- source: /codedeploy
destination: /usr/local/src/codedeploy
permissions:
- object: /var/www/uqlife-codedeploy
owner: deploy
group: deploy
mode: 755
- object: /usr/local/src/codedeploy
owner: deploy
group: deploy
mode: 755
hooks:
AfterInstall:
- location: codedeploy/deploy.sh
timeout: 600
-
appspec.yml
処理概要-
/var/www/uqlife-codedeploy/base
ディレクトリに GitHub リポジトリの全ソースを配置する。 -
/usr/local/src/codedeploy
ディレクトリにデプロイスクリプトを配置する。 -
AfterInstall
ライフサイクルイベントにて、deploy.sh
を実行する。CodeDeploy Agent の実行ユーザー(今回の場合だと deploy ユーザー)で実行されます。
-
デプロイスクリプト
-
deploy.sh
を実装します。 - アクセス出来る環境変数は こちら
deploy.sh
#!/bin/bash
NOW=$(date +%Y%m%d%H%M%S)
mkdir -p /var/www/uqlife-codedeploy/releases/${NOW}
cp -r /var/www/uqlife-codedeploy/base/* /var/www/uqlife-codedeploy/releases/${NOW}
# Composer インストール
export HOME=/home/deploy
export COMPOSER_HOME=/var/www/uqlife-codedeploy/releases/${NOW}/src/
php /var/www/uqlife-codedeploy/releases/${NOW}/src/composer.phar install --no-dev
# 権限設定
sudo chown -Rf nginx:nginx /var/www/uqlife-codedeploy/releases/${NOW}/src/*
sudo chmod -R 775 /var/www/uqlife-codedeploy/releases/${NOW}/src/*
# OPcache リセット
sh -c "echo '<?php opcache_reset(); ?>' > /var/www/html/opcache_reset.php"
curl -s http://localhost/opcache_reset.php
rm -f /var/www/html/opcache_reset.php
# APIシンボリックリンク設定
if [ "$DEPLOYMENT_GROUP_NAME" == "uqlife-api-deployment-group" ]; then
ln -fns /var/www/uqlife-codedeploy/releases/${NOW}/src/api /var/www/uqlife/api
fi
# Webシンボリックリンク設定
if [ "$DEPLOYMENT_GROUP_NAME" == "uqlife-web-deployment-group" ]; then
ln -fns /var/www/uqlife-codedeploy/releases/${NOW}/src/web /var/www/uqlife/web
fi
# 古い releases 削除
RELEASES=(`ls -x /var/www/uqlife-codedeploy/releases`)
if [ ${#RELEASES[@]} -gt 5 ]; then
OLD_RELEASES=(`ls /var/www/uqlife-codedeploy/releases | sort -n | head -n $((${#RELEASES[@]} - 5))`)
for i in ${OLD_RELEASES[@]}; do
rm -rf /var/www/uqlife-codedeploy/releases/${i}
done
fi
exit 0
Slack通知
再びAWS 管理コンソール
SNS
- SNS の新しいトピックを作成する。
CodeDeploy
- CodeDeploy のデプロイグループでトリガーを作成する。
- イベント
- Deployment Status(all)
- Amazon SNS トピック
- 先ほど作成したSNS トピックを選択する。
- イベント
IAM
- Lambda 実行用 IAM ロールを作成する。
-
AWSLambdaBasicExecutionRole
ポリシーを付与する。
-
Lambda
- Lambda 関数の作成を行う。
- 設計図(Blueprints)から
cloudwatch-alarm-to-slack-python3
を選択し、適宜修正する。- CloudWatch アラームをSlack通知する実装となっているため、
message
変数をCodeDeploy 用に修正します。 - ロールは、先ほど作成した IAM ロールを選択する。
- SNS トピックは、先ほど作成した SNS トピックを選択する。
- CloudWatch アラームをSlack通知する実装となっているため、
- 設計図(Blueprints)から
- 環境変数
slackChannel
、kmsEncryptedHookUrl
を適宜設定する。
codedeploy-to-slack.py
import boto3
import json
import logging
import os
from base64 import b64decode
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
# The base-64 encoded, encrypted key (CiphertextBlob) stored in the kmsEncryptedHookUrl environment variable
ENCRYPTED_HOOK_URL = os.environ['kmsEncryptedHookUrl']
# The Slack channel to send a message to stored in the slackChannel environment variable
SLACK_CHANNEL = os.environ['slackChannel']
HOOK_URL = "https://" + boto3.client('kms').decrypt(CiphertextBlob=b64decode(ENCRYPTED_HOOK_URL))['Plaintext'].decode('utf-8')
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
logger.info("Event: " + str(event))
message = json.loads(event['Records'][0]['Sns']['Message'])
logger.info("Message: " + str(message))
region = message['region']
account_id = message['accountId']
event_trigger_name = message['eventTriggerName']
application_name = message['applicationName']
deployment_id = message['deploymentId']
deployment_group_name = message['deploymentGroupName']
create_time = message['createTime']
complete_time = message['completeTime']
status = message['status']
if status == 'FAILED':
slackColor = 'danger'
else:
slackColor = 'good'
slack_message = {
'channel': SLACK_CHANNEL,
'attachments': [
{
'fallback': 'Required plain-text summary of the attachment.',
'color': slackColor,
'title': 'Execute AWS CodeDeploy',
'fields': [
{
'title': 'status',
'value': status
},
{
'title': 'applicationName',
'value': application_name
},
{
'title': 'deploymentGroupName',
'value': deployment_group_name
},
{
'title': 'deploymentId',
'value': deployment_id
},
{
'title': 'createTime',
'value': create_time
}
]
}
]
}
req = Request(HOOK_URL, json.dumps(slack_message).encode('utf-8'))
try:
response = urlopen(req)
response.read()
logger.info("Message posted to %s", slack_message['channel'])
except HTTPError as e:
logger.error("Request failed: %d %s", e.code, e.reason)
except URLError as e:
logger.error("Server connection failed: %s", e.reason)
- 同時に カスタマーマスターキー(CMK)を作成し、先ほど作成したIAM ロールに下記ポリシーをアタッチする。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1443036478000",
"Effect": "Allow",
"Action": [
"kms:Decrypt"
],
"Resource": [
"<your KMS key ARN>"
]
}
]
}
まとめ
- GitHub の Pull Request マージでデプロイを実現することが出来ます。
- アプリケーション名に 環境 を定義することで、各環境に対応したデプロイを実現することが出来ます。
- デプロイグループ名に サーバーロール を定義することで、サーバーロールに対応したデプロイを実現することが出来ます。
- CodeDeploy Agent の実行ユーザーを変更したが、root でも問題ないかもしれない。
- 既存のデプロイシステムが存在し、運用上の問題がないのであれば、AWS CodeDeploy に乗り換えるメリットはないかもしれない。