Rails6の環境構築を自動でしてくれるシェルスクリプトを作ってみた (3) ~ECS導入編~ 

#はじめに

この記事は以前、私が書いた以下記事の改良版となります。

[Rails6の環境構築を自動でしてくれるシェルスクリプトを作ってみた ~CircleCI導入編~ (2)](https://qiita.com/drafts/22ab3359920eb97f2e01)

前回は開発環境構築まででしたが、今回はCircleCIによる自動テストまでをできるようにしました。
また、今回は手動で設定するべき項目が結構多いので「構築サポート」してくれるシェルスクリプトとしています。。。

私の環境は以下の通りです。

| OS                         | 環境  
| ---------------------------|------
| Windows 10 Home 64bit      | Ubuntu 18.04(WSL2)

#構築する環境

構築する環境は以下の通りです。
GitHubにプッシュ → 自動テスト → ESRにイメージプッシュ → ECSにデプロイができるようになることをゴールとします。

| 項目　　　   | 使用したもの  | バージョン  
|-------------| ------------ |----------
|言語         | ruby         | 2.6.6    
|Webフレームワーク| rails        | 6.0.2   
|テストフレームワーク|rspec|3.9
|データベース　| MySQL        | 8.0      
|Webサーバ    | Nginx        | 1.17.10  
|APサーバ     | puma         |  4.3.5

コンテナ構成は以下の通りです。

| appコンテナ                 
| ---------------------------
| ruby
| rails
| puma

| dbコンテナ                 
| ---------------------------
| MySQL

| webコンテナ                 
| ---------------------------
| Nginx

| 項目　　　   | 使用したもの   
|-------------| ------------ 
|コンテナリポジトリ| ECR
|コンテナオーケストレーション| ECS

#できること

GitHubへコードをpushしたら、CircleCIがそれを検知して、
自動的テスト → ECRにイメージを自動プッシュ → ECSに自動デプロイ
を実行してくれます。

最終的に目指す構成は以下の通りです。
(`ALB`や`Route53`が記載されていますが、必須ではないです。)

![ECS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/626975/ea4d6271-b3db-11e2-183b-ede1eef9ebcd.png)

今回作成したシェルは以下の通りです。

[今回作成したシェルスクリプト](https://github.com/watanavi/rails_on_docker/blob/master/docker_rails_template_ver3.0.sh)

まずこちらをローカルに落としてきて、[アプリ名]を引数として `sudo`で実行します。

この[アプリ名]は後の`ECS`の設定に使うので覚えておいてください。

実行権限が付与されていない場合は`chmod`で実行権限を付与してあげてください。

１０分ほど待って、以下状態になったら実行完了です。

```
app_1  | * Listening on tcp://0.0.0.0:3000
app_1  | * Listening on unix:///myapp/tmp/sockets/puma.sock
app_1  | Use Ctrl-C to stop
```

自動テストをするために　CircleCI ⇔　GitHub のプロジェクト連携が必要です。
CircleCIのアカウントからポチポチすれば簡単に連携できます。

↑↑↑↑↑↑↑↑↑前回はここまで↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑

次に、CircleCIの環境変数を追加します。

<img width="601" alt="2020-06-16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/626975/deba0209-7816-0fae-aa9f-a2d304e12d1c.png">

定義する環境変数としては以下の通りです。

```
# CircleCI上で指定が必要な環境変数
# AWS_ACCOUNT_ID          : AWSのデフォルトリージョン
# AWS_ACCESS_KEY_ID       : AWSのアクセスキー
# AWS_SECRET_ACCESS_KEY   : AWSのシークレットキー
# AWS_REGION              : AWSのデフォルトリージョン
# AWS_ECR_ACCOUNT_URL     : AWS ECRリポジトリのURL
```

次にAWSに各サービスを構築していくのですが、コンテナ関連サービス以外は割愛します。。。
主な作業は以下の通りです。

- VPCの作成
- PublicSubnet、PrivateSubnetの作成
- RDSの構築
- セキュリティグループの設定

`ECR`にはあらかじめレポジトリを作成します。
このとき、レポジトリの名前は以下の通り設定します。

- [アプリ名]_app(appコンテナ用)
- [アプリ名]_web(webコンテナ用)

イメージも適当なものをpushしておきます。

<img width="601" alt="2020-06-16 (1).png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/626975/11c221d6-fce2-bcff-8b05-eec17e4cc739.png">

`ECS`のクラスタを作成します。

クラスタ名は`[アプリ名]-cluster`としてください。
それ以外は適当でOKです。

<img width="748" alt="2020-06-16 (2).png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/626975/0fca757d-7055-f628-9ede-587391340f15.png">

次に`ECS`のタスク定義を作成していきます。
クラスタ名は`[アプリ名]-task`としてください。

<img width="748" alt="2020-06-17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/626975/038f04da-ffd8-dae1-b142-5b3bbdb24886.png">

タスク定義内、コンテナ名は以下の通り設定してください

- app(appコンテナ用)
- web(webコンテナ用)

<img width="748" alt="2020-06-17 (2).png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/626975/e5529dc9-bce5-ed43-5fc7-16b3b26bb213.png">


また、appコンテナには環境変数を定義する必要があります。

定義する環境変数は以下の通りです。

```
# AWS上で指定が必要な環境変数
# RAILS_MASTER_KEY          : ローカル環境にあるmaster.keyの値
# MYAPP_DATABASE_USER       : 本番環境データベースユーザ
# MYAPP_DATABASE_PASSWORD   : 本番環境データベースパスワード
```

<img width="748" alt="2020-06-17 (3).png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/626975/795a6943-c14e-b7bd-d627-9f273ebfeea4.png">

次に`ECS`のサービスを作成していきます。
クラスタ名は`[アプリ名]-service`としてください。
今回は`ALB`を使用したのでターゲットグループが設定されていますが、こちらは必須ではありません。

<img width="748" alt="2020-06-17 (4).png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/626975/e5d764d0-0c8b-bbf4-a1c6-eb49c0e64ede.png">

サービス内のタスク定義が`13`であることに注目してください。

自動デプロイを試すためにgithubにpushします。
すると、CirleCI上で処理が始まります。

CircleCIによるテスト → ECRにpush → ECSにdeploy

が自動で行われています。

<img width="601" alt="2020-06-17 (5).png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/626975/fa5a5008-11bf-b3c1-a2ee-c63dd33a9cbd.png">

CircleCI上の処理が終わった後、もう一度`ECS`のサービスの状態を見てみると。。


<img width="748" alt="2020-06-17 (6).png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/626975/50abfe1e-bf32-2cc1-2d89-1c709a085759.png">


デプロイが成功してタスク定義が`15`に置き換わっていることが分かります。
appコンテナ、webコンテナそれぞれデプロイしたため`+2`されています。
`ECR`のレポジトリも確認すると新しいイメージがpushされていると思います。

#シェルの内容

###前回からの変更点
[前回記事](https://qiita.com/drafts/22ab3359920eb97f2e01)からの主な変更点は以下の通りです。

- Dockerfileを開発環境と本番環境で分ける
- CircleCI用のコンフィグファイルを修正

### Dockerfileを開発環境と本番環境で分ける

スクリプト内、以下部分で`開発用Dockerfile`, `本番用Dockerfile` を分けて作成しています。

```
# app用のDockerfile 作成(開発用)
echo "make app Dockerfile"
cat << EOF > docker/app/Dockerfile_development
FROM ruby:${RUBY_VERSION}
RUN apt-get update -y && \\
    apt-get install -y default-mysql-client nodejs npm sudo && \\
    npm install -g -y yarn
RUN mkdir /${APP_NAME}
WORKDIR /${APP_NAME}
COPY ./src/Gemfile Gemfile
COPY ./src/Gemfile.lock Gemfile.lock
RUN bundle install && \\
    rails webpacker:install
RUN useradd -Nm -u ${USER} ${USER_NAME} && \\
    groupadd -g ${GROUP} ${USER_NAME} && \\
    usermod -aG sudo ${USER_NAME} && \\
    usermod -u ${USER} -g ${GROUP} ${USER_NAME} && \\
    echo ${USER_NAME}:${USER_PASSWORD} | chpasswd
COPY ./src /${APP_NAME}
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]
EOF

# app用のDockerfile 作成(本番用)
cat << EOF > docker/app/Dockerfile_production
FROM ruby:${RUBY_VERSION}
RUN apt-get update -y && \\
    apt-get install -y default-mysql-client nodejs npm sudo && \\
    npm install -g -y yarn
RUN mkdir /${APP_NAME}
WORKDIR /${APP_NAME}
COPY ./src/Gemfile Gemfile
COPY ./src/Gemfile.lock Gemfile.lock
RUN bundle install && \\
    rails webpacker:install
COPY ./src /${APP_NAME}
CMD ["bundle", "exec", "puma", "-e", "production", "-C", "config/puma.rb"]
EOF
```

分けている理由としては

- 本番環境では開発用ユーザが不要であるため
- アプリケーションを本番環境として起動するため

が挙げられます。

`本番用Dockerfile`の中身を見てみると、`development`ユーザを作成しておらず,
`puma`の起動を`-e production`というオプションを付けて実行していることが分かります。

また、本番では`RDS`を使うので`dbコンテナ`は使用しません。
加えて本番では`ECS`を使うので`docker-compose`も使用しません。

### CircleCI用のコンフィグファイルを修正

以下処理でCircleCIのコンフィグファイルを作成しています。
基本的に[CircleCIの公式ドキュメント](https://qiita.com/shshimamo/items/76db04d5a367aa84ca92)を参考にしています。

```
# .circleci/config.yml 作成
echo "make .circleci/config.yml"
mkdir -p .circleci
cat << EOF > .circleci/config.yml
version: 2.1
jobs:
  test:
    machine:
      image: circleci/classic:edge
    working_directory: ~/repo
    steps:
      - checkout
      - run:
          name: Install Docker Compose
          command: |
            curl -L https://github.com/docker/compose/releases/download/1.19.0/docker-compose-`uname -s`-`uname -m` > ~/docker-compose
            chmod +x ~/docker-compose
            sudo mv ~/docker-compose /usr/local/bin/docker-compose
      - run:
          name: docker-compose up --build -d
          command: docker-compose up --build -d
      - run: sleep 30
      - run:
          name: docker-compose run app rails db:create
          command: docker-compose run app rails db:create
      - run:
          name: docker-compose run app rails db:migrate
          command: docker-compose run app rails db:migrate
      - run:
          name: docker-compose run app bundle exec rspec spec
          command: docker-compose run app bundle exec rspec spec
      - run:
          name: docker-compose down
          command: docker-compose down

#
orbs:
  aws-ecr: circleci/aws-ecr@6.0.0
  aws-ecs: circleci/aws-ecs@0.0.8

workflows:
  test:
    jobs:
      - test
　#webコンテナをデプロイ
  deploy-web:
    jobs:
　　　#webコンテナのイメージをECRにプッシュ
      - aws-ecr/build-and-push-image:
          filters:
            branches:
              only: master
          account-url: AWS_ECR_ACCOUNT_URL
          create-repo: true
          dockerfile: docker/web/Dockerfile
          repo: "${APP_NAME}_web"
          region: AWS_REGION
          tag: "\${CIRCLE_SHA1}"
　　　#webコンテナのイメージをECSにデプロイ
      - aws-ecs/deploy-service-update:
          requires:
            - aws-ecr/build-and-push-image
          family: "${APP_NAME}-task"
          cluster-name: "${APP_NAME}-cluster"
          service-name: "${APP_NAME}-service"
          container-image-name-updates: "container=web,tag=\${CIRCLE_SHA1}"
　#appコンテナをデプロイ
  deploy-app:
    jobs:
      #webコンテナのイメージをECRにプッシュ
      - aws-ecr/build-and-push-image:
          filters:
            branches:
              only: master
          account-url: AWS_ECR_ACCOUNT_URL
          create-repo: true
          dockerfile: docker/app/Dockerfile_production
          repo: "${APP_NAME}_app"
          region: AWS_REGION
          tag: "\${CIRCLE_SHA1}"
      #webコンテナのイメージをECSにデプロイ
      - aws-ecs/deploy-service-update:
          requires:
            - aws-ecr/build-and-push-image
          family: "${APP_NAME}-task"
          cluster-name: "${APP_NAME}-cluster"
          service-name: "${APP_NAME}-service"
          container-image-name-updates: "container=app,tag=\${CIRCLE_SHA1}"
EOF
```

ECR、ECSのorbsをインポートして、`deploy-web`以下の処理でwebコンテナをデプロイ、`deploy-app`以下の処理でappコンテナをデプロイしています。

configファイル内にはいくつか変数が使用されていますが、CircleCI側でそれらを定義する必要があります。
「できること」内でも記載したように、CircleCI上で以下環境変数を定義します。

```
# CircleCI上で指定が必要な環境変数
# AWS_ACCOUNT_ID          : AWSのデフォルトリージョン
# AWS_ACCESS_KEY_ID       : AWSのアクセスキー
# AWS_SECRET_ACCESS_KEY   : AWSのシークレットキー
# AWS_REGION              : AWSのデフォルトリージョン
# AWS_ECR_ACCOUNT_URL     : AWS ECRリポジトリのURL
```


以上になります、これでCircleCIによるCI/CDができるようになりました。
何かの参考に慣れば幸いです。

# 参考記事

以下記事を参考にさせて頂きました。ありがとうございましたm(_ _)m

[AWS ECR/ECS へのデプロイ](https://circleci.com/docs/ja/2.0/ecs-ecr/)

[丁寧すぎるDocker-composeによるrails5 + MySQL on Dockerの環境構築(Docker for Mac)](https://qiita.com/azul915/items/5b7063cbc80192343fc0)

[CircleCI Orbsで ECR/ECS にデプロイ](https://qiita.com/shshimamo/items/76db04d5a367aa84ca92)