Skip to content

필사 모드: レジストリを増やしたのに可用性は変わらない — zotのスケールアウトが実際に売っているもの

日本語
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

はじめに — インスタンスを三台に増やした日の錯覚

社内レジストリひとつにすべてのデプロイがぶら下がっている構造が不安で、インスタンスを三台に増やしたとしましょう。前段にロードバランサを置き、ヘルスチェックを付け、ダッシュボードに緑のランプが三つ点きます。

ところが一台を落とした瞬間、特定のリポジトリに対するdocker pullが全部失敗します。残りの二台はぴんぴんしているのにです。

これはバグではなく設計です。皆さんがオンにしたのは高可用性ではなくシャーディングでした。そしてこの区別はレジストリを選ぶとき性能の数値よりはるかに重要なのに、ほとんどのドキュメントでは「scale out」という一語にまとめられています。

この記事はzotの公式ドキュメントを根拠にその区別を押さえます。zotを選べとか選ぶなという話ではなく、ドキュメントで何を確認してから進むべきかについての話です。

レジストリの本質は保存ではなく名前付けだ

レジストリを「イメージのファイルサーバ」として理解すると拡張設計を読み違えます。レジストリが実際に管理しているのは二種類の名前です。

第一はダイジェストです。sha256:abc...の形で、ブロブ内容のハッシュです。内容が同じなら名前が同じで、名前が同じなら内容が同じです。ここでは衝突が無くキャッシュ無効化も必要ありません。

第二はタグです。myapp:latestのようなもので、人が付け、いつでも別のダイジェストを指すように変わります。ここが状態であり、分散システムで難しい部分は全部ここにあります。

この構造を理解すると拡張設計の制約が自然に見えてきます。ブロブはどこに何個複製しておいても構いません。しかしmyapp:latestが今何を指しているかについては、インスタンスたちが同じ答えを返さなければなりません。だからレジストリのクラスタリングは結局タグ更新をどう直列化するかの問題に収束します。

zotの選択 — ディスクがそのままOCIイメージレイアウト

zotの設計前提はドキュメントの最初の行に出ています。ディスクにはOCI Image Layout Specificationをそのまま使い、ネットワークにはOCI Distribution Specificationをそのまま使います。ドキュメントの表現では「Distribution Specificationに準拠したどんなクライアントでもzotレジストリから読み書きできる」です。

実務的にこれがなぜ重要かというと、ディスクのディレクトリをそのままコピーすれば、それが有効なイメージ保管庫だという意味だからです。マイグレーションとバックアップの性格がまったく変わります。独自のメタデータデータベースにイメージ構造を収めているレジストリなら、そのデータベースも一緒に移さなければならず、バージョンが違えば移せません。

ストレージバックエンドはローカルファイルシステム(NFSやFUSEでマウントしたリモートファイルシステムを含む)、AWS S3、GCS、Azure Blob Storageに対応します。ドキュメントに明記された例外がひとつあり、パス区切りの問題でS3がWindowsでは対応されないという点です。

スケールアウトはシャーディングでありHAではない

ここからが本題です。zotのscale-outドキュメントをそのまま読むとこうです。

リクエストが入るとインスタンスはリポジトリのパスをハッシュしてハッシュテーブルを引き、そのリポジトリを担当するインスタンスを割り出します。ハッシュ関数は衝突と原像攻撃への耐性を理由にSipHashを使います。自分が担当でなければ担当インスタンスへリクエストを転送し、自分はプロキシの役割で応答を返します。

設定はこんな形です。

"cluster": {
  "members": [
    "zot-server1:9000",
    "zot-server2:9000",
    "zot-server3:9000"
  ],
  "hashKey": "loremipsumdolors",
  "tls": {
    "cacert": "test/data/ca.crt"
  }
}

membersが静的なリストであることに注目してください。合意プロトコルも無く、リーダー選出も無く、メンバーシップのゴシップも無いのです。すべてのインスタンスが同じリストと同じhashKeyを持っているので、同じ計算をして同じ答えに到達します。これがこの設計の優雅なところです。タグ更新の直列化問題をリポジトリ単位の所有権に置き換えて、合意を丸ごと無くしました。

代償はドキュメントにそのまま書かれています。この構成は自己修復せず、インスタンスひとつが死ぬとそのインスタンスにマッピングされたリポジトリはクラスタを再起動するまで影響を受けます。原文は高可用性と対比しながら「インスタンスやストレージがオフラインになるとサービスの可用性に影響する」と書きます。

つまり三台は処理量を三つに分けたものであって、障害を三倍耐えるものではありません。

二つのモードがそれぞれ諦めるもの

zotのドキュメントはスケールアウトを二つの形に分けます。

コンピュート専用の拡張は、すべてのインスタンスがS3互換ストレージひとつと分散キャッシュ(RedisまたはDynamoDB)を共有します。ローカルキャッシュは使いません。ストレージが共有されるのでインスタンスは状態を持たない計算ノードに近くなります。

