GitHub + CircleCI + AWSなBuild & Deploy方法ちょっとまとめ

本投稿は TECOTEC Advent Calendar 2020 の16日目の記事です。

こんにちは。決済認証システム開発事業部の杉本です。

本ブログには2度目の登場となります。 前回の記事では Google Apps Script について書きましたが、その際に今後の課題としていたもの……は未解決です。

tec.tecotec.co.jp

本稿はそれより少し前、昨年のQiita Advent Calendarに参加した記事の、続きと言えなくもない、実際そんなに関係ない話です。

qiita.com

さて今日は2020年12月12日です。何の日だか、皆様ご存知でしょうか。

2020……2020……

1212……1212……

ご覧の通り循環(circulation)していますね。これは紛う方なきなでk……いやCircleCI記念日。

※ 循環数列の英訳は recurring sequence です。あと12月12日は「漢字の日」だそうです。

……という前フリを用意していたのですが、私の筆が遅く12日に間に合わず、16日の記事に変更となりました。

そこで12/16は何の日なのかと検索してみますと、実に111年前、 山手線が運行開始した日 だそうです。

山手線と言えば 状線 ですが、当初は右側が繋がっていない「C」の字型運転だったようで。

ここまで要素が揃っていると、運命を感じてしまいます。逃れることのできない円環の理のよう。

というわけで、私がこの1年の間にやってみたCircleCIの使い方を、ここに記録したいと思います。

概要

  • ひとつのリポジトリから、それぞれ異なるインスタンス、異なるディレクトリにデプロイしたい
  • AWS Amplify を組み込んだWebアプリを、Amplifyコンソールを使わずにデプロイしたい

というような、ちょっと変わった要望に応えるための記法を紹介します。

本論

基本の流れ

まずはGitHubのソースをCircleCI経由でデプロイする際の基本的な流れを記載します。

  1. GitHubの対象リポジトリでPush
  2. WebhookでCircleCIが稼働
  3. CircleCIがDockerコンテナを立ち上げて、その中で対象ブランチをCheckoutする
  4. Dockerコンテナに引っ張ってきたソースを、なんやかやする
  5. いろいろいじくって整えたものを、CodeDeployに渡す/s3に配置する
  6. (CodeDeployに渡した場合)CodeDeployが対象ソースをインスタンスにデプロイする/(s3に配置した場合)CloudFrontのInvalidationを実行する

図にするとこんな感じ。

f:id:teco_sugimoto:20201211173212p:plain
GitHub->CircleCI->AWS基本フロー

この設計図を、ymlファイルの記述に落とし込んでいきます。

/.circleci/config.yml ファイルは 1リポジトリでひとつ となりますので、develop・staging・productionの各環境用の処理をすべて記載することになります。

とは言え、環境毎に変わるのはAWSの認証情報、CodeDeployのアプリケーション・グループ名/s3のバケット名くらいで、行うべきことは同じであるというケースも多いかと思います。

そういった場合は、 references を定義し、各jobからは環境変数 environment を渡した上で参照を行うのが効果的です。references の中で ${ENVIRONMENT_NAME} と環境変数を参照することで、同じ処理を再利用することができます。

そして、 workflows のセクションで、「どのブランチに対するPushで、どのjobを動かすか」を定義します。しかし、ただGitHub + CircleCIを連携しただけですと、対象リポジトリのすべてのブランチへのPushでワークフローを回そうとしてしまいますので、フローの最初に実行される job で、稼働させるべきブランチをホワイトリストで設定しておきましょう。また、AWSの認証情報など機密性の高い値については、config.ymlに直接記載するのではなく、 context にて管理し、 workflows から各環境のjob毎に参照するようにします。

今回は develop・staging・productionの各環境に対応するブランチを用意したとします。さらに、 production へは管理者が認めた時のみデプロイが行われるよう、 approve も挟んでおきます。 type: approval のジョブは、CircleCIのコンソール上から承認を行うと成功するものになります。

……といったような考慮を加えた上で、ソースをCodeDeployに渡すシンプルな構成で、サンプルを記載いたします。

