839の日記

趣味の話を書くブログです。

GitHub Actionsでcontainerの挙動を調査した

備忘録としてまとめる。

TL;DR

  • container.image に指定するimageに現状alpineベースは使えないのでslimベースを使う
  • services側に指定するimageはalpineベースのimageも利用可能
  • container.imageに指定された場合はHOMEが /github/home に置換されるのでそれを考慮してロジックを書く
  • gcloudコマンドを使いたい場合は google/cloud-sdk:slim imageを使うとVMにaptベースで入れるより1分半ほど早くなる
    • 08/25の現在ではVMに最初からgcloudコマンドが入っている、08/23の暗黙的更新で入るようになった模様

背景

ワークフローの中で gcloud container clusters get-credentials をしているので、apt経由でgcloud sdkのセットアップをしていた。
install部分で1m30s - 2m30sほどかかってしまい、この時間を短縮したかった。

そのため、 google/cloud-sdk のalpineベースのimageを利用しようとしたらハマったのでcontainerの挙動を調査した。

GitHub Actionsのcontainerのプロセス

自動で行われるプロセスのInitalize containersStop containersについて。
今回はcontainer.imageに google/cloud-sdk:slim を指定している前提。

バージョン確認

$ /usr/bin/docker version --format '{{.Server.APIVersion}}'
'1.40'
Docker daemon API version: '1.40'
$ /usr/bin/docker version --format '{{.Client.APIVersion}}'
'1.40'
Docker client API version: '1.40'

とりあえず出してるだけ?

コンテナ環境の準備

$ /usr/bin/docker ps --all --quiet --no-trunc --filter "label=29fe0f"
$ /usr/bin/docker network prune --force --filter "label=29fe0f"
$ /usr/bin/docker network create --label 29fe0f github_network_f081154afaeb49f8a7d58af20922dbb4
20455dfa0b2cb1d5c8f711753389843afe129706b6e913f987aecfeef0ef68cd

動作しているコンテナに今から使おうとしているラベルが使われていないかを表示し、
使おうとしているラベルのネットワークをforce optionをつけて削除実行。
その後にこれからの実行で使うネットワークを作成する。

作成したネットワークは実行されているjobの中で使い回され、middlewareの連携に使われる(e.g. MySQL, Redis)。

イメージの準備

$ /usr/bin/docker pull google/cloud-sdk:slim
slim: Pulling from google/cloud-sdk
...
Digest: sha256:0ceb5c90f22070d2cb9a7ca9839ff83daf2ae3003cacd25bfababb659874e200
Status: Downloaded newer image for google/cloud-sdk:slim
docker.io/google/cloud-sdk:slim
$ /usr/bin/docker inspect --format="{{index .Config.Labels \"github.actions.runner.node.path\"}}" google/cloud-sdk:slim
$ /usr/bin/docker create --name 56ec245c1e0d43f9905adf5cc005fa58_googlecloudsdkslim_77abfc --label 29fe0f --workdir /__w/check-event-for-gh-actions/check-event-for-gh-actions --network github_network_f081154afaeb49f8a7d58af20922dbb4  -e "HOME=/github/home" -v "/var/run/docker.sock":"/var/run/docker.sock" -v "/home/runner/work":"/__w" -v "/home/runner/runners/2.157.0/externals":"/__e":ro -v "/home/runner/work/_temp":"/__w/_temp" -v "/home/runner/work/_actions":"/__w/_actions" -v "/opt/hostedtoolcache":"/__t" -v "/home/runner/work/_temp/_github_home":"/github/home" -v "/home/runner/work/_temp/_github_workflow":"/github/workflow" --entrypoint "/__e/node12/bin/node" google/cloud-sdk:slim -e "setInterval(function(){}, 24 * 60 * 60 * 1000);"
c4cb2d68eb23ea352a5ab62c9fef4788ef19f49896918b9bebde1c285a8d3298
$ /usr/bin/docker start c4cb2d68eb23ea352a5ab62c9fef4788ef19f49896918b9bebde1c285a8d3298
c4cb2d68eb23ea352a5ab62c9fef4788ef19f49896918b9bebde1c285a8d3298
$ /usr/bin/docker ps --all --filter id=c4cb2d68eb23ea352a5ab62c9fef4788ef19f49896918b9bebde1c285a8d3298 --filter status=running --no-trunc --format "{{.ID}} {{.Status}}"
c4cb2d68eb23ea352a5ab62c9fef4788ef19f49896918b9bebde1c285a8d3298 Up Less than a second

このあたりからGitHub Actions特有の操作が入っていて色々ハマりポイントがあった。
まずimageをpullしてくる。 その後

docker inspect --format="{{index .Config.Labels \"github.actions.runner.node.path\"}}" google/cloud-sdk:slim

ConfigLabels の値を確認している。
cloud-sdk imageでは空が返ってくるだけだが、inspectしたimageの形式が以下のようであれば何らかの値が返ってくる。

[
  {
    "Id": "sha256:digest",
    ...,
    "Config": {
      "Labels": {
        "github.actions.runner.node.path": "something"
      }
    },
   ...,
  }
]

