普段は CircleCI でCI/CDを構築していた自分が何もわからない状態から GitHub Actions をどうやって導入したかを紹介しようと思います
自作のRubyのコマンドラインツールにCIを導入した時の話になります
qiita_command という Qiitaのトレンド情報(Daily, Weekly, Monthly)をコマンドラインで簡単に見れるツールです
とりあえず簡素な状態でGitHub Actionsが動く状態まで持っていきます
まず始めに Actions をクリックし Ruby の Set up this workflow をクリックします
リポジトリの内容にあった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 を実装することができました
name が上に表示され、 jobs の内容が name 配下に表示されます
jobs は複数追加することができます
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
ここで実行する環境を指定します
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 を使用することにより、再利用可能なコードを宣言することができる
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へメッセージを送付します'
これは CircleCI と同じでコマンドを実行できます
- name: Install dependencies
run: bundle install
- name: Run tests
run: bundle exec rspec
コマンドの実行履歴で name で設定した部分が 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 を参考にして作成していきたいと思います
プロジェクトごとファイル構成を確認する
ci.yml は複数のOS、複数のRubyのバージョンでのテストを実行しているようだ
diff-lcs
└ .github
└ workflows
└ ci.yml
主にテストをOSごと実行する Workflow に Linter を実行する Workflow に分けているようだ
bundler
└ .github
└ workflows
├ jruby.yml
├ ubuntu-bundler3.yml
├ ubuntu-lint.yml
├ ubuntu.yml
└ windows.yml
development.yml は複数のOS、複数のRubyのバージョンでのテストを実行しているようだ
rack
└ .github
└ workflows
└ development.yml
主にテストをOSごと、複数のRubyバージョンでのテストを実行しているようだ
rake
└ .github
└ workflows
├ macos.yml
├ test.yml
└ windows.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
下記のようにすると
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 名を 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]
on句を上記の書き方に変更する
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
- RuboCopが実行されるようにする
- name: Run Rubocop
run: bundle exec rubocop
上記設定を追加する
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
CircleCIだと store_artifacts を使用するとアーティファクトのアップロードができるようになります
同じことをGitHub Actionsでもできるようにします
公式のドキュメントの以下の記事を参考にテストのカバレッジをアーティファクトとしてアップロードされるようにしようと思います
記事を参考に下記の設定を追加します
- name: Archive code coverage results
uses: actions/upload-artifact@v2
with:
name: code-coverage-report
path: coverage
設定追加後
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
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 設定は大丈夫です
上記設定が完了したら設定を追加します
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()
上記で編集した workflows のファイルを push することで GitHub Actions が実行されるようになります
OS、RubyのバージョンごとCIが実行されるようになっています
完了すると Artifacts にテストのカバレッジ結果が zip で圧縮されてダウンロードできるようになっています
CircleCIと違って画面から結果を確認することはできません
また1つのCIが完了するごとにSlackに完了通知が飛びます
これで一通りやりたかったことができるようになりました!
応用編として、Gemの自動アップデート用のプルリクを自動で作成する workflows を作ろうと思います
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 式で設定した以外は先程、作成したものとほぼ同じである
設定内容
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
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_20201001 みたいな形がブランチ名になります
git checkout -b "gem_update_`date +%Y%m%d`"
前の段階で bundle install を行っているので update を行うだけです
bundle update
ファイルをコミットして先程作成したブランチに 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 でログインしながら確かめると良いです
こちらは先程、作成したもので既に行っているので特に説明はしません
- 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()
実際に作成したもので最終形は以下になります
完成品
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()
on.schedule は下記の時だけ実行されます
デフォルトまたはベースブランチの直近のコミットで実行されます
これに気づかないといくらやってもスケジュールが実行されずハマることになります……自分は実行されずにすごく困ったらこれが原因でした
自分が感じた CircleCI と GitHub Actions の違いをまとめようと思います
GitHub Actionsを使っていて気になったのだがなぜかすごく遅い時がある CircleCIだとだいたい終了時間が同じ感覚を受けるがGitHub Actionsはやたらと遅い時がある
使用されるサーバーのスペックガチャにより早かったり、遅かったりするのかも知れない……
CircleCI と違っていろいろな webhookイベントをトリガーに実行できるようだ 詳しくはドキュメントを参考にすると良い
CircleCIと違ってファイルの分割ができるため、行数を少なくすることができる
ずっとGitHub Actionsが難しそうで逃げていましたが実際にやってみるとすぐに実装することができました
先人の知恵をお借りしたことにより自分の中で思ったよりも早く理解することができたのだと思います
これからもGitHub Actions を使っていきましょう!!