ガラシのパルプンテ頼み

地方産限界エンジニアのグローバル独り言

【GitHub Actions】CircleCIからGitHubActionsに乗り換えてRailsプロジェクトのCIを回す(RSpec・rubocop)

これまでCI/CDツールとしてはCircleCIを使っていましたが、昨今の潮流やコスト面での比較検討から弊社のプロジェクトでもGitHub Actionsへの乗り換えを実施しました。

今回はRailsのプロジェクトでGitHub ActionsによるCI/CDを適用する方法をまとめていきます。

本記事の対象読者としては以下の通りです。

  • 現状CircleCIを使っていてGitHub Actionsへの乗り換えを検討している
  • Rails製のプロダクトにGithub Actionsを適用して、RSpecとrubocopを自動で実行したい

なぜ乗り換えるのか

  • 同じことができるならほぼ無料で済むGitHub Actionsの方がコスト面でお得だから
  • GitHubでコード管理を行なっているから
  • CIの専門家がいるわけではない弊社チームにおいては、何でもできるCircleCIよりも最低限やりたいことを実現できるGithub Actionsのほうが取り回しがしやすい

CircleCIでのテスト内容

  1. Dockerイメージから環境をビルド
  2. コードのチェックアウト
  3. キャッシュからgemを復元
  4. gem install(キャッシュがあればAleady Update)
  5. gemをキャッシュに保存(キャッシュがなければ)
  6. Node.js と npm を更新(Dockerイメージに内包されているNodeバージョンがアプリのNodeのバージョンと異なるため)
  7. yarnライブラリをキャッシュから復元
  8. yarn install(キャッシュがあればAleady Update)
  9. yarnライブラリをキャッシュに保存(キャッシュがなければ)
  10. データベースをセットアップ
  11. rubocop実行
  12. RSpec実行
version: 2.1

# ビルド
jobs:
  build:
    docker:
      - image: circleci/ruby:3.0.0-node
        environment:
          RAILS_ENV: test
          POSTGRES_HOST: 127.0.0.1
      - image: circleci/postgres:11.5
        environment:
          POSTGRES_USER: app_user
          POSTGRES_DB: app_test
          POSTGRES_PASSWORD: password
          POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --locale=C"

    working_directory: ~/app_name
        
       # コードのチェックアウト
    steps:
      - checkout

      # キャッシュからgemを復元
      - restore_cache:
          keys:
            - gem-cache-v1-{{ checksum "Gemfile.lock" }}
            - gem-cache-v1-

      # gem install(キャッシュがあればAleady Update)
      - run:
          command: |
            gem install bundler
            bundle config set path 'vendor/bundle'
            bundle install --jobs=4 --retry=3

           # gemをキャッシュに保存
      - save_cache:
          paths:
            - ./vendor/bundle
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}

      - run:
          name: "Node.js と npm を更新"
          command: |
            curl -sSL "https://nodejs.org/dist/v16.13.1/node-v16.13.1-linux-x64.tar.xz" | sudo tar --strip-components=2 -xJ -C /usr/local/bin/ node-v16.13.1-linux-x64/bin/node
            curl https://www.npmjs.com/install.sh | sudo bash

      # yarnライブラリをキャッシュから復元
      - restore_cache:
          keys:
            - yarn-packages-{{ checksum "yarn.lock" }}
            - yarn-packages-

      # yarn install(キャッシュがあればAleady Update)
      - run: yarn install

           # ライブラリをキャッシュに保存
      - save_cache:
          paths:
            - ~/.cache/yarn
          key: yarn-packages-{{ checksum "yarn.lock" }}

      # Database setup
      - run: bundle exec rails db:create
      - run: bundle exec rails db:migrate

      # Rubocop
      - run: bundle exec rubocop app
      # Rspec
      - run: bundle exec rspec

GitHub Actionsへの置き換え

1 .github/workflows/配下にワークフローを配置

GitHub Actionsを導入したいプロジェクトのリポジトリに移動したら、「Add file」から「Create new file」を選択して、ファイル作成画面に遷移します。

次にファイル作成画面で.github/workflowsディレクトリにYAMLファイルを配置します。

今回は「testing.yml」という名前でファイルを作成しました。

そのまま画面下までスクロールし、「Create new branch〜」のラジオボタンを選択して、新しく作成するブランチ名を入力。「Propose new file」を押下すると所定のディレクトリにtesting.ymlを配置したブランチが新たに作成されます。

作成後は自身のローカルマシンからたった今作成したブランチにチェックアウトしてワークフローファイルを編集していきます。

2 ワークフローファイルを編集する

GitHub Actionのワークフローとして書き換えたものが以下の通り。

コード内にコメントも付与していますが、以降これを題材としてGitHub Actionsで用いられる概念や用語簡単に説明していきます。

name: testing

on:
  # pushトリガーはmasterブランチへのpush時にのみ実行
  push:
    branches:
      - master
  # pull_requestはopen, synchronize, reopenのタイミングでの実行がデフォルト設定になっている
  # synchronizeはPRに変更があった場合という意味
  pull_request:

