GitHub Actions の基本


はじめに

GitHub Actions は、GitHub 上のリポジトリの操作に応じて、任意のタスクを自動実行できる機能です。

CircleCI や Travis CI といった継続的インテグレーション・継続的デリバリーのサービスと同様ですが、GitHub 内で完結でき、追加投資なしで利用できる点が魅力です。Free プランの場合は月に 2,000 分の実行時間が無料枠となっています。

2019年11月に一般公開で いまさら感はあるものの、適当な粒度の説明がヒットしないためここにまとめます。


GitHub Actions の構成

GitHub Actions は、リポジトリの .github/workflows にチェックインした YAML形式のファイルで定義します。このファイルをワークフローと呼びます。

ワークフローは自動化したいジョブの集合であり、複数のワークフローをリポジトリに登録できます。Actions タブには、リポジトリのワークフローが一覧され、実行履歴が参照できます。

ワークフローは GitHub上の操作をトリガに自動実行できる他、スケジュールに応じた自動実行も可能です。


ワークフローの構成

ワークフローは .github/workflows にYAML形式のファイル(.yml .yaml)で定義します。リポジトリには複数のワークフローを持つことができ、それぞれのワークフローで異なる処理を実行できる他、ワークフローの中で別のワークフローを参照することもできます。

ワークフローファイルの基本的な構成は以下のようになります。

name: Greeting Workflow           # ワークフロー名
on: [push]                        # トリガーとなる GitHub イベント

jobs:
  job1:                           # ジョブ名
    runs-on: ubuntu-latest        # ジョブの実行環境(runner)
    steps:                        # ジョブのステップ
      - name: Greeting
        run: echo "Hello, world!"

on: では、ワークフローのトリガーとなる GitHub イベントの名前を指定します。上記例では、リポジトリへの push によりこのワークフローを実行する定義になります。

jobs: 以下にはワークフローで実行するジョブを定義します。 ワークフローは複数のジョブを持つことができ、それぞれのジョブは並行して実行されます(needs: によりジョブ間の依存関係を定義すれば、順次実行させることもできます)。 上記例では job1 という名前のジョブを1つだけ定義しています。

runs-on: でランナーを指定します。ランナーとは、このワークフローを実行するサーバであり、上記では Ubuntu を指定しています。 Windows や macOS の runner も提供されていますし、自身でホストしたものを利用することもできます。

steps: 配下に、このジョブで実行する複数の手続きを配列形式で定義します(各ステップ(配列の要素)は - で始めます)。 上記例では、Greeting という名前で、「Hello, world!」 と出力する1つのステップを定義しています。

run: はオペレーティングシステムのシェルを使ってコマンドラインプログラムを実行します。

ワークフローは、並行実行されるジョブの集合であり、各ジョブには直列実行される複数のステップ(コマンドの実行や、後述するアクションの実行)を含むという構成になります。


アクション

頻繁に繰り返される複雑なタスクは、GitHub Actions プラットフォームで、アクションとして事前定義されています。また、GitHub Marketplace で提供されるサードパーティ製のアクションを利用することもできます。

ジョブの中でアクションを使うことで、ワークフローファイルのコード量を削減することができます。

例えば、ジョブで利用するリポジトリをチェックアウトするには actions/checkout というアクションが提供されています。uses キーワードを使いアクションを実行することができます。

steps:
  - uses: actions/checkout@v2

@v2 はアクションのバージョン番号です(これについては後述します)。

Java 実行環境を設定する actions/setup-java というアクションは以下のように指定します。

steps:
  - name: Set up JDK 17
    uses: actions/setup-java@v2
    with:
      java-version: '17'
      distribution: 'adopt'

with: でこのアクションのパラメータを指定します。上記では、AdoptOpenJDK の バージョン17 を指定しています。それぞれのアクションで有効なパラメータは、アクションの README を参照します。

Github から提供されるアクションは GitHub Actions のリポジトリで管理されており、一例とし以下のようなものがあります。

  • checkout リポジトリからファイルをチェックアウト
  • cache 生成物をキャッシュして処理を高速化
  • labeler GitHub上でのラベルを管理ファイル .github/labeler.ymlを作成
  • github-script GitHub APIを使ってGitHubの各種機能にアクセス
  • upload-artifact 指定したファイルを artifact として保存
  • download-artifact artifact として保存されているファイルをダウンロード
  • setup-java Java 環境のセットアップ
  • setup-node Node 環境のセットアップ
  • setup-go Go 環境のセットアップ
  • setup-python Python 環境のセットアップ

この他、多数のアクションがあり、自身で定義したり GitHub Marketplace から探すこともできます。


ワークフローの作成

ワークフローは、ローカルで作成して Push することもできますが、Actions タブの「New workflow」から画面上で操作することもできます。

テンプレートの選択画面になるので、テンプレートを選択するか、「set up a workflow yourself →」でワークフローの編集画面に遷移します。

