【CircleCI】初めて書いた愚直な設定ファイル(config.yml)を解説してみる


愚直：正直なばかりで臨機応変の行動をとれないこと。また、そのさま。ばか正直。

- [goo 国語辞書](https://dictionary.goo.ne.jp/word/%E6%84%9A%E7%9B%B4/)

# はじめに

こんにちは、とまだです。
[CircleCI Advent Calendar 2020](https://qiita.com/advent-calendar/2020/circleci) の 20 日目を担当させていただきます。

今回、自分が個人開発として作った Web アプリケーションで使っている設定ファイル config.yml を解説する記事を書こうとしたのですが、
[16 日目](https://qiita.com/naota7118/items/2f9f880285d15c31a342)の方の記事と内容が丸かぶりでした。

そこで、今回は私が CircleCI を使うにあたり**初めて書いた**設定ファイルを解説してみたいと思います。

先に申し上げておくとそのコードには **スタイリッシュな様相**や、**実運用を重視した工夫** はございません。
あえて **初めて書いた設定ファイル** をテーマとすることで、
CircleCI を使ったことが無い人でも「**これなら分かるかも**」と思ってもらえることを目的としています。

# きっかけ

何かツールを使うにあたり、例えそれが便利そうだとしても事前の準備が複雑だったり
やっていることがよく分からなかったりすると、つい敬遠してしまいませんか？
また、例え解説があったとしてもパッと見で複雑そうなだけで「もういいや」となったりしませんか？

例えば何らかの目的でコードを書こうとする時、Google で検索して参考になりそうなコードを探すことがあるかと思います。
その時に **コード量は極限まで減らしていてスッキリしているコード** よりも
**愚直なコードでも意図が分かるコード** の方が、最初の一歩としてはとっつきやすいですよね。

CircleCI を使うにあたり必要な設定ファイルも多種多様な書き方があり、
工夫を凝らすことでコードもすっきりし、結果的に利便性は向上するはずなのですが
初心者目線でも直感的に理解できる設定ファイルの書き方から触れてもらうことで、
最初の一歩を踏み出すきっかけになればいいなと思いました。

# 解説パート

## システム構成 (CI/CD pipeline)

今回のシステム構成と CI/CD pipeline の流れは以下の通りです。

1. 修正したコードを GitHub に push し、
2. 指定のリポジトリに push されたことを CircleCI が検知して、
3. CircleCI 上で立ち上がったマシン上でビルド＆テストを行い、
4. テストが通ったらまた別なマシンから EC2 に SSH でログインして、
5. GitHub 上のコードを EC2(本番環境) に反映し、
6. コンテナを再起動して本番環境に修正内容を反映させる

という流れになってます。

## 初めて書いた愚直な設定ファイル

いよいよ本題です。
最初に設定ファイルの全貌をご紹介します。

```.circleci/config.yml
# circleCIのバージョン指定
version: 2

jobs:
  build:
    machine:
      image: circleci/classic:edge
    steps:
    # Djangoのテストを実行
      - checkout
      - run:
          name: make env file
          command: |
            echo DEBUG=${DEBUG} > src/.env
            echo SECRET_KEY=${SECRET_KEY} >> src/.env
            echo APIKEY=${APIKEY} >> src/.env
            echo MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} >> src/.env
            echo MYSQL_DATABASE=${MYSQL_DATABASE} >> src/.env
            echo MYSQL_USER=${MYSQL_USER} >> src/.env
            echo MYSQL_PASSWORD=${MYSQL_PASSWORD} >> src/.env
            echo MYSQL_HOST=${MYSQL_HOST} >> src/.env
      - run:
          name: docker-compose up
          command: docker-compose up -d
      - run:
          name: docker ps
          command: docker ps
      - run:
          name: collectstatic
          command: docker-compose run python ./manage.py collectstatic
      - run:
          name: migrate
          command: docker-compose run python ./manage.py migrate
      - run:
          name: test
          command: docker-compose run python ./manage.py test
      - run:
          name: docker-compose down
          command: docker-compose down

# EC2にSSH接続し、デプロイを実行
  deploy:
    machine:
      image: circleci/classic:edge
    steps:
      - checkout
      # CircleCIに登録した秘密鍵を呼び出す。
      - add_ssh_keys
      - run: ssh ${USER_NAME}@${HOST_NAME} -p ${SSH_PORT} 'cd xxxxx && git pull origin main && docker-compose restart && docker-compose run python ./manage.py migrate'

# テストが成功した場合のみ、deployを実行するようにします。
workflows:
  version: 2
  build_and_deploy:
    jobs:
      - build
      - deploy:
          requires:
            - build
          # mainブランチにpushされた場合のみdeployするようにする。
          filters:
            branches:
              only: main
```

この設定ファイルについて、いくつか抜粋して解説していきます。

### version

```yml
version: 2
```

指定するバージョンによって使用するキーが異なるのですが、当時はバージョン「2」を使用していました。

### jobs - build - machine

```yml
jobs:
  build:
    machine:
      image: circleci/classic:edge
```

**image:** で、ビルドに使用する公式のマシンイメージを指定しています。
ここで指定しているマシンイメージでは下記が実装されています。

- Ubuntu バージョン 14.04 イメージ
- Docker バージョン 17.10.0-ce
- docker-compose バージョン 1.16.1

まっさらの Linux からビルドしなくていいのはありがたいですね。

→[公式ドキュメント：CircleCI を設定する](https://circleci.com/docs/ja/2.0/configuration-reference/)

### jobs - build - steps

build の後、**steps** というところで Docker コンテナの構築からテストまでを行っていきます。

ここでお伝えしたいことは **実行したいコマンドを記述していくだけ** ということです。
普通ならば人間の手でコマンドを叩いてコンテナを構築し、テスト用のコマンドまで実行していくんですが
そういった一つひとつの作業もすべて自動化してやってくれるのがメリットですね。
こういった自動化は大好物でございます。

このパートはどうしても一番長くなるので、コメントアウトで解説をしていきます。
なお、Django という Python 製のフレームワークを使っているゆえのコマンドを含みますが、あまり気にしなくて大丈夫です。

```yml
steps:
  - checkout
  - run:
      # .env ファイルからセキュアな情報を読み出す設計のため、CircleCI に設定した環境変数を .env ファイルに書き出す処理
      # 他に良いやり方もありそうですが一つひとつ愚直に書き出していました
      name: make env file
      command: |
        echo DEBUG=${DEBUG} > src/.env
        echo SECRET_KEY=${SECRET_KEY} >> src/.env
        echo APIKEY=${APIKEY} >> src/.env
        echo MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} >> src/.env
        echo MYSQL_DATABASE=${MYSQL_DATABASE} >> src/.env
        echo MYSQL_USER=${MYSQL_USER} >> src/.env
        echo MYSQL_PASSWORD=${MYSQL_PASSWORD} >> src/.env
        echo MYSQL_HOST=${MYSQL_HOST} >> src/.env
  - run:
      # テストに必要なコンテナを立ち上げ
      name: docker-compose up
      command: docker-compose up -d
  - run:
      # トラブルシューティング時、CircleCI の UI 上でコンテナの起動状況を確認できるようにコンテナ一覧を表示させる（趣味）
      name: docker ps
      command: docker ps
  - run:
      # Django で必要な処理
      name: collectstatic
      command: docker-compose run python ./manage.py collectstatic
  - run:
      # Django で必要な処理
      name: migrate
      command: docker-compose run python ./manage.py migrate
  - run:
      # Django の自動テスト
      # ここで失敗すると次の処理には進まないようになっているため、問題があるコードは本番環境に反映されないようになっている
      name: test
      command: docker-compose run python ./manage.py test
  - run:
      # コンテナを停止 (今思えばいらないかも？)
      name: docker-compose down
      command: docker-compose down
```

なお、**name:** を書いておくと一つひとつのジョブを CircleCI の UI 上で確認する時に成功したジョブ、失敗したジョブがひと目で分かるので便利です。
UI 上ではこんな感じで表示されますので一目瞭然ですね。
緑のマークで埋め尽くされていくのが気持ちいい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364501/c0ac8612-202a-3f10-5948-bc2cfb7441e7.png)

### deploy - machine

前項の build でテストに成功した時、はじめて deploy のジョブが走ります。
CircleCI の UI 上では、build のジョブと deploy のジョブがこのように繋がれて表示されます(スクショではデプロイまで成功してますが)。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364501/5b08e52b-1c5b-12a0-3e88-869602130d7b.png)

この環境では AWS の EC2 上でコンテナを起動させているので、EC2 に SSH ログインをして git pull を叩くことを目的としています。

そのために、EC2 に SSH ログインをするためのマシンを立ち上げます。
ここでは build の時と同じマシンイメージを使用していますが、SSH さえできる Linux であれば大丈夫なので
もっと軽量のマシンイメージを使ってもよかったかもしれません。

```yml
deploy:
  machine:
    image: circleci/classic:edge
```

### deploy - steps - add_ssh_keys (秘密鍵の登録)

EC2 にログインするためには、事前に作成しておいた秘密鍵を使います。
通常であれば特定のフォルダとかに秘密鍵を格納しておいて、SSH コマンドを叩く時に呼び出すことになります。
そのため、環境変数とは少し扱いが異なるため注意が必要です。

```yml
steps:
  - checkout
  # CircleCIに登録した秘密鍵を呼び出す
  - add_ssh_keys
```

事前に UI 上の操作で登録しておくことになるのですが **add_ssh_keys** というコマンドで呼び出せます。
※以下は [公式ドキュメント](https://circleci.com/docs/ja/2.0/add-ssh-key/)の引用です。

> すべての CircleCI ジョブは、ssh-agent を使用して登録済みのすべての SSH 鍵に自動的に署名します。
> ただし、コンテナに実際に鍵を登録するには、add_ssh_keys キーを使用する必要があります。

### deploy - steps - SSH & git pull

おまたせしました。
ここで一気に ECS に SSH でログインし、git pull してからコンテナの立ち上げ、およびアプリケーションで必要なタスクを実行しています。

```yml
      - run: ssh ${USER_NAME}@${HOST_NAME} -p ${SSH_PORT} &&
        'cd xxxxx &&
         git pull origin main &&
         docker-compose restart &&
         docker-compose run python ./manage.py migrate'
```

当然ながらユーザー名やホスト名、SSH ポートの情報も必要になるのですが
こういった情報は CircleCI に環境変数を事前に登録しておくほうがセキュアなので、ベタ書きはしないようにしましょう。
(config.yml に直接書くとリポジトリごと公開されてしまうので。)

# 最後に

いかがでしたでしょうか。
スタイリッシュではなく、一つひとつの処理を愚直に記述しているからこそ
やろうとしている処理のイメージが付きやすかったのではないかと思います。

今回はデプロイの部分まで紹介しましたが、ビルド＆テストの自動化だけでも十分便利ですので
普段、コマンドを手入力でやっているようなテストがあったら CircleCI を試してみてはいかがでしょうか。

Django 以外に、Ruby on Rails で自動ビルド＆テストについて環境構築から解説している記事も書いてますので、是非ご覧ください。
→[Docker 環境の Ruby on Rails アプリを CircleCI で自動ビルド＆テスト(RSpec)する方法](https://qiita.com/tmasuyama/items/5f1c224d5bc5fe2bf8d9)