コンピュート兼ストレージの拡張はインスタンスごとにローカルストレージを持ちます。その代わりドキュメントはこの形ではUIが対応されないと明記します。

そして二つの形に共通して、スケールアウト構成ではCVEスキャンが無効化され、信頼拡張(trust extension)は共有ディレクトリを要求するため対応されません。

この一覧がなぜ重要かというと、組織が自前のレジストリを立てる理由はたいてい「脆弱性スキャンの結果をレジストリでそのまま見るため」だからです。拡張をオンにした瞬間その機能が切れるなら、スキャンはCIパイプライン側へ移さなければなりません。拡張を決める前に知っておくべき事実であり、障害を経験してから知ると困る事実です。

boltdbがインスタンス数を事実上ひとつに縛る

重複排除をオンにすると、zotは同じ内容のブロブをディスクに一部だけ置き、複数のマニフェストが参照するようにします。ローカルファイルシステムではハードリンクで実装します。起動時に設定された状態を強制し、オンなら既存のブロブを重複排除し、オフならクラウドストレージのブロブを元の状態に戻します。

この重複排除は「どのダイジェストがどこに実体としてあるか」を覚えるメタデータを必要とし、それを収めるのがキャッシュドライバです。

  • boltdb — ローカルストレージの既定値。zotのルートディレクトリにファイルとして埋め込まれます。ドキュメントに書き込みの同時アクセスを提供しないと明記されており、したがって複数のzotインスタンスが共有できません。
  • DynamoDB — リモートストレージ用。AWSの資格情報とIAM権限が必要です。
  • Redis — リモートストレージ用の代替。単一インスタンスとクラスタ構成に対応し、キーのプレフィックスを設定できます。

ここが静かな落とし穴です。既定値のまま二台を立てて同じNFSボリュームをつなぐと、それぞれが自分のboltdbを持ったまま同じディレクトリを触ることになります。ドキュメントが禁じた組み合わせなのに、設定ファイルだけ見ても分かりません。共有ストレージで拡張するにはキャッシュドライバから変えなければならないことを覚えておいてください。

ミラーリングでダイジェスト固定が壊れる条件

zotは上流のレジストリをミラーリングでき、モードが二つあります。周期ポーリングはpollIntervalごとに上流を走査して条件に合うイメージをローカルへコピーし、オンデマンドはリクエストが入ったときに取得してキャッシュします。二つのモードは併用もできます。

{
  "urls": ["https://registry1:5000"],
  "onDemand": false,
  "pollInterval": "6h",
  "content": [{ "prefix": "/repo", "destination": "/local" }]
}

ドキュメントが押さえる制約が実務で特に痛いです。

第一に、Docker Hubはカタログの列挙に対応しないのでオンデマンドだけを使わなければなりません。周期ポーリングは「何があるか」の一覧を受け取らないと動かないのに、それができません。

第二に、DockerイメージをOCIフォーマットに変換すると、ダイジェストで固定したpullと署名検証が壊れます。 変換がマニフェストのバイト列を変えるのでハッシュが変わるからです。元のダイジェストを維持するにはpreserveDigestをオンにする必要があり、これはhttp.compatdocker2s2を入れることを要求します。

デプロイパイプラインがタグではなくダイジェストでイメージを固定しているなら(そうするのが正しいです)、ミラーを導入する瞬間にこの条件を確認しなければなりません。

「軽量な単一バイナリ」の実際のサイズ

zotの紹介文には「依存関係なく静的にビルドされた単一バイナリ」がいつも付いてきます。前半は事実です。別のランタイムやデータベースプロセスが必要なく、権限昇格なしで動作します。

ただし「軽い」という表現は確認してから使うほうがよいです。GitHubリリースv2.1.20(2026年8月4日公開)のLinux資産のサイズはこうです。

資産サイズ
zot-linux-amd64約215MiB
zot-linux-amd64-minimal約78MiB
zot-linux-arm64約200MiB
zot-linux-arm64-minimal約73MiB

fullビルドが大きい理由は、脆弱性スキャンとウェブUIを含むすべての機能をひとつのバイナリに入れるからです。ドキュメントもfullとminimalの二つの変種を提供すると案内しながら、minimalを「セキュリティのために機能を削った」ビルドとして説明します。

ここで判断すべきなのはサイズそのものではなく、何がその中に入っているかです。スキャナをレジストリのバイナリの中に入れると、スキャナを更新するのにレジストリを再起動しなければなりません。その結合を望まないならminimalを使ってスキャンを外に出すほうがよいです。どちらであれ、意識的な選択であるべきです。

参考資料

현재 단락 (1/65)

社内レジストリひとつにすべてのデプロイがぶら下がっている構造が不安で、インスタンスを三台に増やしたとしましょう。前段にロードバランサを置き、ヘルスチェックを付け、ダッシュボードに緑のランプが三つ点きま...

작성 글자: 0원문 글자: 5,112작성 단락: 0/65