生産性向上ブログ

継続的な生産性向上を目指すエンジニアのためのブログ

新 GitHub Actions 入門

github.blog

GitHub Actions の新バージョンが 8/8 に発表されました。

www.kaizenprogrammer.com

自分は過去にも旧バージョン時に GitHub Actions の入門記事を書いていたのですが、新バージョンがこれまでと大きく変わってしまっているので、この記事ではあらためて GitHub Actions についていろいろ調べたり動かしてみたりした内容をまとめます。

目次

注意事項

GitHub Actions はこの記事の執筆時点(2019/08/19)ではまだベータです。11/13 の GitHub Universe のタイミングで GA になる予定との公式発表があるので今後大きくは変わらないと思いますが、まだなにかしら変更になる可能性はあります。

ベータへの申しこみは、GitHub Actions のページから可能です。自分は元々 GitHub Actions のベータに参加していましたが、新バージョンの発表から数日してからあらためてベータ利用可能になったというメールがきて、そのタイミングから新バージョンの機能が使えるようになりました。

GitHub Actions とは

GitHub Actions とは、GitHub のリポジトリでイベントが発生したときにワークフローを実行できる仕組みです。ワークフローは複数のアクションと呼ばれるタスクから構成されます。

例えば、リポジトリに push されたときに自動テストを実行したり、新しい PR や issue が作成されたときに slack に通知を飛ばしたりといったことができます。

アクションは GitHub リポジトリや Docker Hub などで公開できるので、世界中で共有することもできます。

これまでの GitHub Actions とどこが変わったか

GitHub Actions はこれまでもベータとして利用可能でしたが、新バージョンになって大きく変更になりました。なので、旧バージョンの GitHub Actions についての情報は基本的にもう参考にならないと考えてもらっていいです。

コンセプト

まず、コンセプト面としては、CI/CD を前面に押し出してきています。これまでも CI/CD としての利用はもちろん可能だったのですが、公式としてはワークフローという言葉を使うことが多く、あまり CI/CD ツールとしてはアピールしていないという印象でした。しかし、新バージョンでは明確に CI/CD 用途として使ってもらうことを目的としているようです。

マルチプラットフォーム対応

これまでは Linux のみでしたが、WindowsmacOS も利用できるようになりました。マトリクスビルドも可能なので、各 OS 上で同時にテストを実行することができるようになりました。

HCL から YAML へ

ワークフローの記述方法も大きく変わりました。これまでは HCL という HashiCorp 社製の設定言語で記述していたのですが、新バージョンでは YAML で記述するようになっています。公式ドキュメントに、

Support for the HCL syntax in GitHub Actions will be deprecated on September 30, 2019.

とあるので、GitHub Actions の HCL 記法版は 9/30 で少なくとも非推奨となるようです。このタイミングで完全に利用できなくなるのかはよくわかりませんが、HCL を YAML に変換するマイグレーションツールが公開されており、移行手順も公式ドキュメントに書かれているので、旧バージョンユーザーは新バージョンのベータが有効になり次第、早めに移行しましょう。

https://github.com/actions/migrategithub.com

help.github.com

料金

料金についても明確にされており、パブリックリポジトリは完全無料プライベートリポジトリは従量制の課金体系が発表されています。具体的な金額は公式ページの下の方に書かれています。

その他

ここまでに書いた以外にも、GitHub Package Registry との連携やログの改善など、様々な点が変更になっています。一方でなくなった機能もあり、ビジュアルエディタとかが見当たらなくなっています。

GitHub Actions と Azure Pipelines

今回の大きな変更には、GitHub Actions のバックエンドが Azure Pipelines の fork 版になったということが背景としてあります。公式ドキュメントに以下のように書かれています。

The GitHub Actions virtual environments are hosted on virtual machines in Microsoft Azure with the GitHub Actions runner installed. The GitHub Actions runner is a fork of the Azure Pipelines Agent. (訳: GitHub Actions の仮想環境は Microsoft Azure 上の VM に GitHub Actions runner をインストールしてホストされています。GitHub Actions runner は Azure Pipelines Agent の fork です。)

