書いた契機
GitHub Actions上でテストを実行する際に、使い捨てのデータベースにアクセスしたいと思いドキュメントを読んでいた。
これを読んでいて「コンテナーからサービスコンテナにアクセスするのと、ランナーマシンからサービスコンテナにアクセスするときの違いってどういうこと?」と思ったのであれこれ試行錯誤したメモ。
初歩的な理解ができていなかったので結構理解するのに詰まってしまった。
要約
- GitHub ActionsのランナーマシンではDockerが動くので、コンテナ上でもJob を実行させることができる
- サービスコンテナはJobを実行させるコンテナとは別にコンテナを起動させて、Jobと通信することができる
- 用途として、PostgreSQLとかRedisを起動させて、スクリプトを実行するなど
- ランナーマシン上で直接Jobを実行する時はマシン内のDockerにアクセスするのでホストのパスが
localhost
になる (図の左) - コンテナ上でJobを実行する時は、同じDocker内のコンテナにアクセスするので ホストのパスがサービスコンテナのラベルになる (
postgres
/redis
) (図の右)
自分がコンテナ上でJobを実行させたことがほぼなかったので、直接ランナー上で実行する違いを全然理解していなくてちょっと混乱した。
選べるならどっちがいいの?
個人的な開発スタイルだと、Node.jsなどはローカルで、必要なデータベースなどのミドルウェアはDockerで建てることが多い。ので、GitHub Actionsで動かすときも似たような環境がいいので、Jobはランナー本体、PostgreSQLはサービスコンテナで運用したい。
setup-node
みたいなActionを使った方がcacheのコントロールもしやすいと思うので、その点でもランナー上で実行するようにしたい。
最終的にWorkflowはこんな感じになった。
name: PostgreSQL Service Example on: push jobs: runner-job: runs-on: ubuntu-latest services: postgres: image: postgres env: POSTGRES_PASSWORD: postgres options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 5432:5432 steps: - name: Check out repository code uses: actions/checkout@v4 - name: Setup node uses: actions/setup-node@v4 with: node-version: 20 cache: "npm" - name: Install dependencies run: npm ci - name: Connect to PostgreSQL run: node client.js env: POSTGRES_HOST: localhost POSTGRES_PORT: 5432
コンテナでJob実行する場合の差分はこんな感じ。
jobs: runner-job: runs-on: ubuntu-latest - container: node:20 steps: + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "npm" - name: Connect to PostgreSQL run: node client.js env: - POSTGRES_HOST: postgres + POSTGRES_HOST: localhost
余談
actions/setup-node
よりもコンテナーで実行した方がコンテナをPullする分時間がかかるんじゃないかと思ったけどそんなに変わらなかった。
おまけ: ローカルで実行するとき
GitHub Actionsの公式ドキュメントにPostgreSQLをサービスコンテナで起動して実行するスクリプトが例題としてある。
実際に開発するときはローカルでテストしてからPushしたい。
こんな感じのスクリプトを実行したい。
// client.js const { Client } = require("pg"); const main = () => { const pgclient = new Client({ host: process.env.POSTGRES_HOST, port: process.env.POSTGRES_PORT, user: "postgres", password: "postgres", database: "postgres", }); pgclient.connect(); const table = "CREATE TABLE IF NOT EXISTS student(id SERIAL PRIMARY KEY, firstName VARCHAR(40) NOT NULL, lastName VARCHAR(40) NOT NULL, age INT, address VARCHAR(80), email VARCHAR(40))"; const text = "INSERT INTO student(firstname, lastname, age, address, email) VALUES($1, $2, $3, $4, $5) RETURNING *"; const values = [ "Mona the", "Octocat", 9, "88 Colin P Kelly Jr St, San Francisco, CA 94107, United States", "octocat@github.com", ]; pgclient.query(table, (err, res) => { if (err) throw err; }); pgclient.query(text, values, (err, res) => { if (err) throw err; }); pgclient.query("SELECT * FROM student", (err, res) => { if (err) throw err; console.log(err, res.rows); // Print the data in student table pgclient.end(); }); }; main();
これに対して用意したいdocker-compose.yaml
をこんな感じで建てる。
使い捨ての前提なので、永続化のためのvolumes
は指定しない。
# docker-compose.yaml version: "3.9" services: postgres: image: postgres ports: - 5432:5432 environment: POSTGRES_USER: "postgres" POSTGRES_PASSWORD: "postgres" POSTGRES_DB: "postgres"
Docker Composeでコンテナを起動してスクリプトを実行。
$ docker compose up -d $ POSTGRES_HOST=localhost POSTGRES_PORT=5432 node client.js null [ { id: 1, firstname: 'Mona the', lastname: 'Octocat', age: 9, address: '88 Colin P Kelly Jr St, San Francisco, CA 94107, United States', email: 'octocat@github.com' } ]
Docker Desktopの代替?となるOrbStackで確認した。早くて良い。