はじめに
GitHub Actionsでテストを行う際に、毎回使い捨てのDBを立てて、そのDBを使ってテストをする方法について調べました。
環境
記事を書くにあたって作成したサンプルコードはGitHubのKentarouSuzuki/actions-service-containerにアップロードしてあるので、必要に応じてこちらを参照してください。
今回はGoで実装をしました。本来、Goでテストコードを書く際には hoge_test.go
というファイル名にしますが、今回はコードを cmd/sample/main.go
に書きました。
データベース | PostgreSQL |
言語 | Go 1.14 |
マイグレーションツール | rubenv/sql-migrate |
テストの順序
次のような順序でテストを進めていきます。
- GitHubからソースコードをチェックアウトしてくる。
- DBマイグレーションを行う。
- Accountテーブルの作成
- テストコードを実行する。
- データを追加
- 追加したデータを全て取得して、標準出力に表示
- データを全件削除する
サービスコンテナ
GitHub ActionsのワークフローでDBに接続してテストを行うには、GitHub Actionsのサービスコンテナを使います。このサービスコンテナの正体はDockerコンテナでDocker Hubのイメージを指定し、作成します。この記事ではPostgreSQLを使っていますが、これをMySQLといった他のRDBやRedis、nginxを使うこともできます。
例えば、ジョブを実行するにあたってPostgreSQLのサービスコンテナを用意するには以下のように書きます。
jobs:
my-job:
runs-on: ubuntu-latest
services:
postgres:
# Docker Hubのイメージ
image: postgres
# 動かすコンテナで使う環境変数
# この場合はpostgresqlのパスワードを指定している。
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
各ジョブの直下にあるservicesに動かしたいサービスコンテナのラベルを書き(この例ではpostgresですが、dbとかでも大丈夫です)、その下に動かしたいDockerイメージや環境変数、ヘルスチェックの設定を指定します。
サービスコンテナと通信する
ワークフロー中で作成したサービスコンテナと通信する方法はジョブを実行している環境によって変わってきます。そこで、まずはジョブを実行する環境について説明します。その後、それぞれの方法でサービスコンテナへ通信する方法について説明をします。
GitHub Actionsでジョブを実行する環境
GitHub Actionsでは以下の2つの環境でジョブを実行することができます。
- ランナーマシン上でジョブを直接実行する
- コンテナ内でジョブを実行する
ランナーマシン上でジョブを直接実行する
あくまで私が公式ドキュメントやQiitaなどの記事で見た限りですが、この方法で実行している場合が多いです。公式ドキュメントのクイックスタートもこの方法で実行しています。
この方法では以下のように各ジョブの runs-on
で指定されているマシンの種類でジョブが実行されます。GitHub ActionsではデフォルトでWindows、macOS、Ubuntuのマシンが用意されています。ジョブの中に定義されている steps
は runs-on
で指定されたマシンで実行されます。この記事のようにサービスコンテナを実行するにはGitHub Actionの仕様でLinuxのランナーを指定しなければならないようです。1
jobs:
my-job:
runs-on: ubuntu-latest
steps:
- name: Hello, world
run: |
echo "Hello, world"
コンテナ内でジョブを実行する
こちらの方法はジョブ直下の runs-on
でマシンの種類を、 steps
で実行するジョブの内容を設定するところは同じです。しかし、それらに加えて container
を設定します。この項目ではジョブが実際に実行するコンテナの設定を指定しています。この例の場合は、 go version
はubuntu上に動いている golang:1.14
のコンテナ内で実行されます。
この方法でも先ほどのランナーマシン上でジョブを実行する時と同様にGitHub Actionsの仕様でLinuxのランナーを指定しなければならないようです。1
jobs:
my-job:
runs-on: ubuntu-latest
container:
# Docker Hubのイメージ
image: golang:1.14
steps:
- name: Print Go version
run: |
go version
サービスコンテナとの通信方法
ランナーマシン上で動いているジョブ
まずは、ランナーマシン上で動いているジョブからサービスコンテナに通信する方法について説明します。
ジョブをランナーマシン上で動かしている場合、サービスコンテナはランナーマシンに対してデフォルトではポートを公開していません。したがって、GitHub Actionsでサービスコンテナを定義する際は、サービスコンテナとランナーマシンのポートをマップする必要があります。例えば、この記事を作成するにあたって実装したサンプルではPostgreSQLをサービスコンテナとしたので、ランナーマシンの5432番ポートとサービスコンテナの5432番ポートをマップします。実際の設定ファイルは以下のようになります。
jobs:
runner-job:
runs-on: ubuntu-latest
env:
ENV: runner-job
services:
postgres:
image: postgres
env:
POSTGRES_USER: sue
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
そして、サービスコンテナのホストは local
または 127.0.0.1
に設定されています。したがって、 localhost:5432
に対してアクセスすればPostgreSQLと通信ができるようになります。実際に、サンプルでもランナーマシン上でジョブを実行している場合のsql-migrateは以下のように設定しており、 localhost:5432
に対してアクセスするようになっています。
runner-job:
dialect: postgres
datasource: host=localhost dbname=postgres user=sue password=postgres sslmode=disable
dir: migrations
table: migrations
コンテナ内で動いているジョブ
続いて、コンテナ内で動いているジョブからサービスコンテナに通信する方法について説明します。
コンテナ内でジョブを実行している時には、ランナーマシン上でジョブを実行している時よりもシンプルにサービスコンテナと通信することができます。この時にはDockerのユーザー定義ブリッジネットワークを使っています。そのため、任意のホスト名をつけられるので、GitHub Actionsではサービスコンテナのラベル名を使ってアクセスすることができます。また、コンテナはお互いに全てのポートを公開しあっているために、ポートのマッピングをする必要がありません。
なので、サンプルでもサービスコンテナを定義する際には以下のような設定をして、
container-job:
runs-on: ubuntu-latest
container:
image: golang:1.14
env:
ENV: container-job
services:
postgres:
image: postgres
env:
POSTGRES_USER: sue
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
sql-migrateでDBのマイグレーションを行う際の設定も
container-job:
dialect: postgres
datasource: host=postgres dbname=postgres user=sue password=postgres sslmode=disable
dir: migrations
table: migrations
のように設定し、 postgres:5432
にアクセスできます。