これまでCI/CDツールとしてはCircleCIを使っていましたが、昨今の潮流やコスト面での比較検討から弊社のプロジェクトでもGitHub Actionsへの乗り換えを実施しました。
今回はRailsのプロジェクトでGitHub ActionsによるCI/CDを適用する方法をまとめていきます。
本記事の対象読者としては以下の通りです。
- 現状CircleCIを使っていてGitHub Actionsへの乗り換えを検討している
- Rails製のプロダクトにGithub Actionsを適用して、RSpecとrubocopを自動で実行したい
なぜ乗り換えるのか
- 同じことができるならほぼ無料で済むGitHub Actionsの方がコスト面でお得だから
- GitHubでコード管理を行なっているから
- GitHub Actionsのシェアが急速に伸びているのを肌感覚的に感じる(気がする)
- 長い物には巻かれろ的なスピリッツ
CircleCIでのテスト内容
- Dockerイメージから環境をビルド
- コードのチェックアウト
- キャッシュからgemを復元
- gem install(キャッシュがあればAleady Update)
- gemをキャッシュに保存(キャッシュがなければ)
- Node.js と npm を更新(Dockerイメージに内包されているNodeバージョンがアプリのNodeのバージョンと異なるため)
- yarnライブラリをキャッシュから復元
- yarn install(キャッシュがあればAleady Update)
- yarnライブラリをキャッシュに保存(キャッシュがなければ)
- データベースをセットアップ
- rubocop実行
- 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を選択するのはごく自然な選択肢となりそうです。