🐳

Docker コンテナの起動を待つ - MySQL の使い方 - [GitHub Actions]

2022/03/11に公開
1

概要

Docker コンテナを扱う際に起動を待ちたいケースは多々あると思います。
この記事では MySQL コンテナを例に3パターンと、 Docker ではないのですが MySQL の場合プレインストールもされているので併せて紹介します。

TL;DR

長々と説明していますが重要なポイントはこの2つです。

  • Docker コンテナのヘルスチェック
  • CLI から起動する場合は untildocker compose up -d --wait で待機

プレインストール

GitHub ホストランナーには MySQL や PostgreSQL がインストールされています
最初は無効化されているので sudo systemctl start postgresql.service で有効化します。
すぐに使える状態になるので起動時間は最速です。

.github/workflows/docker.yml
jobs:
  pre-installed:
    runs-on: ubuntu-20.04
    timeout-minutes: 5
    env:
      MYSQL_PWD: root

    steps:
      - run: sudo systemctl start mysql.service
      - run: mysql -uroot -e "SHOW DATABASES"

サービスコンテナ

options でコンテナのヘルスチェックを設定しておくと起動してから steps を実行してくれます。
ヘルスチェックの内容は各イメージに依存します。

https://docs.github.com/ja/actions/using-containerized-services/about-service-containers

ランナーマシン上で直接のジョブの実行

runs-on: ubuntu-20.04 で steps を実行します。

.github/workflows/docker.yml
jobs:
  service-container-on-host:
    runs-on: ubuntu-20.04
    timeout-minutes: 5
    env:
      MYSQL_PWD: root

    services:
      mysql-server:
        image: mysql:8.0.28
        ports:
          - 3306:3306
        env:
          MYSQL_ROOT_PASSWORD: ${{ env.MYSQL_PWD }}
        options: >-
          --health-cmd "mysqladmin ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - run: mysql --protocol=tcp -uroot -e "SHOW DATABASES"
#       - run: docker exec -e MYSQL_PWD ${CONTAINER_ID} mysql -uroot -e "SHOW DATABASES"
#         env:
#           CONTAINER_ID: ${{ job.services.mysql-server.id }}

Docker のネットワークを使わない通信になるのでちょっと強引です。
--protocol=tcp を付ける必要があります。
付けていないと以下のエラーが発生します。

ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

コメントで docker exec でコンテナへアクセスする方法も記載していますが、通常はサーバーコンテナ外部からアクセスすると思うので参考程度に。

コンテナ内でのジョブの実行

container: mysql:8.0.28 で steps を実行します。
コンテナ内で実行すると同一ネットワークになり mysql-server:3306 (サービス名)でアクセスできるようになります。

.github/workflows/docker.yml
jobs:
  service-container-on-container:
    runs-on: ubuntu-20.04
    container: mysql:8.0.28 # 実際は Node.js や PHP などのアプリコンテナ
    timeout-minutes: 5
    env:
      MYSQL_PWD: root

    services:
      mysql-server:
        image: mysql:8.0.28
        env:
          MYSQL_ROOT_PASSWORD: ${{ env.MYSQL_PWD }}
        options: >-
          --health-cmd "mysqladmin ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - run: mysql -hmysql-server -uroot -e "SHOW DATABASES"

Docker

Docker CLI も使えます。
コンテナのヘルスチェックを設定して until で起動を待ちます。

.github/workflows/docker.yml
jobs:
  docker:
    runs-on: ubuntu-20.04
    timeout-minutes: 5
    env:
      MYSQL_PWD: root

    steps:
      - run: docker network create bridge-network
      - run: |
          docker run \
            --name mysql-server \
            -e MYSQL_ROOT_PASSWORD=${MYSQL_PWD} \
            --network bridge-network \
            -d \
            --health-cmd "mysqladmin ping" \
            --health-interval 10s \
            --health-retries 5 \
            --health-timeout 5s \
            mysql:8.0.28
      - name: Wait for starting
        run: |
          set -x
          until [ "$(docker inspect --format='{{.State.Health.Status}}' mysql-server)" = 'healthy' ]; do
            sleep 1s
          done
      # 実際は Node.js や PHP などのアプリコンテナ
      - run: |
          docker run \
            --name mysql-client \
            --rm \
            -e MYSQL_PWD \
            --network bridge-network \
            mysql:8.0.28 \
            mysql -hmysql-server -uroot -e "SHOW DATABASES"
#       - run: docker exec -e MYSQL_PWD mysql-server mysql -uroot -e "SHOW DATABASES"

docker inspect --format='{{.State.Health.Status}}' mysql-server でヘルスチェックの状態が確認できます。
healthy になるまで until で待機します。

Docker Compose

Docker Compose CLI も使えます。
コンテナのヘルスチェックを設定して until --wait オプションで起動を待ちます。

docker-compose.yml
version: "3.9"

services:
  mysql-server:
    image: mysql:8.0.28
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_PWD}
    healthcheck:
      test: ["CMD", "mysqladmin", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
  mysql-client: # 実際は Node.js や PHP などのアプリコンテナ
    image: mysql:8.0.28
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_PWD}
.github/workflows/docker.yml
jobs:
  docker-compose:
    runs-on: ubuntu-20.04
    timeout-minutes: 5
    env:
      MYSQL_PWD: root

    steps:
      - uses: actions/checkout@v3
      - run: docker compose up -d --wait
#       - name: Wait for starting
#         run: |
#           set -x
#           until [ "$(docker inspect --format='{{.State.Health.Status}}' $(docker compose ps -q mysql-server))" = 'healthy' ]; do
#             sleep 1s
#           done
      - run: docker compose exec -T -e MYSQL_PWD mysql-client mysql -hmysql-server -uroot -e "SHOW DATABASES"
#       - run: docker compose exec -T -e MYSQL_PWD mysql-server mysql -uroot -e "SHOW DATABASES"

docker compose には inspect がないので docker compose ps -q mysql-server でコンテナ ID を取得して docker inspect に渡しています。
Docker Compose V2 では --wait オプションが追加されてシンプルに書けるようになりました。

docker compose の部分は旧コマンドの docker-compose (ハイフンあり)でも動きます。
exec だけ -e MYSQL_PWD=${MYSQL_PWD} に変更してあげてください。
--wait オプションは使えないのでコメントアウトしている until を使用してください。

https://matsuand.github.io/docs.docker.jp.onthefly/compose/cli-command/

どれを使うべき?

サービスコンテナだと GitHub Actions に依存してしまうのでローカル環境の構築が難しいです。
当然ローカルでは別途 Docker コマンドを利用して構築こともできますが CI と同じ環境を構築できていることを保証できませんしオプションが増えてきたりすると大変です。
Docker Compose を使うとネットワーク等を含めて定義できるのでローカルでも CI でも同じ環境で実行しやすいのでおすすめです。

起動時間を気にする場合はプレインストールされているものを使ってもよいと思いますが、バージョンが GitHub ホストランナーに依存します。

各ジョブの実行時間はこんな感じでした。

補足

コードの簡略化のため root ユーザーを使用していますが、本来は別途ユーザーを作成した方が良いです。
root ユーザーだと本番で運用するユーザーが参照できないテーブルも参照できてしまってテストにならない可能性があるので本番で運用するユーザーと同じ権限にしておきましょう。

使い捨て環境のためパスワードはべた書きしています。
気になるようでしたら Secrets を使用するとよいかと思います。

Discussion