references:
  commands:
    push: &push
      command: |
        # AWS CLIを利用して、CodeDeployのデプロイ用ソースとして、s3にソースをpushする
        echo upload source
        aws deploy push \
          --application-name ${AWS_CODEDEPLOY_APPLICATION_NAME} \
          --s3-location s3://${AWS_CODEDEPLOY_SOURCE_BUCKET}/${CIRCLE_BRANCH}/${AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME}/${CIRCLE_WORKFLOW_ID}.zip

    deploy: &deploy
      command: |
        # AWS CLIを利用して、前の"push"でアップロードしたソースをCodeDeployで展開する
        echo execute codedeploy
        aws deploy create-deployment \
          --application-name ${AWS_CODEDEPLOY_APPLICATION_NAME} \
          --deployment-group-name ${AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME} \
          --s3-location bucket=${AWS_CODEDEPLOY_SOURCE_BUCKET},key=${CIRCLE_BRANCH}/${AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME}/${CIRCLE_WORKFLOW_ID}.zip,bundleType=zip

version: 2.1

# この辺はおまじないのようなもの
# cf) https://aibou.hateblo.jp/entry/2020/04/30/145117
executors:
  aws-cli:
    environment:
      AWS_PAGER: ""
    docker:
      - image: amazon/aws-cli

jobs:
  build:
    docker:
      - image: circleci/php:7.4
    working_directory: ~/circleci-build

    steps:
      - checkout

  deploy_develop:
    executor: aws-cli
    working_directory: ~/circleci-deploy
    environment:
      AWS_CODEDEPLOY_APPLICATION_NAME: develop
      AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME: app
      AWS_CODEDEPLOY_SOURCE_BUCKET: codedeploy-circleci-source-develop
    steps:
      - checkout # /root/circleci-deploy にGitのソースを落とす
      - run: echo deploy_develop 
      - run: *push # /root/circleci-deploy 以下のソースを.zip圧縮してs3にあげる
      - run: *deploy # あげた.zipファイルをCodeDeployでインスタンスに展開する

  deploy_staging:
    executor: aws-cli
    working_directory: ~/circleci-deploy
    environment:
      AWS_CODEDEPLOY_APPLICATION_NAME: staging
      AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME: app
      AWS_CODEDEPLOY_SOURCE_BUCKET: codedeploy-circleci-source-staging
    steps:
      - checkout
      - run: echo deploy_staging
      - run: *push
      - run: *deploy

  deploy_production:
    executor: aws-cli
    working_directory: ~/circleci-deploy
    environment:
      AWS_CODEDEPLOY_APPLICATION_NAME: production
      AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME: app
      AWS_CODEDEPLOY_SOURCE_BUCKET: codedeploy-circleci-source-production
    steps:
      - checkout
      - run: echo deploy_production
      - run: *push
      - run: *deploy

workflows:
  version: 2
  build-and-deploy:
    jobs:
      - build:
          filters:
            branches:
              only:
                - develop
                - staging
                - production
      - approve-deploy:
          type: approval
          requires:
            - build
          filters:
            branches:
              only: production
      - deploy_develop:
          requires:
            - build
          filters:
            branches:
              only: develop
          context: AWS-DEVELOP
      - deploy_staging:
          requires:
            - build
          filters:
            branches:
              only: staging
          context: AWS-STAGING
      - deploy_production:
          requires:
            - build
            - approve-deploy
          filters:
            branches:
              only: production
          context: AWS-PRODUCTION

アレンジ

では、この基本フローを頭に入れた上で、上記要件に適う記述を行っていきましょう。

①ひとつのブランチから、異なるインスタンス、異なるディレクトリにデプロイする

複数のAPIサーバを建てて、それぞれで異なるAPIが稼働するようにするけれど、基本構造やライブラリは共通だから、ソース管理はひとつのリポジトリにしたい……というケースが考えられます。またその際、サーバ毎にドキュメントルートを変えることもあるでしょう。

「どのインスタンスにデプロイするか」は、CodeDeployのデプロイグループの設定です。CodeDeployにて必要なアプリケーション・デプロイグループを用意した上で、CircleCIでは 「同じソースを複数のデプロイグループに渡す」 ように記述します。

