4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

RailsアプリをEC2からECS/Fargate構成に移行してホストする【CircleCI連携編】

Posted at

はじめに

当記事は、【ECS準備編】【ECS起動編】【HTTPS/ドメイン編】の続編です。

まだお読みでない方は、そちらからご確認ください。

使用技術

  • ECS/Fargate(blue/greenデプロイメント)
  • CircleCI: 2.1
  • Rails: 6

作業内容

  1. ECRへのpush
  2. ECSのblue/greenデプロイメント
  3. masterブランチにマージ時のみデプロイさせる

前提

当記事は、CircleCI Orbを活用した実装方法になります。

CircleCI Orbを使うとCircleCIの設定ファイルをシンプルに構成できるようになります。

前提条件として、CircleCIのバージョンは2.1を使う必要があります。

1. ECRへのpush

ここではECRへのpushまでを目標にして実装していきます。

使用するOrbcircleci/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タブから対象のプロジェクトをクリックします。

スクリーンショット 2021-12-15 14.06.40.png

右側のProject Settingsをクリックします。

スクリーンショット 2021-12-15 14.08.57.png

Environment VariablesタブからAdd Environment Variableをクリックします。

スクリーンショット 2021-12-15 14.10.38.png

あとは、先ほどの表に従って5つの秘匿情報を登録します。

スクリーンショット 2021-12-16 7.28.41.png

⑵Dockerfileから.circleci/config.ymlに環境変数を渡す

まず、ここから各ファイルの編集に入りますので
作業ブランチを切っておきましょう。

ターミナル

# 現時点でファイル変更がないことを確認する
% git status

# 新しくブランチを作成して移動する
% git checkout -b new-branch-circleci-test

# 現在いるブランチの確認
% git branch

では、本番用のDockerfileを編集していきます。

先ほど、CircleCIに登録した環境変数のうち
マスターキーRailsコンテナに渡す必要があります。
(他の4つはCircleCIが暗黙的にECRへのpush時に利用します。)

そのため、少々周りくどいですが、
ARGCircleCI環境変数をDockerfile.productionで受け取り、
ENVそれを.circleci/config.ymlに渡しています。
(受け取った.circleci/config.yml内でRails環境変数にセットします。)

なお、他コードは当記事の趣旨に関わっている訳ではないですが、
生成に使うDockerイメージなので、参考のために載せております。

Dockerfile.production

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の公式ドキュメント通りです。

スクリーンショット 2021-12-15 14.54.44.png

以下の2点だけ個人的に編集しています。

1. イメージは本番用のDockerfile.productionとしているのでファイル名を指定する
2. Dockerfile.productionで設定したマスターキー情報を受け取ってセットしている

repo:は、AWSのECRページからリポジトリ名をコピペすればOKです。

.circleci/config.yml
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です。

スクリーンショット 2021-12-15 15.10.29.png

2. ECSのblue/greenデプロイメント

次は、blue/greenデプロイメントの実装です。

使用するOrbcircleci/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: '生成されるコンテナ名' タスク定義からコピペ
.circleci/config.yml

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です。

スクリーンショット 2021-12-15 16.49.43.png

3. masterブランチにマージ時のみデプロイさせる

最後にイメージのプッシュ&デプロイを行うブランチを制限します。

現行のままでは、どの作業ブランチでもgit pushした瞬間に イメージのプッシュ〜デプロイまで走ってしまい、無駄です…

そのため、masterブランチが他ブランチからのプルリクエストを マージした時に実行されるように設定します。

実装自体は簡単で、CircleCI設定ファイルの編集のみで完了しますので
ご安心ください。

では、下記からCircleCI設定ファイルをご確認ください。

ちょっとコードが長くなってしまってますが、気にせずに
- aws-ecr/build-and-push-image:以降のfilters:に注目します。

そうすれば、masterブランチのみで指定を行っていることが分かります。
(- aws-ecs/deploy-service-update:も同様です。)

.circleci/config.yml
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学習に入れます!

最後まで、お読みいただき、ありがとうございました!

4
2
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
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?