GitHubの新機能「GitHub Actions」で試すCI/CD

GitHubが2019年11月、新機能「GitHub Actions」を正式に公開した。GitHub上のリポジトリやイシューに対するさまざまな操作をトリガーとしてあらかじめ定義しておいた処理を実行できる機能で、今まで外部サービスとの連携が必要だった自動テストや自動ビルドなどがGitHubだけで実現できるようになる。今回はこのGitHub Actionsについて、機能の概要や基本的な使い方などを紹介する。

GitHubだけでCI/CD的な機能を実現できる「GitHub Actions」

昨今では、ソフトウェア開発におけるさまざまな工程を自動化するような技術の開発や普及が進んでいる。その1つに、CI(Continuous Integration、継続的インテグレーション)やCD(Continuous Delivery、継続的デリバリー)と呼ばれるものがある。CIはソフトウェアのビルドやテストを自動化して頻繁に実行することでソフトウェアの品質向上や開発効率化を目指す手法で、CDはCIに加えてリリースやデプロイまでも自動化する手法だ。

CIやCDに関する技術やソフトウェアについては過去にもたびたび紹介しているが、どれも基本的にはGitなどのバージョン管理システムと組み合わせての利用が前提となっている。たとえば、多くのCI/CDツールではリポジトリへのプッシュやマージ、プルリクエストなどをトリガーにしてさまざまな処理を自動実行できるようになっている。最近ではクラウド型のCI/CDサービスも登場しており、こういったサービスではGitHubリポジトリとCI/CDサービスを関連付ける設定を行うだけで簡単にCI/CDが利用できるようになっている。

こういったCI/CDに関連する機能をGitHubが自ら実装したものが、今回紹介する「GitHub Actions」という機能だ。GitHub Actionsは2018年に発表された機能で、しばらくは利用申請をした一部のユーザーのみが利用できる機能だったが、2019年11月には正式版の機能となり、現在はGitHubを利用するすべてのユーザーが特に申請や手続きを行うことなく利用できるようになっている。

GitHub Actionsでできること

GitHub Actionsは、ほかのCI/CDツールと同様、リポジトリに対するプッシュやプルリクエストといった操作、もしくは指定した時刻になるといったイベントをトリガーとして、あらかじめ定義しておいた処理を実行する機能だ。たとえばリポジトリにコミットが行われた際に特定の処理を実行したり、毎日決まった時刻に特定の処理を実行したりする、といったことを実現できる。これらの処理はGitHubが提供するサーバー上に用意された仮想マシン内で実行できるため、ユーザーが独自にサーバーなどを準備する必要はない点が最大のメリットだ。

仮想マシン上で利用できるOSとしては、Linux(Ubuntu)およびWindows、macOSに対応している。仮想マシン上にはOSだけでなく、さまざまな言語のコンパイラや各種ランタイム、主要ライブラリといったソフトウェア開発環境も標準でインストールされている。さらに、sudoコマンドを使ってroot権限でコマンドを実行させることもできる。つまり、一般的なサーバー上で実行できるほとんどの処理を実行できるわけだ。利用規約によって禁止されている処理ですら(アカウント凍結といったペナルティなどを受ける可能性は高いが)実行自体は可能だ。

また、特別なハードウェアが必要な場合や、後述する制約を回避したい場合、また仮想マシン内でなく実マシン上で実行させたいといった場合は、ユーザーのサーバー上で処理を実行させることもできるようになっている。

ActionとWorkflow

GitHub Actionsでは、実行する処理とその処理を実行する条件を定義したものを「Workflow(ワークフロー)」と呼ぶ。ワークフローはYAML形式で記述し、リポジトリ内の.github/workflowsディレクトリ内に保存することで実行できるようになる。

ワークフロー内では、シェル経由で任意のコマンドを実行できるほか、「Action(アクション)」という、あらかじめ定義済みの処理を呼び出せるようになっている。

たとえばGitHubの提供しているCheckoutというActionでは、指定したリポジトリからソースコードをチェックアウトする処理が定義されている。プライベートリポジトリからチェックアウトを行う場合は認証処理などが必要となるが、このActionを利用すれば数行の設定を記述するだけで簡単にチェックアウトを実行できる。そのほか、記事執筆時点でGitHubが提供しているActionとしては、表1のものがある。