問題は「異なるディレクトリにデプロイする」ほうです。CodeDeployでのデプロイ先は、指示書である appspec.ymldestination として定義します。しかし、この appspec.yml はルートにひとつしか置けず、CircleCIの指示書 config.yml と違って、その中で環境毎に記述を分けることもできません。そしてこのファイルはデプロイ操作の冒頭で参照されるため、デプロイするソースが出来た時点で、ルートに適切なものが置かれていなければなりません。

そのため、 appspec.yml については、 CircleCIでデプロイソースを用意する時に、環境に応じたファイルを「appspec.yml」としてルートに配置する 必要があります。

※ Jenkinsでは、「環境毎に異なるappspec.ymlファイルを利用する」というオプションがあり、同様のことを設定ひとつでやってくれるようになっています。

以上の条件で、ソース管理とconfig.ymlをアレンジしてみましょう。

ソース管理

develop・staging・productionの環境ごとにCodeDeployアプリを命名し、その中でデプロイ対象(APPLI1, APPLI2とする)ごとにグループを定義するようにします。

ルート
└ appspec
 └ ${CodeDeployアプリ名}
  └ ${CodeDeployグループ名}.yml
ルート
└ appspec
 └ develop
  └ appli1.yml
  └ appli2.yml
 └ staging
  └ appli1.yml
  └ appli2.yml
 └ production
  └ appli1.yml
  └ appli2.yml

それぞれの中身は以下のように記述します。 AfterInstallでは、ソースのデプロイ後に行うこと(PHPであれば、 composer install だったり、cacheの削除だったり)を記述したshellを実行するようにしています。

version: 0.0
os: linux
files:
  - source: /
    destination: /var/www/appli1
permissions:
  - object: /var/www/appli1
    owner: teco
    group: teco
hooks:
  AfterInstall:
    - location: sh/deploy/afterInstall.sh
      runas: teco
version: 0.0
os: linux
files:
  - source: /
    destination: /var/www/appli2
permissions:
  - object: /var/www/appli2
    owner: teco
    group: teco
hooks:
  AfterInstall:
    - location: sh/deploy/afterInstall.sh
      runas: teco

config.ymlの調整

前項のように管理した.ymlファイルを、正しく「appspec.yml」として置くジョブ appspec を新たに用意します。

その上で、「appspec.ymlを置く→デプロイ用ソースをs3にpushする→CodeDeployを実行する」という一連の流れを、APPLI1・APPLI2のそれぞれについて実行するように、記述を拡張します。

references:
  commands:
    appspec_appli1: &appspec_appli1
      command: |
        # 環境に応じたディレクトリから.ymlファイルをコピーし、rootに「appspec.yml」として配置する
        echo arrange appspec for ${AWS_CODEDEPLOY_APPLICATION_NAME}.${AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME_APPLI1}
        cp -f appspec/${AWS_CODEDEPLOY_APPLICATION_NAME}/${AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME_APPLI1}.yml appspec.yml

    push_appli1: &push_appli1
      command: |
        echo upload source
        aws deploy push \
          --application-name ${AWS_CODEDEPLOY_APPLICATION_NAME} \
          --s3-location s3://${AWS_CODEDEPLOY_SOURCE_BUCKET}/${CIRCLE_BRANCH}/${AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME_APPLI1}/${CIRCLE_WORKFLOW_ID}.zip

    deploy_appli1: &deploy_appli1
      command: |
        echo execute codedeploy
        aws deploy create-deployment \
          --application-name ${AWS_CODEDEPLOY_APPLICATION_NAME} \
          --deployment-group-name ${AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME_APPLI1} \
          --s3-location bucket=${AWS_CODEDEPLOY_SOURCE_BUCKET},key=${CIRCLE_BRANCH}/${AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME_APPLI1}/${CIRCLE_WORKFLOW_ID}.zip,bundleType=zip

    appspec_appli2: &appspec_appli2
      command: |
        echo arrange appspec for ${AWS_CODEDEPLOY_APPLICATION_NAME}.${AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME_APPLI2}
        cp -f appspec/${AWS_CODEDEPLOY_APPLICATION_NAME}/${AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME_APPLI2}.yml appspec.yml

    push_appli2: &push_appli2
      command: |
        echo upload source
        aws deploy push \
          --application-name ${AWS_CODEDEPLOY_APPLICATION_NAME} \
          --s3-location s3://${AWS_CODEDEPLOY_SOURCE_BUCKET}/${CIRCLE_BRANCH}/${AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME_APPLI2}/${CIRCLE_WORKFLOW_ID}.zip

    deploy_appli2: &deploy_appli2
      command: |
        echo execute codedeploy
        aws deploy create-deployment \
          --application-name ${AWS_CODEDEPLOY_APPLICATION_NAME} \
          --deployment-group-name ${AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME_APPLI2} \
          --s3-location bucket=${AWS_CODEDEPLOY_SOURCE_BUCKET},key=${CIRCLE_BRANCH}/${AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME_APPLI2}/${CIRCLE_WORKFLOW_ID}.zip,bundleType=zip

