GitLab CI/CD での依存関係のキャッシュ
GitLabCI/CDは、ジョブ実行の際に時間を節約できる、キャッシュの仕組みを提供しています。
キャッシュとは、前のジョブで利用したコンテンツを再利用することで、ジョブの実行時間を短縮することです。 ビルド時にインターネット経由で取得した他のライブラリに依存するソフトウェアを開発している場合などには特に有効です。
キャッシュを有効にすると、GitLab9.0以降ではデフォルトで、パイプラインとジョブ間のプロジェクトレベルでキャッシュが共有されます。 プロジェクト間では共有されません。
``キャッシュの
リファレンスを読み、.gitlab-ci.yml
でどのように定義されているかを確認してください。
Cache vs artifacts
注意 キャッシュやアーティファクトを使用してジョブに同じパスを保存する場合、キャッシュはアーティファクトの前に復元され、コンテンツ上書きの可能性があり、注意が必要です。
キャッシュは、プロジェクトのコンパイルに必要なランタイムの依存関係を保存するように設計されているので、ステージ間でのアーティファクトの受け渡しには使用しないでください。
-
cache
: プロジェクトの依存関係を保存するためキャッシュは、ダウンロードした依存関係を保存することで、後続のパイプラインでのジョブの実行を高速化するために使用され、インターネットから再度取得する必要がないようにします (npmパッケージやGoベンダーパッケージなど)。キャッシュはステージ間で中間的なビルド結果を渡すように設定できますが、これは代わりにアーティファクトで処理すべきです。
-
artifacts
: ステージ間で渡されるステージ結果に使用します。アーティファクトは、ジョブによって生成されたファイルで、保存およびアップロードされ、同じパイプラインの後のステージのジョブから取得して使用できます。 言い換えれば、ステージ1のジョブAでアーティファクトを作成し、ステージ1のジョブBでこのアーティファクトを使用できません。このデータは異なるパイプラインでは利用できませんが、UIからダウンロード可能になっています。
アーティファクト
という名前は、最終イメージのダウンロードなどジョブの外でしか使えないように聞こえますが、パイプライン内の後続のステージでも利用可能です。 したがって、必要なモジュールをすべてダウンロードしてアプリケーションを構築した場合、後続のステージで利用できるように、それらをアーティファクトとして宣言する必要があるかもしれません。 アーティファクトを長持ちさせないために有効期限を宣言したり、どのジョブがアーティファクトを取得するかを制御するために依存関係を使用したりするなどの最適化もあります。
Caches:
- グローバルに定義されていない場合、またはジョブごとに定義されていない場合(
キャッシュ:
を使用している場合)は無効になります。 - グローバルに有効になっていれば、
.gitlab-ci.yml
内のすべてのジョブで利用可能です。 - キャッシュが作成されたのと同じジョブで、後続のパイプラインで使用できます(グローバルに定義されていない場合)。
- Runnerがインストールされている場所に保存され、分散キャッシュが有効になっている場合はS3にアップロードされます。
- ジョブごとに定義されている場合は、以下のように使用されます。
- 後続のパイプラインの同じジョブ単位
- 同一の依存関係を持つ場合、同じパイプライン内の後続のジョブ単位
Artifacts:
- ジョブごとに定義されていない場合(
artifacts:
)は無効になります。 - グローバルではなく、ジョブごとにのみ有効にできます。
- パイプライン実行中に作成され、現在アクティブなパイプラインの後続のジョブで使用できます。
- 常にGitLab(コーディネーターとしての)にアップロードされています。
- ディスク使用量を制御するために有効期限を持つことができます(デフォルトでは30日)。
注: 注意: アーティファクトとキャッシュは、どちらもプロジェクトディレクトリからの相対パスを定義しており、プロジェクト外のファイルにリンクできません。
良いキャッシングの実践例
開発者(ジョブ内でキャッシュを消費する)の視点から見たキャッシュと、Runnerの視点から見たキャッシュがあります。 どのタイプのRunnerを使用しているかによって、キャッシュは異なる動作が可能となります。
開発者の視点では、キャッシュの可用性を最大限に確保するために、ジョブで キャッシュ
を宣言する際には以下のいずれかまたは複数を組み合わせて使用します。
- Runnerにタグを付け、キャッシュを共有しているジョブでタグを使用します。
- 特定のプロジェクトでのみ利用可能なSticky Runnerを使用します。
- ワークフローに合った
キー
を使用してください(例えば、ブランチごとに異なるキャッシュを使用するなど)。 そのために、CI/CDの定義済み変数を利用できます。
:ヒント: パイプラインに同じRunnerを使用することは、1つのステージやパイプラインでファイルをキャッシュし、このキャッシュを後続のステージやパイプラインに保証された方法で渡す最もシンプルで効率的な方法です。
Runnerのキャッシュが効果的に機能するためには、以下のうちのどれかが満たされていなければなりません。
- すべてのJobでシングル構成のRunnerを使用する。
- (GitLab.comの共有Runnerのように)S3バケットに保存されているような分散型キャッシュを使用した複数のRunner(オートスケールモードかどうかは不問)を使用する。
- キャッシュが保存される共通のネットワークマウントされたディレクトリ(NFSなどを使用)を共有する同じアーキテクチャの(オートスケールモードではない)複数のRunnerを使用する。
ヒント:cacheの可用性を読むと、その構造について詳細に学べ、キャッシュがどのように動作するかについてのより良いアイデアを得られるでしょう。
同じブランチ間でのキャッシュの共有
各ブランチのジョブが常に同じキャッシュを利用するように、${CI_COMMIT_REF_SLUG}をキーに
キャッシュを定義してください。
cache:
key: ${CI_COMMIT_REF_SLUG}
これはキャッシュを誤って上書きしてしまうことを防ぐための安全策のように考えられるかもしれませんが、マージリクエストの最初のパイプラインが遅くなることを意味し良くない実装となります。 次に新しいコミットがブランチにプッシュされたときには、キャッシュが再利用されます。
ジョブ単位およびブランチ単位のキャッシングを有効にしたい場合:
cache:
key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
ブランチごと、ステージごとのキャッシングを有効にしたい場合:
cache:
key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"
異なるブランチ間でのキャッシュの共有
キャッシングするファイルをすべてのブランチとすべてのジョブで共有する必要がある場合は、すべてのブランチで同じキーを使用できます。
cache:
key: one-key-to-rule-them-all
ブランチ間で同じキャッシュを共有するが、ジョブごとに分離したい場合:
cache:
key: ${CI_JOB_NAME}
特定のジョブでのキャッシュの無効化
キャッシュをグローバルに定義している場合、各ジョブが同じ定義を使用することを意味します。 しかし、ジョブごとにオーバーライドできます。完全に無効にしたい場合は以下のように空のハッシュを使用してください。
job:
cache: {}
グローバル設定を継承しながら、ジョブごとに特定の設定をオーバーライドする場合
アンカーを使用することで、グローバルキャッシュを上書きせずにキャッシュ設定をオーバーライドできます。 例えば、1つのジョブのポリシー
をオーバーライドしたい場合は以下のように記載します:
cache: &global_cache
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- public/
- vendor/
policy: pull-push
job:
cache:
# inherit all global cache settings
<<: *global_cache
# override the policy
policy: pull
詳細なチューニングについては、以下の記事もお読みください。キャッシュ: ポリシー
.
一般的な使用例
キャッシュの最も一般的な使用例は、依存関係や一般的に使用されるライブラリ(Node.jsパッケージ、PHPパッケージ、rubygems、Pythonライブラリなど)のような前後関係があるジョブ間で利用されるコンテンツを保存することで、それらをインターネットから再取得する必要がないようにしています。
NOTE:Note:より多くの例については、GitLab CI/CDtemplatesを参照してみてください。
Node.js の依存関係のキャッシュ
デフォルトでは、npmはキャッシュ
データをホームフォルダ~/.npm
に保存しますが、プロジェクトディレクトリ以外ではキャッシュできないので、代わりに./.npm
を使用するように npm に指示し、ブランチごとにキャッシュされます。
#
# https://gitlab.com/gitlab-org/gitlab/tree/master/lib/gitlab/ci/templates/Nodejs.gitlab-ci.yml
#
image: node:latest
# Cache modules in between jobs
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .npm/
before_script:
- npm ci --cache .npm --prefer-offline
test_async:
script:
- node ./specs/start.js ./specs/async.spec.js
PHP の依存関係をキャッシュする
プロジェクトでComposerを使用してPHPの依存関係をインストールしている場合、次の例では、すべてのジョブがキャッシュ
を継承するようグローバルにキャッシュ
を定義しています。PHPライブラリモジュールはvendor/
にインストールされ、ブランチごとにキャッシュされます。
#
# https://gitlab.com/gitlab-org/gitlab/tree/master/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
#
image: php:7.2
# Cache libraries in between jobs
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- vendor/
before_script:
# Install and run Composer
- curl --show-error --silent https://getcomposer.org/installer | php
- php composer.phar install
test:
script:
- vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never
Python の依存関係をキャッシュする
Pythonの依存関係をインストールするためにpipを使用している場合、以下の例ではすべてのジョブがキャッシュ
を継承するようグローバルにキャッシュ
を定義しています。Pythonのライブラリはvenv/
以下の仮想環境にインストールされ、pipのキャッシュは.cache/pip/
以下に定義されており、両方ともブランチごとにキャッシュされています。
#
# https://gitlab.com/gitlab-org/gitlab/tree/master/lib/gitlab/ci/templates/Python.gitlab-ci.yml
#
image: python:latest
# Change pip's cache directory to be inside the project directory since we can
# only cache local items.
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
# Pip's cache doesn't store the python packages
# https://pip.pypa.io/en/stable/reference/pip_install/#caching
#
# If you want to also cache the installed packages, you have to install
# them in a virtualenv and cache it as well.
cache:
paths:
- .cache/pip
- venv/
before_script:
- python -V # Print out python version for debugging
- pip install virtualenv
- virtualenv venv
- source venv/bin/activate
test:
script:
- python setup.py test
- pip install flake8
- flake8 .
Ruby の依存関係をキャッシュする
Bundlerを使用してgemの依存関係をインストールしている場合、以下の例では、すべてのジョブがそれを継承するようにグローバルにキャッシュ
を定義しています。 gemsはベンダー/ruby/
にインストールされ、ブランチごとにキャッシュされます。
#
# https://gitlab.com/gitlab-org/gitlab/tree/master/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml
#
image: ruby:2.6
# Cache gems in between builds
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- vendor/ruby
before_script:
- ruby -v # Print out ruby version for debugging
- bundle install -j $(nproc) --path vendor/ruby # Install dependencies into ./vendor/ruby
rspec:
script:
- rspec spec
Goの依存関係をキャッシュする
Goの依存関係をインストールするためにGoモジュールを使用している場合、次の例では、どのジョブでも拡張できるGoキャッシュ
テンプレートでキャッシュ
を定義しています。 Goモジュールは${GOPATH}/pkg/mod/
にインストールされ、すべてのGo
プロジェクトのためにキャッシュされています。
.go-cache:
variables:
GOPATH: $CI_PROJECT_DIR/.go
before_script:
- mkdir -p .go
cache:
paths:
- .go/pkg/mod/
test:
image: golang:1.13
extends: .go-cache
script:
- go test ./... -v -short
キャッシュの可用性
キャッシングは最適化されていますが、常に動作するとは限らないので、キャッシュされたファイルを必要とするジョブごとに再生成する準備をしておく必要があります。
ワークフローに応じて.gitlab-ci.yml
で適切にキャッシュ
を定義していると仮定すると、キャッシュの可用性は最終的にRunnerの設定方法(executorの種類やジョブ間でキャッシュを渡すために異なるRunnerを使用しているかどうか)に依存します。
キャッシュの格納場所
Runnerはキャッシュを保存しておく責任があるので、どこにキャッシュが保存されているかを知ることが重要です。.gitlab-ci.yml
のジョブで定義されたすべてのキャッシュパスは、1つのcache.zip
ファイルにアーカイブされ、Runnerの設定したキャッシュの場所に保存されます。 デフォルトでは、Runnerがインストールされたマシンのローカル領域に保存され、executorのタイプによって異なります。
GitLab Runnerのexecutor | キャッシュのデフォルトパス |
---|---|
Shell | ローカルでは、gitlab -runner のユーザーのホームディレクトリ以下に保存されます。
|
Docker | ローカルでは、/var/lib/docker/volumes<volume-id>//_data<user><project><cache-key>////cache.zip</cache-key></project></user></volume-id> というDockerボリュームの下に格納されています。
|
Docker machine (autoscale Runners) | Docker executorと同じ動作をします。 |
How archiving and extracting works
Runnerがインストールされているマシンを1台だけ使用し、プロジェクトのすべてのジョブを同じホスト上で実行しているという最もシンプルなシナリオで説明します。
2つの連続したステージに属する2つのジョブの例を見てみましょう。
stages:
- build
- test
before_script:
- echo "Hello"
job A:
stage: build
script:
- mkdir vendor/
- echo "build" > vendor/hello.txt
cache:
key: build-cache
paths:
- vendor/
after_script:
- echo "World"
job B:
stage: test
script:
- cat vendor/hello.txt
cache:
key: build-cache
中ではこんなことが起きています。
- パイプライン開始。
-
ジョブA
が実行されます。 -
before_script
が実行されます。 -
スクリプト
が実行されます。 -
after_script
が実行されます。 -
cache
が実行され、vendor/
ディレクトリがcache.zip
にzip圧縮されます。このファイルは、その後、Runnerの設定とcache:key
に基づいてディレクトリに保存され -
ジョブB
が実行されます。 - キャッシュが抽出されます(見つかった場合)。
-
before_script
が実行されます。 -
スクリプト
が実行されます。 - パイプライン完了
単一のマシンで単一のRunnerを使用すると、ジョブB
が ジョブA
とは異なるランナーで実行されたり、ステージ間のキャッシュの保証等の問題は発生しません。 ビルドがステージビルド
から同じRunner/マシンでテスト
に移行する場合にのみ機能します。そうでない場合はキャッシュが利用できない可能性があります。
キャッシングのプロセスで、考慮すべき点もいくつかあります。
- 別のキャッシュ設定を持つ他のジョブが同じzipファイルにキャッシュを保存していた場合、上書きされます。 S3ベースの共有キャッシュを使用している場合、ファイルはキャッシュキーに基づいたオブジェクトに追加してS3にアップロードされます。 そのため、異なるパスで同じキャッシュキーを持つ2つのジョブは、それらのキャッシュを上書きします。
-
cache.zip
からキャッシュを抽出する場合、zipファイル内のすべてのものがジョブの作業ディレクトリ(通常はPullされたリポジトリ)に抽出され、Runnerはジョブ A
のアーカイブがジョブ B
のアーカイブ内のものを上書きしても問題ありません。
このように動作する理由は、あるランナーのために作成されたキャッシュが異なるアーキテクチャで動作する別のランナーで使用された場合(例:キャッシュにバイナリファイルが含まれている場合)、有効にならないことが多いからです。 また、異なるステップは、異なるマシン上で動作するRunnerによって実行される可能性があるため安全を期してこのようになっています。
キャッシュの不一致
次の表では、不一致に陥る可能性のあるいくつかの理由と、それを修正する方法についてのいくつかのアイデアを案内しています。
キャッシュの不一致の理由 | 修正方法 |
---|---|
1つのプロジェクトに接続された複数のスタンドアロンRunner(オートスケールモードではない)を共有キャッシュなしで使用します。 | プロジェクトに1つのRunnerのみを使用するか、分散キャッシュを有効にして複数のランナーを使用します。 |
分散キャッシュを有効にせずにオートスケールモードでRunnerを使用する場合 | 分散キャッシュを使用するようにオートスケールRunnerを設定します。 |
Runnerがインストールされているマシンのディスク容量が少ないか、分散キャッシュを設定している場合、キャッシュが保存されているS3バケットに十分な容量がない。 | 新しいキャッシュを保存できるようにスペースを空けておきましょう。 現状では自動化できる方法はありません。 |
異なるパスをキャッシュするジョブでは、同じキー を使用します。
| キャッシュアーカイブが別の場所に保存され、間違ったキャッシュを上書きしないように、異なるキャッシュキーを使用します。 |
いくつかの例をみてみましょう。
使用例
プロジェクトに割り当てられたRunnerは1台だけで、キャッシュはデフォルトでRunnerのマシンに保存されているとします。2つのジョブAとBが同じキャッシュキーを持っていて、異なるパスをキャッシュしている場合、パス
が一致しなくても、キャッシュBがキャッシュAを上書きします。
パイプラインが2回目に実行されたときに、ジョブA
とジョブB
がそのキャッシュを再利用するようにしたい。
stages:
- build
- test
job A:
stage: build
script: make build
cache:
key: same-key
paths:
- public/
job B:
stage: test
script: make test
cache:
key: same-key
paths:
- vendor/
-
ジョブA
が実行されます。 -
public/
はcache.zipとしてキャッシュされます。 -
ジョブB
が実行されます。 - 前のキャッシュがあれば、それを解凍します。
-
vendor/
はcache.zipとしてキャッシュされ、前のものを上書きします。 - 次回の
ジョブA
の実行時には、異なるジョブB
のキャッシュを使用するため、有効ではありません。
それを修正するには、ジョブごとに異なるキーを
使用します。
別のケースでは、プロジェクトに複数のRunnerが割り当てられているが、分散キャッシュが有効になっていないとします。 2回目のパイプライン実行時に、ジョブA
とジョブB
にキャッシュを再利用させたいとします(このケースでは、キャッシュは異なるものになります)。
stages:
- build
- test
job A:
stage: build
script: build
cache:
key: keyA
paths:
- vendor/
job B:
stage: test
script: test
cache:
key: keyB
paths:
- vendor/
その場合、キー
が異なっていても(上書きの恐れがない)、ジョブが後続のパイプラインでジョブが異なるRunner上で実行された場合、各ステージの前にキャッシュされたファイルが「クリアされた状態」になるでしょう。
キャッシュのクリア
GitLab Runnersはキャッシュを使用して、既存のデータを再利用してジョブの実行を高速化します。 しかし、これは時に一貫性のない動作を引き起こす可能性があります。
キャッシュの新しいコピーから始めるには、2つの方法があります。
cache:key
を変更してキャッシュをクリアする
.gitlab-ci.yml
に新しいcache: key
を設定するだけです。 次のパイプラインの実行時には、キャッシュは別の場所に保存されます。
キャッシュを手動でクリアする
GitLab 10.4 で導入されました。
.gitlab-ci.yml
の編集を避けたい場合は、GitLabのUIから簡単にキャッシュをクリアできます。
- プロジェクトのCI/CD > パイプラインページに移動します。
- Runnerのキャッシュを削除ボタンをクリックして、キャッシュを削除します。
- 次のプッシュで、CI/CDジョブは新しいキャッシュを使用します。
裏では、これはデータベース内のカウンタを増加させることで動作しています。そのカウンタの値は
-1
、-2
など、整数にしてください。プッシュ後、新しいキーが生成され、古いキャッシュはもう有効ではなくなります。