使っているイメージではLabelsに上記のような値が入っているものはないので、入っていた場合はどういう処理になるのかは未調査。

次に

docker create \
  --name 56ec245c1e0d43f9905adf5cc005fa58_googlecloudsdkslim_77abfc  \
  --label 29fe0f \
  --workdir /__w/check-event-for-gh-actions/check-event-for-gh-actions  \
  --network github_network_f081154afaeb49f8a7d58af20922dbb4  \
  -e "HOME=/github/home" \
  -v "/var/run/docker.sock":"/var/run/docker.sock" \
  -v "/home/runner/work":"/__w" \
  -v "/home/runner/runners/2.157.0/externals":"/__e":ro \
  -v "/home/runner/work/_temp":"/__w/_temp" \
  -v "/home/runner/work/_actions":"/__w/_actions" \
  -v "/opt/hostedtoolcache":"/__t" \
  -v "/home/runner/work/_temp/_github_home":"/github/home" \
  -v "/home/runner/work/_temp/_github_workflow":"/github/workflow" \
  --entrypoint "/__e/node12/bin/node" \
  google/cloud-sdk:slim \
  -e "setInterval(function(){}, 24 * 60 * 60 * 1000);"

でcontainerを作成している。上記は見やすいようにフォーマットしている。
色々と設定された状態でコンテナが作られるが、やっていることはいい感じに設定しながらコンテナを起動し続けるようにしてくれているだけ。

自分が大きくハマったのは以下の2点の挙動。

  • HOMEが /github/home に上書きされている
  • runs-onで指定されたホストのnodeがentrypointで使用されている

まず、HOMEに関しては cloud-sdk imageがimage内で持っているHOMEが /root だったのでそれを基準に手元で検証していた。
実際にactionsのほうで実行すると、HOMEがずれており意図せぬ挙動になった上、sshもできないのでHOMEが原因と気づくまでに時間がかかった。

次にnode周りの実行についてだが、nodeで実行しているのは--evalの引数として setInterval(function(){}, 24 * 60 * 60 * 1000); を実行している。
これをしている意図としてはコンテナが即座に終了しないように保持し続けるのが目的だと推測できる。
その対応をnodeを使って実行するためにruns-onで指定されたホストにインストールされたnodeをvolumeでマウントして実行しようとしている。

こういうことをしようとすると、例えば runs-on: ubuntu-latest のときにalpineイメージを実行しようとするとubuntu側からマウントされたnodeがalpine側で使えないので落ちる。
最初GitHub Actionsでcontainer周りを触ったときにalpineベースのimageを使おうとしていたのでこの問題を引いてcontainerは使えないものと思い込んでいた。

ubuntuでマウントされたnodeを使えるimageであればよいので、slimベースのimageを使うことで解決した。
image sizeが小さいほどpull時間が短くなりCIの実行が早くなるのでalpineベースを使いたいが、slimベースでもaptでgcloud sdkを入れるよりはだいぶ早くなった。

作成したcontainerを維持したいだけならshellで書けばいいだけだと思うので、updateでぜひnode依存を取り払ってほしい。
もしalpineベースでも実行できた人がいたら教えてくれるとありがたい…。

$ /usr/bin/docker start c4cb2d68eb23ea352a5ab62c9fef4788ef19f49896918b9bebde1c285a8d3298
c4cb2d68eb23ea352a5ab62c9fef4788ef19f49896918b9bebde1c285a8d3298
$ /usr/bin/docker ps --all --filter id=c4cb2d68eb23ea352a5ab62c9fef4788ef19f49896918b9bebde1c285a8d3298 --filter status=running --no-trunc --format "{{.ID}} {{.Status}}"
c4cb2d68eb23ea352a5ab62c9fef4788ef19f49896918b9bebde1c285a8d3298 Up Less than a second

最後に作ったコンテナの起動とちゃんと動いているかの確認をしている。

コンテナの破棄(Stop containers)

$ /usr/bin/docker rm --force c4cb2d68eb23ea352a5ab62c9fef4788ef19f49896918b9bebde1c285a8d3298
c4cb2d68eb23ea352a5ab62c9fef4788ef19f49896918b9bebde1c285a8d3298
Remove container network: github_network_f081154afaeb49f8a7d58af20922dbb4
$ /usr/bin/docker network rm github_network_f081154afaeb49f8a7d58af20922dbb4
github_network_f081154afaeb49f8a7d58af20922dbb4
Cleaning up orphan processes

container を使用している場合は自動的にコンテナとnetworkの破棄が行われる(よくやるやつ)。

servicesについて

VMベースとcontainerベースがある。

jobs:
  vm-job:
    runs-on: ubuntu-latest
    services:
      redis:
        image: redis
        ports:
        - 6379/tcp
    - uses: actions/checkout@v1
    - run: something
      env:
        REDIS_HOST: localhost
        REDIS_PORT: ${{ job.services.redis.ports[6379] }}
  container-job:
    runs-on: ubuntu-latest
    container:
      image:  something
    services:
      redis:
        image: redis
        ports:
        - 6379:6379
    steps:
    - uses: actions/checkout@v1
    - run: something
      env:
        REDIS_HOST: redis
        REDIS_PORT: ${{ job.services.redis.ports[6379] }}