version: 2.1

executors:
  aws-cli:
    environment:
      AWS_PAGER: ""
    docker:
      - image: amazon/aws-cli

jobs:
  build:
    docker:
      - image: circleci/php:7.4
    working_directory: ~/circleci-build

    steps:
      - checkout

  deploy_develop:
    executor: aws-cli
    working_directory: ~/circleci-deploy
    environment:
      AWS_CODEDEPLOY_APPLICATION_NAME: develop
      AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME_APPLI1: app1
      AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME_APPLI2: app2
      AWS_CODEDEPLOY_SOURCE_BUCKET: codedeploy-circleci-source-develop
    steps:
      - checkout
      - run: echo deploy_develop
      # 「appspec.ymlを置く→デプロイ用ソースをs3にpushする→CodeDeployを実行する」を、対象それぞれに行う
      - run: *appspec_appli1
      - run: *push_appli1
      - run: *deploy_appli1
      - run: *appspec_appli2
      - run: *push_appli2
      - run: *deploy_appli2

# 後略

これを実行すると、appli1は、appli1グループの指定したインスタンスにデプロイされた後、その中の /var/www/appli1 に展開されます。一方appli2は、appli2グループのインスタンスの、 /var/www/appli2 に展開されることになります。

.envファイルも同じ発想で、環境ごとに以下のように用意しておくことができます。

ルート
└ env
 └ develop
  └ appli1.env
  └ appli2.env
 └ staging
  └ appli1.env
  └ appli2.env
 └ production
  └ appli1.env
  └ appli2.env

こちらはCircleCIでデプロイソースを作成する際に配置してもよいですし、デプロイ後にAfterInstallにて実行するshellで配置することもできます。後者の場合には、実行されるshellはデプロイ先のディレクトリではなく、 /opt/codedeploy-agent/deployment-root/... という、デプロイ用の一時置き場にあるものだということに注意が必要です。感覚が混乱するので、shellの冒頭で、デプロイ先のディレクトリに移動しておくとよいかと。

いずれの場合でも、環境に応じたenvファイルを「.env」として配置した後は、cacheを消すことを忘れずに。

#!/bin/sh

echo "start"
echo ${APPLICATION_NAME}
echo ${DEPLOYMENT_GROUP_NAME}

cd /var/www/${DEPLOYMENT_GROUP_NAME}

echo "change permission"
sudo chmod -R 0777 storage
sudo chmod -R 0777 bootstrap/cache

echo "put envfile"
cp -au env/${APPLICATION_NAME}/${DEPLOYMENT_GROUP_NAME}.env .env

echo "cache clear"
php artisan cache:clear
php artisan config:cache

# 後略

幕間:SDKなどで、環境ごとに異なる証明書ファイルを配置したい(ただしAutoScalingのインスタンス間では共通とする)

.pemだったり.keyだったりのファイルは、あまりバージョン管理にあげたくないな、と思います。 外部ライブラリの証明書は、storagevendor の下に置くように指示されることがありますが、そもそもそれらのディレクトリはGit管理していないことも多いはず。

しかし、同じサービスに対して複数台のサーバが稼働しているような、ELBにAutoScalingでぶら下がっているような構成の場合、そのインスタンス間では同じ証明書を持っていなければなりません。

