RailsアプリCICDパイプライン構築の手順（備忘録）（Docker+Rspec+GitHubActions+heroku）

#はじめに

つい最近DockerとCICD（Travis CI）を学んだので、実践がてらローカルのRailsアプリをDocker環境に移行し、CICDパイプラインを構築してみました。いろいろな方の知恵をお借りしてなんとか構築できたので、自分の備忘録として記録します。CICDツールには、最近登場したGitHubActionsを使用しています。
※まだまだ初学者ですので、間違い等あればご指導ご鞭撻のほどよろしくお願いいたします。

#環境
Mac OS　Monterey12.1
Ruby2.5.3
Rails5.2.5
MySQL（latest）

#1.Dockerfileの作成

<details><summary>**Dockerfile**</summary><div>


```Dockerfile:Dockerfile
FROM ruby:2.5.3
RUN apt-get update && apt-get install -y \
	build-essential \
	libpq-dev \
	node.js \
	mysql-client \
	yarn
WORKDIR /workdir
COPY Gemfile Gemfile.lock /workdir/
RUN bundle install
```
</div></details>

<details><summary>**docker-compose.yml**</summary><div>

```yml:docker-compose.yml
version: '3'

volumes:
  mysqldb-data:

services:
  web:
    build: .
    ports:
      - '3000:3000'
    volumes:
      - '.:/workdir'
    environment:
      - 'DATABASE_PASSWORD=mysqlpass'
      - 'DB_HOST=db'
    tty: true
    stdin_open: true
    depends_on:
      - db
    links:
      - db
    networks:
      - app-net

  db:
    container_name: mysql
    image: mysql
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - 'mysqldb-data:/var/lib/mysql'
    environment:
      - 'MYSQL_ROOT_PASSWORD=mysqlpass'
      - 'MYSQL_DATABASE=collections_development'
      - 'MYSQL_PASSWORD=mysqlpass'
    ports:
      - '3306:3306'
    networks:
      - app-net

networks:
  app-net:
    driver: bridge
```
</div></details>

<details><summary>**database.yml**</summary><div>

```yml:database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  host: db  #⬅︎追加
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: <%= ENV.fetch("DATABASE_PASSWORD") %>  #⬅︎追加
  socket: /tmp/mysql.sock
```
</div></details>

`docker-compose.yml`でDBはMySQLのlatest imageを使用するので、`command`の部分を忘れないようにします。また、`networks`にて`webコンテナ`から`dbコンテナ`にアクセスできるようにしています。
このままだと後からGitHubActionsでエラーが発生して修正しますが、とりあえず次に進みます。



#2.Rspecの作成

Rspecの作成については、以下の記事を参考にさせていただきました。

https://qiita.com/Ushinji/items/522ed01c9c14b680222c


試しにコンテナに入ってテストを実行してみます。

```
$ docker-compose up --build -d
$ docker-compose exec web bash
$ bundle exec rspec
```

テストが問題なく通りました。

次はCICDパイプラインを構築していきます。


#3.GitHub Actionsの作成（test実行まで）

GitHubのActionsのタブで確認し、`.github/workflows/`ディレクトリを作成し、その中にymlファイルを作成します。

```
$ mkdir -p .github/workflows
$ touch .github/workflows/rails_ci.yml
```

続いて、ymlファイルを作成していきます。
<details><summary>**.github/workflows/rails_ci.yml**</summary><div>

```yml:.github/workflows/rails_ci.yml
name: Ruby

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  Rspec_test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      - name: Set up docker
        shell: bash
        env:
          RAILS_ENV: test
        run: |
          docker-compose up --build -d
          docker-compose exec -T web rails db:create
          docker-compose exec -T web rails db:migrate

      - name: RUN Rspec
        shell: bash
        env:
          RAILS_ENV: test
        run: |
          docker-compose exec -T web bundle exec rspec
```
</div></details>

`docker-compose exec`の後に`-T`オプションを付けないと以下のエラーが発生します。

```text:GithubActions
the input device is not a TTY
```

ここから最初にハマった箇所です。CIの中で無事にコンテナをbuildできましたが、DBにつながりません。

```text:GithubActions
Mysql2::Error::ConnectionError: Can't connect to MySQL server on 'db' (111 "Connection refused")
```

いろいろ調べて試しましたがうまくいかず、最終的にたどり着いた答えが、
「MySQLが起動していないのではないか？」

恐らくMySQLが起動する前にdb:createしてエラーが出ているのではないかと思い調べると、
「wait-for-it 」という起動を確認できるライブラリがあるようなので、それを利用してみる。

https://github.com/vishnubob/wait-for-it

<details><summary>**docker-compose.yml修正**</summary><div>

```yml:docker-compose.yml
version: '3'

volumes:
  mysqldb-data:

services:
  web:
    build: .
    ports:
      - '3000:3000'
    volumes:
      - '.:/workdir'
    environment:
      - 'DATABASE_PASSWORD=mysqlpass'
      - 'DB_HOST=db'
    tty: true
    stdin_open: true
    depends_on:
      - db
    links:
      - db
    networks:
      - app-net


  wait:　　　　#⬅︎追加
    image: willwill/wait-for-it:latest

  db:
    container_name: mysql
    image: mysql
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - 'mysqldb-data:/var/lib/mysql'
    environment:
      - 'MYSQL_ROOT_PASSWORD=mysqlpass'
      - 'MYSQL_DATABASE=collections_development'
      - 'MYSQL_PASSWORD=mysqlpass'
    ports:
      - '3306:3306'
    networks:
      - app-net

networks:
  app-net:
    driver: bridge
```
</div></details>

