8
9

More than 5 years have passed since last update.

Travis CI + AWS CodeDeploy + Slack

Last updated at Posted at 2017-12-09

Travis CI + AWS CodeDeploy + Slack

この記事は mediba Advent Calendar 2017 の10日目です。

@mediba-moritake です。au WebポータルUQライフのサーバーサイド開発をしています。
今回はUQライフの一部で使用しているTravis CIAWS CodeDeploySlack を使用した、PHP アプリケーション(Amazon Linux, Nginx, PHP-FPM, PHP5.6)のデプロイを紹介したいと思います。

AWS CodeDeploy とは

  • EC2 インスタンスに対するアプリケーションのデプロイを自動化するデプロイサービスです。

概要

  • 今回は下記 GitHub ブランチへのマージで、各環境へのデプロイを行います。
    • deployment/production
    • deployment/staging
    • deployment/develop

traisci_codedeploy_slack.png

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 トピックを選択する。
  • 環境変数 slackChannelkmsEncryptedHookUrl を適宜設定する。
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 に乗り換えるメリットはないかもしれない。

今後の CodeDeploy

ご参考

8
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
9