このような場合には、AutoScalingのbaseとなるインスタンスにて、 /var/www 配下に対象のファイルを持っておき、CodeDeployのAfterInstallで(.envファイルと同じように)配置することができます。

# 前略
    cp -au /var/www/certificate.pem storage/certificate.pem
# 後略

みたいな具合に。

他にもやり方はあると思いますが、一例としてご紹介しました。

②AWS Amplify を組み込んだWebアプリを、Amplifyコンソールを使わずにデプロイしたい

Amplifyを組み込んだWebアプリを、色々な事情があってGitで管理している場合は、CircleCIのDockerコンテナの上でamplify-cliを利用してbuildしていきます。

基本的には、ローカル環境でAmplifyを稼働させた時と同様に

  • amplify-cliをはじめ、必要なパッケージをインストール
  • AWS認証情報を設定
  • Amplifyプロジェクトをpull
  • Webアプリ用ソースをbuild

を行うことになります。

CircleCIのconfig.ymlを書く上で気にするのは、以下3点です。

  1. 環境ごとに異なる.envファイルを配置すること → これまでと違い、「CodeDeployの後に」はできませんので、CircleCIの中で配置します。
  2. amplify-cliは対話型のインターフェイスであること → それ用の書き方がありますので紹介します(実は公式ドキュメントに載っています)。
  3. buildしたものをdeployすること → EC2上で動かす場合は、①のようにCodeDeployに渡します。s3 + CloudFrontで動かす場合は、s3にあげた後でCloudFrontのキャッシュを消します。

あとは①でご紹介したように、環境変数を参照できるように references を利用して、指示を書いていけば完成です。

CircleCI上でAmplifyを引っ張ってきて、buildの後、s3 + CloudFrontの環境にデプロイする場合の config.yml は、以下のようになります。

references:
  commands:
    install_package: &install_package
      command: |
        # 必要なパッケージをインストール。aws-amplify/cliはバージョン上がるとエラー起こすことがあるので、確認環境で正常に動いたバージョンに固定するのが吉
        echo Install yarn
        npm install yarn
        yarn install
        echo Install aws-amplify CLI
        sudo npm install -g @aws-amplify/cli@4.32.1
        # Amplifyの操作を行うために参照する、AWS認証情報を設定
        echo aws amplify configure
        aws configure set amplify.aws_access_key_id ${AWS_AMPLIFY_ACCESS_KEY_ID}
        aws configure set amplify.aws_secret_access_key ${AWS_AMPLIFY_SECRET_ACCESS_KEY}
        aws configure set amplify.region ${AWS_DEFAULT_REGION}
        aws configure set amplify.output json
        echo Show AWS CLI and Amplify Versions
        aws --version
        amplify --version
        # 環境に応じた.envファイルを、「.env」として配置
        echo Put envfile
        cp -f env/${AWS_DEPLOYMENT_GROUP_NAME}.env .env

    amplify_setting: &amplify_setting
      command: |
        # デプロイ先の環境に対応するAmplifyリソースをリモートからpullする
        # 本来は対話型のインターフェイスで実行していくものだが、以下のように書くことで動かすことができる
        echo Checkout Amplify Environment
        amplify pull \
        --amplify "{\
          \"projectName\":\"${AWS_AMPLIFY_APP_NAME}\",\
          \"appId\":\"${AWS_AMPLIFY_APP_ID}\",\
          \"envName\":\"${AWS_AMPLIFY_APP_ENV}\",\
          \"defaultEditor\":\"Visual Studio Code\"\
          }" \
        --frontend "{\
          \"frontend\":\"javascript\",\
          \"framework\":\"vue\",\
          \"config\":"{\
            \"SourceDir\":\"src\",\
            \"DistributionDir\":\"dist\",\
            \"BuildCommand\":\"npm run-script build\",\
            \"StartCommand\":\"npm run-script serve\"\
            }"\
          }" \
        --providers "{\
          \"configLevel\":\"project\",\
          \"useProfile\":true,\
          \"profileName\":\"amplify\"\
          }" \
        --yes

    amplify_deploy: &amplify_deploy
      command: |
        # buildして、dist/以下に出来たソースを、CloudFrontのソースに設定したs3バケットに配置後、CloudFrontのキャッシュを削除する(Invalidationを実行する)
        echo Build project
        yarn build
        echo Deploy to s3 and invalidate CloudFront cache
        aws s3 sync ./dist s3://${AWS_CLOUDFRONT_SOURCE_BUCKET} --delete
        aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DISTRIBUTION_ID} --paths "/*"

