RailsアプリのCI/CDを"GitHub Actions"で作ってみた

## 記事のきっかけ
初学者ながら自身のRailsアプリにCI/CDを入れるぞ❗️と考えました。
最近話題になっているGitHub Actionsってなんだろう❓
GitHubでCI/CDも管理できるらしい。凄い❗️
その気持ちから、他のCI/CDサービスと自分なりに比較選択をし、
GitHub Actionsを選択。実装に突き進みました。
本記事はその時のコード記録と実施内容の忘備録を兼ねて記事にしました。

## アプリ開発環境
- Rails v6.0.3.4
- Ruby v2.6.6
- PostgreSQL v12.4
- Docker-compose v1.26.2
- Docker v19.03.12

## GitHub Actionsとは
言わずも知れた開発管理必須ツールであるGitHub。
そのGitHubが提供するCI/CDサービス。
GitHubと高度に連携されており、設定によりGitHub上のコードを
自動でビルド・テスト・デプロイが可能。

## なぜGitHub Actionsなのか？
私なりにCIを選ぶにあたって他のサービスと検討しました。
他のサービスもありますが、私が比較したのは下記の２つのサービスです。

- Circle CI
SaaS型のCI/CDサービス。FacebookやCyberAgentでの活用事例がありCI/CDサービス大手
QiitaでもCI/CDの記事が豊富にあり、アプリ作成事例での活用も多いイメージがあります。

- Code Pipeline<br> AWSのCI/CDサービス。各種AWSとの連携ができるのが強み

#### 私が選択した理由
①：GitHub Actionsの圧倒的なメリット点<br>
GitHubのリポジトリの場所でCI/CDが実行・確認できる事です。
アプリ開発はGitHubは開発では必然的に使うのでこれは非常に便利です。
またAWSのリソースを触る場合においてアクセスキーを登録する必要がありますが
リポジトリ管理環境とAWSのアクセスキーを一度に管理できるのは、
AWSを使用したデプロイを想定している中で
リポジトリと外部SaaSでCI/CDの管理を分けるより、
進捗及び危機管理しやすい点があると考えました。

②：他のCI/CDとのサービス比較

