生産性向上ブログ

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

GitHub Actions 入門

-----

2019/08/18 追記:GitHub Actions が新バージョンで大きく変わったため、新しい記事を書きました。こちらの記事は古い内容なので、新しい記事を参照してください。

www.kaizenprogrammer.com

-----

developer.github.com

現在パブリックベータの GitHub Actions が自分も使えるようになったので、いろいろ調べたり動かしてみたりした内容をまとめます。

目次

注意事項

GitHub Actions は、この記事の執筆時点(2019/01/13)ではまだパブリックベータの段階です。まだ本格的な開発用途では使わないほうがいいですし、将来的に機能や仕様などが大きく変更になる可能性があります。

パブリックベータへの参加は、GitHub Actions のページから可能です。自分のときは有効になっても特にメールとかは来なくて、けっこう経ってからいつの間にか有効になってました。

今は GitHub Actions でワークフローを作成できるのは、

  • プライベートリポジトリ
  • パブリックリポジトリの push イベントのみ

に限定されています。

プライベートリポジトリは年明けに無料プランでも利用できるようになったので、GitHub Actions を実験するのにもちょうどいいタイミングですね。

blog.github.com

GitHub Actions とは

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

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

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

簡単な例 (Hello, World)

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

GitHub Actions が有効になったユーザーか organization 下に適当なリポジトリを作成し、以下のような階層でファイルを用意します。

|-- (repository)
|   |__ .github
|       |__ main.workflow
|   |__ action-a
|       │__  Dockerfile
|       |__  entrypoint.sh
|

action-a/Dockerfile の中身は以下のようにします。

FROM debian:9.5-slim

LABEL "com.github.actions.name"="Hello World"
LABEL "com.github.actions.description"="Write arguments to the standard output"
LABEL "com.github.actions.icon"="mic"
LABEL "com.github.actions.color"="purple"

ADD entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

このファイルは、ワークフロー内で実行されるアクションを定義しています。中身としてはリポジトリ内に置いた entrypoint.sh を呼び出すだけです。(LABEL については後述)

action-a/entrypoint.sh の中身は以下です。

#!/bin/sh -l

sh -c "echo $*"

このシェルスクリプトが先ほどの Dockerfile から実行されます。

entrypoint.sh はワークフロー内で実行するために実行権限が必要なので、chmod コマンドで実行権限を与えておきます。

$ chmod +x entrypoint.sh

.github/main.workflow の中身は以下です。

workflow "New workflow" {
  on = "push"
  resolves = ["Hello World"]
}

action "Hello World" {
  uses = "./action-a"
  env = {
    MY_NAME = "Mona"
  }
  args = "\"Hello world, I'm $MY_NAME!\""
}

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

最初の workflow ブロックの on でリポジトリへの push 時に実行されるワークフローということを定義しています。resolves でこのワークフローでは Hellow World アクションを実行することを定義しています。

action ブロックでは uses でリポジトリ内への相対パスで action-a ディレクトリをアクションの内容として参照しています。env では環境変数を設定しています。args はアクションに渡される引数を定義しており、先ほどの DockerfileENTRYPOINT で指定した entrypoint.sh に渡されます。

ちなみに、GitHub Actions のワークフローの設定ファイルの文法は、HCL という HashiCorp 社製の設定言語で書かれています。

こららの 3 つのファイルを作成してリポジトリに push すると、GitHub Actions が実行されます。GitHub のリポジトリのページ上で Actions タブをクリックすると見られます。(自分のときは push してから最初のビルドまで 1 分くらい待ちました)

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

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

Log をクリックするとログが見られ、最後の標準出力に Hello world, I'm Mona! と表示されているのが確認できます。

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

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

ワークフローの設定

例で書いた .github/main.workflow の設定オプションについて説明します。

workflow ブロック