fork なので Azure Pipelines そのまま使えるというわけではなく、YAML の記述などで異なるところがあるようです。

簡単な例 (Hello, World)

実際に簡単な GitHub Actions を作成してみます。

GitHub Actions が有効になったユーザーか organization 下に適当なリポジトリを作成し、以下のような .github/workflows/hello.yml ファイルを作成します。

name: Hello, World!
on: push

jobs:
  build:
    name: Greeting
    runs-on: ubuntu-latest
    steps:
      - run: echo "Hello, World!"

このファイルはワークフローを定義しています。

最初の name はワークフローの名前です。次の on でリポジトリへの push イベント時にこのワークフローは実行されると定義しています。

jobs ブロックでワークフロー内で実行されるジョブを定義しています。build と書いているところはジョブの ID で、jobs のキーとしてユニークであれば他の文字列でも大丈夫です。name でジョブ名、runs-on でこのジョブが実行される仮想環境を指定しています。

steps でジョブ内で実行されるステップを定義しています。今回は、run でシェルスクリプトを実行するだけです。

このファイルを作成してリポジトリに push すると、GitHub Actions が実行されます。GitHub のリポジトリのページ上で Actions タブをクリックすると見られます。

f:id:miya-jan:20190818202450p:plain

Workflow runs に表示されている最新の実行をクリックすると各ステップが表示され、Run ステップをクリックすると標準出力に "Hello, World!" と表示されていることが確認できます。

f:id:miya-jan:20190818202512p:plain

以上で、リポジトリに push されるたびに "Hello, World!" と表示するだけの簡単なワークフローが作成できました。

ワークフローの設定

ワークフローについてより詳細に解説していきます。

ワークフローとは

ワークフローの定義は、リポジトリの .github/workflows ディレクトリ下に YAML 形式のファイルとして保存されます。

ワークフローは複数のジョブから構成される自動化されたプロセスです。1 つのリポジトリに対して複数のワークフローが作成可能です。

ジョブは複数のステップから構成されます。ステップはコマンドの実行かアクションの使用が可能です。アクションは自作することもできますし、コミュニティで共有されたアクションを利用することも可能です。

ワークフローを実行するイベント

ワークフローを実行させる方法は、3 つあります。

1 つ目は、GitHub イベントで実行させる方法です。これは簡単な例と同じように on: push のように記述することで指定した GitHub イベントが発生したときにワークフローを実行できます。on: [push, pull_request] のように書くことで複数のイベントに対応させることもできます。イベントの種類については公式ドキュメントを参照してください。

ちなみに、fork 先のリポジトリから PR が作成された場合、pull_request イベントは fork 元のリポジトリで実行されます。fork 先で CI 的なワークフローを実行したい場合は、push イベントを設定したほうがいいようです。

2 つ目は、cron 形式で記述して定期実行させる方法です。例として、15 分ごとに実行させる場合は on に以下のように記述します。

on:
  schedule:
    - cron: "*/15 * * * *"

* は YAML で特殊文字なので、cron の記述はクオートする必要があります。

3 つ目は、外部からワークフローを実行するための方法です。repository_dispatch イベントを引き起こすための REST API エンドポイントが用意されているので、ワークフロー側は on: repository_dispatch のように記述し、外部からエンドポイントにリクエストを投げると実行されます。例として、curl なら以下のようにエンドポイントにリクエストを投げてワークフローを実行できます。

curl -X POST -H "Authorization: token PERSONAL_ACCESS_TOKEN" -H "Accept: application/vnd.github.everest-preview+json" --data '{"event_type": "test-hello"}' https://api.github.com/repos/:owner/:repo/dispatches

Authorization ヘッダーに乗せるパーソナルアクセストークンには repo 権限が必要なようです。POST するデータの event_type は必須ですが、任意の値で大丈夫です。Accept: application/vnd.github.everest-preview+json ヘッダーはこの機能が現時点でプレビューなので必要になっています。

ワークフローの実行をブランチやタグ、変更されたファイルのパスでフィルタする

