はじめに
当記事は、【ECS準備編】【ECS起動編】【HTTPS/ドメイン編】の続編です。
まだお読みでない方は、そちらからご確認ください。
使用技術
- ECS/Fargate(blue/greenデプロイメント)
- CircleCI: 2.1
- Rails: 6
作業内容
- ECRへのpush
- ECSのblue/greenデプロイメント
- masterブランチにマージ時のみデプロイさせる
前提
当記事は、CircleCI Orbを活用した実装方法になります。
CircleCI Orbを使うとCircleCIの設定ファイルをシンプルに構成できるようになります。
前提条件として、CircleCIのバージョンは2.1を使う必要があります。
1. ECRへのpush
ここではECRへのpushまで
を目標にして実装していきます。
使用するOrb
はcircleci/aws-ecr@7.3.0
とします。
CircleCIの公式ドキュメントに軽く目を通してみると、
どんな記述をすれば良いかが明確に分かります。
今回は
・公開すべきではない秘匿情報はCircleCIの環境変数
・それ以外はconfig.yml
に分けて実装しています。
⑴CircleCIに環境変数を登録する
まず、以下の表に従って、CircleCIに秘匿情報を登録していきましょう。
番号 | 項目 | 値 | 備考 |
---|---|---|---|
1 | AWS_ECR_ACCOUNT_URL | AWSアカウントID.dkr.ecr.us-west-2.amazonaws.com | ECRからコピペ |
2 | AWS_ACCESS_KEY_ID | ランダムな文字列 | IAMからコピペ |
3 | AWS_SECRET_ACCESS_KEY | ランダムな文字列 | IAM生成時に個人保管しているもの |
4 | AWS_REGION | ap-northeast-1 | 東京リージョンなら左記 |
5 | RAILS_MASTER_KEY | ランダムな文字列 | Railsアプリ作成時に自動生成 |
補足すると
1.と4.は、ECRの対象リポジトリにアクセスするため
2.と3.は、AWSの個人アカウントにアクセスするため
5.は、Railsがcredential.ymlファイルの解錠に使うため
で必要な情報です。
登録する手順は以下の通りです。
CircleCIのProjects
タブから対象のプロジェクト
をクリックします。
右側のProject Settings
をクリックします。
Environment Variables
タブからAdd Environment Variable
をクリックします。
あとは、先ほどの表に従って5つの秘匿情報
を登録します。
⑵Dockerfileから.circleci/config.ymlに環境変数を渡す
まず、ここから各ファイルの編集に入りますので
作業ブランチを切っておきましょう。
# 現時点でファイル変更がないことを確認する
% git status
# 新しくブランチを作成して移動する
% git checkout -b new-branch-circleci-test
# 現在いるブランチの確認
% git branch
では、本番用のDockerfileを編集していきます。
先ほど、CircleCIに登録した環境変数のうち
マスターキー
はRailsコンテナに渡す
必要があります。
(他の4つはCircleCIが暗黙的にECRへのpush時に利用します。)
そのため、少々周りくどいですが、
ARG
でCircleCI環境変数をDockerfile.productionで受け取り、
ENV
でそれを.circleci/config.ymlに渡しています。
(受け取った.circleci/config.yml内でRails環境変数にセットします。)
なお、他コードは当記事の趣旨に関わっている訳ではないですが、
生成に使うDockerイメージなので、参考のために載せております。
FROM ruby:2.7.3-alpine
ARG RUNTIME_PACKAGES="bash imagemagick nodejs yarn tzdata mysql-dev mysql-client git"
ARG DEV_PACKAGES="build-base curl-dev"
### CircleCI経由でイメージのプッシュ/デプロイを行う際に使用
ARG RAILS_MASTER_KEY
ENV RAILS_MASTER_KEY ${RAILS_MASTER_KEY}
WORKDIR /app
ENV RAILS_ENV="production"
COPY Gemfile Gemfile.lock /app/
RUN apk update && \
apk upgrade && \
apk add --no-cache ${RUNTIME_PACKAGES} && \
apk add --virtual build-dependencies --no-cache ${DEV_PACKAGES} && \
bundle install -j4 && \
apk del build-dependencies
COPY . /app
RUN SECRET_KEY_BASE=placeholder bundle exec rails assets:precompile \
&& yarn cache clean \
&& rm -rf node_modules tmp/cache
ENV RAILS_SERVE_STATIC_FILES="true"
COPY entrypoint.production.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.production.sh
ENTRYPOINT ["entrypoint.production.sh"]
EXPOSE 80
CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0", "-p", "80"]
ちなみに、私はマスターキーを登録していなかったので下記エラー文が出ました。
ローカルにはmaster.keyは当然ありますが、
CircleCIにはgitignoreで除外されているので複製されていないので
CircleCIがマスターキーありませんよー
って忠告してくれてるんですね…
■エラー文:
Missing encryption key to decrypt file with. Ask your team for your master key and write it to /app/config/master.key or put it in the ENV['RAILS_MASTER_KEY'].
⑶CircleCIの設定
では、本題のOrbを用いたCircleCIの設定です。
正直言って、ほとんどCircleCIの公式ドキュメント通りです。
以下の2点だけ個人的に編集しています。
1. イメージは本番用のDockerfile.productionとしているのでファイル名を指定する
2. Dockerfile.productionで設定したマスターキー情報を受け取ってセットしている
repo:は、AWSのECRページからリポジトリ名をコピペ
すればOKです。
version: 2.1
orbs:
aws-ecr: circleci/aws-ecr@7.3.0
workflows:
build_and_push_image:
jobs:
- aws-ecr/build-and-push-image:
# ECRに実在するリポジトリ名(任意名称は不可)
repo: ECRリポジトリ名
# イメージ生成のもとになるDockerfile
dockerfile: Dockerfile.production
# Dockerfile.productionから渡された秘匿情報を受け取ってRails環境変数に登録する
extra-build-args: '--build-arg RAILS_MASTER_KEY=${RAILS_MASTER_KEY}'
※イメージpushの検証
これでターミナルから以下コマンドを実行して検証します。
# 現在のtestブランチを確認
% git branch
# 変更をステージングへ移行
% git add .
# メッセージをつけてコミットする
% git commit -m 'CircleCIからECRにイメージをpushできるか検証する'
# GitHubへプッシュしてCircleCIの検証
% git push origin ローカルリポジトリ
あとはAWSのECR画面で新しくイメージが生成されているか確認できればOKです。
2. ECSのblue/greenデプロイメント
次は、blue/greenデプロイメントの実装です。
使用するOrb
はcircleci/aws-ecs@2.2.1
とします。
こちらもCircleCI公式ドキュメントに目を通しましょう。
ただし、やることはシンプルで
⑴.circleci/config.ymlを編集する
⑵CircleCI環境変数を登録する
の2作業だけです。
⑴.circleci/config.ymlを編集する
- aws-ecs/deploy-service-update:
以降のコードから解説します。
まず、requires:
によって
ECRへのイメージをpush後に
ECSでデプロイを行う
という順番を整えています。
その後は、ECSが動作するように必要な設定です。
以下に表をまとめましたので、整理しながら
ECSやCodeDeployの画面から間違えがないように
コピペしていきましょう。
番号 | 項目 | 値 | 備考 |
---|---|---|---|
1 | cluster-name: | '作成済みのクラスター名' | ECSからコピペ |
2 | service-name: | '作成済みのサービス名' | ECSからコピペ |
3 | family: | '作成済みのタスク定義名' | ECSからコピペ |
4 | deployment-controller: | 'CODE_DEPLOY' | 左記そのままコピペ |
5 | codedeploy-application-name: | '生成されるアプリケーション名' | CodeDeployからコピペ |
6 | codedeploy-deployment-group-name: | '生成されるグループ名' | CodeDeployからコピペ |
7 | codedeploy-load-balanced-container-name: | '生成されるコンテナ名' | タスク定義からコピペ |
version: 2.1
orbs:
# ECR用のOrb
aws-ecr: circleci/aws-ecr@7.3.0
# ECS用のOrb
aws-ecs: circleci/aws-ecs@2.2.1
workflows:
# デプロイまで行うので作業名を変更
build-and-deploy:
jobs:
# 先の実装と同じ
- aws-ecr/build-and-push-image:
repo: ECRリポジトリ名
dockerfile: Dockerfile.production
extra-build-args: '--build-arg RAILS_MASTER_KEY=${RAILS_MASTER_KEY}'
# ECS用の実装コード
- aws-ecs/deploy-service-update:
# 先に「aws-ecr/build-and-push-image」を実行させる依存関係を作る
requires:
- aws-ecr/build-and-push-image
# ECS上に存在するクラスター名
cluster-name: 'クラスター'
# ECS上に存在するサービス名
service-name: 'サービス'
# ECS上に存在するタスク定義の名称
family: 'タスク定義'
# デプロイを実行するコントローラーを指定する
deployment-controller: 'CODE_DEPLOY'
# CodeDeploy内のアプリケーション名
codedeploy-application-name: 'アプリケーション名'
# CodeDeploy内のグループ名
codedeploy-deployment-group-name: 'グループ名'
# タスク定義で設定しているコンテナ名
codedeploy-load-balanced-container-name: 'コンテナ名'
⑵CircleCI環境変数を登録する
私の場合は、⑴の作業だけでは下記エラーが発生しました。
どうもAWS_DEFAULT_REGION
というCircleCI環境変数が設定できてなさそうです。
■エラー文:
#!/bin/bash -eo pipefail
aws configure set default.region $AWS_DEFAULT_REGION \
--profile default
ということで、CircleCI環境変数に
下記の通り、**6 AWS_DEFAULT_REGION
**を追加しました。
番号 | 項目 | 値 | 備考 |
---|---|---|---|
1 | AWS_ECR_ACCOUNT_URL | AWSアカウントID.dkr.ecr.us-west-2.amazonaws.com | ECRからコピペ |
2 | AWS_ACCESS_KEY_ID | ランダムな文字列 | IAMからコピペ |
3 | AWS_SECRET_ACCESS_KEY | ランダムな文字列 | IAM生成時に個人保管しているもの |
4 | AWS_REGION | ap-northeast-1 | 東京リージョンなら左記 |
5 | RAILS_MASTER_KEY | ランダムな文字列 | Railsアプリ作成時に自動生成 |
6 ※追加※ | AWS_DEFAULT_REGION | ap-northeast-1 | 東京リージョンなら左記 |
※blue/greenデプロイメントの検証
先ほどと同様にターミナルからpushコマンドを実行して検証します。
# 現在のtestブランチを確認
% git branch
# 変更をステージングへ移行
% git add .
# メッセージをつけてコミットする
% git commit -m 'CircleCIからblue/greenデプロイメントが走るか検証する'
# GitHubへプッシュしてCircleCIの検証
% git push origin ローカルリポジトリ
コマンド実行後に、数分待ってから
ECSのサービス画面のイベントタブで
has reached a steady state
のメッセージが確認できればOKです。
3. masterブランチにマージ時のみデプロイさせる
最後にイメージのプッシュ&デプロイを行うブランチを制限します。
現行のままでは、どの作業ブランチでもgit pushした瞬間に イメージのプッシュ〜デプロイまで走ってしまい、
無駄です…
そのため、masterブランチが他ブランチからのプルリクエストを マージした時に実行される
ように設定します。
実装自体は簡単で、CircleCI設定ファイルの編集のみで完了しますので
ご安心ください。
では、下記からCircleCI設定ファイルをご確認ください。
ちょっとコードが長くなってしまってますが、気にせずに
- aws-ecr/build-and-push-image:
以降のfilters:
に注目します。
そうすれば、masterブランチのみで指定を行っていること
が分かります。
(- aws-ecs/deploy-service-update:も同様です。)
version: 2.1
jobs:
build-and-test:
docker:
- image: circleci/ruby:2.7.3-node-browsers
environment:
RAILS_ENV: 'test'
- image: circleci/mysql:8.0
command: mysqld --default-authentication-plugin=mysql_native_password
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: 'true'
MYSQL_ROOT_HOST: '%'
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "Gemfile.lock" }}
- v1-dependencies-
- run:
name: install dependencies
command: |
bundle install --jobs=4 --retry=3 --path vendor/bundle
- save_cache:
paths:
- ./vendor/bundle
key: v1-dependencies-{{ checksum "Gemfile.lock" }}
- run: yarn add @fortawesome/fontawesome-free
- run: mv config/database.yml.ci config/database.yml
- run: bundle exec rake db:create
- run: bundle exec rake db:schema:load
- run:
name: Rubocop
command: bundle exec rubocop
- run:
name: RSpec
command: |
mkdir /tmp/test-results
TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \
circleci tests split --split-by=timings)"
bundle exec rspec \
--format progress --format RspecJunitFormatter \
--out /tmp/test-results/rspec.xml \
$TEST_FILES
- store_test_results:
path: /tmp/test-results
- store_artifacts:
path: tmp/screenshots
destination: test-screenshots
orbs:
aws-ecr: circleci/aws-ecr@7.3.0
aws-ecs: circleci/aws-ecs@2.2.1
workflows:
build-and-deploy:
jobs:
# 開発用イメージをベースにビルド&テストする
- build-and-test
# 本番用イメージをベースにビルド&デプロイする
- aws-ecr/build-and-push-image:
# masterブランチのみ実行する
filters:
branches:
only: master
repo: ECRリポジトリ名
dockerfile: Dockerfile.production
extra-build-args: '--build-arg RAILS_MASTER_KEY=${RAILS_MASTER_KEY}'
- aws-ecs/deploy-service-update:
# masterブランチのみ実行する
filters:
branches:
only: master
requires:
- aws-ecr/build-and-push-image
cluster-name: 'クラスター'
service-name: 'サービス'
family: 'タスク定義'
deployment-controller: 'CODE_DEPLOY'
codedeploy-application-name: 'アプリケーション名'
codedeploy-deployment-group-name: 'グループ名'
codedeploy-load-balanced-container-name: 'コンテナ名'
テスト部:参考まで
orbs:
以前のコードは、今回の趣旨とは違うテスト部となりますので
無視して頂いて問題ありません。
(紛らわしくして、申し訳ありません…)
ちょっとだけ補足すると、workflows:で
- build-and-test (開発用イメージのビルドとテスト)
- aws-ecr/build-and-push-image: (本番用イメージのビルドとデプロイ)
を並走させています。
以上です。
検証作業
filtersを加えただけなので、同じように
イメージのプッシュも
blue/greenデプロイメントも
できるはずです。
心配であれば、git push
して確認してみてください。
作業完了です!お疲れ様でした!
参考記事
終わりに
Orbを用いて実装してみましたが、terraformで構成しようとすると
どうなるのやら…
ようやくGUI上でのインフラ構成をQiita記事で
アウトプットできたので、terraform学習に入れます!
最後まで、お読みいただき、ありがとうございました!