main.workflow は複数の workflow ブロックを含むことができます。workflow ブロックは次のパラメータを設定できます。

  • on: このワークフローをトリガーする GitHub のイベント名を指定する(指定できるイベント名は後述)
    • ワークフローをトリガーするイベントによってどのバージョンのワークフローファイルが使われるかが変わる
      • 例えば、push なら push されたコミット時のワークフローファイルが使われるけど、issue_comment ならデフォルトブランチの最新のワークフローファイルが使われる
  • resolves: ワークフローで実行するアクションを指定する(文字列 or 文字列の配列)
    • 複数のアクションを指定したときは並列で実行される
    • 後述する action ブロック側で needs で依存関係を指定するとそちらが先に実行されるので、直列で複数のアクションを実行することもできる

action ブロック

main.workflow は最大で 100 ブロックまで action ブロックを含むことができます。action ブロックは次のパラメータを設定でき、uses は必須です。

  • needs: このアクションを実行する前に完了しなければならないアクションを指定する(文字列 or 文字列の配列)
    • 共通の needs を持つアクションが並列に実行されてるとき、片方が失敗すると、もう片方も自動でキャンセルされる
  • uses: アクションを実行する Docker イメージを指定する
    • 指定方法は複数あるので後述
  • runs: Docker イメージが実行するコマンドを指定する
    • 省略したときは DockerfileENTRYPOINT で指定したコマンドが実行される
  • args: アクションに渡す引数を指定する(文字列 or 文字列の配列)
    • 文字列で指定したときは空白文字で分割されるので、args = "container:release --app web"args = ["container:release", "--app", "web"] は同じ意味
  • env: アクションの実行環境に渡す環境変数を指定する
  • secrets: アクションが環境変数としてアクセスできるようにする秘密情報の名前を指定する
    • 秘密情報の管理については後述

needs を使ったフロー制御

例えば、最初に書いた例の main.workflow を次の書き換えると、ACTION1ACTION2 の順番で実行されるワークフローになります。

workflow "New workflow" {
  on = "push"
  resolves = ["ACTION2"]
}

action "ACTION1" {
  uses = "./action-a"
  args = "\"Hello world, I'm Action1!\""
}

action "ACTION2" {
  needs = "ACTION1"
  uses = "./action-a"
  args = "\"Hello world, I'm Action2!\""
}

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

ACTION2 のログに表示される実行時間は、ACTION1 の完了を待ってる時間も含まれるようです。

uses における Dockerfile の指定方法

action ブロックの uses では、いくつかのパターンでアクションを実行する Docker イメージを指定できます。

  • {user}/{repo}@{ref}: GitHub の公開リポジトリ内の特定のブランチ or ref or SHA
    • 例: actions/heroku@master
  • {user}/{repo}/{path}@{ref}: GitHub の公開リポジトリ内の特定のブランチ or ref or SHA におけるサブディレクトリ
    • 例: actions/aws/ec2@v2.0.1
  • ./path/to/dir: ワークフローと同じリポジトリ内のパス
    • 例: ./.github/action/my-action
  • docker://{image}:{tag}: Docker Hub に公開されている Docker イメージ
  • docker://{host}/{image}:{tag}: 公開レジストリの Docker イメージ

外部のアクションを利用する場合はバージョンを指定することが推奨されています。指定しない場合は、リポジトリや Docker イメージの更新によって Action が壊れて予期しない挙動になる可能性があるので、注意してください。

ワークフローがサポートしているイベント

workflow ブロックの on ブロックでサポートされているイベントは、リンク先を見てください。

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

秘密情報の設定

action ブロックの secrets で設定する秘密情報は、リポジトリの Settings → Secrets で追加できます。

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

この秘密情報は、リポジトリに write 権限がある人は誰でもアクセスできることになるので、注意してください。

さらに注意点として、ログ出力はこの秘密情報をマスクしてくれません。一部の CI サービスなどとは違う点です。誤って echo したりしないよう気をつけてください。

f:id:miya-jan:20190113191027p:plain
秘密情報(THIS_IS_SECRET)を echo に渡すとそのまま表示される

秘密情報は最大 64 KB までなので、より大きな秘密情報を扱うときはその情報を暗号化してリポジトリ内にファイルとして保存し、復号化の鍵を secrets として保存するような手段をとる必要があります。

今はパブリックベータ期間なので、本当に重要な秘密情報はまだ保存しないようにしておいたほうが無難です。

2019/01/14 追記

