EC2/Laravel環境とCode4兄弟による継続的デプロイ環境をCloudFormation/Ansibleでコード化してみました

AWS CLIでの構築手順だけだとインフラのメンテナンス大変そうだなと思ったのでAWS CLIでCode4兄弟によるEC2+nginx+Laravelの継続的デプロイ環境を構築するで作ったの環境をCloudFormationとAnsibleでコード化してみました。

今回のコード

下記、タグv0.0.0になります。

github.com

環境構成

以前の記事の構成とほとんど同じ構成です。

f:id:nihma:20190707124913p:plain

CloudFormation化にあたりスタックを下記の5つに分けました。

スタック 説明 リソース
CodeStore コード管理 AWS::CodeCommit::Repository
BuildEnv ビルド環境 AWS::ECR::Repository, AWS::IAM::Role, AWS::CodeBuild::Project
Network VPC, サブネット環境 AWS::EC2::VPC, AWS::EC2::InternetGateway, AWS::EC2::VPCGatewayAttachment, AWS::EC2::Subnet, AWS::EC2::RouteTable, AWS::EC2::Route, AWS::EC2::SubnetRouteTableAssociation
EC2 EC2環境 AWS::EC2::SecurityGroup, AWS::EC2::EIP, AWS::IAM::Role, AWS::IAM::Policy, AWS::IAM::InstanceProfile, AWS::EC2::Instance, AWS::EC2::EIPAssociation
DeployPipeline デプロイ環境 AWS::S3::Bucket, AWS::IAM::Role, AWS::CodeBuild::Project, AWS::CodeDeploy::Application, AWS::CodeDeploy::DeploymentGroup, AWS::CodePipeline::Pipeline, AWS::Events::Rule

EC2インスタンス内のセットアップはUserDataとCodeDeploy/Ansibleで行うようにしました。

UserDataで行うのはamazon-linux-extrasやyumによるパッケージインストールとCodeDeploy agentのインストールです。

infra/EC2.cfn.yml:L75-L85

  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      # ...(省略)...
      UserData: !Base64
        Fn::Sub: |
          #!/bin/bash
          yum -y update
          amazon-linux-extras install ansible2 nginx1.12 php7.3 -y
          yum install -y php-fpm php-mbstring php-xml php-bcmath
          yum install -y python2-pip ruby
          cd /home/ec2-user
          curl -O https://aws-codedeploy-${AWS::Region}.s3.amazonaws.com/latest/install
          chmod +x ./install
          ./install auto

上記以外のセットアップ処理はCodeDeployにてansible-playbookのローカル実行で行うようにしました。

aws_deploy.sh:L7

# Setup EC2
ansible-playbook -i localhost, -c local ${DESTINATION_PATH}/infra/setup_ec2.yml

コードのディレクトリ構成は下記です。

.
├── Dockerfile_ecr    ... ECR向けのdockerイメージのDockerfile
├── appspec.yml       ... CodeDeployデプロイ定義
├── aws_deploy.sh     ... CodeDeployデプロイ処理
├── buildspec.yml     ... CodeBuildビルド定義
├── buildspec_ecr.yml ... ECR向けのCodeBuildビルド定義
├── infra             ... CloudFormationやAnsibleのテンプレート
│   └── templates     ... Ansibleのテンプレート
└── laravel           ... Laravelプロジェクト

構築手順

README.mdの通りです。

CodeCommitを構築

前回の記事の「LaravelプロジェクトをCodeCommit管理にする」です。

コード管理の場所を用意してGitHubから取り込みます。

git clone https://github.com/nihemak/laravel-ec2-sample.git
cd laravel-ec2-sample
aws cloudformation validate-template \
    --template-body file://infra/CodeStore.cfn.yml
aws cloudformation create-stack \
    --stack-name laravel-ec2-sample-CodeStore \
    --template-body file://infra/CodeStore.cfn.yml
git push ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/laravel-ec2-sample --all

Laravelプロジェクトのビルドに必要なECR環境を構築

前回の記事の「CodeBuild環境を整える」です。

Laravelプロジェクトのビルドで使用するEC2インスタンスと同じ環境のdockerイメージが登録されたECRリポジトリを用意します。

aws cloudformation validate-template \
    --template-body file://infra/BuildEnv.cfn.yml
aws cloudformation create-stack \
    --stack-name laravel-ec2-sample-BuildEnv \
    --capabilities CAPABILITY_NAMED_IAM \
    --parameters \
      ParameterKey=CodeCommitStackName,ParameterValue=laravel-ec2-sample-CodeStore \
    --template-body file://infra/BuildEnv.cfn.yml