CI/CD でよくあるのが、master ブランチが変更されたときや特定の命名規則のタグが作成されたときだけ実行したい、特定のディレクトリ以下に変更されたときだけ実行したい、といったワークフロー実行のフィルタリングです。

GitHub Actions でも on の設定でそういったことが可能です。デフォルトで push イベントのみを指定したときはすべてのブランチ、すべてのタグへの push イベントに対して実行されてしまいますが、例えば、master ブランチに push されたときのみ実行したいときは以下のように記述します。

on:
  push:
    branches:
      - master
    tags:
      - "!*"

v で始まるタグが作成されたときのみ実行したいときは以下のように記述します。1

on:
  push:
    branches:
      - "!*"
    tags:
      - "v*"

tests ディレクトリ直下のファイルが変更されたときのみ実行したいときは以下のように記述します。

on:
  push:
    paths:
      - "tests/**"

branches, tags, paths の文法は、.gitignore と同じと公式ドキュメントには書かれています。2

所感としては、なにも指定しないとタグでも動いてしまうのがちょっと罠ですね。タグを除外する指定を忘れると、master ブランチのみにしたつもりでも気づいたらタグでも動いててトラブル発生とかはありそうです。

needs を使ったフロー制御

なにも指定しないとワークフロー内のすべてのジョブは並列実行されますが、needs を指定することによりジョブの実行順序を制御することができます。

name: Flow Control
on: push

jobs:
  job1:
    name: Greeting 1
    runs-on: ubuntu-latest
    steps:
      - run: echo "Hello, World 1"
  job2:
    name: Greeting 2
    needs: job1
    runs-on: ubuntu-latest
    steps:
      - run: echo "Hello, World 2"
  job3:
    name: Greeting 3
    needs: job1
    runs-on: ubuntu-latest
    steps:
      - run: echo "Hello, World 3"
  job4:
    name: Greeting 4
    needs: [job2, job3]
    runs-on: ubuntu-latest
    steps:
      - run: echo "Hello, World 4"

上記の例だと、job1 が完了した後に job2, job3 が並列実行され、両方のジョブが完了すると job4 が実行されます。

このあたり、現時点だとジョブの依存関係が視覚的に可視化されていないので、よくあるパイプラインのグラフ的なのが表示されるようになってほしいです。

仮想環境の選択とコンテナ内ビルド

ジョブごとに runs-on は必須で、ジョブが実行される仮想環境を選択する必要があります。指定できる環境は公式ドキュメントを参照してください。

仮想環境に入ってるソフトウェアも公式ドキュメントで公開されています。一通りの開発環境は全部入りという感じですね。

一つのジョブのすべてのステップは同じ仮想環境インスタンス上で実行されます。同じジョブ内であればファイルシステムを通じてデータのやり取りが可能ということです。

通常だと仮想環境を直接使ってすべてのステップが実行されますが、container を指定することでコンテナ内ビルドが可能です。

name: Hello, World!
on: push

jobs:
  build:
    name: Greeting
    runs-on: ubuntu-latest
    container:
      image: node
    steps:
      - run: node -e 'console.log("Hello, World!");'

上の例だと、node イメージを使ってそのジョブのステップがすべて実行されます。(後述するコンテナ内で実行されるアクションについてはその限りではありません)

context と expression と if によるステップの条件実行

ワークフローは自身の実行時情報を context として持っています。どういう情報を持っているかは、公式ドキュメントをご参照ください。

ワークフローの YAML ファイルからこの context 情報にアクセスするために、expression を使用します。expression は context にアクセスするだけでなく、演算式や関数を呼び出すこともできます。

${{ <expression> }} のようにワークフローファイルに書くことで実行時に評価してくれます。例として、以下のように記述することで GitHub のコンテキスト情報を出力することができます。github が組み込みの GitHub のコンテキスト情報を表す変数で、toJson が受け取った値を JSON 形式の文字列にして返す組み込み関数です。

name: GitHub Context
on: push

