- Unregistry は、Docker デーモンのストアからイメージを直接保存・配信する軽量なコンテナイメージレジストリで、
docker pussh コマンドによりリモート Docker サーバーへイメージを SSH でそのまま転送できる
- 従来の選択肢は Docker Hub/GitHub Container Registry、自前運用レジストリ、
docker save | ssh... docker load、リモート再ビルドだが、それぞれ公開・有料リポジトリ、運用負担、イメージ全体の転送、サーバー資源の浪費といった問題がある
docker pussh myapp:latest user@server は SSH トンネルを作成し、リモートサーバー上で一時的な unregistry コンテナ を起動したうえで、転送した localhost ポートに対して docker push を実行し、リモートに存在しないレイヤーだけを送る
- リモートサーバー側には Docker の実行環境、
docker コマンド実行権限、初回実行時に ghcr.io/psviderski/unregistry:latest へアクセスできることが必要で、unregistry コンテナは /run/containerd/containerd.sock へアクセスするため root で実行される
- Docker の containerd image store を有効にすると、転送されたイメージを Docker からすぐ利用でき、重複保存も避けられるが、既存の classic storage driver で作成したイメージやコンテナが一時的に見えなくなる場合がある
Unregistry が解決するデプロイの課題
- Unregistry は、外部レジストリなしで Docker イメージをリモートサーバーへ移すための軽量なコンテナイメージレジストリである
- 付属の Docker CLI プラグインコマンドは
docker pussh で、名前の追加された s は SSH を意味する
- ローカルで作成した Docker イメージをサーバーへ移す際、一般的な選択肢には次のような制約がある
- Docker Hub / GitHub Container Registry: コードを公開するか、private リポジトリの費用を負担する必要がある
- Self-hosted registry: 保守、セキュリティ、ストレージコストを伴う別サービスの運用が必要になる
- Save/Load:
docker save | ssh <remote server> docker load は、サーバー側にすでに 90% あってもイメージ全体を転送してしまう
- Remote rebuild: 時間とサーバー資源を消費し、本番環境でビルド失敗の原因をデバッグしなければならないことがある
docker pussh の動作方式
docker pussh myapp:latest user@server
- このコマンドは、レジストリ設定、サブスクリプション、中間ストレージ、公開ポートなしで、不足しているレイヤーだけ を SSH 経由で直接転送する
- 内部動作は次の順序で進む
- リモートサーバーへの SSH トンネル を作成する
- サーバー上で一時的な unregistry コンテナを起動する
- 任意の localhost ポートをトンネル経由で unregistry のポートへフォワードする
- フォワードされたポートに対して
docker push を実行し、リモートに存在しないレイヤーだけを転送する
- 転送されたイメージはリモート Docker デーモンですぐ利用できる
- unregistry コンテナを停止し、SSH トンネルを閉じる
- このプロジェクトは、Docker イメージ向けの
rsync のような、シンプルで効率的な転送方式を目指している
- Unregistry は、複数の Docker ホストへコンテナをデプロイする軽量ツール Uncloud のために作られ、フル機能のレジストリより簡単で、save/load より効率的な方式が必要だった
実行要件と制約
- ローカルマシンでは Docker CLI プラグイン対応 が必要で、Docker 19.03 以上と OpenSSH クライアントが必要である
- リモートサーバーには Docker がインストールされ、実行中である必要がある
- SSH ユーザーには
docker コマンドを実行する権限が必要である
- ユーザーが
root であるか、非 root ユーザーが docker グループに属している必要がある
- 関連ドキュメントとして Docker の Manage Docker as a non-root user を参照する
sudo が必要な場合は、パスワードプロンプトなしで sudo docker を実行できる必要がある
- 初回の
docker pussh 実行時には、リモートサーバーが ghcr.io から ghcr.io/psviderski/unregistry:latest イメージを取得できる必要がある
- プロキシが必要なサーバーは、Docker の Daemon proxy configuration ガイドに従って設定する必要がある
- air-gapped 環境や
ghcr.io へのアクセスが制限された環境では、必要な unregistry イメージのバージョンを確認したうえで手動でプリロードできる
- unregistry コンテナは
/run/containerd/containerd.sock にアクセスする必要があるため、必要権限を確保するため root で実行される
インストールと対応プラットフォーム
- macOS/Linux では Homebrew で
docker-pussh をインストールできる
brew install psviderski/tap/docker-pussh
- Homebrew インストール後に Docker CLI プラグインとして
docker pussh を使うには、~/.docker/cli-plugins/docker-pussh のシンボリックリンクを作成する必要がある
- macOS/Linux では直接ダウンロード方式も提供されている
- 現在のバージョン例は
v0.4.3 の docker-pussh スクリプトを Docker プラグインディレクトリへダウンロードし、実行権限を付与する方式である
- main ブランチの最新スクリプトをダウンロードする方式も提供されている
- Debian では、@dariogriffo が作成・保守している非公式パッケージリポジトリ unregistry-debian を通じてインストールできる
- Windows は現在未対応だが、WSL 2 と Linux 向けインストール手順を試すことはできる
- インストール確認は次のコマンドで行う
docker pussh --help
containerd image store の設定
使用例
- 基本転送ではイメージ名とリモート SSH 対象だけを指定する
docker pussh myapp:latest user@server.example.com
- SSH agent に private key が登録されていない場合は
-i でキーを指定できる
docker pussh myapp:latest ubuntu@192.168.1.100 -i ~/.ssh/id_rsa
- カスタム SSH ポートは対象の後ろにポートを付けて指定する
docker pussh myapp:latest user@server:2222
- カスタム SSH config ファイルは
-F で指定する
docker pussh myapp:latest prod-server -F ~/.ssh/config.prod
- マルチプラットフォームイメージで特定のプラットフォームだけを push するには
--platform を使う
docker pussh myapp:latest user@server --platform linux/amd64
- リモートホストで特定の unregistry イメージバージョンを使うには
UNREGISTRY_IMAGE 環境変数を指定する
UNREGISTRY_IMAGE=ghcr.io/psviderski/unregistry:A.B.C docker pussh myapp:latest user@server.example.com
代表的なユースケース
- 本番サーバーへのデプロイでは、ローカルでビルドしたイメージを中間レジストリなしでサーバーへ直接 push して実行できる
docker build --platform linux/amd64 -t myapp:1.2.3 .
docker pussh myapp:1.2.3 deploy@prod-server
ssh deploy@prod-server docker run -d myapp:1.2.3
- CI/CD パイプラインでは、レジストリの複雑さを省いてデプロイ先へ直接イメージを送れる
- name: Build and deploy
run: |
docker build -t myapp:${{ github.sha }} .
docker pussh myapp:${{ github.sha }} deploy@staging-server
- homelab や air-gapped 環境では、公開レジストリへインターネット経由でアクセスできない隔離ネットワークでもイメージをデプロイできる
高度な使い方と関連プロジェクト
- Unregistry はスタンドアロンのローカルレジストリとしても利用できる
/run/containerd/containerd.sock をマウントし、ghcr.io/psviderski/unregistry コンテナを起動する
- その後
localhost:5000 を通常のレジストリのように docker tag や docker push に使える
- SSH 設定は標準の SSH config ファイルを使うか、
-F で別の config ファイルを渡せる
- 関連するサードパーティプロジェクトも提供されている
- 実装には Spegel と Docker Distribution が言及されている
- Spegel は、containerd image store をバックエンドに使うレジストリ実装への着想を与えた P2P コンテナイメージレジストリである
- Docker Distribution は、unregistry の基盤となっている Docker registry 実装である
1件のコメント
Hacker News のコメント
Docker を作った側としては気に入っている。理想的な設計は、Docker エンジンとレジストリの区別がない単一サーバーだったのだと思う
必要に応じてコンテナを保存・転送・実行できていれば、はるかに堅牢な構成要素になり、エンジンとレジストリでイメージの保存方式が食い違うという残念な流れも避けられたはず
さらに、すべての本番クラスタには分散イメージストアがあるべきで、そのストアへイメージをプッシュすることがデプロイのトリガーになるべきだったと思う。今のようにレジストリへプッシュし、クラスタを設定し、各ノードがレジストリからプルする方式は脆弱で非効率だ。より良い設計を主張したが、すでに慣性が大きすぎ、初期の Kubernetes コミュニティは Docker 発のアイデアに敵対的だった
クラスタへプッシュするデプロイは賢そうに見える。全ノードに unregistry を入れて互いにイメージを取得・共有させるような分散イメージストアは考えているが、プッシュがデプロイをトリガーする方式はもう少し考える必要がありそう
良い。
pusshコマンドは本当に優雅な言葉遊びとして認められるべきだ。覚えやすく、意味がすぐ分かり、姉妹標準コマンドと1文字違いでしかないdocker push-over-sshのような、もっと公式っぽい別名があってもよさそう共同で開発される自動化では、
pusshは機能を知らない人にはタイポに見えて、不要な混乱を招くかもしれない。一方push-over-sshなら意図された名前だと明確だ。短いオプションと長いオプションのように考えればいいsはssshのsだ「その余分な
sは何?」「タイポだよ」
2015年にこの機能を Docker 本流に入れようと、素朴に PR[1] を送ったら、すぐに別の領域を手伝う方向へ回された。レジストリを使わなくてもよくするのは、Docker のビジネスモデルを揺るがしすぎたのかもしれない
[1]: https://github.com/richardcrichardc/docker2docker
結局、デフォルトを containerd のイメージストアへ切り替える計画なのだと思う。ローカルとリモートの両方で containerd のイメージストアを使うようになれば、レジストリラッパーなしで、もともと実装していたことができるようになりそう
Ansible のようなプッシュ型デプロイツールをすでに使っているシステムと相性が良さそうな、素晴らしいアイデアだ。Docker レジストリに24時間365日のサポートがない会社では、良いホットフィックス配布メカニズムとしても使えそう
buildah のような OCI ツールときれいに統合できるのか、それとも両端に完全な Docker インストールが必要なのかが気になる。まだ深くは見ていないが、こうした構成で skopeo が動くには、リモートサーバー上にミニレジストリをブートストラップすることが欠けているピースに見える
Unregistry は API 層に公式 Docker レジストリのコードを再利用しているので、https://hub.docker.com/_/registry と同じように見え、動作する
クライアントでは skopeo、crane、regclient、BuildKit など、OCI レジストリを話すツールを使える。ただしそれらを使うには、リモートホストで unregistry を手動実行する必要がある。
docker pusshコマンドはローカル Docker を使ってその流れを自動化しているだけ単なる Bash スクリプトだと思えばいい: https://github.com/psviderski/unregistry/blob/main/docker-pu...
自分の望む方式に簡単に直せる
最初からこうあるべきだった。素晴らしい
Docker レジストリにも使いどころはあるが、全体としては過剰設計で、ハッカー精神とは反対側にある
非公開 ghcr.io レジストリにイメージをビルドしてプッシュする
.yamlワークフローを設定するのに20分、サーバーがそこからイメージを取得できるよう許可するのに5分ほどかかった。かなり実用的な構成だGitLab でイメージをビルドして Artifactory にプッシュし、その後デプロイがトリガーされて Artifactory から取得し AWS ECR へ再度プッシュし、EKS のデプロイテンプレートを更新すると ECR からノードへ取得して Pod コンテナを立ち上げる、というパイプラインを見ている
自分の人生にはこれが必要だ
これを見て uncloud を知った。サイドプロジェクトのサーバー構成用に、dokku みたいだけどもう少し強力なものを探していたところで、まさにそんな感じだった
スタック機能も本当に良くて、実質的には Docker Compose。ある EC2 インスタンスでは Portainer Agent がコンテナ内の Caddy を実行し、Caddy がロードバランサーとリバースプロキシの役割を果たしている
Docker が最初からこのように動作していなかったのは、かなり不思議。かっこよく見える
アーカイブの保存は
docker save -o may-app.tar my-app:latestのように行い、ロードはdocker load -i /path/to/my-app.tarのように行うAnsible のようなツールを使えば、「Unregistry」が自動化していることを簡単に実現できる。GitHub リポジトリによると、save/load 方式の欠点は イメージ全体をネットワーク経由で転送する点で、実際に問題になり得る。アーカイブファイルではなくイメージ自体を管理するほうが便利そうでもある
すっきりしたプロジェクトとアプローチ。高価なレジストリにうんざりして Zot[1] を自分でホストするようになったが、一部の用途ではこちらのほうがずっと簡単そう
簡単に設定できて、安価で、従量課金の プライベートレジストリサービス があればいいと思っている人がほかにもいるのか気になる
[1]: https://zotregistry.dev
悪くないアイデア。ただしデプロイがサービスに結合されるという欠点はあり得る。たとえばスケールアウトや red/green デプロイをどうするのかは分からないし、そういうことをする側がプッシュを把握している必要がありそう
修正: それをやるのが uncloud だった。今気づいた
それでもトレードオフではある。規模が小さく、Hetzner の VM 1台を使い、シンプルさに満足できて、ローカルでイメージをビルドするのも問題ないなら素晴らしい