より詳細なexampleは公式を参照すると良さそう。
GitHub - actions/example-services: Example workflows using service containers

挙動については Initalize containersStop containers にまとめられている。

vmの場合はコンテナを立ち上げてportのmappingを行い、localhost上にredisがいるように見えている。
e.g. localhost:6379

$ usr/bin/docker create --name 9c34e4a1c0b844aaa939b31977869d67_redis_756ef3 --label 29fe0f --workdir /__w/example-services/example-services --network github_network_075e98bbff874715ae6b3d2cf196e483 --network-alias redis -p 6379/tcp  -e "HOME=/github/home" -v "/home/runner/work":"/__w" -v "/home/runner/runners/2.157.0/externals":"/__e":ro -v "/home/runner/work/_temp":"/__w/_temp" -v "/home/runner/work/_actions":"/__w/_actions" -v "/opt/hostedtoolcache":"/__t" -v "/home/runner/work/_temp/_github_home":"/github/home" -v "/home/runner/work/_temp/_github_workflow":"/github/workflow" redis
 76d070f99216dc7457a9839bd9702b67f80c5ea41a6507ec9655ef96520ca3d2
$ /usr/bin/docker start 76d070f99216dc7457a9839bd9702b67f80c5ea41a6507ec9655ef96520ca3d2
76d070f99216dc7457a9839bd9702b67f80c5ea41a6507ec9655ef96520ca3d2
$ /usr/bin/docker ps --all --filter id=76d070f99216dc7457a9839bd9702b67f80c5ea41a6507ec9655ef96520ca3d2 --filter status=running --no-trunc --format "{{.ID}} {{.Status}}"
76d070f99216dc7457a9839bd9702b67f80c5ea41a6507ec9655ef96520ca3d2 Up Less than a second
$ /usr/bin/docker port 76d070f99216dc7457a9839bd9702b67f80c5ea41a6507ec9655ef96520ca3d2
6379/tcp -> 0.0.0.0:32768
/usr/bin/docker inspect --format="{{if .Config.Healthcheck}}{{print .State.Health.Status}}{{end}}" 76d070f99216dc7457a9839bd9702b67f80c5ea41a6507ec9655ef96520ca3d2

containerの場合は同じネットワーク上にredisを立ち上げておき、serviceで指定した名前をHOST名として接続できる。
e.g. redis:6379

$ usr/bin/docker create --name 286235f7d8134c71a9ca342a1f56102b_redis_29007b --label 29fe0f --workdir /__w/example-services/example-services --network github_network_8d140662e9cc4c1ca6f4e69f6afbf640 --network-alias redis -p 6379:6379  -e "HOME=/github/home" -v "/home/runner/work":"/__w" -v "/home/runner/runners/2.157.0/externals":"/__e":ro -v "/home/runner/work/_temp":"/__w/_temp" -v "/home/runner/work/_actions":"/__w/_actions" -v "/opt/hostedtoolcache":"/__t" -v "/home/runner/work/_temp/_github_home":"/github/home" -v "/home/runner/work/_temp/_github_workflow":"/github/workflow" redis
 80c0e46c00714d01f8875de12dbff24c637dfaa4dd18adec9fcd3e46269da516
$ /usr/bin/docker start 80c0e46c00714d01f8875de12dbff24c637dfaa4dd18adec9fcd3e46269da516
80c0e46c00714d01f8875de12dbff24c637dfaa4dd18adec9fcd3e46269da516
$ /usr/bin/docker ps --all --filter id=80c0e46c00714d01f8875de12dbff24c637dfaa4dd18adec9fcd3e46269da516 --filter status=running --no-trunc --format "{{.ID}} {{.Status}}"
80c0e46c00714d01f8875de12dbff24c637dfaa4dd18adec9fcd3e46269da516 Up Less than a second
$ /usr/bin/docker port 80c0e46c00714d01f8875de12dbff24c637dfaa4dd18adec9fcd3e46269da516
6379/tcp -> 0.0.0.0:6379
$ /usr/bin/docker inspect --format="{{if .Config.Healthcheck}}{{print .State.Health.Status}}{{end}}" 80c0e46c00714d01f8875de12dbff24c637dfaa4dd18adec9fcd3e46269da516

VM、containerどちらの場合も container.image で指定される場合と違ってentrypointの上書きやコンテナを維持するための--evalの指定がない。
そのため、serviceで指定するimageについてはalpine系のimageを利用しても問題なさそう。

dindについて

  dind:
    runs-on: ubuntu-latest
    container:
      image: docker:stable-dind
    steps:
      - run: docker -v

で試してみたが使えないので現状できなさそう。

Error response from daemon: Container de125048c3bfb37fb0ac805da204b6b2365aa87a70228d56cc01b9bfac9bb864 is not running
##[error]Process completed with exit code 1.

おそらく docker:stable-dind image内で--evalが成功していない気がする。
このあたり何が起きているか調べる術がないので難しい…。