jobs:
  github-context:
    name: GitHub Context
    runs-on: ubuntu-latest
    steps:
      - name: Dump GitHub Context
        env:
          GITHUB_CONTEXT: ${{ toJson(github) }}
        run: echo "${GITHUB_CONTEXT}"

f:id:miya-jan:20190818203117p:plain

ワークフローをトリガーした GitHub のイベント情報とかが含まれているので、ジョブの最初にとりあえず出力しておくとデバッグ時に役に立ちそうです。

さらに、if とこの expression を組み合わせることで、ステップを特定の条件を満たすときだけ実行されるように設定できます。例えば、pull_request イベントで actionopened なときだけ実行されるようにしたいときは以下のように設定します。3

name: If
on: pull_request

jobs:
  if-pull-request-is-opened:
    name: If
    runs-on: ubuntu-latest
    steps:
      - name: echo if pull request is opened
        if: github.event_name == 'pull_request' && github.event.action == 'opened'
        run: echo "pull request is opened"

if の設定では、${{ <expression> }} のように記述するのではなく、直接 expression が書けるようになっています。また、alwaysfailure のような組み込みの条件式もあるので、公式ドキュメントをご参照ください。

環境変数と秘密情報

GitHub Actions は実行時にデフォルトで環境変数が設定されます。公式ドキュメントをご参照ください。

さらに、ワークフローの設定ファイルでも env で環境変数を設定できます。

name: env hello
on: push

jobs:
  build:
    name: Greeting
    runs-on: ubuntu-latest
    steps:
      - env:
          NAME: Mona
        run: echo "Hello, ${NAME}"

上の例では、NAME 環境変数に値を設定して run の中で使用しています。ちなみに、GITHUB_ で始まる環境変数は GitHub 側の予約語として使えないようになっているようです。

秘密情報については、リポジトリの SettingsSecrets から設定します。

f:id:miya-jan:20190818203229p:plain

すると、以下のように expression で secrets コンテキストから利用できるようになります。

name: secret hello
on: push

jobs:
  build:
    name: Greeting
    runs-on: ubuntu-latest
    steps:
      - run: echo "Hello, ${{ secrets.SECRET_VALUE }}"

ちなみに、ログ上では秘密情報はマスクされるようです。

f:id:miya-jan:20190818203245p:plain

さらに、組み込みで GITHUB_TOKEN という秘密情報が存在します。ジョブ内で GitHub API を叩いたりするときに使います。トークンが持ってるパーミッションは公式ドキュメントをご参照ください。

ちなみに、fork されたリポジトリからトリガーされたワークフローでは GITHUB_TOKEN 以外の秘密情報は渡されません。GITHUB_TOKEN は渡されはしますが、write 権限が剥奪され read 権限のみになります。

アクション

よくある処理は、アクションという単位で公開や共有が可能で、steps の中で呼び出すことが可能です。

例として、リポジトリをチェックアウトする actions/checkout アクションを使用してみます。

name: Checkout
on: push

jobs:
  build:
    name: Greeting
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - run: cat README.md

uses: actions/checkout@v1 でアクションが実行され、リポジトリがチェックアウトされます。@ で git の SHA やブランチ、リリースなどを指定して特定のバージョンのアクションを利用することができます。

以下のように with でアクションにパラメータを渡すことも可能です。

name: Checkout
on: push

jobs:
  build:
    name: Greeting
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
        with:
          fetch-depth: 1
      - run: cat README.md

actions/checkoutfetch-depth: 1 を指定することで、いわゆる shallow clone 相当の挙動になるようです。

actions/checkout のような GitHub 社製のアクションは actions org で公開されています。

アクションは 2 種類の方法で作成することができます。1 つ目は Docker コンテナで、この種類のアクションは Linux の仮想環境のみで利用可能です。もう 1 つは JavaScript で、こちらはすべての仮想環境で利用可能です。具体的な作成方法については、この記事では省きます。

マトリクスビルド

strategy を使うことで複数の OS や言語バージョンの組み合わせで同じジョブを実行する、いわゆるマトリクスビルド的なことが可能になります。以下は、actions/setup-node アクションを利用した、複数 OS、複数 Node バージョンでのマトリクスビルドの例です。

