はじめに
Self-Managed GitLabを運用していたところ、サーバのディスク容量がほぼ枯渇状態になっていることに気づきました。
調査を進めた結果、原因は特定プロジェクトのCI/CD Artifactsが肥大化していたことでした。
さらに深掘りすると、その裏には [Keep artifacts from most recent successful jobs] という設定の仕様が関係していました。
この設定が有効な場合、expire_in や expire_at を設定して期限切れになっていても、
Artifactがlockされているため通常のexpiration cleanupの対象になりません。
そして、このKeep対象は ref(ブランチ・タグなど)単位 で管理されるため、Artifactを生成するジョブを含むrefが多いプロジェクトでは、保持されるArtifactsも増え、想定以上にストレージを消費する可能性があります。
同じ事象に遭遇した方の参考になればと思い、記録として残します。
発生環境は次の通りです。
発生環境
- 形態: Self-Managed GitLab (GitLab Community Edition / Linux Package)
- GitLabバージョン: 18.1.0
- 割り当てディスク容量: 300GB
注意
以下はGitLab 18.1.0環境で確認した結果です。
GitLabではArtifacts削除処理の実装がバージョンによって変更される可能性があるため、
ここで説明するWorkerやServiceの構成はGitLab 18.1.0に限定した情報として扱ってください。
詳細
現象:GitLabサーバのディスク枯渇
$ df -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 1.6G 20M 1.6G 2% /run
/dev/nvme0n1p1 296G 275G 8.4G 98% /
tmpfs 7.8G 3.1M 7.8G 1% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 1.6G 4.0K 1.6G 1% /run/user/1000
原因調査:特定プロジェクトのArtifactsが肥大化
sudo du などでディレクトリごとの使用量を調査したところ、特定プロジェクトのArtifactsが原因であることが判明しました。
このプロジェクトのArtifacts合計サイズは100GBを超えており、サーバに割り当てられたディスク容量の1/3近くを占めていました。
expire_in の設定は入っていたため、当初は「期限切れのはずなのに、なぜ削除されないのか?」という点が疑問でした。
Keep artifacts from most recent successful jobsとは
調査の結果、プロジェクトの Settings > CI/CD > Artifacts にある [Keep artifacts from most recent successful jobs] という設定が有効になっていることが分かりました。
この設定が有効な場合、次のような挙動になります。
- 各refについて、最新の成功パイプラインに関連するArtifactsが保持され、expire_in による通常の期限切れ削除の対象になりません。
- ref: GitLab/Gitでブランチやタグなど、特定のコミットを指し示す名前。
- そのため、ブランチやタグの数が多いプロジェクトでは、「keepされるArtifactsの数」が増えていきます。
注意
この設定により、最新の成功パイプラインのArtifactsを、expire_in による通常の期限切れ削除から保護できます。
今回のプロジェクトで肥大化した理由
対象プロジェクトには大量のブランチ・タグが存在していました。
- ブランチ・タグ数: ブランチのみでも約120件
- Artifact 1件あたりのサイズ: 大きいもので約250MB程度
- Artifacts合計サイズ: 100GB超
Artifactを生成するジョブを含むrefが増えることで、Keep対象となるArtifactsも増加し、
結果としてArtifacts合計容量が想定以上に膨らんでいました。
対処方法
実環境で不要なArtifactsを実際に削除するまでの手順を確認しました。
確認時のGitLabバージョンは 18.1.0 です。
1. Keep artifacts設定を無効化
対象プロジェクトで以下の設定を確認します。
Settings > CI/CD > Artifacts > Keep artifacts from most recent successful jobs
不要であれば、この設定を無効化します。
注意
実環境で確認した限り、設定変更後も、既にKeep対象になっていた既存Artifactsのlock状態は自動的には解除されませんでした。
2. 削除対象Artifacts数を確認
GitLab Rails Consoleを起動します。
gitlab-rails console
期限切れ(expire_at が過去)のArtifacts数を確認します。
Ci::JobArtifact.where("expire_at < ?", Time.current).count
このうち、期限切れになっているものの、artifacts_locked のため通常のCleanup対象にならないArtifacts数を確認します。
Ci::JobArtifact.where(locked: :artifacts_locked)
.where("expire_at < ?", Time.current)
.count
3. 対象Artifactsのlock状態を解除
期限切れかつ artifacts_locked になっているArtifactsをunlockします。
Ci::JobArtifact.where(locked: :artifacts_locked)
.where("expire_at < ?", Time.current)
.find_each(&:artifact_unlocked!)
解除後、対象件数が0件になっていることを確認します。
Ci::JobArtifact.where(locked: :artifacts_locked)
.where("expire_at < ?", Time.current)
.count
# => 0
4. Cleanup Workerを実行
GitLabバージョンに関する注意
以下の手順は GitLab 18.1.0 の実環境で確認したものです。
ExpireBuildArtifactsWorker などのArtifacts削除処理はGitLabのバージョンによって実装が変更される可能性があります。
実際、後のGitLabバージョンではArtifacts削除処理の仕組みが変更されています。そのため、以下のRails Console操作をGitLabの一般的な管理手順として扱わず、
GitLab 18.1.0における障害対応・検証事例として参照してください。
期限切れArtifactsの削除処理を手動実行します。
ExpireBuildArtifactsWorker.new.perform
戻り値だけでは削除件数を判断できない場合があるため、Sidekiqログを確認します。
tail -f /var/log/gitlab/sidekiq/current
以下のようなログが出力され、削除件数を確認できます。
"extra.expire_build_artifacts_worker.destroyed_job_artifacts_count": xxxx
5. 削除処理状態を確認
削除予約オブジェクトの件数を確認します。
Ci::DeletedObject.count
Ci::DeletedObject で削除待ちレコードの件数を確認できます。
実際にストレージから削除されたかどうかは、最終的にArtifactの使用量やdf -hなどで確認します。
6. ディスク使用量を確認
OS上でディスク使用量を確認します。
Before
$ df -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 1.6G 20M 1.6G 2% /run
/dev/nvme0n1p1 296G 275G 8.4G 98% /
tmpfs 7.8G 3.1M 7.8G 1% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 1.6G 4.0K 1.6G 1% /run/user/1000
After
$ df -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 1.6G 20M 1.6G 2% /run
/dev/nvme0n1p1 296G 188G 96G 67% /
tmpfs 7.8G 3.1M 7.8G 1% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 1.6G 4.0K 1.6G 1% /run/user/1000
約87GB削減されました。
動作概要:GitLab 18.1.0におけるArtifacts削除条件
なぜ「lockを解除しないと削除されないのか」を、内部処理から確認しました。
期限切れArtifactsの削除は ExpireBuildArtifactsWorker が担っており、内部で以下のServiceを呼び出しています。
Ci::JobArtifacts::DestroyAllExpiredService
このService内では、次の条件で削除対象のArtifactsを取得しています。
Ci::JobArtifact.expired_before(@start_at)
.non_trace
.artifact_unlocked
つまり、削除対象になる条件は次の3つすべてを満たすことです。
-
expire_atが現在時刻より過去である - trace(ジョブログ)以外である
-
lockedがunlockedである
artifacts_locked のままのArtifactsは、expire_at が過去であっても削除対象から外れる、という仕様でした。
今回の検証で確認できたこと
-
expire_atだけでは削除されない
expire_atが過去になっていても、lockedがartifacts_lockedの場合は削除対象になりません。 -
Keep artifacts設定はArtifactsをlockする
[Keep artifacts from most recent successful jobs] が有効な場合、対象パイプラインのArtifactはlocked = artifacts_lockedになります。 -
Keep設定をOFFにしても既存Artifactsはunlockされない
設定変更後も、過去にKeep対象になったArtifactsはlocked = artifacts_lockedのまま残ります。そのため、Cleanup対象にはなりません。 -
unlock後はCleanup対象になる
artifact.artifact_unlocked!を実行後、ExpireBuildArtifactsWorkerを実行すると、Ci::DeletedObjectが作成され、削除処理へ進むことを確認しました。
まとめ
GitLabの [Keep artifacts from most recent successful jobs] は、
expire_in/expire_at によって期限切れになったArtifactsでも、Keep対象としてlockされている間は通常の削除処理の対象になりません。
また、各refの最新の成功パイプラインに関連するArtifactsがKeep対象となるため、
ブランチ・タグ数が多いプロジェクトではディスク枯渇の原因になり得ます。
さらに、GitLab 18.1.0で確認した限り、
この設定によって一度lockされたArtifactsは、設定をOFFにしても直ちにunlockされるわけではありません。
不要なArtifactsを今回の実環境で早急に削除するためには、以下の対応が必要でした。
- [Keep artifacts from most recent successful jobs] を無効化する
- 既存の
artifacts_lockedな期限切れArtifactsをunlockする -
ExpireBuildArtifactsWorkerを実行する - Sidekiqログで削除件数を確認する
- ディスク使用量を確認する
同様のディスク枯渇に遭遇した場合は、expire_in の設定だけでなく、この設定と既存Artifactsのlock状態も併せて確認してみてください。