表1 GitHubが提供しているAction
Action名 説明
setup-node Node.js環境のセットアップを行う
github-script GitHub APIを使ってGitHubの各種機能にアクセスする
upload-artifact 指定したファイルを「artifact」として保存する
cache 生成物をキャッシュして処理を高速化する
checkout リポジトリからファイルをチェックアウトする
setup-ruby Ruby環境のセットアップを行う
setup-java Java環境のセットアップを行う
setup-python Python環境のセットアップを行う
upload-release-asset GitHubのリリース機能を使ってファイルを公開する
setup-dotnet .NET core環境のセットアップを行う
setup-elixir Elixir環境のセットアップを行う
create-release リリースを作成する
setup-go Go環境のセットアップを行う
labeler GitHub上でのラベルを管理する「.github/labeler.yml」ファイルを作成する
download-artifact 「artifact」として保存されているファイルをダウンロードする
stale 一定期間活動のないイシューやプルリクエストを閉じる処理を行う
first-interaction 初めてプルリクエストやイシューを登録したユーザーに対しメッセージを出す
setup-haskell Haskell環境のセットアップを行う

これ以外にも、ユーザーが独自にActionを作成することも可能だ。ActionについてはNode.jsで記述できるほか、Dockerコンテナを利用して任意の処理を実行させるようなものも作成できる。ユーザー定義のActionを集めたマーケットプレイス機能も提供されている。ただ、マーケットプレイスではさまざまなActionが公開されているが、現状のマーケットプレイスでは単純な検索しかできないため、やや使い勝手が悪い(図1)。

図1 GitHub Actionsのマーケットプレイスでは多数の定義済みActionを閲覧・検索できる
図1 GitHub Actionsのマーケットプレイスでは多数の定義済みActionを閲覧・検索できる

GitHubが公式に提供するActionやGitHubの公認Actionもあり、これらは一覧表示の作者横にチェックマーク(「Verified」マーク)が入っているのだが、現時点ではこのマーク付きのものだけを検索することもできないようだ(図2)。

図2 公式/公認のものは作者名横にチェックマークが付く
図2 公式/公認のものは作者名横にチェックマークが付く

GitHub公式のActionについては、個別リポジトリ(actions/)で参照できるので、こちらもチェックすると良いだろう(図3)。

図3 GitHubが公式に提供するActionはactions/というユーザー名で提供されている
図3 GitHubが公式に提供するActionはactions/というユーザー名で提供されている

GitHub Actionsにおける制約

GitHub Actionsは仕様としてはかなり自由度が高く、GitHubの提供するサーバー上でほぼ任意の処理を実行できる。そのため、利用に対してはいくつかの制約が設けられている。これらについてはドキュメントの「Usage limits」項で説明されているが、記事執筆時では次のような制約となっている。

  • 同時に(並列で)実行できるワークフローは1リポジトリあたり20まで
  • ワークフロー内で実行されるジョブの最大実行時間は6時間まで
  • 1つのアカウントで同時に(並列で)実行できるジョブの最大数は無料プラン(Free版)では20、Proプランでは40、Teamプランでは60、Enterpriseプランでは180
  • 1つのアカウントで同時に(並列で)実行できるmacOSジョブ(macOS環境内で実行されるジョブ)の最大数は無料プラン(Free版)およびProプラン、Teamプランでは5、Enterpriseプランでは15

また、GitHubの利用規約に反するような使い方は禁止されている。具体的には暗号資産(仮想通貨、暗号通貨)のマイニングが禁止されているほか、そのワークフローを起動したリポジトリやプロジェクトでの開発やテスト、リリース、デプロイなどに関連しない処理での利用も禁止されている。

非公開の(プライベートな)リポジトリについては、ストレージおよびジョブの実行時間に上限がある。こちらもプランによって上限が異なるが、たとえばFree版ではストレージ上限500MB、ジョブ実行時間は月あたり最大2000分となっている(表2)。

表2 GitHub Actionsのプライベートリポジトリにおけるストレージ/実行時間制限
プラン 利用できる最大ストレージ容量 月あたりに利用できるジョブ実行時間上限
Free 500MB 2000分
Pro 1GB 3000分
Team 2GB 10000分
Enterprise Cloud 50GB 50000分

なお、この実行時間上限については、Windows環境で実行されるジョブは2倍、macOS環境で実行されるジョブは10倍で計算される。つまり、Freeプランで、macOS環境上にてジョブを実行する場合、月あたりの上限は200分となる。