どのような権限があればログが閲覧可能かについて見当たらなかったので、id:teppeis 先生に協力してもらって実験したところ、リポジトリの Actions タブ以下のページを見られるのはコラボレータのみのようです。なので、万が一秘密情報をログに書き出してしまっても、流出するのはコラボレータのみにおさまりそうです。

GITHUB_TOKEN

デフォルトで GITHUB_TOKEN という GitHub の API にアクセスするためのトークンが秘密情報として存在しており、secrets で指定することで利用することができます。

GITHUB_TOKENcurl で使うときは以下のような感じになります。

curl -s -X GET -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/miyajan/test-github-actions/commits

GITHUB_TOKEN を使った API アクセスは、リポジトリ内で 1 時間で最大 1,000 回まで実行できます。

GITHUB_TOKEN が持っている権限についてはリンク先を見てください。

ビジュアルエディタ

ブラウザ上で .github/main.workflow を編集するとビジュアルエディタでワークフローを設定できます。

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

最終的には全部コードで書いてしまう気はしますが、featured のアクション一覧とか見てみるとどういうことができるかパッと理解できるのでいいかと思います。

ワークフローの制限事項

現時点で、ワークフローには次の制限事項があります。

  • 各ワークフローは最長 58 分まで動く(キューと実行時間含めて)
  • 各ワークフローは最大 100 アクションまで実行できる
  • 1 リポジトリにつき 2 つのワークフローまで同時に実行できる
  • アクション内で実行されるタスクからさらにアクションをトリガーすることはできない
    • 上で書いた GITHUB_TOKEN を使って push や deploy を行っても、それらのイベントに設定されているワークフローはトリガーされない
  • Docker コンテナのログは最大で 64 KB までに制限されている

アクションの作成と公開

上で書いたように、作成したアクションは公開リポジトリに置くことで他リポジトリからも参照できるようになります。

例えば、最初に書いた簡単な例のアクションを他リポジトリから利用するには以下のように書きます。

action "EXTERNAL_ACTION" {
  uses = "miyajan/test-github-actions/action-a@master"
  args = "Hello world!"
}

上で書いたようにバージョンを指定する関係上、アクションを公開するリポジトリは他のアプリなどとは独立して管理することを推奨されています。

また、アクションを公開するリポジトリには README.md を用意し、以下のような内容を記述することが推奨されています。

  • アクションの詳細な実行内容
  • アクションが使用する環境変数
  • アクションが使用する秘密情報
  • 必須の引数
  • 任意の引数

Docker イメージの作成

アクションを実行する Docker イメージを作成する上での注意点を書きます。そもそもの Dockerfile などについては、Docker のドキュメントを参照してください。

USER

アクションは root ユーザーで実行されます。Dockerfile 内で USER を指定してしまうと GITHUB_WORKSPACE にアクセスする権限がなくなってしまうので、指定してはいけません。

既存のイメージでは USER を指定してしまっているものもありそうなので、そういったイメージをそのまま GitHub Actions で使い回そうとするとハマりそうです。

FROM

FROM を設定するときは以下が推奨されています。

  • 公式の Docker イメージを使う(python:, ruby: など)
  • バージョンタグを指定する(node:10 のようにメジャーバージョンを指定するのが望ましい)
  • latest は指定しない
  • Debian OS ベースの Docker イメージが推奨

Debian 推奨がなぜなのかはよくわかりません。

WORKDIR

GitHub Actions は /github/workspace をワーキングディレクトリとして設定します。そのため、DockerfileWORKDIR を指定しないことが推奨されています。

ENTRYPOINT

action ブロックで runs を設定しないときは ENTRYPOINT で指定したコマンドが実行されます。

注意点として、コマンドシェル形式じゃなくて exec 形式でコマンドを実行すると環境変数の埋め込みが行われないので注意してください。

例えば、簡単な例の entrypoint.sh で、以下のようにしてしまうと Hello world, I'm $MY_NAME! のように環境変数がそのまま表示されてしまいます。

#!/bin/sh -l

echo $*

最初の例のように sh -c で実行すると環境変数が処理されます。

CMD

action ブロックで args を指定すると CMD が置き換えられます。

