Gitlab Runner で concurrent
が 1 より大きい場合、パイプラインを 1 つの Runner で実行するよう構成したとしてもジョブ間でキャッシュが共有されないことがあります。
例えば concurrent = 4
な Runner で次のパイプラインを実行します。
image: alpine
stages:
- stage1
- stage2
1A:
stage: stage1
tags: [ore]
cache:
key: {}
script:
- echo ok
1B:
stage: stage1
tags: [ore]
cache:
key: hoge
paths:
- cache.txt
script:
- touch cache.txt
- cat cache.txt
- echo "$CI_JOB_NAME" > cache.txt
2A:
stage: stage2
tags: [ore]
cache:
key: hoge
paths:
- cache.txt
script:
- touch cache.txt
- cat cache.txt
- echo "$CI_JOB_NAME" > cache.txt
1B と 2A は stage が異なる& key: hoge
でキャッシュキーを固定値にしているのでキャッシュが共有されそうですが、実際には共有されません。
concurrent = 4
の場合、Runner が 1 つしか登録されていなくても実際には下記の 4 つの Runner が存在するのと同じような状態になっているためです。
runner-XXXXXXXX-project-N-concurrent-0
runner-XXXXXXXX-project-N-concurrent-1
runner-XXXXXXXX-project-N-concurrent-2
runner-XXXXXXXX-project-N-concurrent-3
ジョブ開始時の runner-XXXXXXXX-project-N-concurrent-0 via ...
のようなメッセージでどの Runner で実行されているかわかります。
キャッシュはこのそれぞれの Runner ごとに保持されます。そのため、↑のパイプラインだと次のように Runner とジョブが対応して実行されるので、
runner-XXXXXXXX-project-N-concurrent-0
: 1A -> 2A
runner-XXXXXXXX-project-N-concurrent-1
: 1B
1A と 2A ならキャッシュが共有されますが、1B とは共有されません。
.gitlab-ci.yml
上で 1A と 1B の位置を入れ替えたり、あるいは同じ Runner で実行されている別のジョブがあってたまたま 1B と 2A が同じ runner-XXXXXXXX-project-N-concurrent-?
で実行されれば共有されることもあります。
Docker executor のキャッシュの保存先
ジョブが実行されるコンテナは --cache-dir
で指定されたコンテナ内のパスにキャッシュのアーカイブを保存します。これはデフォルトでは /cache
です。
ジョブの実行時、--docker-volumes
で指定されたディレクトリがジョブを実行するコンテナにマウントされます。これは <host-path>:<path>
の形式と <path>
の形式で指定することができて、<host-path>:<path>
の形式ならホストのディレクトリがコンテナに bind マウントされます。
<path>
の形式の場合、--docker-cache-dir
が指定されていれば、そのディレクトリの中の runner-<short-token>-project-<id>-concurrent-<job-id>/<unique-id>
のようなサブディレクトリをコンテナにマウントします。--docker-cache-dir
が指定されていなければ runner-<short-token>-project-<id>-concurrent-<job-id>-cache-<unique-id>
のような名前のデータボリュームコンテナを作成して、そのボリュームをコンテナにマウントします。ただし --docker-disable-cache
が指定されていると --docker-cache-dir
が指定されていてもデータボリュームコンテナが作成され、かつ、そのデータボリュームコンテナはジョブの終了時に自動で削除されます。
<unique-id>
の部分はコンテナのパス(<path>
)に基づくハッシュ値です。
要約すると・・・
- ジョブのコンテナは
--cache-dir
のパスにアーカイブを保存する
--docker-volumes
が・・
<host-path>:<path>
の形式なら・・
- ホストのディレクトリをジョブのコンテナにマウントする
<path>
の形式なら・・
--docker-disable-cache
が指定されていれは・・
- データボリュームコンテナを作成してボリュームをジョブのコンテナにマウントする
- ジョブの終了時に自動で削除される
--docker-cache-dir
が指定されていれば・・
- そのディレクトリのサブディレクトリをジョブのコンテナにマウントする
--docker-cache-dir
が未指定なら
- データボリュームコンテナを作成してボリュームをジョブのコンテナにマウントする
- デフォルトは
/cache
デフォルトの動きは次のようになります。
- ジョブのコンテナは
/cache
にアーカイブを保存する
- ジョブの開始時にデータボリュームコンテナを作成して
/cache
にマウントする
Docker executor でジョブ間でキャッシュを共有
--docker-volumes
でホストのディレクトリを /cache
にマウントすれば runner-<short-token>-project-<id>-concurrent-<job-id>/<unique-id>
のようなサブディレクトリは作成されないため(サブディレクトリが作成されるのは <path>
の形式のときだけだから)、前述のような異なる Runner の concurrent になってもジョブ間でキャッシュが共有されるようになります。
gitlab-runner register \
--non-interactive \
--url "$url" \
--registration-token "$token" \
--name 'ore-no-runner' \
--executor 'docker' \
--run-untagged \
--tag-list 'ore' \
--docker-image 'alpine:latest' \
--docker-volumes '/srv/gitlab-runner/cache:/cache'
ただ、並列に実行される複数のジョブが同じアーカイブを読み書きしようとするだろうので、壊れたアーカイブが作成されたりしてしまいそうです、たぶんこれはやめておいたほうが良いでしょう。
Docker executor のビルドディレクトリ
--docker-disable-cache
や --docker-cache-dir
の指定はソースがチェックアウトされるディレクトリにも関係します。これらの指定に基づいてソースファイルをチェックアウトするためのデータボリュームやホストのディレクトリのマウントが行われます(cache
という名のついたパラメータなのに build
ディレクトリに関係するのわかりにくい・・・)。
--build-dir
でコンテナのどこにマウントするかを指定できます(デフォルトは /builds
)。このディレクトリに /builds/<namespace>/<project-name>
のようにサブディレクトリを掘ってチェックアウトされます。
つまり、<--docker-cache-dir>/runner-<short-token>-project-<id>-concurrent-<job-id>/<unique-id>/
のようなホストのディレクトリ、または、runner-<short-token>-project-<id>-concurrent-<job-id>-cache-<unique-id>
のようなデータボリュームが、ジョブのコンテナの /builds/<namespace>/<project-name>
にマウントされます。
なお、/cache
とは異なり、--docker-volumes
でホストのディレクトリを /builds
にマウントしている場合は /builds/<short-token>/<concurrent-id>/<namespace>/<project-name>
のようなサブディレクトリが掘られてそこにチェックアウトされるようになります。
分散キャッシュ
--docker-volumes
で変なことをやらなくても、分散キャッシュを使えばジョブ間でキャッシュを共有させられます。
S3 や Google Cloud Storage を使えば楽そうですけど minio でホストさせても OK です。
docker run --detach \
--name minio \
--hostname minio \
--restart always \
--publish 9005:9000 \
--volume /srv/minio/root/.minio:/root/.minio \
--volume /srv/minio/export:/export \
minio/minio:latest server /export
sudo mkdir /srv/minio/export/runner
hostname -i
docker exec minio cat /export/.minio.sys/config/config.json | grep Key
gitlab-runner register \
--non-interactive \
--url "$url" \
--registration-token "$token" \
--request-concurrency 4 \
--name 'ore-no-runner' \
--executor 'docker' \
--run-untagged \
--tag-list 'ore' \
--docker-image 'alpine:latest' \
--docker-cache-dir '/srv/gitlab-runner/cache' \
--cache-type 's3' \
--cache-s3-server-address '192.0.2.123:9005' \
--cache-s3-access-key 'abc...' \
--cache-s3-secret-key 'abc...' \
--cache-s3-bucket-name 'runner' \
--cache-s3-insecure true
Gitlab Runner も Docker で実行して minio と同じネットワークに入れれば --publish 9005:9000
などせずに --cache-s3-server-address 'minio:9000'
で大丈夫かと思いきやそんなことはありません(最初そうしようとしてあれー?と思いました)。
minio にはジョブとして実行されるコンテナの中からアクセスできる必要があり、ジョブのコンテナを minio と同じネットワークにはできないので、minio で --publish 9005:9000
のようにポートを晒して --cache-s3-server-address '192.0.2.123:9005'
のようにホストのアドレス・ポートを指定する必要があります。
参考