version: 2.1

orbs:
  aws-cli: circleci/aws-cli@1.2.1

executors:
  node:
    docker:
      - image: circleci/node:12.4.0

jobs:
  build_and_test:
    executor: node
    working_directory: ~/circleci-build
    steps:
      - checkout
      - run:
          name: 'Show node and NPM versions'
          command: |
            node -v
            npm -v
      - run:
          name: 'install yarn'
          command: |
            npm install yarn
            yarn -v
      - restore_cache:
          key: v1-dependencies-{{ .Branch }}-{{ checksum "package-lock.json" }}
      - run: yarn install
      - save_cache:
          paths:
            - node_modules
          key: v1-dependencies-{{ .Branch }}-{{ checksum "package-lock.json" }}
      - run:
          name: 'Run tests'
          command: |
            echo test skip

  deploy_develop:
    executor: node
    working_directory: ~/circleci-deploy
    environment:
      AWS_AMPLIFY_APP_NAME: sample
      AWS_AMPLIFY_APP_ENV: develop
      AWS_DEPLOYMENT_GROUP_NAME: develop
      AWS_CLOUDFRONT_SOURCE_BUCKET: cloudfront-source-develop
      AWS_CLOUDFRONT_DISTRIBUTION_ID: ******
    steps:
      - checkout
      - restore_cache:
          key: v1-dependencies-{{ .Branch }}-{{ checksum "package-lock.json" }}
      - aws-cli/install
      - run: *install_package
      - run: *amplify_setting
      - run: *amplify_deploy

  deploy_staging:
    executor: node
    working_directory: ~/circleci-deploy
    environment:
      AWS_AMPLIFY_APP_NAME: sample
      AWS_AMPLIFY_APP_ENV: staging
      AWS_DEPLOYMENT_GROUP_NAME: staging
      AWS_CLOUDFRONT_SOURCE_BUCKET: cloudfront-source-staging
      AWS_CLOUDFRONT_DISTRIBUTION_ID: ******
    steps:
      - checkout
      - restore_cache:
          key: v1-dependencies-{{ .Branch }}-{{ checksum "package-lock.json" }}
      - aws-cli/install
      - run: *install_package
      - run: *amplify_setting
      - run: *amplify_deploy

  deploy_production:
    executor: node
    working_directory: ~/circleci-deploy
    environment:
      AWS_AMPLIFY_APP_NAME: sample
      AWS_AMPLIFY_APP_ENV: production
      AWS_DEPLOYMENT_GROUP_NAME: production
      AWS_CLOUDFRONT_SOURCE_BUCKET: cloudfront-source-production
      AWS_CLOUDFRONT_DISTRIBUTION_ID: ******
    steps:
      - checkout
      - restore_cache:
          key: v1-dependencies-{{ .Branch }}-{{ checksum "package-lock.json" }}
      - aws-cli/install
      - run: *install_package
      - run: *amplify_setting
      - run: *amplify_deploy

workflows:
  version: 2
  build-and-deploy:
    jobs:
      - build_and_test:
          filters:
            branches:
              only:
                - develop
                - staging
                - production
      - approve-deploy:
          type: approval
          requires:
            - build_and_test
          filters:
            branches:
              only: production
      - deploy_develop:
          requires:
            - build_and_test
          filters:
            branches:
              only: develop
          context: AWS-DEVELOP
      - deploy_staging:
          requires:
            - build_and_test
          filters:
            branches:
              only: staging
          context: AWS-STAGING
      - deploy_production:
          requires:
            - build_and_test
            - approve-deploy
          filters:
            branches:
              only: production
          context: AWS-PRODUCTION

EC2上で稼働させる場合は、buildまでの手順は変わりません(.envもCircleCIの処理の中で配置します)が、CodeDeployの appspec.yml が必要になりますので、①同様に環境ごとに用意し、CircleCI内でデプロイ先に応じたものを配置するようにします。