以下のように画面上でワークフローを登録することができます。

エディタ上では構文チェックも行われます。

Marketplace サイドバーを使用してアクションを参照できます。

バッジが付いたアクションは、GitHubがアクションの作成者をパートナー組織として確認したことを示します。


アクションの指定方法

アクションは以下で定義されたものを使うことができます。

  • 公開リポジトリ
  • ワークフローファイルがアクションを参照するのと同じリポジトリ
  • DockerHubで公開されたDockerコンテナイメージ


それぞれの指定方法は以下のようになります。

  • パブリックリポジトリのサブディレクトリのアクション
    • {owner}/{repo}/{path}@{ref}
    • 例: uses: actions/aws/ec2@main
  • ワークフローディレクトリに配備したアクション
    • ./path/to/dir
    • 例: uses: ./.github/actions/my-action
    • リポジトリをチェックアウトする必要あり
  • DockerHubアクション
    • docker://{image}:{tag}
    • 例: uses: docker://alpine:3.8
  • GitHubパッケージコンテナレジストリ
    • docker://{host}/{image}:{tag}
    • 例: uses: docker://ghcr.io/OWNER/IMAGE_NAME


アクションのバージョン指定は Git ref、SHA、またはDockerタグ番号が利用できます。

actions/checkout を例にすれば、以下のようなバージョン指定が考えられます。

steps:
  # 特定のコミット SHA
  - uses: actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675

  # 特定バージョンの参照
  - uses: actions/checkout@v2.2.0

  # メジャーバージョンの参照
  - uses: actions/checkout@v2

  # ブランチの参照
  - uses: actions/checkout@main

@main のように指定した場合、アクションが不用意に更新され、ワークフローが予期しない動作をする可能性があります。 @v2.2.0 のように指定した場合、重要な修正とセキュリティパッチを受け取ることができない可能性があります。

サードパーティーの提供するアクションに、悪意のある更新がなされるリスクを考慮すれば、リリースされたバージョンのコミットSHAを指定することが最も安全です。

よって、信頼できるアクションについては、@v2 のようにメジャーバージョンを指定し、サードパーティー製の信頼できないアクションについてはリリースバージョンのコミットSHA を利用するのが良いかと思います。

GitHub Marketplace で 「Verified creator」 のバッジは、GitHub により身元が確認されたチームによって作成されたアクションとなるため、これを参考にするのも良いでしょう。また、GitHub に統合された Dependabot を ON にすれば、依存ライブラリの更新に応じて PR が自動作成されるようになるため、これをトリガとして更新を検討するという運用もありかもしれません。


ワークフロー サンプル

ワークフロー全体のサンプルとして、Gradle による CI を行う例は以下のようになります。

name: Java CI

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      - name: Set up JDK 11
        uses: actions/setup-java@v2
        with:
          java-version: '11'
          distribution: 'adopt'
      - name: Validate Gradle wrapper
        uses: gradle/wrapper-validation-action@v1
      - name: Build with Gradle
        run: ./gradlew build
  • push pull_request をトリガにしてワークフローを実行
  • actions/checkout でランナーに対象のブランチをチェックアウト
  • actions/setup-java で Java の実行環境を作成
  • gradle/wrapper-validation-actiongradle-wrapper.jar のチェックサムの妥当性を検証
  • コマンドラインで ./gradlew build テストとビルドを実行


トリガの指定

リポジトリ内の任意のブランチにコードがプッシュされたときに起動

on: push

ブランチ指定のプッシュ

on:
  push:
    branches: [ master ]

プッシュまたはプルリクエストイベントで起動

on: [push, pull_request]

ブランチを指定したプッシュまたはプルリクエスト、ページビルドとリリース作成イベントで起動

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
  page_build:
  release:
    types:
      - created

毎日UTCの1:00に起動

on:
  schedule:
  - cron: "0 1 * * *"

ワークフローを手動でトリガーできるようにするには、workflow_dispatch イベントを構成する必要があります。

on: workflow_dispatch

ブラウザ画面から、または GitHub API の場合は以下でワークフローを実行できます。

curl -L \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer <YOUR-TOKEN>" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/<OWNER>/<REPO>/actions/runs/RUN_ID


依存のキャッシュ

ビルド時の依存はキャッシュを利用することで高速化が可能です。

これには、actions/cache を使い、例えば npm の依存をキャッシュするには以下のようになります。

steps:
  - name: Cache node modules
    uses: actions/cache@v2
    env:
      cache-name: cache-node-modules
    with:
      path: ~/.npm
      key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
      restore-keys: |
        ${{ runner.os }}-build-${{ env.cache-name }}-

ただし、現在では、大抵のケースで setup-* アクション側でキャッシュ可能となっています。

例えば、actions/setup-java で gradle を使う場合は以下のようにすることでキャッシュが有効になるため、actions/cache を自身で利用する必要はありません。

steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
  with:
    java-version: '11'
    distribution: 'adopt'
    cache: 'gradle'
- run: ./gradlew build

GitHubは、7日以上アクセスされていないキャッシュエントリを削除します。


コンテキスト

コンテキストは、ワークフローの実行、ランナー環境、ジョブ、およびステップに関する情報にアクセスするための方法です。

コンテキストには以下の構文を使用してアクセスできます。

${{ <context> }}

例えば以下のようなものを参照できます。

- run: echo "triggered by a ${{ github.event_name }} event"
- run: echo "running on a ${{ runner.os }} server"
- run: echo "branch is ${{ github.ref }} and repository is ${{ github.repository }}"
- name: Check out repository code
uses: actions/checkout@v2
- run: echo "${{ github.repository }} repository has been cloned"
- name: List files in the repository
run: |
  ls ${{ github.workspace }}
- run: echo "This job's status is ${{ job.status }}."

どのようなものがあるかはコンテキストを参照してください。 なお、コンテキストはタスクの実行状況により、アクセスできるものとできないものがあるため注意が必要です。


${{ }} には式を書くことができ、例えば、以下のようにジョブのステータスを判断することもできます。

steps:
  ...
  - name: The job has succeeded
    if: ${{ success() }}


環境変数

ワークフローでは環境変数を利用できます。

GitHub が定義するデフォルトの環境変数に加え、独自の環境変数を定義することもできます。

環境変数は、ワークフロー全体(env)、ジョブ別(jobs.<job_id>.env)、ステップ別(jobs.<job_id>.steps[*].env) で定義できます。

以下の例では、ジョブの環境変数 DAY_OF_WEEK と、ステップの環境変数 FIRST_NAME Last_Name を定義しています。

jobs:
  weekday_job:
    runs-on: ubuntu-latest
    env:
      DAY_OF_WEEK: Mon
    steps:
      - name: "Hello world when it's Monday"
        if: ${{ env.DAY_OF_WEEK == 'Mon' }}
        run: echo "Hello $FIRST_NAME $Last_Name, today is Monday!"
        env:
          FIRST_NAME: Mona
          Last_Name: Octocat

GITHUB では始まる名前は、デフォルトの環境変数で利用されるため利用できません。 ファイルシステム上の場所を指すように設定する新しい環境変数には、_PATH接尾辞を付ける必要があります。


Secrets

Settings - Secrets から、特定の名前で secret を作成しておけば、秘密鍵をワーフクローファイルに直書きすることなく、安全に利用できます。

ワークフローファイルでは ${{ secrets.<シークレット名> }} のように secrets コンテキストから取得できます。

steps:
  - shell: bash
    env:
      SUPER_SECRET: ${{ secrets.SuperSecret }}
    run: |
      example-command "$SUPER_SECRET"


Build Matrix

異なるOS、異なるプラットフォームの組み合わせでテストを実行したいケースにはビルドマトリックスが使えます。

ビルドマトリックスは strategy 配下にビルドオプションを配列として定義します。

以下の例では、os と node のバージョン別で6つのジョブのマトリックスを作成している例になります。

runs-on: ${{ matrix.os }}
strategy:
  matrix:
    os: [ubuntu-18.04, ubuntu-20.04]
    node: [10, 12, 14]
steps:
  - uses: actions/setup-node@v2
    with:
      node-version: ${{ matrix.node }}


アーティファクト

同じワークフローのジョブ間でデータを共有するにはアーティファクトを使うことができます。

アーティファクトはワークフローの実行中に生成されたファイルまたはファイルのコレクションです。 ジョブ内でアーティファクトをアップロードしておくことで、他のジョブでアーティファクトをダウンロードして利用することができます。

アーティファクトを操作するために actions/upload-artifact actions/download-artifact が提供されています。

job1 でファイルをアップロードし、

jobs:
  job1:
    name: Save output
    steps:
      - shell: bash
        run: |
          expr 1 + 1 > output.log
      - name: Upload output file
        uses: actions/upload-artifact@v2
        with:
          name: output-log-file
          path: output.log

job2 でファイルをダウンロードして利用することができます。

jobs:
  job2:
    steps:
      - name: Download a single artifact
        uses: actions/download-artifact@v2
        with:
          name: output-log-file

アップロードしたアーティファクトは、同じワークフローからであれば、前回保存時のアーティファクトを利用できます。

保存されたアーティファクトは、ワークフローの実行履歴画面から照会できます。保持期間はデフォルトで90日となっています。


ジョブの依存関係

ジョブは、デフォルトで並列実行されます。ジョブ間に依存関係を定義するには needs を使います。

jobs:
  build:
  test:
    needs: build
  release:
    needs: [build, test]

build -> test -> release の順で動作します。


ステータスバッチ

各ワークフローのステータスバッチは、「Create status badge」から作成できます。

様々なイベント用のバッチがあります。

作成された内容をコピーして README.md に貼り付けることでステータスバッチが表示できます。