CircleCIマン が GitHub Actions を導入するまで
普段は CircleCI でCI/CDを構築していた自分が何もわからない状態から GitHub Actions をどうやって導入したかを紹介しようと思います
自作のRubyのコマンドラインツールにCIを導入した時の話になります
自作のRubyのコマンドラインツールについて
qiita_command という Qiitaのトレンド情報(Daily, Weekly, Monthly)をコマンドラインで簡単に見れるツールです
GitHub Actionsの初回導入
とりあえず簡素な状態でGitHub Actionsが動く状態まで持っていきます
テンプレートの選択
まず始めに Actions をクリックし Ruby の Set up this workflow をクリックします
リポジトリの内容にあったWorkflowsテンプレートを表示してくれるので良さそうなものを選択し導入します
Workflowsのファイルをリポジトリに追加
当たり前だがCircleCIとymlファイルの中身が違う
見てもよくわからない……
とりあえず、 RSpecが実行される ように Run tests の部分を変更する
変更前
- name: Run tests
run: bundle exec rake
変更後
- name: Run tests
run: bundle exec rspec
リポジトリに .github/workflows 配下にGitHub Actionsのファイルが出来上がることがわかります
最後に Start commit をクリックして Commit new file をクリックしてリポジトリにコミットします
実行結果を確認する
再度、 Actions をクリックし Workflows の一覧から先程作成したWorkflowsのコミットの Create ruby.yml をクリックします
左側の test をクリックし詳細を確認します
CIが実行中の場合です
CIが完了すると以下のような画面になります
以上で簡単に RSpec を実行するだけの CI を実装することができました
workflows ファイルの中身を確認してみる
name、jobs について
name が上に表示され、 jobs の内容が name 配下に表示されます
jobs は複数追加することができます
on について
GitHub Actions が実行されるトリガーを設定することができます(いろいろなイベントに対して トリガー を実行できる)
下の例だと main ブランチでpush、pull request が行われた時に実行されるように設定されています
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
CircleCIでもブランチによるCIの制御を行うことができるが push や pull request で制御を行うことができない
CircleCIだと以下のようになる
workflows:
build-deploy:
jobs:
- build_server_pdfs:
filters:
branches:
only: main
runs-on について
ここで実行する環境を指定します
ubuntu の最新バージョンになります
runs-on: ubuntu-latest
CircleCIで言うところの下記のような記述の一部と同じになります
GithubAcitonsではOS寄りな環境設定になりますが CircleCI では言語に寄っていることが多い気がします
Docker Image の circleci/ruby:2.6.0 が何で作られているかによってOSが決まります
executors:
base:
docker:
- image: circleci/ruby:2.6.0
uses について
uses を使用することにより、再利用可能なコードを宣言することができる
with を使用して設定を追加することができる
- actions/checkout@v2はリポジトリをチェックアウトしてくれる
- ruby/setup-rubyはビルド済みのRubyをダウンロードしPATHに追加して Ruby を使えるようにしてくれる
steps:
- uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@ec106b438a1ff6ff109590de34ddc62c540232e0
with:
ruby-version: 2.6
CircleCI で言うところの CircleCI Orbs に近い気がします
orbs:
slack: circleci/slack@3.4.2
・
・
・
workflows:
version: 2.1
main:
jobs:
- slack/approval-notification:
message: ':circleci-pass: Slackへメッセージを送付します'
run について
これは CircleCI と同じでコマンドを実行できます
- name: Install dependencies
run: bundle install
- name: Run tests
run: bundle exec rspec
コマンドの実行履歴で name で設定した部分が GitHub Actions に表示されます
自分の思い通りにGitHub Actionsを設定する
とりあえずGitHub Actionsというものを雰囲気分かってもらえたと思います
最小構成で実装することができたが他のプロジェクトではどのように設定しているのだろうか……
先人の知恵をお借りする
BestGems.org という Ruby gems のダウンロードランキングを確認することができるサイトから総ダウンロード数TOP10のプロジェクトを参考にしてみたいと思います
ランキング | 名前 | GitHub Actions使用有無 |
---|---|---|
1 | rspec-expectations | ❌ |
2 | rspec-core | ❌ |
3 | rspec-mocks | ❌ |
4 | diff-lcs | ⭕ |
5 | rspec-support | ❌ |
6 | rspec | ❌ |
7 | bundler | ⭕ |
8 | multi_json | ❌ |
9 | rack | ⭕ |
10 | rake | ⭕ |
※ランキングは 2020年10月16日のものです
diff-lcs、bundler、rack、rake を参考にして作成していきたいと思います
ファイル構成を確認する
プロジェクトごとファイル構成を確認する
diff-lcs
ci.yml は複数のOS、複数のRubyのバージョンでのテストを実行しているようだ
diff-lcs
└ .github
└ workflows
└ ci.yml
bundler
主にテストをOSごと実行する Workflow に Linter を実行する Workflow に分けているようだ
bundler
└ .github
└ workflows
├ jruby.yml
├ ubuntu-bundler3.yml
├ ubuntu-lint.yml
├ ubuntu.yml
└ windows.yml
rack
development.yml は複数のOS、複数のRubyのバージョンでのテストを実行しているようだ
rack
└ .github
└ workflows
└ development.yml
rake
主にテストをOSごと、複数のRubyバージョンでのテストを実行しているようだ
rake
└ .github
└ workflows
├ macos.yml
├ test.yml
└ windows.yml
ファイルの中身を参考にする
ファイルの中身を確認し参考になりそうな部分を確認してみる
rake/.github/workflows/test.yml
下記のようにすると
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ 'ubuntu-latest', 'macos-latest', 'windows-latest' ]
ruby: [ 2.6, 2.5, 2.4, 2.3, 2.2, jruby, jruby-head, truffleruby, ruby-head ]
・
・
・
steps:
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
OSとRubyのバージョンを配列で宣言して1つのファイルでCIを実行することができるようだ
name: ubuntu
on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ 'ubuntu-latest', 'macos-latest', 'windows-latest' ]
ruby: [ 2.6, 2.5, 2.4, 2.3, 2.2, jruby, jruby-head, truffleruby, ruby-head ]
exclude:
- os: windows-latest
ruby: truffleruby
- os: windows-latest
ruby: jruby-head
- os: windows-latest
ruby: jruby
steps:
- uses: actions/checkout@v2
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
- name: Install dependencies
run: gem install minitest
- name: Run test
run: ruby -Ilib exe/rake
diff-lcs/.github/workflows/ci.yml
下記のようにすると
runs-on: ${{ matrix.os }}-latest
runs-onの指定の時 -latest で宣言するとOSの指定をOS名だけで宣言することができるらしい
つまりOS名とバージョンの指定を分離することができる
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
name: CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
test:
strategy:
matrix:
os:
- ubuntu
- macos
- windows
ruby:
- 2.5
- 2.6
- 2.7
- head
- debug
- mingw
- mswin
- jruby
- jruby-head
- truffleruby
- truffleruby-head
exclude:
- os: macos
ruby: mingw
- os: macos
ruby: mswin
- os: ubuntu
ruby: mingw
- os: ubuntu
ruby: mswin
- os: windows
ruby: debug
- os: windows
ruby: truffleruby
- os: windows
ruby: truffleruby-head
runs-on: ${{ matrix.os }}-latest
continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' || matrix.os == 'windows' }}
steps:
- uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- name: Install dependencies
run: bundle install
- name: Run tests
run: bundle exec ruby -S rake
workflows のファイルを編集する
上記情報を元にファイルを編集していく
複数OS、複数Rubyバージョンで実行できるようにする
- workflows 名を CI に変更
- OSを ubuntu、macos で実行できるようにする
- Rubyのバージョンを 2.7、2.6 で実行できるようにする
- uses で使用している ruby/setup-ruby のバージョンを v1 にする
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
ci:
strategy:
matrix:
os: [ubuntu, macos]
ruby: [2.7, 2.6]
runs-on: ${{ matrix.os }}-latest
steps:
- uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
- name: Install dependencies
run: bundle install
- name: Run tests
run: bundle exec rspec
ブランチでの絞り込みをなくす
- push と pull_request のイベント時に実行されるよう on 句を変更する
on: [push, pull_request]
<details>
<summary>on句を上記の書き方に変更する</summary>
<div>
name: CI
on: [push, pull_request]
jobs:
ci:
strategy:
matrix:
os: [ubuntu, macos]
ruby: [2.7, 2.6]
runs-on: ${{ matrix.os }}-latest
steps:
- uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
- name: Install dependencies
run: bundle install
- name: Run tests
run: bundle exec rspec
</div>
</details>
静的解析ツールを実行する
- RuboCopが実行されるようにする
- name: Run Rubocop
run: bundle exec rubocop
<details>
<summary>上記設定を追加する</summary>
<div>
name: CI
on: [push, pull_request]
jobs:
ci:
strategy:
matrix:
os: [ubuntu, macos]
ruby: [2.7, 2.6]
runs-on: ${{ matrix.os }}-latest
steps:
- uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
- name: Install dependencies
run: bundle install
- name: Run Rubocop
run: bundle exec rubocop
- name: Run tests
run: bundle exec rspec
</div>
</details>
テストのカバレッジ結果を見れるようにする
CircleCIだと store_artifacts を使用するとアーティファクトのアップロードができるようになります
同じことをGitHub Actionsでもできるようにします
公式のドキュメントの以下の記事を参考にテストのカバレッジをアーティファクトとしてアップロードされるようにしようと思います
記事を参考に下記の設定を追加します
- name: Archive code coverage results
uses: actions/upload-artifact@v2
with:
name: code-coverage-report
path: coverage
<details>
<summary>設定追加後</summary>
<div>
name: CI
on: [push, pull_request]
jobs:
ci:
strategy:
matrix:
os: [ubuntu, macos]
ruby: [2.7, 2.6]
runs-on: ${{ matrix.os }}-latest
steps:
- uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
- name: Install dependencies
run: bundle install
- name: Run Rubocop
run: bundle exec rubocop
- name: Run tests
run: bundle exec rspec
- name: Archive code coverage results
uses: actions/upload-artifact@v2
with:
name: code-coverage-report
path: coverage
</div>
</details>
Slack通知を実装する
CircleCI の場合は Orb を使用することでSlack通知を行うことができるようになる
GitHub Actionsでは同じように uses を使用して行うことができるようだ
幾つかSlack通知が行えるものがあるようだが今回はドキュメントもしっかりとある action-slack を使用して実装する
ドキュメントを参考に以下を追加する
- name: Github Actions notify to Slack
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
fields: repo,message,commit,author,action,eventName,ref,workflow,job,took
mention: 'here'
if_mention: failure
env:
GITHUB_TOKEN: ${{ github.token }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
MATRIX_CONTEXT: ${{ toJson(matrix) }}
if: always()
このままでは以下の部分が設定されてないので読み取ることができない
secrets.SLACK_WEBHOOK_URL
以下の公式の記事を参考にSlackの WEBHOOK_URL をリポジトリに設定します
CircleCI の環境変数を設定することと同じことをしています
下記の画面みたいな表示になっていれば SLACK_WEBHOOK_URL 設定は大丈夫です
<details>
<summary>上記設定が完了したら設定を追加します</summary>
<div>
name: ci
on: [push, pull_request]
jobs:
ci:
strategy:
matrix:
os: [ubuntu, macos]
ruby: [2.7, 2.6]
runs-on: ${{ matrix.os }}-latest
steps:
- uses: actions/checkout@v2
- name: set up ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
- name: install dependencies
run: bundle install
- name: run rubocop
run: bundle exec rubocop
- name: run tests
run: bundle exec rspec
- name: archive code coverage results
uses: actions/upload-artifact@v2
with:
name: code-coverage-report
path: coverage
- name: github actions notify to slack
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
fields: repo,message,commit,author,action,eventname,ref,workflow,job,took
mention: 'here'
if_mention: failure
env:
github_token: ${{ github.token }}
slack_webhook_url: ${{ secrets.slack_webhook_url }}
matrix_context: ${{ tojson(matrix) }}
if: always()
</div>
</details>
実際に実行する
上記で編集した workflows のファイルを push することで GitHub Actions が実行されるようになります
詳細を確認していく
OS、RubyのバージョンごとCIが実行されるようになっています
完了すると Artifacts にテストのカバレッジ結果が zip で圧縮されてダウンロードできるようになっています
CircleCIと違って画面から結果を確認することはできません
また1つのCIが完了するごとにSlackに完了通知が飛びます
これで一通りやりたかったことができるようになりました!
応用編:Gemの自動アップデート用のプルリクをGitHub Actionsで自動化する
応用編として、Gemの自動アップデート用のプルリクを自動で作成する workflows を作ろうと思います
SSH で GitHub Actions に入る
CIを作成していく段階で個人的に必須な SSH で接続する機能は CircleCI では当たり前だが GitHub Actions ではどうするのか……
公式では用意されていないようなので uses を使用して行うことができる
下記の記事を参考にするとよい
スケジュールで実行するにはどうしたよいのか?
CircleCI だとトリガーを使用することでスケジュールされたCIを実行することができる
GitHub Actions では on.schedule を使用することで実現できそうです
CircleCI でも GitHub Actions のどちらも cron 形式でスケジュールを設定することができます
crontab guru のサイトを参考にしてスケジュールを設定するとよいでしょう
こんな感じで設定する
on:
schedule:
# * はYAMLに置ける特殊文字なので、この文字列は引用符で囲まなければならない
- cron: '*/15 * * * *'
schedule の詳しい仕様については公式のドキュメントを参考にすること
CircleCI と同じで GitHub Actions も cron は UTC で設定する必要があります
手順を考える
以下の手順をCIで実行することができれば実現できそうである
- ① Gitの設定を行う(メールアドレス、ユーザー名)
- ② Gem Update 用のブランチを作成する
- ③ bundle update を行う
- ④ commit して push する
- ⑤ プルリクエストを自動で作成する
- ⑥ Slackに完了通知を行う
実装する
上記で考えた手順を元に実際に実装していく
手順以外の実装
毎月の1日の9時に実行されるようにする
cron 式で設定した以外は先程、作成したものとほぼ同じである
<details>
<summary>設定内容</summary>
<div>
name: GemUpdate
on:
schedule:
- cron: '0 0 1 * *'
jobs:
create-gem-update:
strategy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
- name: Install dependencies
run: bundle install
</div>
</details>
① Gitの設定を行う(メールアドレス、ユーザー名)
git checkout ができているので git はおそらくインストールされている前提ですすめる
インストールされているかなど調査する時は直接 ssh で接続して確認すると良い
複数行の時はCircleCIと同じく | を使用するようだ
- name: Settings Git
run: |
git config --global user.email ${{ secrets.MAIL_ADDRESS }}
git config --global user.name "dodonki1223"
secrets.MAIL_ADDRESS こちらの設定は SLACK_WEBHOOK_URL と同じように設定します
この場合はメールアドレス直打ちでもいいような気がする
② Gem Update 用のブランチを作成する
ブランチを名前を付けて切り替えるコマンドです
gem_update_20201001 みたいな形がブランチ名になります
git checkout -b "gem_update_`date +%Y%m%d`"
③ bundle update を行う
前の段階で bundle install を行っているので update を行うだけです
bundle update
④ commit して push する
ファイルをコミットして先程作成したブランチに push します
git add .
git commit -m ':wrench: Gem Update'
git push origin "gem_update_`date +%Y%m%d`"
⑤ プルリクエストを自動で作成する
hubコマンドを使用してプルリクエストを作成する
最近、GitHub CLI がリリースされたので hubコマンドの代わりに GitHub CLI を使用するでもよいかもしれません
hub pull-request -b master -m "🔧 Gem Update `date +%Y-%m-%d`"
②〜⑤を組み立てる
基本的には②〜⑤をそのまま繋げるだけで大丈夫なのですが下記の記述が追加で必要です
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
上記記述に関しては hubコマンドのリポジトリに Readme に GitHub Actions で使用する時のサンプル例に書かれている例になります
- name: Create gem update pull request
run: |
git checkout -b "gem_update_`date +%Y%m%d`"
bundle update
git add .
git commit -m ':wrench: Gem Update'
git push origin "gem_update_`date +%Y%m%d`"
hub pull-request -b master -m "🔧 Gem Update `date +%Y-%m-%d`"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
コマンドを組み上げる時は基本的には実際に ssh でログインして実際の環境で使用できるか確認しながらすすめるとよいです
コマンドのインストールが必要かどうかなども ssh でログインしながら確かめると良いです
⑥ Slackに完了通知を行う
こちらは先程、作成したもので既に行っているので特に説明はしません
- name: Github Actions notify to Slack
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
fields: repo,message,commit,author,action,eventName,ref,workflow,job,took
mention: 'here'
if_mention: failure
env:
GITHUB_TOKEN: ${{ github.token }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: always()
完成品
実際に作成したもので最終形は以下になります
<details>
<summary>完成品</summary>
<div>
name: GemUpdate
on:
schedule:
- cron: '0 0 1 * *'
jobs:
create-gem-update:
strategy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
- name: Install dependencies
run: bundle install
- name: Settings Git
run: |
git config --global user.email ${{ secrets.MAIL_ADDRESS }}
git config --global user.name "dodonki1223"
- name: Create gem update pull request
run: |
git checkout -b "gem_update_`date +%Y%m%d`"
bundle update
git add .
git commit -m ':wrench: Gem Update'
git push origin "gem_update_`date +%Y%m%d`"
hub pull-request -b master -m "🔧 Gem Update `date +%Y-%m-%d`"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Github Actions notify to Slack
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
fields: repo,message,commit,author,action,eventName,ref,workflow,job,took
mention: 'here'
if_mention: failure
env:
GITHUB_TOKEN: ${{ github.token }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: always()
</div>
</details>
注意事項
on.schedule は下記の時だけ実行されます
デフォルトまたはベースブランチの直近のコミットで実行されます
これに気づかないといくらやってもスケジュールが実行されずハマることになります……自分は実行されずにすごく困ったらこれが原因でした
CircleCIとGitHub Actions の違うところ
自分が感じた CircleCI と GitHub Actions の違いをまとめようと思います
GitHub Actionsは遅い時がある
GitHub Actionsを使っていて気になったのだがなぜかすごく遅い時がある
CircleCIだとだいたい終了時間が同じ感覚を受けるがGitHub Actionsはやたらと遅い時がある
使用されるサーバーのスペックガチャにより早かったり、遅かったりするのかも知れない……
いろいろなトリガーが用意されている
CircleCI と違っていろいろな webhookイベントをトリガーに実行できるようだ
詳しくはドキュメントを参考にすると良い
ファイルの構成がスッキリする
CircleCIと違ってファイルの分割ができるため、行数を少なくすることができる
最後に
ずっとGitHub Actionsが難しそうで逃げていましたが実際にやってみるとすぐに実装することができました
先人の知恵をお借りしたことにより自分の中で思ったよりも早く理解することができたのだと思います
これからもGitHub Actions を使っていきましょう!!
Discussion