Amplifyを使わない、シンプルなWebアプリを、EC2上にデプロイするための記述は以下のようになります。

references:
  commands:
    install_package: &install_package
      command: |
        # 必要なパッケージのインストール、envファイルの配置
        echo Install yarn
        npm install yarn
        yarn install
        aws --version
        echo Put envfile
        cp -f env/${AWS_CODEDEPLOY_APPLICATION_NAME}.env .env

    appspec: &appspec
      command: |
        # 環境に応じた appspec.yml ファイルの配置
        echo arrange appspec for ${AWS_CODEDEPLOY_APPLICATION_NAME}.${AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME}
        cp -f appspec/${AWS_CODEDEPLOY_APPLICATION_NAME}.yml appspec.yml

    build: &build
      command: |
        # buildしてデプロイ用ソースを作成
        echo Build project
        yarn build

    push: &push
      command: |
        # CodeDeployのデプロイ用ソースとして、s3にソースをpush
        echo upload source
        aws deploy push \
          --application-name ${AWS_CODEDEPLOY_APPLICATION_NAME} \
          --s3-location s3://${AWS_CODEDEPLOY_SOURCE_BUCKET}/${CIRCLE_BRANCH}/${AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME}/${CIRCLE_WORKFLOW_ID}.zip

    deploy: &deploy
      command: |
        # pushしたソースをCodeDeployに渡してデプロイ
        echo execute codedeploy
        aws deploy create-deployment \
          --application-name ${AWS_CODEDEPLOY_APPLICATION_NAME} \
          --deployment-group-name ${AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME} \
          --s3-location bucket=${AWS_CODEDEPLOY_SOURCE_BUCKET},key=${CIRCLE_BRANCH}/${AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME}/${CIRCLE_WORKFLOW_ID}.zip,bundleType=zip

version: 2.1

orbs:
  aws-cli: circleci/aws-cli@1.2.1

executors:
  node:
    docker:
      - image: circleci/node:12.4.0

jobs:
  build_and_test:
    executor: node
    working_directory: ~/circleci-build
    steps:
      - checkout
      - run:
      # 中略

  deploy_develop:
    executor: node
    working_directory: ~/circleci-deploy
    environment:
      AWS_CODEDEPLOY_APPLICATION_NAME: develop
      AWS_CODEDEPLOY_DEPLOYMENT_GROUP_NAME: sample
      AWS_CODEDEPLOY_SOURCE_BUCKET: codedeploy-circleci-source-spa-develop
    steps:
      - checkout
      - restore_cache:
          key: v1-dependencies-{{ .Branch }}-{{ checksum "package-lock.json" }}
      - aws-cli/install
      - run: echo deploy_develop
      - run: *install_package
      - run: *appspec
      - run: *build
      - run: *push
      - run: *deploy

# 後略

また、実際に稼働させるソースは、buildしてできた /dist 以下のものですので、 appspec.yml 内の記述も①とは少し変える必要があります。

version: 0.0
os: linux
files:
  - source: /dist
    destination: /var/www/sample
permissions:
  - object: /var/www/sample
    owner: teco
    group: teco

むすびに

CircleCIは自動テストも主要な役割でありますが、そこまで書くとヒュージな記事になってしまいますので、今回はビルド・デプロイに焦点を当ててご紹介いたしました。

開発対象の要件に合わせて、今後も色々な調整、組み換えを試して見たいと思います。

なお、表題のアルファベットを眺めてみると、頭文字でe,fが欠けていますが、偶然の産物です。

参考記事

CodeDeployのライフサイクルについて詳しい。 sqlazure.jp

Amplify入りデプロイに悩んでいた時に見つけた光明。 github.com

AmplifyのCLI操作は公式ドキュメントを参照すべし。 docs.amplify.aws

Amazon Web Services、『Powered by Amazon Web Services』ロゴ、[およびかかる資料で使用されるその他の AWS 商標] は、米国および/またはその他の諸国における、Amazon.com, Inc. またはその関連会社の商標です。

PR

www.tecotec.co.jp