LabHub
ブログ

ブログ

Docker Hub の前にプロキシキャッシュを置く三つの方法 — registry:2、Nexus、ECR pull-through cache

한국어English日本語中文

問題は速度ではなく制限だ

CI が一回回るたびに FROM python:3.12-slim が Docker Hub から降りてくる。ノードが十台なら十回、パイプラインが一日に百回回れば百回。Docker Hub はそれを数えている。

Docker の文書が示す制限はこうだ。ログインしていないクライアントは IPv4 アドレス一つ(または IPv6 /64)あたり 6 時間に 100 回、個人アカウントでログインすれば 200 回、Pro・Team・Business は無制限。会社のオフィスやクラスタのように複数の機械が NAT の後ろから一つのアドレスで出て行くなら、その 100 回を全員で分け合う。

数字を文書だけで信じず、ヘッダを読んでみた。Docker が制限確認用に置いている ratelimitpreview/test リポジトリに匿名トークンでマニフェストを要求すると、応答ヘッダに残り回数が載る。

TOK=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq -r .token)
curl -s -I -H "Authorization: Bearer $TOK" \
  https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest | grep -i ratelimit
ratelimit-limit: 100;w=3600
ratelimit-remaining: 85;w=3600

何度か実験する間に remaining は 91 → 88 → 85 と減った。ヘッダの窓(w=3600)は秒単位で 3,600 と書いてあるが、文書は 6 時間と言っている。どちらが正しいにせよ、この確認要求そのものも一回として数えられることは確かだ。

プロキシキャッシュはこの数字を守る装置だ。クラスタのすべてのノードが Docker Hub ではなく自分のキャッシュに聞けば、同じイメージは上流から 一度だけ 降りてくる。

最小の実験:registry:2 を pull-through キャッシュに

Docker が配布する registry:2 イメージは、環境変数一つで読み取り専用プロキシになる。

docker run -d --name regmirror -p 5001:5000 \
  -v $PWD/data:/var/lib/registry \
  -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \
  registry:2

この状態で localhost:5001/library/nginx:1.27-alpine を引くと、キャッシュは無いレイヤを Docker Hub から受け取ってディスクに置き、クライアントに渡す。ローカルのデーモンに nginx のレイヤが一つも無いことを確認してから(docker images | grep nginx が 0 行)、同じイメージを三回引き、毎回ローカルの複製を消した。

試行所要時間キャッシュディレクトリ備考
1 回目(キャッシュ経由)7.81 秒22M(ブロブファイル 15 個)キャッシュが空で上流から満たす
2 回目(キャッシュ経由)3.95 秒22M(変化なし)ディスクから直接
3 回目(キャッシュ経由)4.01 秒22M(変化なし)ディスクから直接
Docker Hub 直接2.86 秒比較用

docker images が報告するイメージの大きさは 76.8MB だが、キャッシュは 22M だ。キャッシュが保管するのは圧縮されたレイヤのブロブで、デーモンが報告するのは展開後の大きさだからだ。

実験が語ること

最初の pull は遅くなる。 上流から受け取り、ディスクに書き、また渡す二段階なので、直接引くこと(2.86 秒)より三倍近くかかった。

二回目以降も直接より速くはなかった。 家庭の回線で Docker Hub を直接引くのが 2.86 秒、キャッシュが満たされた後にプロキシを通すのが約 4 秒。プロキシコンテナが同じノートパソコンで動いていて、ホップが一つ多いことになる。回線が速くクライアントが一台だけなら、プロキシキャッシュは 速度のための装置ではない。

得られるのは上流への要求数だ。 2・3 回目の pull でキャッシュディレクトリは 1 バイトも増えなかった。レイヤは上流から受け取り直していないということだ。ノードが二十台のクラスタなら、Docker Hub が数える回数は二十ではなく一に近づく。これがプロキシキャッシュの価値だ。

正直に書いておくことが一つある。registry:2 は上流に出した要求を info ログに残さないので、pull ごとにマニフェスト確認を何回するかは数えられなかった。ディスクが増えなかったという事実から、レイヤの再ダウンロードが無かったことまでしか言えない。

Nexus:プロキシ、ホステッド、そしてグループ

registry:2 のプロキシは上流を一つしか見ない。会社ではふつう Docker Hub も見て、自分で作ったイメージも上げて、両方を一つのアドレスで使いたい。Sonatype Nexus Repository の Docker リポジトリは三種類ある。

クライアント側の設定は、Repository Connector のポート一つ(例:nexus.example.com:8082)をデーモンの mirror に登録すれば終わりだ。その後 docker pull nginx はグループ → メンバーの順に探し、hosted に無ければ proxy が Docker Hub から取ってきてキャッシュする。

Nexus が ECR を上流に 置くときは認証が特殊だ。ECR のトークンは 12 時間ものだが、Nexus の文書によれば最初の pull でそのトークンをキャッシュし、六時間ごとに更新する。AWS の資格情報を Nexus に一度渡せば、あとは Nexus が回す。

ECR pull-through cache:AWS の中でキャッシュする

クラスタが AWS にあるならキャッシュを別に運用する必要はなく、ECR がその仕事をする。ルール(pull through cache rule)に上流と接頭辞を書いておけば、<アカウント>.dkr.ecr.<リージョン>.amazonaws.com/<接頭辞>/library/nginx:1.27-alpine を引くときに ECR が上流から受け取り、自分のアカウントの ECR リポジトリに保管する。

文書で確認したルールはこうだ。

どれを選ぶか

registry:2 proxyNexus(proxy + group)ECR pull-through cache
運用負担コンテナ一つサーバー一台とディスク管理なし(マネージド)
上流一つ複数をグループにルールごとに一つ
自分のイメージの push不可(読み取り専用)hosted へ通常の ECR リポジトリへ
更新確認pull ごとに上流のマニフェストを確認設定可能タグごとに 24 時間に一度
合う場所ホームラボ、ノード数台社内、言語パッケージまで一か所にAWS 内の EKS・ECS

私たちのパイプラインでは

LabHub のビルドは Jenkins が kaniko でイメージを作って私設 Harbor に上げ、ArgoCD がそのタグを配備する。ベースイメージは Dockerfile にダイジェストまで固定してある。

FROM python:3.12-slim@sha256:78387bc3…

プロキシキャッシュを前に置いてもこの行はそのままだ。ダイジェストは内容のハッシュなので、どのキャッシュを経由しても同じバイトでなければ通らない。キャッシュが制限を守り、ダイジェストが内容を守る。二つの装置は別の仕事をしている。

一行で

プロキシキャッシュは pull を速くする装置ではなく、上流が数える回数を一つに減らす 装置だ。ノードが数台なら registry:2 で十分、リポジトリが複数なら Nexus のグループで一つのアドレスを作り、AWS の中なら ECR に代わりにキャッシュさせればよい。

ログインするといいねできます

コメント

まだコメントはありません。

ログインするとコメントできます