レジストリ不要でDockerイメージをサーバーに直接プッシュ!
引用元:https://news.ycombinator.com/item?id=44314085
Dockerイメージのデプロイで、Registryにプッシュしてまたプルするっていう手間が嫌になったんだよね。外部 Registry はもちろん、ローカルでも面倒な時がある。よく考えたら、どの Docker が使えるサーバーにも、既に Registry みたいな Docker 自身のイメージ保管場所があるじゃん。
だから Docker のイメージ保管場所(containerd)を標準 Registry API で公開する Unregistry [1] を作ったんだ。これに docker pussh
コマンドを追加して、SSH 経由でリモートの Docker Daemon にイメージを直接プッシュできるようにしたよ。足りないレイヤーだけ転送するから、速くて効率的。docker pussh myapp:latest user@server
内部では、リモートホストに一時的な Unregistry コンテナを立ち上げて、SSH トンネルでそこにプッシュ、終わったら片付けてるんだ。
これはコンテナをネットワーク上の Docker ホストにデプロイするツール Uncloud [2] を作ってる時の副産物だけど、単体プロジェクトとしても役に立つと思って。みんなの考えやユースケースを聞かせてほしいな!
[1]: https://github.com/psviderski/unregistry
[2]: https://github.com/psviderski/uncloud
Docker の作者だけど、これすごく良いね。僕の考えでは、理想的な設計はこうだったと思うんだ。
1. Docker Engine と Docker Registry の区別がないこと。コンテナを保存、転送、実行できる単一のサーバーがあれば、もっと堅牢な構成要素になっただろうし、Engine と Registry のイメージ保存方法がずれていく regrettable な状況も避けられたはず。
2. push-to-cluster デプロイ。プロダクションのクラスターには分散イメージストアがあって、そこにイメージをプッシュすることがデプロイをトリガーするべきだった。現在の状態(イメージを Registry にプッシュ→クラスターを設定→各ノードが Registry からプル)は brittle だし非効率。もっと良い設計を主張したんだけど、もう inertia が大きすぎたし、初期の Kubernetes コミュニティは Docker からのアイデアには hostile だったんだ。
Solomon さん、コメントどうもありがとう、あなたの仕事すごいよ!
1. うん、同意。イメージのファイルシステムレイアウトが少なくとも3種類あって、Engine にイメージストアが2つあるのはちょっと mess だよね。Docker は今のモデルを壊さずに、あなたが言ったようなことまだできると思うけど、彼らが気にしてるかは…大変そうだしね。
2. ふむ、push-to-cluster デプロイって clever だね。分散イメージストアは考えてたんだ。例えば各ノードに Unregistry を埋め込んで、互いにイメージをプル・共有できるようにするとか。でもプッシュでデプロイをトリガーするっていうのは、ちょっと考えてみる必要があるな。良いアイデアどうも!
いいね。それに pussh
コマンドは、最も elegant な pun の一つとして認められるべきだよ。覚えやすくて、自己説明的だし、姉妹コマンドとたった一文字違いってのがまさに絶妙だね。
良いんだけど、docker push-over-ssh
みたいな、もっと正式な alias があっても悪くないかな。
EDIT: なぜ重要だと思うかっていうと、 collaboratively に開発される automations では、pussh
を unfamiliar な誰かが typo だと見間違えて、不要な confusion を引き起こす可能性があるからなんだ。一方 push-over-ssh
は明らかに deliberate だよね。short-hand と full flags みたいに考えると良いかも。
それは valid な懸念だね。名前は何でも好きなように簡単に付けられるんだよ。Docker は ~/.docker/cli-plugins
ディレクトリにある docker-COMAND
っていう実行ファイルを探して、COMAND を docker
の subcommand にするんだ。
だからファイルを好きな名前に変えれば良いんだ。例えば docker pushoverssh
にするにはこうするんだ。mv ~/.docker/cli-plugins/docker-pussh ~/.docker/cli-plugins/docker-pushoverssh
ただし、Docker は plugin commands に dashes を使えないんだよね。
簡単に想像できるよ、エンジニアが CI/CD workflow か何かで pussh
を見つけて、「これは間違いだ」と思って直しちゃうだろうね。
それに collision しやすい!
まさにそれな!芸術であってエンジニアリングじゃないからだよ。エンジニアリングなら明確に違うコマンドにするはずで、こんな洒落は思いつかないでしょ。
昔、em=mgってエイリアス使ってたわ。mg(1)はちっちゃいEmacsだから”em”が面白いと思って。あのタイプミスするまではな。
俺はsl(1)(ターミナルの蒸気機関車)入れるの好きだな。数ヶ月に一回タイプミスして、毎回爆笑する。https://github.com/mtoyoda/sl
同じノリでgtiってのもあるぜ https://r-wos.org/hacks/gti
実は、この機能をDocker本体に入れようと2015年にPR[1]送ったんだよ。そしたらすぐ他の手伝いを頼まれた。レジストリ不要はビジネスモデル壊すからだろうね。
[1]: https://github.com/richardcrichardc/docker2docker
あんたこそOGだ!マジで脱帽だわ。DockerがまだイメージレイヤーのAPI持ってないの残念だよな。いずれcontainerdイメージストアに移行するつもりなんだろうけど。ローカルもリモートもcontainerdになれば、ようやくレジストリ無しでこれができるぜ。
その通りだけど、dive (https://github.com/wagoodman/dive) とか使えばイメージレイヤー探せるし、コードも流用できるよ。MITライセンスだし。俺はそうしてる。でもAPIがあれば一番だよな。これに時間かけすぎたわ。
これクールなアイデアだな。Ansibleみたいなプッシュデプロイツール使ってるシステムに合いそう。レジストリ24/7サポート無い会社でのホットフィックスにもいいね。BuildahみたいなOCIツールとも合うの?フルDocker必要?まだ詳しく見てないけど、リモートにミニレジストリ立てるのがskopeoで動かすのに必要な部分っぽい。
それ正解。リモートにはcontainerd(DockerとかKubernetesが使う)と、クライアントにはレジストリAPI(OCI Distribution spec: https://github.com/opencontainers/distribution-spec)が話せるやつが必要。Unregistryは公式レジストリコード使ってるから https://hub.docker.com/_/registry みたい。skopeo, crane, regclient, BuildKit 何でも使える。ただunregistryは手動起動ね。『docker pussh』はローカルDocker使って自動化してるだけ。Bashスクリプトだよ: https://github.com/psviderski/unregistry/blob/main/docker-pu… 自分でハック簡単だぜ。
同意!俺が管理してるサービスだと、ローカルでイメージ作って保存、ansibleでアップロードしてリストアしてるんだ。これマジでいつも時間かかりすぎなんだよな!
これさ、両方に Docker デーモン必須なんだぜ。ただ SSH でレイヤー共有する clever なやり方ってだけじゃん。
これずっと欲しかったんだよ!Brilliant!Docker レジストリもまぁいいけど、全体的に over-engineered で hacker って感じじゃないんだよね。
俺は GitHub の ghcr.io と GitHub Actions 推し。GitHub Actions でイメージ作って private な ghcr.io に push、サーバが pull できるようにするのにたった20分と5分で済んだ。マジ実用的だよ。
registry に blobs を push する面倒な手順が複雑さの原因だと思うな。前 OCI-compliant な pull-only の registry 作ったことあるけど、そっちは全然複雑じゃなかったし。
GitLab でイメージ作って Artifactory に push、そこから pull して AWS ECR に push、EKS のデプロイテンプレート更新して ECR から pull して pod 起動、みたいなパイプライン見てみろよ。
こんなの人生に必要だろ!
ちょっと気になったんだけど、なんで Artifactory と ECR 両方使ってんの?
うちは今コスト削減で Artifactory から ECR に変えようか考えてるんだ。
前のプロジェクトのパイプライン、アプリ作るよりコンテナの pull と push に時間かかってたわ。
でもそれも起動して1秒もかからず健康かどうかわかる health check の待ち時間に比べたら全然だったけどな。
おー、この記事のおかげで uncloud ってツール知れたわ。
まさに探してた感じ! dokku みたいなやつで、もっとパワフルな sideproject 用のサーバー設定を探してたんだ。
あと https://skateco.github.io/ っていうのもあるよ。チラッと見た感じ似てるっぽい。
Skateの作者だよ!ぜひ試してみてほしいな!uncloudについてはまだ深く掘れてないんだけど、Skateとの違いは、Skateにはコントロールプレーンがなくて、CLIがコントロールプレーンになってるところだと思う。
Dokkuみたいなマルチホスト体験が欲しくて、標準的なデプロイ設定構文(k8sマニフェスト)を使えるようにSkateを作ったんだ。
始めるにはここを見てね!https://skateco.github.io/docs/getting-started/
uncloudもコントロールプレーンがなくてCLIだけみたいだね。
ここのフィーチャー欄に書いてあるよ:https://github.com/psviderski/uncloud#-features
もしPortainerを使ったことないか、検討したことないならおすすめだよ。
AWSで2台のEC2インスタンスをPortainer Community EditionとPortainer Agentで動かしてるけど、すごくうまくいくんだ。
Docker Composeを使ったスタック機能も超いいね。
1台のEC2インスタンスでPortainer AgentがCaddyをコンテナで動かしていて、それがロードバランサー兼リバースプロキシとして機能してる。
もっとコメントを表示(1)
僕は実際、自宅ラボのセットアップでPortainerを動かしてて、OctoPrintとかOmada controllerとかホストしてるよ。
uncloudのアイデアが君に響いてくれて嬉しいよ!
質問があったりヘルプが必要だったら、遠慮なく僕たちのDiscordに参加してね。
Dockerが最初からこういう風に動かなかったなんて、すごくおかしな話だね。
uncloud、クールに見えるよ!ありがとう!
これと同じことは、イメージをアーカイブにしてサーバーにプッシュして、サーバー側でアーカイブから実行すればもうできるんだ。
アーカイブとして保存するのはこんな感じ: docker save -o may-app.tar my-app:latest
ロードするのはこんな感じ: docker load -i \path\to\my-app.tar
Ansibleみたいなツールを使えば、”Unregistry”が自動でやってることも簡単に実現できるんだ。
Githubリポジトリにもある通り、save/loadはイメージ全体をネットワーク越しに転送するのが欠点っていうのはその通り。
それにアーカイブファイルよりもイメージを管理する方が便利なのは間違いないね。
もし100MBの下位レイヤーを持つイメージがあって、一番上の小さなレイヤーだけ変更した場合、unregistryは一番上のレイヤーだけを送るけど、save/loadだと100MB+全部送っちゃうんだ。
だからこそ価値があるんだね。
そうそう、僕はひどくて巨大なPython機械学習関連のゴミを扱ってるんだけど、1GB超えのイメージなんて全然珍しくないんだ。
これは最高だね、今までこのツールがどれだけ必要だったか、自分でも全く分かってなかったよ。
Dockerにはexport/loadコマンドもあるよ。
これは現在のレイヤーのファイルシステムだけをエクスポートするんだ。
README読めば分かるけど、これってsave|upload|loadを置き換えて、新しいレイヤーだけ送ることでデータ転送量を激減させるのが目的みたいだよ。Ansibleとかでも使えて、もっと速くなるってさ。
いいアドバイスだね。docker exportとdocker saveの違いには気をつけなきゃ。exportはストレージ不足だとボリュームも保存して失敗するから。間違ったコマンド使うと、動いてる唯一のDockerサーバーがおかしくなるかもよ。
面白いプロジェクトだ!高いレジストリに飽き飽きして、結局Zot [1] を自分で立てたんだけど、これは特定のユースケースではもっと簡単そうだね。みんな、設定簡単で安くて従量課金制のプライベートレジストリサービスって欲しくない?[1]: https://zotregistry.dev
Zothub.ioのSSL証明書、期限切れちゃってるよ、知ってた?
機能的にはdocker-pushmi-pullyu [1] (僕が書いた)に似てるね。あれはシンプルなシェルスクリプトで、公式のregistryイメージ [2] 使ってるんだ。ねぇ、なんで独自のレジストリ作ったの?イメージを小さくしたかっただけ?[1]: https://github.com/mkantor/docker-pushmi-pullyu[2]: https://hub.docker.com/_/registry
ねぇ、docker-pusshとかdocker-pushmi-pullyuって、コンテナイメージの署名とかattestationを検証するの?Docker Content Trust (DCT) とかcosignについて知りたいな。参考になる情報これだよ。
https://docs.docker.com/engine/security/trust/
https://docs.sigstore.dev/cosign/verifying/verify/
https://www.google.com/search?q=difference+between+docker+co…
docker-pushmi-pullyuはリモート側で普通のdocker pull [1] するから、リモート環境でDOCKER_CONTENT_TRUSTを設定すれば大丈夫なはず(試してないけど)。もしpushやpullで–disable-content-trustオプション欲しいなら追加するよ。欲しければIssue立ててね。[1]: https://github.com/mkantor/docker-pushmi-pullyu/blob/12d2893…
ローカルとリモート両方で設定いるの?署名がない時はどうなるんだろ?Podmanとcosignで作ったイメージ、Dockerでも使えるのかな?署名って、Docker, nerdctl, Podmanで共通?
あ、nerdctlのドキュメントに答え載ってた!nerdctlでcosign使ってイメージに署名・検証する方法だって。
https://github.com/containerd/nerdctl/blob/main/docs/cosign….
サインしながらプッシュ、プルで検証の例があって、署名されてないのは検証できないってさ。
なんで独自のレジストリ作ったの?っていう自分の質問への答えだけど、リモート側でdocker pullを避けたくて、レジストリのストレージをリモートホストのDockerエンジンと同じにしたかったからかな、と思う。
まさにその通り!主な動機はDocker engineとregistryの区別をなくすことなんだよね。Docker daemonにregistryみたいにプッシュプルできるように、registryラッパーを作った感じ。これは僕が作ってるクラスタリングソリューションuncloudの前提になるんだ。クラスターにイメージをプッシュして(複数マシンのDockerに直接保存)、registryを介さずにクラスター内のどのマシンでも実行できるように(手元になければイメージを持ってるマシンからプル)したいんだよ。
めっちゃクールだね。他の人も言ってるけど、「Docker daemonにregistryみたいにプッシュプル」ってのはDocker本来の姿って感じ。これはさらに次のレベルだけど、クラスター全体で異なるマシンからレイヤーごとに並行してプルすれば、リソース分散できそうじゃん?想像できるよ。
もう少しよく見ると、unregistry/docker-pusshとdocker-pushmi-pullyuの主なコンセプトの違いは、前者が一時レジストリをリモートホストで動かすのに対して、後者はローカルで動かすってことみたいだね。まあ、どっちにしてもユーザーが普通気にする必要はないことだけど。
面白いアイデアだね。これはデプロイがサービスに密結合しちゃうデメリットがあるかも。例えば、どうやってスケールアップしたり、blue/greenデプロイするの?(これをやるものがプッシュを認識する必要があるよね)。
あ、そのものuncloudってやつなのね、今見つけたよ!
とはいえ、これはtradeoffだよね。小さくやってて、Hetzner VM一台とかでシンプルさが好きなら(あとローカルでビルドするのOKなら)最高だよ。
間違いない、常にtradeoffだよね。色んな選択肢があって、それぞれの仕事に一番合ったツールを選べるのはいいね。
これってリモートのDocker contextを使うのとどう違うの?
うちのhomelabでは、ローカルの開発マシンからこういう風にリモートDocker context作ってるんだ…。
> docker context create mylinuxserver –docker ”host=ssh://revicon@192.168.50.70”
そしたら、こうできる。
> docker context use mylinuxserver
> docker compose build
> docker compose up -d
docker-compose.ymlに入ってるイメージは全部、リモートのLinuxサーバーでビルド、デプロイ、実行されるんだ。面倒なregistryも余計なアプリもいらない。Docker swarmとかKubernetesとかよりずっとシンプルだよ。もしかして、psviderskiさんがやってることで、僕の方法では得られない何かを見落としてるかな。
君のワークフローを理解した前提で答えるね。一つの違いは、unregistryはすでにビルドされたイメージで動くってこと。リモートホストでビルドするんじゃなくて、そこにプッシュするだけなんだ。これで、ローカルでテストしたイメージとサーバーにあるイメージが全く同じだって確信できるし、通常は(小さくて構成が良いDockerfileなら)ビルドよりずっと速いよ。
これはほとんどの状況ではアンチパターンだろうね。
検証済みの成果物をプッシュする能力が、ほとんどの状況でアンチパターンだって?どうして?
一人で非プロダクションの作業をしてて、それで満足してるなら全然OKだよ。でも、他の人と一緒に重要なことをやるなら、イメージは一元化されてドキュメント化された場所から公開したいと思うはず。そこでどんなテストにパスしたか、CIパイプラインのバージョン、環境、どのリビジョンでビルドされたかなどが検証されるんだ。イメージにはこの情報がタグ付けされるし、同僚と君は必要な時にこの情報を見る場所を正確に知ってることになる。これは、ローカル開発環境からイメージをプッシュするのとは両立しないんだ。
ビルドは本番サーバーじゃなくてビルドサーバーでやれば?レジストリもちゃんとしたの使うべきで、unregistryは微妙かもね。本番環境でビルドするのは正直よく分かんないな〜、リスク低いならあり?
って感じ。
やり方としてはアリだし、Dockerコンテキスト機能は便利だよね。ただ、本番サーバーでビルドするのはCPUとかIO負荷かかりそうで避けたいかも。専用のビルドホストならいいけど、今度はどうやってイメージ送るかが課題。そこでunregistryが役立つんじゃないかな!
アイデア自体は好きだよ、でも機能は「アンバンドル」してほしいな。ローカルのcontainerdイメージストア上でレジストリを動かせるのは最高。他のマシンにイメージを転送するのはまた別の話で、そこはdocker pull
で十分いけるはず。ネットワーク接続とか認証方法はいっぱいあるから、そこまでこの機能に組み込まないでくれると嬉しいな。でも、すごくスマートだと思う!
もっとコメントを表示(2)
それ、もうアンバンドルされてるよ!unregistryはスタンドアローンで動かせるし、独自のやり方でPush/Pullできるんだ。
https://github.com/psviderski/unregistry?tab=readme-ov-file#…見てみて!
これは素晴らしいね!昔は同じサーバーでプライベートレジストリ立てたり、サーバーで直接ビルドしたりしてたけど、どっちもサーバーに負荷がかかる点でこの方法より劣ってたよ。個人的には、さらに進んでローカルでビルドして、全部まとめてtarにしてlxcで動かすのが究極だと思うんだ。Docker自体いらないんじゃない?僕のイメージは小さいから、Dockerの複雑さは不要に感じるかな。まあ、大企業じゃないからそう思うのかもだけど。
これは最高だね。僕は今もsave/loadでやってて特に問題ないんだけど、足りないレイヤーだけ転送するっていうアイデアはすごくいいと思ったよ。ちなみに僕はmscpを使ってるんだけど、これは複数のscp接続でファイルを速く転送できるからめちゃくちゃ便利だよ!
昔からのchef-soloファンとしては、これはマジでクールだね!僕は今、KamalでのデプロイにDockerレジストリを使ってるんだけど、この記事の方法ならサードパーティへの依存をなくせるのかな?Kamalのこと知ってる?
うん、Kamal知ってるよ。Kamalにインスパイアされて、似た原理でよりクラスタっぽい機能を持つUncloudってのを作ったんだ。unregistryはそのUncloudのために作ったんだけど、Kamalでもきっと役立つと思うよ。
マジでピッタリだと思うよ。これからどうなるか見てみようぜ!
https://github.com/basecamp/kamal/issues/1588
ローカルの一時レジストリとリバースポートフォワーディングを使った方法をクイックで作ってみたよ。もし役に立つ人がいたらどうぞってことで:
すごく長いスクリプトは省略するけど、要は一時レジストリをローカルで立ち上げて、イメージをそこへプッシュ。
それからSSHでリモートサーバーに接続するんだけど、その時にローカルレジストリへ繋がるようにリバーストンネルを設定するんだ。
リモート側ではそのローカルに見えるレジストリ(実際は手元のマシン)からイメージをプルして、タグ付けし直して、サービスを再起動してるよ。
終了時には一時レジストリもクリーンアップするようになってる。
これでレジストリ不要で直接イメージをプッシュできるから便利だね!
Deployment finished successfully.
君のスクリプトのアイデア、https://github.com/mkantor/docker-pushmi-pullyu
っていうツールと似てるね。
これはパブリックドメインだから、もしよかったら参考にしてね!
変更されたレイヤーだけプッシュできるのはいいね!
僕の場合は「docker save my-image | ssh host ’docker load’
」で十分だったけど、そんなに頻繁にイメージをプッシュしないからさ。
毎回全部のレイヤーをプッシュしても僕は問題ないんだ。
こういうツールやSSHツールを活用した自己ホスト型ソリューションに戻る流れがあるのはすごく嬉しいな。
よくやったね、共有してくれてありがとう!
ぜひ試してみるよ。
これめちゃくちゃクールだね。
Docker Composeのサポートはしてるの?それとも今後サポートする予定はある?
ありがとう!
Docker Composeのサポートっていうのは、具体的にどういう意味かな?どんなサポートが必要なのか教えてもらえる?
えっとね、今僕の環境だと、SSHでリモートサーバーのDocker Composeを再起動させて、最新のイメージをレジストリからプルさせるってことやってるんだ。
だから結局セントラルレジストリが必要になるんだよね。
それよりいいのは、docker compose pussh
みたいな感じで、リモートのdocker-compose.yml
に基づいてローカルの最新イメージをまとめてリモートにプッシュできること。
代替案としては、対象コンテナを一つずつpusshしてからDocker Composeを再起動する、っていうのを自動化するのも良いかも。それならそんなに難しくなさそうだね。
僕は永続的なレジストリ不要で、いくつものリモートの更新をオーケストレーションする仕組みを構築したよ。
本社にコンテナビルド用のVMがあって、それがローカルイメージストアを指すレジストリコンテナも実行してるんだ。
更新する時はSSH経由でリモートホストに接続して、リバーストンネルを確立。
それでリモートホストから「localhost」レジストリ(トンネル経由で僕のビルドサーバーのレジストリ)にプルさせるんだ。
本社への接続はレイヤーをプルするのに必要な間だけだよ。
期待通りにタグ付けもできるしね。
オンデマンドのホスト型レジストリみたいで、リモートには余計なものはいらないんだ。
Podmanにも移行してるけど、このプロセスはそっちでも問題なく動いてるよ!
彼は「プロジェクトの個々のコンテナを一つずつプッシュするんじゃなくて、composeファイルみたいなものを使って、関連する全てのコンテナリストをエンドポイントにまとめてプッシュできるか」って意味だと思うよ。
うん、コンテナを一つずつプッシュするのは確かにあんまり便利じゃないよね。
君のComposeファイルに適切なyqとxargsの組み合わせを使えば、ワンショットでできるんじゃない?って話だよ。
docker compose pusshみたいなコマンドがあったら、個人的にはそっちがいいなー。
それは面白いアイデアだね。Composeにサブコマンドとかプラグインを作るのは無理だと思うけど、”docker composepussh”コマンドを作ってComposeファイルを解析して”docker pussh”を実行するのは可能だと思うよ。
次のステップとして、ビルドやデプロイの流れをめっちゃシンプルでスムーズにするためにUnregistryをUncloudに統合する計画さ。Uncloud(元の記事のリンクを見てね)もComposeを使ってるよ。
dockerをbash関数でラップして、それがcompose pusshコマンドじゃないときはcommand dockerに渡すようにすれば実現できるんじゃないかな。
ttl.shは長いこと使ってるけど、公開の一時的なコードにだけだよ。この記事のアイデアはマジでクールだね!
うわー、ttl.shってめっちゃいいアイデアだね!教えてくれてありがとう!
こういうDockerイメージのややこしい操作はdagger shellでもできるらしいよ。ただ、詳しい呪文(コマンド)を教えてあげられるほど使い込んでないんだけどね。詳しくはここを見てね:https://docs.dagger.io/features/shell/
こういった”Dockerイメージのややこしい操作”は、どんなシェルでもできるんじゃない?
dagger shellはDevOpsのために作られてて、サービスやコンテナみたいなdaggerのオブジェクトを直接パイプできるんだ。例えばこんな感じ:
github.com/dagger/dagger/modules/wolfi@v0.16.2 |
container |
with-exec ls /etc/ |
stdout
ここで面白いのは、最初の行でリモートモジュール(Wolfi Linuxコンテナのビルド)を呼び出してることだよ。daggerにはそういうエコシステムがあって、https://daggerverse.dev/ で探せるんだ。
これってskopeoとどう違うの?sshのサポートかな?skopeoにあんまり詳しくなくてごめんね。
https://github.com/containers/skopeo