<details><summary>**.github/workflows/rails_ci.yml修正**</summary><div>

```yml:.github/workflows/rails_ci.yml
name: Ruby

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  Rspec_test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      - name: Set up docker
        shell: bash
        env:
          RAILS_ENV: test
        run: |
          docker-compose up --build -d
          docker-compose -f docker-compose.yml run wait db:3306 -- echo "Database is up"    #⬅︎追加
          docker-compose exec -T web rails db:create
          docker-compose exec -T web rails db:migrate

      - name: RUN Rspec
        shell: bash
        env:
          RAILS_ENV: test
        run: |
          docker-compose exec -T web bundle exec rspec
```
</div></details>

結果DBをcreateでき、テストが無事に実行されました。感激！
ローカル環境ではすぐにDBが立ち上がるのか`depends_on`を記載するのみでうまくいきましたが、CI環境上ではうまくいかないみたいですね。

```
wait-for-it.sh: waiting 15 seconds for db:3306
wait-for-it.sh: timeout occurred after waiting 15 seconds for db:3306
Database is up
Database 'web_development' already exists
Created database 'web_test'
```

次はherokuへのデプロイです。


#4.GitHub Actionsの作成（herokuへのデプロイ）

`rails_ci.yml`にデプロイ部分を追記します。

<details><summary>**.github/workflows/rails_ci.yml　デプロイ**</summary><div>

```yml:.github/workflows/rails_ci.yml
　#続き
  Deploy:
    needs: Rspec_test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: login to heroku
        run:
          docker login -u ${{secrets.HEROKU_USERNAME}} -p ${{secrets.HEROKU_API_KEY}} registry.heroku.com
      - name: deploy to heroku
        run: |
          docker build -t registry.heroku.com/${{secrets.HEROKU_APP}}/web -f Dockerfile.prod .
          docker push registry.heroku.com/${{secrets.HEROKU_APP}}/web
          heroku run -app ${{secrets.HEROKU_APP}} rails db:migrate
```
</div></details>

また、本番環境ではheroku側のDBを使用したいので、`Dockerfile.prod`を作成し、それをbuildすることにします。

<details><summary>**Dockerfile.prod**</summary><div>

```docker:Dockerfile.prod
FROM ruby:2.5.3
RUN apt-get update && apt-get install -y \
	build-essential \
	libpq-dev \
	node.js \
	mysql-client \
	yarn
WORKDIR /workdir
COPY Gemfile Gemfile.lock /workdir/
RUN bundle install
COPY . .     　　　　　#⬅︎追加
CMD ["rails", "s"]　　   #⬅︎追加
```
</div></details>

続いて、各サービスの設定です。

###●herokuの設定

新しくAPPをcreateします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1200881/f7b26af8-2439-551c-e2e0-3d76cabe2c5e.png)


本番環境では、DBにheroku側の`Heroku Postgres`を使用します。
以下の通り準備します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1200881/dd811cd7-9595-4cb9-9254-b5d102768785.png)

`database.yml`の`production`を以下の通りとします。
コメントアウトされているので外し、他の`production`をコメントアウトする。

```yml:database.yml
production:
  url: <%= ENV['DATABASE_URL'] %>
```

そうするとherokuの`Config Vars`に記載されている`DATABASE_URL`の環境変数が使われ、herokuのPostgresが使用できます。
また、`KEY`のところにrailsの`master.key`の値を設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1200881/89bf81a0-c32e-e298-9b59-7a38ce721d37.png)

`Deploy`でGitHubと連携します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1200881/a733557f-0549-bb79-9450-f4018c84f581.png)


###●GitHubの環境変数設定

GitHubの`Actions secrets`に環境変数を設定していきます。
※余計な変数も設定されていますが気にしないでください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1200881/0db89193-e598-e297-691d-ff87883adfea.png)

herokuのAPI_KEYの取得の仕方は以下の通りです。

```
$ brew tap heroku/brew && brew install heroku
$ heroku authorizations:create
```
表示される`Token`のところです。



準備が整いましたので、リモートにpushしてCIを走らせます。

```
$ git add .
$ git commit -m '***'
$ git push origin main
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1200881/9d17c4db-b553-4235-5b41-a027deb7c743.png)


やったー！！デプロイ成功！！herokuでアプリも起動できました！！！
長い道のりでしたが、やっと完成しました。
お知恵をお貸しいただいたみなさん、ありがとうございます！

記載していない途中出会ったエラーは別途まとめる予定です。

#参考

・[Rails+RSpecで気軽に始めるテスト](https://weseek.co.jp/tech/1409/
)
・[GitHub ActionsでRails+Rspec+docker-compose環境のci構築](http://codelabo.com/posts/20201128164634)
・[他のDockerコンテナからコンテナ内のMySQLに接続する](https://qiita.com/YuitoSato/items/4a4b46f5670b45739a37)
・[GitHub Actionsのワークフロー構文](https://docs.github.com/ja/actions/using-workflows/workflow-syntax-for-github-actions)
・[Docker ComposeでDBコンテナが準備状態になるまで待つ](https://note.com/morihirok/n/nacd697f5c4b6)
・[docker-composeで「the input device is not a TTY」](https://www.pnkts.net/2021/09/16/docker-compose-tty)
・[udemy 米国AI開発者がゼロから教えるDocker講座](https://www.udemy.com/course/aidocker/)