このストレージ容量およびジョブ実行時間上限については、追加で利用料金を支払うことで上限を超えた利用が可能だ。詳しくはドキュメントの「About billing for GitHub Actions」ページでまとめられているが、ストレージについては1GBあたり0.25ドル/月、ジョブ実行時間についてはLinux環境では1分あたり0.008ドル、Windowsの場合0.016ドル、macOSの場合0.08ドルという設定になっている。これら料金については月あたりの上限金額を設定できるようになっており、デフォルトでは0ドルに設定されているため、これを変更しない限りは勝手に料金支払いが発生することはない。

GitHub Actionsのバックエンド

GitHub ActionsではGitHubが提供するハードウェア上でプログラムを実行する、いわゆるクラウド型のサービスだ。どういったハードウェアでプログラムが実行されるかについて詳しくは公開されていないものの、ある程度の情報はドキュメント内で言及されている。これによると、LinuxおよびWindows環境の仮想マシンについてはMicrosoftのクラウドサービスであるAzureを使って提供されており、マシンスペックはStandard_DS2_v2(仮想CPU×2、メモリ7GB、ストレージ14GB)だという。また、macOS環境についてはMacStadiumというmacOS環境をホスティングするサービスを使って提供しているとのことだ。

OSについては、記事執筆時点ではWindows Server 2019もしくはUbuntu 18.04および16.04、macOS 10.15(Catalina)が利用できる。もしこれ以外の環境が必要な場合は、自前で用意したホスト上にGitHubが提供する「GitHub Actions runner」と呼ばれるソフトウェアをインストールすることで、その環境をGitHubが提供する環境と同じように利用できる仕組みになっている。

各環境内にデフォルトでインストールされているソフトウェアについてはSoftware installed on GitHub-hosted runnersドキュメントにまとめられているが、基本的なUNIX/LinuxコマンドやDocker、主要プログラミング言語のランタイムおよびコンパイラ、バージョン管理ツール、Android SDKなどが提供される。ここで提供されていないものに関しては別途インストールすることも可能だ。

GitHub Actionsで簡単なCI/CDを実行する

それでは、実際にGitHub Actionsを使ってCI/CD的な処理を定義してみよう。なお、GitHub Actions関連の設定は、リポジトリの「Actions」タブで行える(図4)。

図4 GitHub Actionsの設定はリポジトリの「Actions」タブで行える
図4 GitHub Actionsの設定はリポジトリの「Actions」タブで行える

GitHub Actions関連の設定を行っていないリポジトリでこのタブをクリックすると、リポジトリ内に含まれているファイルに応じてGitHubが推奨するワークフローが提案される。たとえば今回GitHub Actionsを試したリポジトリではRustというプログラミング言語で実装されたライブラリのソースコードが格納されていたため、「Rust」というワークフローが最初に表示された。なお、ここで表示されるワークフロー設定例はactions/starter-workflowsというリポジトリで公開されている。

ここで使用したいワークフローを選んで「Set up this workflow」をクリックするとGitHubのコードエディタが表示され、選択したワークフローを編集する画面に遷移する(図5)。

図5 ワークフローを選択すると、そのワークフローを編集する画面に遷移する
図5 ワークフローを選択すると、そのワークフローを編集する画面に遷移する

また、自分で一からワークフローを定義したい場合は画面右上に表示される「Set up a workflow yourself」をクリックすれば良い。

さて、ここで編集対象になっている「Rust」ワークフローの中身は次のようになっている。詳しくは後述するが、このワークフローではリポジトリからファイルをチェックアウトする「actions/checkout」というActionを実行し、続いてソースコードのビルドを実行する「cargo build --verbose」コマンドとテストを実行する「cargo test --verbose」というコマンドを実行するという内容になっている。ここで実行されているcargoコマンドはRustのパッケージ管理を行うためのツールで、一般的なRustプログラムはこのcargoコマンド経由でビルドやテストを行えるよう設定されている。

name: Rust  ←ワークフロー名を「Rust」に設定

on: [push]  ←リポジトリへのpush時にこのワークフローを実行するよう指定

jobs:  ←ここで実行するジョブを指定する
  build:  ←「build」という名前のジョブを定義

    runs-on: ubuntu-latest  ←Ubuntuの最新版環境内で処理を実行することを指定

    steps:  ←ここで実行する処理やコマンドを指定する
    - uses: actions/checkout@v2  ←リポジトリからのチェックアウトを行う「actions/checkout」アクションを実行する
    - name: Build
      run: cargo build --verbose  ←「cargo build」コマンドを実行してビルドを行う
    - name: Run tests
      run: cargo test --verbose  ←「cargo test」コマンドを実行してテストを行う