jobs:
  test:
    # ホストランナー(仮想マシン)のイメージを選択
    runs-on: ubuntu-latest
    timeout-minutes: 10
    # DB用のサービスコンテナを作成
    services:
      postgresql: # サービス名
        image: postgres:11.5 # DockerHubのイメージ参照
        ports: # 仮想マシン内で使用するポート番号
          - 5432:5432
        env: # 仮想マシン内でサービスから使用される環境変数を定義
          POSTGRES_USER: app_user
          POSTGRES_DB: app_test
          POSTGRES_PASSWORD: password
          POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --locale=C"

    # steps: コードのチェックアウトやRubyのインストールなど、特定の処理の単位
    steps:
      - name: Checkout code # ステップ名
        # uses: アクションを実行するキー。
        uses: actions/checkout@v3

      # GitHubの公式アクションとして「actions/setup-ruby」が存在するが最新バージョンの追随が遅いなどの理由からあまり使われていない模様
      # Railsのリポジトリを見ても「ruby/setup-ruby」を使用しているためこちらを採用
      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with: # アクションに与える引数を指定
          bundler-cache: true
          ruby-version: 3.0.0

      # withに「cache: yarn」を指定しても、ホストランナー上のnode_modulesをキャッシュしない
      # https://dev.classmethod.jp/articles/caching-dependencies-in-workflow-execution-on-github-actions/
      - name: Set up Node.js
        # v2ではNVMを使って複数バージョンを管理可能。単一のバージョンを指定する場合はv3を利用
        uses: actions/setup-node@v3
        with:
          node-version: 16.13.1

      # node関連の依存関係をキャッシュ
      - name: Cache node modules
        uses: actions/cache@v2
        with:
          path: node_modules # キャッシュ対象のディレクトリ
          key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }} # キャッシュ時のキー
          restore-keys: | # キャッシュの復元
            ${{ runner.os }}-node-

      - name: Bundler and gem install
        run: |
          gem install bundler
          bundle install --jobs 4 --retry 3 --path vendor/bundle

      - name: Yarn install
        run: yarn install

      - name: Database create and migrate
        run: |
          cp config/database.yml.ci config/database.yml
          bundle exec rails db:create RAILS_ENV=test
          bundle exec rails db:migrate RAILS_ENV=test

      - name: Run rubocop
        run: bundle exec rubocop app

      - name: Run rspec
        run: bundle exec rspec

3 config配下にdatabase.yml.ciを配置

database.yml.ciは、Railsアプリケーションのテスト環境で使用されるデータベース設定ファイルです。

通常、Railsアプリケーションはdatabase.ymlという名前のファイルを使用してデータベース接続情報を管理します。

ただし、CI/CDツールであるGitHub Actionsでは、通常のdatabase.ymlファイルではうまく動作しない場合があります。たとえば、CI/CD環境で使用するデータベースの接続情報が異なる場合があります。

そのため、database.yml.ciファイルを作成し、CI/CDツールで使用されるデータベース接続情報を定義することが推奨されています。

test:
  adapter: postgresql
  database: app_test
  username: app_user
  password: password
  host: localhost

GitHub Actions内で使用される基本概念、用語

イベント

冒頭のonキーでジョブの実行タイミングが制御している箇所。push、pull_request等が実行タイミングを決めるイベントキー。branchesはどのブランチにそれらが行われたらワークフローを実行するかを決めるキーです。

上述のコードでは「masterブランチにpushされた場合」「pull_requestがオープン・変更・再オープンされた場合」にワークフローを実行するよう指示しています。pull_requestイベントキーの初期値はオープン・変更・再オープン時となっていますが、typesキーでこれを変更することも可能です。

ワークフロー

GitHub Actionsでは、このファイル全体を指してワークフローという単位で数えます。

ワークフローは一つまたは複数のジョブから構成されます。

ジョブ

jobsキー配下に置かれている処理群が「ジョブ」

ジョブはその配下の一つまたは複数のステップから構成されます。

ステップ

ジョブを構成するアクションやrunに続くコマンドの集合を指します。

usesキーでアクションを、runキーでコマンドを実行します。

アクション

GitHub Actionsでは再利用製の高いコード(依存関係のキャッシュやツールチェーンのセットアップなど)を「アクション」として提供してくれています。

アクションとはGitHub、またはサードパーティ製のカスタムライブラリで、再利用製の高いコードを関数化したようなものです。

コマンド

ホストランナー上のOSで使用できるコマンドを指します。

平たく言えば普段ターミナル上で実行しているコマンド類と同様です。

ホストランナー

ワークフローが実行される仮想マシンのことです。

runs-onキーでGitHubが用意する所定のOSを指定して使用します。

まとめ

導入してみてメリットに感じたこと

  • 導入が簡単
    • 所定のディレクトリにymlを置くだけですぐに使えるのが楽
  • 当たり前だけどGitHubとの親和性が高い。
    • ワークフローの実行トリガーがイベント・ブランチに応じてかなり細かく指定できる
  • CIの所要時間が短縮された
    • これはspecのコード量や実行環境によりそうですが、弊社の環境では特に2回目以降のキャッシュ込みでの所要時間が2m程度は短縮されました

これから新たにプロジェクトをオープンし、コードのホスティングをGitHubで行う場合、GitHub Actionsを選択するのはごく自然な選択肢となりそうです。