name: Node.js Matrix Build
on: push

jobs:
  build:
    name: Node.js ${{ matrix.os }} ${{ matrix.node }}
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest]
        node: [6, 8, 10]
    steps:
      - name: Set Node.js
        uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node }}
      - run: node -v

actions/setup-node は、仮想環境上の node 環境を指定したバージョンにしてくれます。この例では node -v を叩いてるだけですが、実際の CI では npm installnpm test といったことを行っていく想定です。

権限

ワークフローの作成、編集、Actions タブの閲覧にはリポジトリの writeadmin の権限が必要なようです。

2019/08/22 追記: ↑嘘でした。Actions タブの閲覧は read 権限あればできるようです。

通知

GitHub の SettingsNotifications から GitHub Actions の通知についての設定があります。

f:id:miya-jan:20190818203721p:plain

メール通知と Web Notifications による通知があり、失敗したときだけ送るようにするかの設定があります。

制限事項

GitHub Actions の利用については以下の制限があります。

  • リポジトリごとに最大で 20 ワークフローまで同時実行できる
  • リポジトリ内のすべてのアクション全体で 1 時間につき 1,000 API リクエストまで実行できる
  • ワークフロー内のすべてのジョブの最大実行時間は 6 時間まで
  • リポジトリごとにすべてのワークフロー全体で 20 ジョブまで同時実行できる

あとは、当たり前ですが暗号通貨採掘のような本来使われるべきでない目的での利用も明確に禁止されています。気になる人は公式ドキュメントを読みましょう。

まとめ

GitHub Actions について、旧バージョンとの違い、ワークフローの設定でどのようなことが設定できるかといったことを中心に書きました。実践的なワークフローとか、アクションの作成&共有方法とかについてはまとめきれなかったので、また別の記事でやります。(時間があれば…)

基本的には公式ドキュメントの情報をまとめただけの記事なので、最新情報を確認するときはそちらを見てください。

個人的な所感としましては、やはりパブリックリポジトリ完全無料で 20 並列を使えるのはとても強いですね。Windows や macOS 対応も強いですし、ちょっとした OSS のビルドの選択肢としてはかなり有力だと思います。

一方で、現状気になってる点としては以下の部分があります。

  • CPU やメモリといったリソースを強化できるのか
  • キャッシュ
  • ジョブ間の成果物の受け渡し
    • upload-artifact というそれっぽいアクションはあるけど、公式ドキュメント上では見当たらない

さらに、やはりまだベータだからか挙動が怪しいところもちょいちょいあります。本格的な運用は GA まで待ってもよさそうです。

また、旧バージョンとはだいぶ別物になってしまってるので、GitHub Actions という名前はこの機会に変えてしまってもよかったのではと思います。今後 GitHub Actions について検索するときに、旧バージョンについての記事が邪魔をしそうです。

全体としては CI/CD 業界が盛り上がること自体はとてもありがたく、また新たな CI 探求沼が増えたなという気持ちなので、引き続きいろいろ試していきたいです。


追記

アクションの作成方法を続編として書きました。

www.kaizenprogrammer.com


2020/02/27 追記

『GitHub Actions 実践入門』の電子書籍版を BOOTH で販売開始しました。より詳細な GitHub Actions の解説や最新情報が書かれています。

www.kaizenprogrammer.com



  1. 2019/08/22 追記: こちらの書き込みを見て確認したところ、branchesrefs/tags/* を指定するとタグへの push だけビルドできるようになりました(正直このあたりの挙動はまだ変わりそうな気がします。。)

  2. tests/** を指定すれば tests ディレクトリ直下だけでなくサブディレクトリ全体を対象にできるはずですが、手元で試したときはそうならず、ディレクトリ直下のファイルを変更したときのみワークフローが開始されました。このあたりはサポートに問い合わせ中です。

  3. 現状なぜか pull_request イベントが発生するのが opened だけになってしまってるので意味がない設定になってしまってます。サポートに問い合わせ中です。