ここで必要に応じてワークフローの内容を編集し、最後に「Start commit」ボタンをクリックしてコミットを実行する(図6)。コミットを実行しない限りワークフローの内容は保存されないので注意しよう。

図6 最後にコミットを実行する必要がある
図6 最後にコミットを実行する必要がある

今回作成したワークフローはリポジトリへのPushに応じて実行されるよう設定されている。GitHubのWebUIからのコミットもPush扱いになるので、コミット後にこのワークフローが実行される。実行結果は「Actions」タブで確認できる(図7)。

図7 ワークフローの実行結果は「Actions」タブで確認できる
図7 ワークフローの実行結果は「Actions」タブで確認できる

図7では、「Rust」というワークフローが4分前に実行されたことが分かる。また、ワークフロー名をクリックするとその詳細なログを確認できる(図8)。

図8 ワークフローの実行ログ
図8 ワークフローの実行ログ

この画面では実行されたジョブがステップごとに表示され、それぞれの実行時間と出力を確認できるようになっている(図9)。

図9 各ステップの出力を確認できる
図9 各ステップの出力を確認できる

今回はビルド・テストともに成功しているため、ワークフローは成功というステータスとなっている。また、ワークフロー内のジョブのいずれかが失敗した場合はその時点で処理は中断され、ワークフローの実行に失敗した旨のメールがリポジトリのオーナーやメンバーに送信される。

ワークフロー作成のルールと書式

GitHub Actionsでは、さまざまな言語向けにビルドやテストを行うための設定を記述したワークフローが提供されている。ビルドやテストの自動化を行うだけであれば、多くの場合それらを利用するだけで簡単にCIが実現できる。ただ、独自の処理を実行させたい場合や、ビルドに独自の手順が必要なものの場合は自前でワークフローを定義する必要がある。以下では、独自のワークフローの記述方法や記述できる内容を説明していこう。

GitHub Actionsでは、前述のとおり実行する処理や実行する条件などをYAML形式(拡張子は「.yml」もしくは「.yaml」)のファイルに記述することでワークフローを定義する。このファイルは、リポジトリのルートディレクトリ直下にある.github/workflowsディレクトリ内に作成する必要がある。このファイルに記述すべき内容についてはWorkflow syntax for GitHub Actionsというドキュメントにまとめられているが、まず必須となっているのが「on」および「runs-on」という2つの要素だ。

まず「on」要素だが、これはワークフローを起動するトリガーとなるイベント、もしくはワークフローを起動する時刻を記述するものだ。たとえばリポジトリへのプッシュが行われた際にワークフローを起動したい場合、次のように「push」を指定する。

on: push

ここで指定できるイベントは、Events that trigger workflowsドキュメントやEvents that trigger workflowsにて解説されているが、GitHubのWebhook機能で定義されているイベントと同一のものが指定できるようになっている。そのため、リポジトリへのプッシュ(「push」)やブランチもしくはタグの作成および削除(「create」および「delete」)といったGitリポジトリを対象とした操作だけでなく、たとえばプルリクエストやissuesの作成(「pull_request」および「issues」)、issuesへのコメント投稿(「issue_comment」)、リポジトリのフォーク(「fork」)、Wikiページの作成(「gollum」)といった、GitHub特有の機能をトリガーとしてワークフローを起動することもできるようになっている。

「on」要素はリスト形式で複数のイベントを指定することもできる。たとえば「create」と「delete」の両方のイベントに対応したい場合、次のように指定すれば良い。

on: [create, delete]

特定のブランチやタグのみを対象に指定することも可能だ。この場合、「*」や「**」といったワイルドカードを使用して対象のブランチやタグを指定することもできる。たとえば「master」ブランチと「foo」で始まるブランチへのプッシュのみを対象にしたい場合、次のように記述する。

on:
  push:
    branches:
      - master
      - foo*

イベントによっては複数のサブタイプを持つものもある。その場合、「type」要素を指定することで特定のサブタイプにマッチするイベントが発生した場合にのみワークフローを起動するよう指定できる。たとえば「issues」イベントでは「opened」や「 edited」、「deleted」、「reopened」といったタイプが定義されており、issueがオープン(opened)もしくは再オープンされた(reopened)場合にのみワークフローを実行したい場合、次のように記述する。

on:
  issues:
    types: [opened, reopened]

特定のイベントではなく、決まった日、時刻にワークフローを実行させたい場合は、「on.schedule」要素で日時の指定を行う。ここではUNIX(POSIX)のcronと同じ形式(「<分> <時> <日> <月> <曜日>」という形式)で日時の指定が可能だ。たとえば毎時30分にワークフローを実行したい場合、次のように記述する。