CODEBUILD_ID=$(aws codebuild start-build --project-name laravel-ec2-sample-ecr --source-version master | tr -d "\n" | jq -r '.build.id')
echo "started.. id is ${CODEBUILD_ID}"
while true
do
  sleep 10s
  STATUS=$(aws codebuild batch-get-builds --ids "${CODEBUILD_ID}" | tr -d "\n" | jq -r '.builds[].buildStatus')
  echo "..status is ${STATUS}."
  if [ "${STATUS}" != "IN_PROGRESS" ]; then
    if [ "${STATUS}" != "SUCCEEDED" ]; then
      echo "faild."
    fi
    echo "done."
    break
  fi
done

VPC/サブネット環境とECインスタンスを構築

前回の記事の「最初のEC2+nginx+Laravelな環境を作成する」です。

まずEC2インスタンスへのSSHアクセスの為にキーペア/pemを作成します。

key_pair=$(aws ec2 create-key-pair --key-name test-laravel)
echo $key_pair | jq -r ".KeyMaterial" > test-laravel.pem
chmod 400 test-laravel.pem

そしてVPC/サブネット環境とLaravelプロジェクトを動かすECインスタンスを用意します。

aws cloudformation validate-template \
    --template-body file://infra/Network.cfn.yml
aws cloudformation create-stack \
    --stack-name laravel-ec2-sample-Network \
    --capabilities CAPABILITY_NAMED_IAM \
    --template-body file://infra/Network.cfn.yml
aws cloudformation validate-template \
    --template-body file://infra/EC2.cfn.yml
aws cloudformation create-stack \
    --stack-name laravel-ec2-sample-EC2 \
    --capabilities CAPABILITY_NAMED_IAM \
    --parameters \
      ParameterKey=NetworkStackName,ParameterValue=laravel-ec2-sample-Network \
    --template-body file://infra/EC2.cfn.yml

デプロイのパイプラインを構築

前回の記事の「CodeDeploy環境を整える」「CodeBuild環境を整える」「CodePipeline環境を整える」「CodeBuildをキャッシュ対応する」「CloudWatch EventsでCodePipelineが自動実行されるよう設定する」です。

CodePipelineおよびCodeBuildとCodeDeploy、CodeCommitを監視してCodePipelineを実行するCloudWatch Eventsを用意します。

aws cloudformation validate-template \
    --template-body file://infra/DeployPipeline.cfn.yml
aws cloudformation create-stack \
    --stack-name laravel-ec2-sample-DeployPipeline \
    --capabilities CAPABILITY_NAMED_IAM \
    --parameters \
      ParameterKey=CodeCommitStackName,ParameterValue=laravel-ec2-sample-CodeStore \
    --template-body file://infra/DeployPipeline.cfn.yml

作成が終わると自動でCodePipelineが実行されEC2インスタンスにLaravelプロジェクトがデプロイされます。

使い方

README.mdの通りです。

SSHアクセス

まずEC2のCloudFormationスタックからIPアドレスを取得します。

ec2_ip=$(\
  aws cloudformation describe-stacks --stack-name laravel-ec2-sample-EC2 \
   | jq -r '.Stacks[].Outputs[] | select(.OutputKey == "EC2IP").OutputValue')
echo ${ec2_ip}

pemとIPアドレスでEC2インスタンスSSHできます。

ssh -i test-laravel.pem ec2-user@${ec2_ip}

ブラウザアクセス

ブラウザからhttps://${ec2_ip}にアクセスできます。

ただ今回はSSL自己証明書にしたので初回は怒られます。

f:id:nihma:20190707174513p:plain

xxxxにアクセスする(安全ではありません)をクリックするとトップページを見ることができます。

f:id:nihma:20190707174914p:plain

自動デプロイ

masterへのpushを行うとCodePipelineが自動実行されてデプロイされます。

$ vi laravel/resources/views/welcome.blade.php
$ git diff
diff --git a/laravel/resources/views/welcome.blade.php b/laravel/resources/views/welcome.blade.php
index 044b874..7d7190b 100644
--- a/laravel/resources/views/welcome.blade.php
+++ b/laravel/resources/views/welcome.blade.php
@@ -81,7 +81,7 @@

             <div class="content">
                 <div class="title m-b-md">
-                    Laravel
+                    Hello World!
                 </div>

                 <div class="links">
$ git add -A
$ git commit -m "Test master push"
$ git push ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/laravel-ec2-sample --all

しばらくしてブラウザからhttps://${ec2_ip}にアクセスするとトップページの文字列がLaravelからHello World!に変わります。

f:id:nihma:20190707175916p:plain

まとめ

今回はCloudFormationとAnsibleを使ってEC2で動くLaravel環境とCode4兄弟による継続的デプロイ環境をコード管理できるようにしてみました。

  • このままだとデプロイのたびにサービスが止まってしまうのでBlue/Greenデプロイくらいは対応した方が良さそう
  • UserDataとAnsibleの部分をPacker+AnsibleにしてAMIの構築を別のフェーズで行う構成の方が役割分担的に良さそう
  • 素直にECS/Fargate環境にした方が良さそう*1

*1:とはいえ色々な事情で気軽にコンテナを使えない現場もあるので...