なので、CMD には args を指定しなかったときのデフォルト引数を入れたり、--help を入れておいて必須引数を忘れたときに役に立つ情報を表示するような用途で使うといいでしょう。

LABEL

Dockerfile 内で特定の LABEL を指定することで、ブラウザ上でのアクションの見た目を変えることができます。

  • com.github.actions.name: アクション名
  • com.github.actions.description: アクションの説明
  • com.github.actions.icon: Feathers のアイコン名
    • サポートされているアイコン名はリンク先を見てください
  • com.github.actions.color: アクションの背景色
    • white, yellow, blue, green, orange, red, purple, gray-dark から指定する

また、次の LABEL を指定することが推奨されて言います。

  • repository: アクションのリポジトリ URL
  • homepage: アクションのホームページ URL
  • maintainer: アクションのメンテナンス開発者の名前
    • 例: LABEL "maintainer"="Mona the octocat <octocat@github.com>"

アクションの実行環境

1 つのワークフロー内のすべてのアクションはすべて同じ環境で実行されるので、アクション間で情報の受け渡しが可能です。

リソース

アクションの実行環境は以下のリソースで実行されます。

  • 1 vCPU
  • 3.75 GB メモリ
  • インターネットアクセス
  • 後述する環境変数
  • 後述するファイルシステムへの書き込み権限
  • 100 GB のディスク容量

現時点ではちょっとした用途であれば十分ですが、CPU やメモリを大量に利用するような用途には厳しそうです。

環境変数

ワークフローの設定で自身が設定した環境変数以外に、GitHub が設定する環境変数があります。

一覧はリンク先を見てください。

GITHUB_ で始まる環境変数は予約されてるので、自身で設定してしまうとエラーになります。

ファイルシステム

GitHub が /github 下にディレクトリやファイルを作成します。

  • /github/home: HOME 環境変数に設定されるディレクトリ
  • /github/workspace: リポジトリがコピーされるディレクトリ
    • Docker コンテナのワーキングディレクトリになり、GITHUB_WORKSPACE 環境変数に設定される
    • アクション内でこのディレクトリ以下を変更した場合、後続のアクションはその内容にアクセスできる
    • root ユーザーでないとアクセスできない
  • /github/workflow/event.json: ワークフローをトリガーした webhook イベントのレスポンス
    • GITHUB_EVENT_PATH 環境変数に設定される

終了ステータス

アクションの終了ステータスは次の 3 種類があります。

  • 0: success 相当で、成功を意味する
  • 78: neutral 相当で、成功でも失敗でもなく、特定の条件を満たさなかったことを意味する
    • 例えば、filter action で特定の条件以外ではビルドしないような用途でこのステータスを使う
  • その他: failure 相当で、失敗を意味する

filter action については他の方が解説されているので、そちらをご参照ください。

budougumi0617.github.io

まとめ

GitHub Actions についてまとめました。ほとんど本家のドキュメントをまとめただけなので、最新情報を確認するときはそちらを見てください。

個人的な所感としましては、CI/CD 用途として使うよりも、Webhook の用途で使うのが便利そうに感じました。これまでの CI/CD サービスですと push 以外のイベントに対しての処理を実行しづらく、かといって Webhook を利用するにはイベントを受け取るサーバーを立てるなり API Gateway を設定するなりしないといけなくて手間だったのですが、そういったところが GitHub で完結するようになるのはとても嬉しいことだと思います。

もちろん CI/CD 用途としても使えないことはないのでしょうが、その用途では現状だと機能的に少々物足りないところがありそうです。例えば、成果物やテスト結果といった Artifact の保存や、キャッシュ機能、通知、コンテナへの SSH のようなデバッグ機能などです。多少はアクションなどでどうにかできることではあるのですが、そこは素直に専門のサービスを使い、GitHub Actions はそれらでは埋められない部分で活用したり、サービス間の中心となるつなぎの役割を担ったりするのかなと思いました。

まだまだパブリックベータの段階なので、要望とかあれば本家にどんどんフィードバックしていくといいと思います。個人的には、すごい細かいところですが、.github ディレクトリをシェルで補完しようとすると .git ディレクトリとかぶってぐぬぬとなる場面が多かったので、どうにかなると嬉しいです。