on:
  schedule:
    - cron: '30 * * * *'

必須要素の2つ目は、ワークフローを実行する仮想マシン環境を指定する「runs-on」要素だ。記事執筆時で選択できる値は表3のとおりとなっている。

表3 「runs-on」要素で設定できる値
説明
windows-latest Windows Server 2019
ubuntu-latest Ubuntu 18.04
ubuntu-18.04 Ubuntu 18.04
ubuntu-16.04 Ubuntu 16.04
macos-latest macOS 10.15(Catalina)
self-hosted ユーザーが独自に用意した実行環境

たとえばUbuntu 18.04環境でワークフローを実行したい場合、次のように指定する。

runs-on: ubuntu-18.04

こちらもリスト形式で複数を選択可能だ。なお、ユーザーが独自に用意した実行環境でワークフローを実行する方法については、About self-hosted runnersドキュメントを参照してほしい。

また、必須ではないがワークフローに対しては次のように「name」要素で名前を、「env」要素で環境変数を指定することもできる。

name: <ワークフロー名>
env:
  <変数名1>: <値>
  <変数名2>: <値>
  
  

ここで指定した環境変数は、このワークフローで定義されたすべての処理の実行時に指定されたプログラムに渡される。ただし、これらはあくまで環境変数であり、処理の実行時にしか参照できない。ワークフロー内の各種要素を記述する際に変数的なものを利用したい場合は、後述する「コンテキスト」機能を利用することになる。

なお、GitHub Actionsではデフォルトでいくつかの環境変数を定義する。定義されている環境変数についてはUsing environment variablesドキュメントを参照してほしい。

実行する処理内容の記述

実行する処理(ジョブ)は、ワークフロー内の「jobs」要素以下で記述する。jobs要素では次のような形式で複数を指定できる。ここで「ジョブID」はジョブを参照する際などに使われる一意な文字列で、「ジョブ名」は管理画面などに表示される名前として使われる文字列だ。

jobs:
  <ジョブID1>:
    name: <ジョブ名>
    
    ジョブの定義
    
  <ジョブID2>:
    name: <ジョブ名>
    
    ジョブの定義
    
  

GitHub Actionsでは、定義されたジョブは基本的に並列に実行される。もし特定の順序でジョブを実行したい場合、「jobs.<ジョブID>.needs」要素でジョブの依存性を定義する。たとえば「bar」というジョブを「foo」というジョブの完了後に実行したい場合、次のように記述する。

jobs:
  foo:
    name: job foo
  bar:
    name: job bar
    needs: foo

各ジョブで実行する処理は、次のように「steps」要素で記述する。

jobs:
  <ジョブID1>:
    name: <ジョブ名>
    steps:
      - name: <ステップ1の名前>
        run: <実行する処理>
        
        
      - name: <ステップ2の名前>
        run: <実行する処理>
        
        

steps要素はオブジェクトのリストとなっており、指定された順序で処理が実行される。ここでは「name」要素で処理内容を示す名前を、「run」要素で実行するコマンドを指定する。ちなみにrun要素で指定したコマンドはシェル経由で実行されるようになっており、実行環境が非Windowsプラットフォームの場合はbash(bashが存在しない場合はsh)、Windowsプラットフォームの場合はPowerShellがデフォルトのシェルとして使われる。「shell」要素で明示的に使用するシェルを指定することも可能だ。たとえばGitHubが提供するWindows環境ではGit for Windowsと一緒にbashがインストールされているので、次のように明示的に指定することでWindows環境でもbashを使ってコマンドを実行できる。

      - name: <ステップの名前>
        run: <実行する処理>
        shell: bash

指定できるシェルは表4の通りだ。

表4 ジョブを実行する際に選択できるシェル
指定できるシェル 対応プラットフォーム 備考
bash すべて
pwsh すべて PowerShellのクロスプラットフォーム対応版であるPowerShell Core
python すべて 任意のPythonコードを実行できる
sh Linux、macOS
cmd Windows いわゆる「コマンドプロンプト」
powershell Windows Windows PowerShell

