【CircleCI】Rails × PostgreSQL環境作ってHerokuにデプロイするCIとその解説

最近、CIの勉強をするにあたって「[CircleCI実践入門──CI/CDがもたらす開発速度と品質の両立](https://www.amazon.co.jp/dp/4297114119/)」という本を読んでCIの勉強をし直しました。

その整理でRailsでテストやデプロイをするCIを作りました。
その中で学んだ知識とか書いていきます。


## 環境
- Ruby On Rails6系
- Ruby 2.6.5
- PostgreSQL 11.5
- Bundler 1.17.3
- CircleCI 2.1

## 準備
1. https://circleci.com/ から CircleCIに登録
2. .circleciディレクトリ配下にconfig.ymlを置く

   ```bash
   $ mkdir .circleci
   $ touch .circleci/config.yml
   ```
3.config/database.ymlに環境変数を使う設定を書く

   ```yaml
   test:
     <<: *default
     database: <%= ENV['DATABASE_NAME'] || 'test_app_db' %>
     host: <%= ENV['DATABASE_HOST'] || 'localhost' %>
     port: <%= ENV['DATABASE_PORT'] || 5432 %>
     username: <%= ENV['DATABASE_USER'] || '' %>
     password: <%= ENV['DATABASE_PASSWORD'] || '' %>
```


## 用語の説明

ここからCIの説明を書いていきます。

#### ステップ
- ジョブの設定の最小単位
- CircleCIで実行されるコマンドのリストをステップと呼ぶ
- 大別するとRunステップとビルトインステップの2種類に分別できる
    - runステップ：CircleCI上で実行したいシェルコマンドはユーザー自身がrunステップとして定義する
    - ビルトインステップ：リポジトリからのソースコードのチェックアウトやキャッシュなど、CircleCIが用意した特殊なステップをビルトインステップと呼ぶ

▼たとえば▼

```yml
steps:
  # ビルトインステップ
  - checkout # checkoutする。working_directoryにGitリポジトリをコピーする

  # runステップ
  - run:
      # runしているコマンドの名前をつけられる
      name: pg gem の依存関係のインストール
　　　 # 実際に動かすコマンド
      command: sudo apt-get update; sudo apt-get install libpq-dev
```

#### run
- コマンドライン プログラムの呼び出しのために使用する
- `command`は必須

▼たとえば▼

```yml

      # ステップの1つ。ステップのうちのRunステップに該当する
      - run:
          # runの名前。なんでもいい。
          name: pg gem の依存関係のインストール
          # 実行するコマンド
          command: sudo apt-get update; sudo apt-get install libpq-dev
```

#### ジョブ
- ステップの1つ以上のまとまり
- 他のCI/CD ツールではビルドとも呼ばれる
- 実行を開始するたびに実行環境をゼロから構築する(終了すると破棄する)

▼たとえば▼

```yml

jobs:
  # jobの名前
  build_and_test:
    # 動かすマシンを定義
    docker:
      - image: cimg/ruby:2.6.5-node
    # ↓動かすStep
    steps:
      - checkout
      - run:
          name: pg gem の依存関係のインストール
          command: sudo apt-get update; sudo apt-get install libpq-dev
```

#### Executor
- Executorではどのようなマシン環境でジョブ中のステップを実行するのかを定義する

▼docker executor▼

```yml
jobs:
  build_and_test:
    docker:
      - image: cimg/ruby:2.6.5-node
```

#### ワークフロー

- ワークフローはいくつかのジョブの塊とそれらのジョブの実行順序を定めたルール


▼たとえば▼

```yml
# ワークフロー
# ジョブの実行順序を定める
workflows:
  version: 2
  # ワークフローの名前
  main:
    # ↓Jobの実行順序を書いていく
    jobs:
      # Jobの名前。上で書いた名前を実行したい順番に書く
      - build_and_test
      # Jobの名前
      - deploy-production:
          # ↓Jobを実行する条件も書ける
          requires: # build_and_testって「Jobが成功したら」実行する
            - build_and_test
          filters: # masterブランチでのみ実行する
            branches:
              only: master
```

#### working_directory
ステップを実行するディレクトリを明示

▼たとえば▼

```yml
# ステップを実行するディレクトリ
working_directory: ~/repo
```

#### save_cache
- save_cacheはキャッシュを生成する
- pathとkeyが必要

▼たとえば▼

```yml
# キャッシュを生成する
- save_cache:
  # キャッシュに追加するディレクトリのリスト
  paths:
    - ./vendor/bundle
  # キャッシュのキー。識別子
  # checksumによって、Gemfile.lockのファイルの中身が変わると新しいキーになる
  key: rails-bundle-v1-{{ checksum "Gemfile.lock" }}

```

#### restore_cache
- キーを元にキャッシュを復元する
- keyｓで書かれたキャッシュキーのうち最初にヒットしたキャシュを復元する

▼たとえば▼

```yml
# 以前に保存したキャッシュを key に基づいて復元する
# 保存は save_cache によって行われる
- restore_cache:
  # 復元するキャッシュを検索するためのキャッシュ キーのリスト
  # 最初に一致したキーのみが復元される
  keys:
  # checksumは、指定したファイルの中身の SHA256 ハッシュを Base64 エンコードした値が入る
    - rails-bundle-v1-{{ checksum "Gemfile.lock" }}
    - rails-bundle-v1-
```

------------

他にも色々出来ることはありますが、最低限CIでテストしてデプロイするまでがここで出来ました。
最後にまとめて書いたCIのコードを記載します。

### 書いたCI

```yml

# CircleCIの実行バージョン
version: 2.1

# ジョブはステップの集まり
jobs:
  # jobの名前
  build_and_test:
　　 # Docker Executor(動かすマシンの設定)
    docker:
      # cimg/はCircleICがあらかじめ用意しているDockerイメージの新板
      - image: cimg/ruby:2.6.5-node
      # circleci/はCircleICがあらかじめ用意しているDockerイメージの旧板
      - image: circleci/postgres:11.5-alpine
      # 環境変数の設定
        environment:
          RAILS_ENV: test
          BUNDLE_PATH: ./vendor/bundle
          DATABASE_NAME: connpass_tube_api_test
          DATABASE_USER: postgres
          DATABASE_PASSWORD: ""
          DATABASE_HOST: 127.0.0.1
          DATABASE_PORT: 5432
          TZ: Asia/Tokyo

    # ステップを実行するディレクトリ
    working_directory: ~/repo

    # ジョブの設定の最小単位
    # CircleCIで実行されるコマンドのリスト
    steps:
      # checkoutする。working_directoryにGitリポジトリをコピーする
      - checkout

      # 以前に保存したキャッシュを key に基づいて復元する
      # 保存は save_cache によって行われる
      - restore_cache:
          # 復元するキャッシュを検索するためのキャッシュ キーのリスト
          # 最初に一致したキーのみが復元される
          keys:
            # checksumは、指定したファイルの中身の SHA256 ハッシュを Base64 エンコードした値が入る
            - rails-bundle-v1-{{ checksum "Gemfile.lock" }}
            - rails-bundle-v1-

      # ステップの1つ。ステップのうちのRunステップに該当する
      - run:
          # ステップの名前。なんでもいい。
          name: pg gem の依存関係のインストール
          # ステップで実行するコマンド
          command: sudo apt-get update; sudo apt-get install libpq-dev

      - run:
          name: Bundler のインストール
          # gemfile.lockの一番下くらいに書かれているbundlerのバージョンを書く
          command: gem install bundler -v 1.17.3

      - run:
          name: gem の依存関係のインストール
          command: bundle check || bundle install

      # キャッシュを生成する
      - save_cache:
          # キャッシュに追加するディレクトリのリスト
          paths:
            - ./vendor/bundle
          # キャッシュのキー。識別子。
          key: rails-bundle-v1-{{ checksum "Gemfile.lock" }}

      # キャッシュを復元
      - restore_cache:
          keys:
            - rails-yarn-v1-{{ checksum "yarn.lock" }}
            - rails-yarn-v1-

      - run:
          name: node_modules の依存関係のインストール
          command: yarn install

      - save_cache:
          paths:
            - ~/.cache/yarn
          key: rails-yarn-v1-{{ checksum "yarn.lock" }}

      - run:
          name: データベースの起動を待機
          command: dockerize -wait tcp://localhost:5432 -timeout 1m

      - run:
          name: データベースのセットアップ
          command: bundle exec rails db:create db:schema:load --trace

      # RSpecでテスト
      - run:
          name: run tests
          command: |
            bundle exec rspec --format documentation --backtrace

  # 2つめのJob
  deploy-production:
　　 # Docker Executor(動かすマシンの設定)
    docker:
      - image: cimg/ruby:2.6.5-node
        environment:
          RAILS_ENV: production
    working_directory: ~/repo
    steps:
      - checkout
      - run:
          name: Deploy to Heroku Production
          # HEROKU_API_KEYとHEROKU_APP_NAMEはCiecleCI上で設定している環境変数
          # HerokuでAPI_KEYやアプリケーションの名前を調べてCiecleCIに設定してあげてください
          command: |
            git push https://heroku:$HEROKU_API_KEY@git.heroku.com/$HEROKU_APP_NAME.git master

# ワークフロー
# ジョブの実行順序を定める
workflows:
  # ここのバージョンは2である必要がある
  version: 2
  # ワークフローの名前
  main:
    # Jobの実行順序を書いていく
    jobs:
      # Jobの名前。上で書いた名前を実行したい順番に書く(が、requiresなどの設定をしないと同時にJobは実行される)
      - build_and_test
      - deploy-production:
          # build_and_testのJobが成功したらdeploy-productのJobを実行する制御
          requires:
            - build_and_test
          # Jobを実行したいブランチやコミットタグを書く
          filters:
            # BranchがMasterのときだけ動かしたい
            branches:
              only: master
```

「[CircleCI を設定する](https://circleci.com/docs/ja/2.0/configuration-reference/)」このCircleCIのドキュメントもとってもわかりやすいです。