- 上記でも触れましたが、Circle CIは外部SaaSへの権限移譲の問題などの下記資料も検証し<br>＊[GitHub Actions の self-hosted runner と Amazon EKS を使った Docker の Build Pipeline](https://speakerdeck.com/tmnakagawa/build-pipeline-for-docker-with-github-actions-self-hosted-runner-and-amazon-eks)<br>(freee株式会社)<br>今後の実務の中でもこの事は問題になる可能性があるなら、CI/CDとして最初に使うのは<br>GitHub Actionsでもいいのではないか？と考えました。

- Codepipelineとの比較では今回はAPI側の変更作業が多くなる事が予想されたので
GitHub上でCI/CDを随時実行し確認できた方が作業効率がいいと思われました。

以上を総合判断し、最終的にGitHub Actionsを使用する事としました。

ただこれは初心者が考えた事ですので、間違いやそれぞれのCI/CD側の言い分があるかもしれません。
Circle CIはGithub Actionsより優れていると宣伝してますし(そんな堂々と・・。）
CodepipelineもAWSとの連携をする中では大きなメリットを享受出来る事と思います。

![スクリーンショット 2020-09-18 21.48.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555485/0156fae9-7dc0-4a31-f722-d0cba7b65c89.png)

## GitHub Actions 作成手順・コード
前置きが長くなりました。早速GitHubActionsでのCI/CDを作成して行きます。
公式サイトは下記のリンク先となります。
[GitHub Actionsのドキュメント](https://docs.github.com/ja/free-pro-team@latest/actions)

#### 1. 作成リスト
今回RailsでのアプリでCI/CDを組み立てます。

- .github/workflows ディレクトリ配下（新規作成）
  - linter.yml
  - rspec_security.yml
  - main.yml
- database.yml （修正）

#### 2.CIセッティング

今回のCIは2種類でセッティングしました。
1. Linter　（トリガー：GitHubに`push`した時）
2. RSpec, security (トリガー：GitHubに`push`した時　及び　`pull_request`時）


```linter.yml
name: Linter
on: [push]

jobs:
  linters:
    name: Linters
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.6

      - name: Ruby Bundler
        uses: actions/cache@v2
        with:
          path: vendor/bundle
          key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
          restore-keys: |
            ${{ runner.os }}-gems-
      - name: Bundle install
        run: |
          bundle config path vendor/bundle
          bundle install --jobs 4 --retry 3
      - name: Get yarn cache directory path
        id: yarn-cache-dir-path
        run: echo "::set-output name=dir::$(yarn cache dir)"

      - name: yarn install
        run: yarn install

      - uses: actions/cache@v2
        id: yarn-cache 
        with:
          path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
          key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-yarn-
      - name: Run linters
        run: |
          bundle exec rubocop --parallel
```


```rspec_security.yml
name: Rails RSpec and security
on: [push, pull_request]
env:
  RAILS_ENV: test
  CI_HOST: localhost

jobs:
  build:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:12
        ports:
          - 5432:5432
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
        options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5

      chrome:
        image: selenium/standalone-chrome
        ports:
          - "4444:4444"
        volumes:
          - /dev/shm:/dev/shm

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.6

      - name: Ruby Bundler
        uses: actions/cache@v2
        with:
          path: vendor/bundle
          key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
          restore-keys: |
            ${{ runner.os }}-gems-
      - name: Bundle install
        run: |
          bundle config path vendor/bundle
          bundle install --jobs 4 --retry 3

      - name: Get yarn cache directory path
        id: yarn-cache-dir-path
        run: echo "::set-output name=dir::$(yarn cache dir)"

      - name: yarn install
        run: yarn install

      - uses: actions/cache@v2
        id: yarn-cache 
        with:
          path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
          key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-yarn-

      - name: Setup test database
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
        run: |
          bundle exec rake db:create db:schema:load --trace

      - name: run rspec
        run: bundle exec rspec
        env:
          SELENIUM_REMOTE_URL: http://localhost:4444/wd/hub

      - name: Archive rspec result screenshots
        if: failure()
        uses: actions/upload-artifact@v2
        with:
          name: rspec result screenshots
          path: tmp/screenshots/

      - name: security check
        run: |
          bundle exec bundle-audit check --update
          bundle exec brakeman -q -w2

```
#### CD (ECR自動デプロイ）
CDは`ECR`へのコンテナ`build＆push`を行うセッティングです。
今回の設定でのトリガーは`tag push`の`version(v)`で実行しております。

```main.yml
name: Build and Push

on:
  push:
    tags:
      - v*

jobs:
  build-and-push:
    runs-on: ubuntu-18.04
    timeout-minutes: 300

    steps:
      - uses: actions/checkout@v1

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Build, tag, and push image to Amazon ECR
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: ${{ secrets.AWS_ECR_REPO_NAME }}
        run: |
          IMAGE_TAG=$(echo ${{ github.ref }} | sed -e "s#refs/tags/##g")
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

```

#### database.yml
環境変数のセッティングが必要となります。
テスト部に`ENV.fetch`で第一引数で指定した環境変数を確認し、
なければブロックで定義したデフォルト値が帰るようにしています。

```database.yml
default: &default
  adapter: postgresql
  encoding: utf8
  min_messages: WARNING
  host: db
  port: 5432
  username: postgres
  password: postgres
  pool: 5
  timeout: 5000
  stats_execution_limit: 10

development:
  <<: *default
  database: development

test:
  <<: *default
  database: test
  host: <%= ENV.fetch('CI_HOST') { 'db' } %>

production:
  <<: *default
  database: myworkdb
  host: <%= ENV['DB_URL'] %>
  username: <%= ENV['DB_USERNAME'] %>
  password: <%= ENV['DB_PASSWORD'] %>
```

## CI/CD内容

#### CI
- Linter.yml<br>コード内容としては `rubocop` でのLinterチェックとしました。
ローカル開発環境では`rubocop` `prettier` `beautify` の３種を併用しておりますが、
`push`前のLinter整形忘れチェックとして`push`時にチェックする対応で対応しました。<br>
＊ローカル環境下でのLinterセッティングについては下記記事にまとめております。<br>よろしければご参照下さい。
["Rails"でのフォーマット環境を整える（VScode）
](https://qiita.com/tomtang/items/624ad37219fe8c1df1bd)

- RSpec, security
  - テストはRailsの定番RSpecを走らせました。
  - セキュリティは Gemの脆弱性診断`bundle-audit`と総合セキュリティ`brakeman`を活用しました。<br><br>
  ＊Railsのセキュリティに関しては別記事でまとめております。<br>よろしければご参照下さい。<br>
   [Rails6のセキュリティチェック環境を整える](https://qiita.com/tomtang/items/793ac9b16b737403172b)
  
#### CD
セッティングコードはClassmethodさんの下記の記事を参考にしました。
この記事のおかげで以後の開発が凄く捗りました。ありがとうございます。

- 参考元リンク：[GitHub ActionでDockerコンテナをビルドしてAmazon ECRに保存する](https://dev.classmethod.jp/articles/github-action-ecr-push/)<br>（Developers.io Classmethod株式会社）


## 最後に
最後まで読了頂きありがとうございます。
少しでも資料がどなたかの役に立てれば幸いです。
また今回GitHub Actionsの詳細な設定内容（コード）については触れておりませんので
別記事でGitHub Actionsの設定について個人的にまとめたいと思っております。