run要素では複数行のコマンドを指定することもできる。これはYAMLの「ブロックスタイル」式の記述を行うための「|」記号を使えば良い。

      - name: <ステップの名前>
        run: |
          <1行目>
          <2行目>
          *b[:」
          *b[:」
        shell: bash

また、steps以下ではrun要素ではなく「uses」要素を指定することで、任意のActionを実行したり、指定したDockerコンテナを起動してその中で指定した処理を実行したりできる。Actionを実行する場合は、実行するActionを「<オーナー>/<リポジトリ>/<パス>@<タグ|ブランチ|リファレンス>」の形で指定する(パスや@以下については省略可能)。Actionに与えるパラメータ(引数)は「with」要素で指定する。

      - name: <ステップの名前>
        uses: <オーナー>/<リポジトリ>/<パス>@<タグ|ブランチ|リファレンス>
        with:
          <パラメータ1>: <値>
          <パラメータ2>: <値>
          
          

多くの場合、使用するActionのドキュメントにuses要素やwith要素で指定するパラメータについて記載されているはずなので、基本的にはそれに従えば良い。たとえばリポジトリからのチェックアウトを行う「actions/checkout」というActionを利用して、リポジトリ内の「foo」というブランチをチェックアウトする場合、次のように記述する。

uses: actions/checkout@v2
with:
  ref: foo

なお、usesを指定する場合、「name」要素による名前の指定は省略されることが多い。その場合、Action名が名前として指定されたことになる。

特定のコンテナ内で処理を実行したい場合は、uses要素で「docker://<イメージ名>:<タグ>」を指定する。このとき、with要素の「entrypoint」要素や「args」要素でコンテナを実行する際のエントリーポイントや引数を指定できる。

      - name: <ステップの名前>
        uses: docker://<イメージ名>:<タグ>
        with:
          entrypoint: <コンテナのエントリーポイント>
          args: <実行の際に与える引数>

複数のステップで共有するコンテナを立ち上げる

ジョブ全体を特定のコンテナ内で実行させることも可能だ。これは、「jobs.<ジョブID>.container」要素でコンテナを指定することで行える。

jobs:
  hoge:
    container:
      image: <コンテナイメージ>
      env:
        <環境変数名1>: <値>
        <環境変数名2>: <値>
      ports:
        - <割り当てるポート1>
        - <割り当てるポート2>
        
        
      volumes:
        - <割り当てるボリューム1>
        - <割り当てるボリューム2>
        
        
      options: <オプション>

ここで「env」要素はコンテナ実行時に与える環境変数を、「ports」要素は待ち受けを行うポート番号を、「volumes」要素はコンテナ内にマウントするボリュームを指定するもので、それぞれdockerコマンドの「-e」および「-p」「-v」オプションに相当する。指定方法もdockerコマンドの場合とほぼ同じだ。また、「options」要素で任意のオプションを指定することも可能だ。

このように指定されたジョブでは、その各ステップにおいてコンテナが明示的に指定されない限り、ここで指定したコンテナ内で各ステップの処理が実行される。

各ステップとは並列に動作するコンテナを立ち上げる

テスト用のWebサーバーやデータベースなど、各ステップで処理を実行するコンテナとは別にコンテナが必要となる場合がある。その場合、jobs.<ジョブID>.services要素で各ステップとは独立して動作するコンテナを立ち上げるよう指定できる。

ここでは先ほどの「container」要素と同じようなパラメータを指定可能だ。

jobs:
  hoge:
    services:
      <サービス名>:
        image: <コンテナイメージ>
          env:
            <環境変数名1>: <値>
            <環境変数名2>: <値>
          ports:
            - <割り当てるポート1>
            - <割り当てるポート2>
            
            
          volumes:
            - <割り当てるボリューム1>
            - <割り当てるボリューム2>
            
            
          options: <オプション>

パラメータを変数で指定する

ワークフローの実行時には、そのワークフローのトリガーとなったコミットやさまざまな実行時情報が「コンテキスト」としてワークフローに与えられる。これらの情報については、「${{ <コンテキスト名> }}」のように記述することで実行時にアクセスできる。取得できる情報についてはドキュメントのContexts and expression syntax for GitHub Actionsページでまとめられているが、代表的なものとしては表5のものがある。

表5 代表的なコンテキスト
コンテキスト名 説明
github.workflow ワークフロー名
github.repository リポジトリ名
github.sha コミットハッシュ
github.ref ブランチ/ラグを示すref文字列
github.token GitHubにアクセスする際に使用できる認証トークン
os ジョブを実行しているOS環境

たとえば、「${{ github.sha }}」という文字列は、実行時にはそのワークフローの実行のトリガとなったコミットのハッシュ文字列に置き換えられる。

プラットフォームやバージョンを変えながら同じ処理を実行する「build matrix」

異なるバージョンや複数のプラットフォームを対象としたテストやパッケージのビルドを行う場合、それらをいちいち個別のジョブとして定義するのは面倒だ。この問題に対応するため、ワークフローでは「build matrix」という機能が用意されている。これは、あらかじめ繰り返しの際に変更したいパラメータを定義しておくことで、そのパラメータを変えながら順に処理を実行するという機能だ。変更したいパラメータは「strategy.matrix」という要素で定義できる。

たとえば、次のように定義することで指定したActionに対し「version」というパラメータを「1」「2」「3」と変更しながら3回実行させることができる。

strategy:
  matrix:
    version: [1, 2, 3]
steps:
  - uses: <使用するActionを定義>
    with:
      version: ${{ matrix.version }}

また、プラットフォームを変えながら複数回実行したい場合は「os」というパラメータをmatrix以下で指定すれば良い。

strategy:
  matrix:
    os: [ubuntu-16.04, ubuntu-18.04]
steps:
  - uses: <使用するActionを定義>

この場合、steps要素で定義されたActionがUbuntu 16.04環境と18.04環境でそれぞれ実行される。

matrix要素以下では複数のパラメータを指定することもできる。その場合、それらパラメータのすべての組み合わせについて繰り返しActionが実行される。たとえば次のように指定すると、Ubuntu 16.04環境と18.04環境の両方で「version」引数にそれぞれ1、2、3を与えてActionが実行される。

strategy:
  matrix:
    version: [1, 2, 3]
    os: [ubuntu-16.04, ubuntu-18.04]
steps:
  - uses: <使用するActionを定義>
    with:
      version: ${{ matrix.version }}

ワークフローではそのほかジョブ毎に環境変数や実行する環境を指定することもできる。また、特定の条件を満たしたときのみに実行されるジョブを定義することも可能だ。詳しくはドキュメントを参照してほしい。

作成した成果物を保存する

GitHub Actionsではワークフローの実行毎にワークフローを実行する仮想マシンが作成され、実行完了後にはその仮想マシンは削除されてしまう。そのため、GitHub Actionsでは「Artifacts」という指定したファイルを保持しておくための仕組みが用意されている。これは、たとえばコンパイルなどの生成物を出力する処理を実行する場合に有用だ。

Artifactsは、ワークフロー内で指定したファイルをGitHubのサーバー上に「アーティファクト」として保存しておく機能だ。保存期間は最大90日間となっている。また、保存したアーティファクトはジョブどうしでファイルをやりとりするのにも利用できる。

アーティファクトとしてファイルを保存するには、「actions/upload-artifact」というActionを使用する。このActionを利用するには、ワークフロー内で次のように記述する。

- users: actions/upload-artifact@v1
  with:
    name: <アーティファクト名>
    path: <アーティファクトとして保存するファイルのパス名>

このアクションでは、アーティファクト名を指定する「name」と、ファイルのパス名を指定する「path」の2つのパラメータが必須となっており、「path」パラメータで指定されたファイルもしくはディレクトリが、「name」で指定されたアーティファクト名で保存される。

たとえば次の例は、プッシュが行われるごとにdebパッケージを生成し、それをアーティファクトとして保存する処理を行うワークフローだ(https://github.com/hylom/pylucene-kuromoji-deb/blob/master/.github/workflows/dockerimage.yml)。

name: Build Deb Package
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v1

    ↓ビルドに使用するコンテナイメージを作成する
    - name: Build the Docker image 
      run: docker build . --file Dockerfile --tag pylucene_build

    ↓コンテナを実行してビルドを行う
    - name: Build .deb package
      run: docker run -v $(pwd):/host:rw pylucene_build
    
    ↓生成物を「deb」ディレクトリにコピーする
    - name: Copy artifact
      run: mkdir -p deb && cp -a python-lucene* pylucene_* deb/

    ↓「deb」ディレクトリ内のファイルをアーティファクトとして保存する
    - uses: actions/upload-artifact@v1
      with:
        name: python-lucene_${{ github.sha }}_amd64
        path: deb

このリポジトリではパッケージに必要なソースコードやパッチと、パッケージをビルドするために必要なコンテナを作成するためのDockerfileを管理しており、プッシュ時にはまずそのDockerfileからコンテナを作成し、そのコンテナ内でビルドを実行している。ビルド後の成果物はコンテナを実行したディレクトリ内に出力されるので、それを「deb」という名前のディレクトリに移動した後、それらを「python-lucene_${{ github.sha }}_amd64」という名前のアーティファクトとして保存している(前述のように、「${{ github.sha }}」の部分はコミットのハッシュ文字列に置換される)。

保存されたアーティファクトは、ワークフローの詳細表示画面右上に表示される「Artifacts」リンクからダウンロードできる(図10)。

図10 保存されたアーティファクトはワークフローの詳細表示画面からダウンロードできる
図10 保存されたアーティファクトはワークフローの詳細表示画面からダウンロードできる

このダウンロードリンクからダウンロードできるファイルは、ワークフロー内で指定したファイル/ディレクトリをZIP形式で圧縮されたものとなる。

また、ワークフロー内では「actions/download-artifact」というActionを使用してアーティファクトをダウンロードできる。このActionでは、「name」というパラメータでダウンロードしたいアーティファクトを指定する。

- uses: actions/download-artifact@v1
  with:
    name: <アーティファクト名>

認証情報などの公開したくない情報を管理する

GitHub Actionsでは、リポジトリやワークフロー設定ファイル中に認証情報などの秘密情報を格納することは避けるべきだとされている。そのリポジトリへのアクセス権限さえあればそれらの情報に簡単にアクセスできてしまうからだ。そういった秘密情報をワークフロー内で扱いたい場合、「encrypted secrets」という仕組みを利用する。

encrypted secretsは、事前にリポジトリの設定画面から秘密情報(「secret」)を登録しておくことで、この情報に安全にアクセスするための手段を提供する仕組みだ。登録した秘密情報は、ワークフロー設定ファイル内からはコンテキスト(「{{ Secrets.<名前> }}」)を使って参照できる。

encrypted secretsを利用するには、まず使用したい情報を登録する必要がある。これは、リポジトリの設定画面内の「Secrets」から行える(図11)。

図11 リポジトリの設定画面内の「Secrets」で秘密情報の管理を行える
図11 リポジトリの設定画面内の「Secrets」で秘密情報の管理を行える

ここで「Add a new secret」リンクをクリックすると、名前(Name)と値(Value)を入力する画面が表示される。たとえば外部にSSHで接続するためのSSH秘密鍵を指定したい場合、この「Value」欄にSSH秘密鍵の内容をペーストすれば良い(図12)。

図12 「Name」および「Value」を指定して秘密情報を登録する
図12 「Name」および「Value」を指定して秘密情報を登録する

なお、一度入力した情報はその後は閲覧・編集できないので注意したい。対応する値を変更したい場合は、一度削除してもう一度同じ「Name」を指定して再作成する(図13)。

図13 作成したSecretの中身を閲覧することはできない
図13 作成したSecretの中身を閲覧することはできない

また、登録できるデータは最大64KBまでという制約がある。それ以上のデータを登録したい場合は、ファイルを暗号化してリポジトリ内に格納しておき、それを復号するためのキーをSecrets経由で与えてワークフロー内で復号する、という手法がある。これについてはドキュメント内で説明されているので、詳しくはそちらを参照してほしい。

登録した情報は、ワークフローからは「${{ secrets.<名前> }}」という形で参照できる。コマンドラインではなくファイルで秘密情報を与えなければならない場合は、次のようにechoコマンドなどを使って適宜ファイルに出力すれば良い。

    - name: Exports SSH private key
      run: echo "${{ secrets.SSHPrivateKey }}" > id_rsa && chmod 600 id_rsa

    - name: Scp artifact
      run: scp -o 'StrictHostKeyChecking no' -i id_rsa -r deb/ foobar@example.com:~/

ここでは、「SSHPrivateKey」という名前で登録した秘密鍵情報を「id_rsa」というファイルに出力し、その上でその秘密鍵を使ってscpコマンドでファイルを外部ホストにコピーしている。なお、ここではホスト鍵のチェックを省略するため、「-o 'StrictHostKeyChecking no'」オプションを指定している。

設定が容易で自由度も高く使い勝手は良好

このようにGitHub Actionsは比較的設定が容易で、また自由度も高い。公開リポジトリにおいては利用コストが一切かからない点も魅力的で、オープンソースソフトウェアの開発においては非常に有用だろう。ただ、利用できるCPUリソースやメモリ使用量には制限があるため、大規模なソフトウェアのビルド等での利用については工夫が必要になるかもしれない。

また、非公開リポジトリでの利用については比較的制約が大きいが、そちらについては使用料金の支払いで対応は可能だ。そのため、ほかのサービスと利用コストを比較しつつ検討してみると良いだろう。