依存関係プロキシ
依存プロキシはDockerHubの公開レジストリイメージのプルスルーキャッシュです。このドキュメントでは、この機能がGitLabでどのように構築されているかを説明します。
コンテナレジストリ
コンテナレジストリの依存プロキシは、リモートコンテナレジストリの代用となります。この場合、リモートレジストリは公開 DockerHub レジストリです。
ユーザーから見れば、GitLab インスタンスは単なるコンテナレジストリに過ぎず、イメージのプルにはdocker login gitlab.com
docker login gitlab.com
を使うと、Dockerクライアントはリクエストを行うためにv2 APIを使います。
認証をサポートするために、1つのルートを含める必要があります:
docker pull
リクエストをサポートするには、さらに2つのルートを含める必要があります:
これらのルートはgitlab-org/gitlab/config/routes/group.rb
で定義されています。
最も単純な形では、依存プロキシは3つのリクエストを管理します:
- ログイン/JWTのリターン
- マニフェストのフェッチ
- ブロブのフェッチ
依存プロキシの一般的なリクエストシーケンスは次のようになります:
認証と作成者
Dockerクライアントがレジストリで認証するとき、レジストリはクライアントにJSON Web Token(JWT) を取得する場所と、それ以降のすべてのリクエストにそれを使用するように指示します。これにより、認証サービスをレジストリとは別のアプリケーションで行うことができます。たとえば GitLab コンテナレジストリは、Docker クライアントにhttps://gitlab.com/jwt/auth
からトークンを取得するように指示します。このエンドポイントはgitlab-org/gitlab
プロジェクトの一部で、Rails プロジェクトや Web サービスとしても知られています。
ユーザーがDockerクライアントで依存プロキシにサインインしようとするとき、JWTをどこで取得するかを伝えなければなりません。コンテナレジストリで使っているのと同じエンドポイントを使うことができます:https://gitlab.com/jwt/auth
。しかし私たちのケースでは、Dockerクライアントにパラメータでservice=dependency_proxy
を指定するように指示し、トークンを生成するために別の基礎となるサービスを使用できるようにしています。
このシーケンス図は、依存プロキシにログインするためのリクエストフローを示しています。
依存プロキシは、UI (ApplicationController
) や API (ApiGuard
) が管理する認証とは別に、独自の認証サービスを使います。サービスがJWTを作成すると、DependencyProxy::ApplicationController
は残りのリクエストの認証と認可を管理します。GitLab::Auth::Result
を使ってユーザーを管理し、GitHttpClientController
の Git クライアントリクエストで実装されている認証に似ています。
キャッシュ
ブロブはキャッシュされたアーティファクトで、ロジックはありません。ダイジェストでキャッシュします。新しいblobのリクエストを受け取ると、リクエストされたダイジェストを持つblobがあるかどうかをチェックし、それを返します。そうでない場合は、外部レジストリから取得してキャッシュします。
マニフェストは、DockerHubのレート制限のせいもあり、より複雑です。マニフェストは基本的にイメージを作成するためのレシピです。マニフェストには、特定のイメージを作成するためのblobのリストがあります。そのため、イメージのalpine:latest
作成に必要なblobを指定するマニフェストが関連付けられて alpine:latest
います。興味深いのは、マニフェストはalpine:latest
時間の経過とともに変更される可能性が alpine:latest
あるということです。alpine:latest
その代わりに、マニフェストのダイジェスト(ETag)をチェックする必要があります。マニフェストのリクエストにはダイジェストが含まれていないことが多いので、これは興味深いことです。では、私たちがキャッシュしたマニフェストがまだ最新か alpine:latest
どうかを知るにはどうすればよいのでしょうか?DockerHubはレート制限にカウントされない無料のHEADリクエストを許可しています。HEADリクエストはマニフェストのダイジェストを返すので、今あるものが古いかどうかを知ることができます。
この知識をもとに、マニフェストリクエストを管理するための以下のロジックを構築しました:
ファイル処理の Workhorse
ファイルのアップロードとキャッシュの管理はWorkhorseで行われます。依存プロキシに追加されたPOST
ルート はこのためです。
send_dependency
メソッドは、外部レジストリから以前に取得した JWT を含むリクエストを Workhorse に行います。Workhorse はそのトークンを使用して、ユーザーが最初にリクエストしたマニフェストまたは blob をリクエストできます。Workhorse コードはworkhorse/internal/dependencyproxy/dependencyproxy.go
にあります。
すべてをまとめると、画像ファイルをリクエストするシーケンスは次のようになります:
クリーンアップポリシー
依存プロキシのクリーンアップポリシーは、time-to-live ポリシーとして動作します。ユーザーによって、未読のファイルがキャッシュされたままになる日数を設定できます。BLOBを所属するイメージに関連付ける方法はないため(これを行うには、コンテナレジストリの人々が構築したメタデータデータベースを構築する必要があります)、「このBLOBが90日間プルされていない場合は削除する」といったルールを設定できます。これは、継続的にプルされているファイルはキャッシュから削除されないことを意味しますが、例えば、alpine:latest
が変更され、基礎となるブロブの1つが使用されなくなった場合、プルされなくなったため、最終的にクリーンアップされます。read_at
属性を使用して、指定されたdependency_proxy_blob
またはdependency_proxy_manifest
が最後にプルされた時間を追跡します。
これらはDependencyProxy::CleanupDependencyProxyWorkerというcronワーカーを使って動作します。容量はアプリケーション設定で設定します。
歴史的な参照リンク
- 非公開グループの依存プロキシ - 初期認証の実装
- マニフェスト・キャッシング- マニフェスト・キャッシングの初期実装
- blob 用 Workhorse- Workhorse の初期実装
- マニフェスト用Workhorse- マニフェストキャッシュロジックのWorkhorseへの移行
- デプロイトークンのサポート- 認可の大幅な更新
- SSOのサポート - ポリシーのチェック方法を変更