Posts for: #Docker

おいしい 🦀 を食べに行く

22時頃に寝てしまって1時に起きて5時に起きて6時過ぎに起きた。とくになにもしていないのにバテている気がする。今週はずっと mongodb のレプリカセットの調査やインフラの移行作業などをやっていたせいか、普段よりもエネルギーを消費しているのかもしれない。朝から疲労困憊でオフィスへ向かった。

docker のコンテナネットワークの調査

docker のコンテナネットワークから解決できる名前がなになのか、よくわかってなくて、その調査のためにサンプルの compose サービスを作った。

myimage から nginx のコンテナの名前解決がどうなるかを試してみる。

c67a5ca94a77:/app# dig +short 00c719491558
192.168.240.3
c67a5ca94a77:/app# dig +short mynginx
192.168.240.3
c67a5ca94a77:/app# dig +short nginx
192.168.240.3
c67a5ca94a77:/app# dig +short yournginx
192.168.240.3

基本的にはサービス名、コンテナ名 (container_name)、コンテナー ID、ホスト名 (hostname) はすべて名前解決できる。hostname があるときはそのコンテナの /etc/hosts にその名前が追加され、ないときはコンテナ ID が追加されていた。

yourcontainer:/app# cat /etc/hosts
127.0.0.1	localhost
...
172.18.0.3	yourcontainer

冬の開発合宿の準備

日程を決めたのが5月末 で、うちの会社のワークスペースに slack のチャンネルを開設したのが10月。現時点で7人の参加者がいる。もうこのメンバーでいいかなと考えている。今回はコミュニティのワーケーションイベントというより、自社の開発合宿という体をとっている。スポンサーとしていくらか会社からお金も出す。その理由の1つは slack チャンネルにログを残したいという意図がある。コミュニティの slack だとフリープランになるので3ヶ月以上過去のログがみえなくなってしまう。それを解消するには自社の有料プランの slack チャンネルに置いておくのがもっともログを制御できて嬉しい。

城崎温泉では11月から 🦀 が解禁となって、今年は冬に行くので 🦀 を食べるというのも目的の1つ。チャンネルで盛り上げようと、たまに城崎温泉の記事を貼り付けたりしていた。そろそろ、メンバーと顔合わせの情報共有の打ち合わせをしようと思って調整さんを作った。他の人たちは、わざわざうちの slack のワークスペースにゲスト参加しているから、あまり無理強いをせずに情報共有できるようにしておきたい。たった1つの、ほとんどやり取りしないチャンネルのために slack のワークスペースをオープンしておかないといけないという用途はなかなか面倒だ。私が逆の立場でもそう思う。どうにか普段使っているワークスペースから、必要なときだけ連携できるような仕組みがないだろうか?

年末・年始の情報共有の打ち合わせへ向けて旅のしおりも準備していく。日々の業務に忙殺されて後回しにしがちなので自分を追い込むためにも予定を入れた。

インフラの式年遷宮

1時に寝て何度か起きて5時に起きた。それからだらだらして寝てまた7時に起きた。

テスト環境の再整備と rootless コンテナ

インフラの式年遷宮のようなことをしていて、テスト環境をリファクタリングして再整備していた。これまで root でコンテナを実行していたが、最近は rootless コンテナがセキュリティ強化の観点から望ましいということで次のドキュメントをみながら設定した。

設定はとくに難しくないが、dockerd や containerd の起動を systemd のユーザーインスタンスに依存することになる。systemd のユーザーインスタンスは基本的にユーザーがログインしたときに生成されるものなので OS が再起動したときなどに困る。OS 再起動時にも systemd のユーザーインスタンスを生成するには linger という仕組みを有効にすればよいらしい。systemd –user の扱いと linger のことまで理解していれば、たぶん大丈夫なのかな?これで運用がうまくいくことを祈りたい。

$ sudo loginctl enable-linger ucidm

久しぶりのテックブログの執筆

3時に寝て7時に起きた。昨日は連休明け初日なのに2時まで作業していた。開発が落ち着いたので夜に自社のお仕事をする余力が戻ってきたとも言える。

Docker についてのテックブログ執筆

先日から Docker エコシステムの調査 をして、そこでわかったことをテックブログとしてまとめていた。一通り書き終えてマージリクエストを作成して社内レビューを依頼した。当初は 「ライブラリとしての Docker とは何か?」 というタイトルで書いていたものの、レビューを経て「ライブラリとしての」という修飾は必要ないことに気付いた。そのまま Docker のコンポーネントのソフトウェアスタックや過去の経緯や現状の雰囲気がわかるような記事にした。Docker を中心としたコンテナプラットフォームと標準化の概要について追っていく記事に仕上がった。インフラに関心をもつ人たちは少ないかもしれないけど、個人的にはコンテナの学びになってよい記事に仕上がったと思う。調査と執筆とレビューを含めると5人日ぐらいかけている。かけた工数を考えれば当然と言えるかもしれない。

デザイナーさんと契約締結

昨日の続き。デザイナーさんからいただいたワイヤーフレームをレビューして、契約書の叩き台も送付して、先方の所感や意見も伺いながら契約を締結した。基本的にはこちらが提示した契約書の通りに進んだのでとくに揉めることなくうまくいったと言える。顧問のはらさんからデザイナーと契約をするときの要項を事前にヒアリングしていて、その詳細を盛り込めんたこともよかったと思える。サイトデザインをお願いするものの、hugo のテンプレートは私が実装しないといけない。ある意味デザイナーと協調してサイトデザインを構築すると言えるかもしれない。私もそこから学ぶ機会があるだろうし、その過程でできた成果物は hugo のテーマとして oss で公開したい。

飛び石のお仕事

0時に寝て何度か起きて7時に起きた。休んでもよかったんだけど、休む理由がないので飛び石でお仕事することにした。

docker エコシステムの調査

少し前に docker をライブラリとして使って運用ツールを作った 。その内容をテックブログに書こうと思って docker のソフトウェアスタックやアーキテクチャの背景を調べ直していた。もっとたくさんいろんな記事を読んだのだけど、次の記事とそこから辿れるものを読むとよいだろうと思う。

コンテナに関して cri と oci という2つの標準化があることを学び、その実装として docker 社が使っているツールに関係があるのが次の3つになる。おもしろいことにすべて docker 社のリポジトリにはなく、oss として然るべき所管の organization にリポジトリがある。

すべて docker 社がオリジナルを作って、いまも moby は docker 社が主体となって開発を継続しているだろうけれど、コンテナの実行環境のプラットフォームは k8s に取って変わられ、cri のコンテナランタイムとしての containerd があれば moby は docker daemon や docker engine のためのツールでしかなくなっている。当初 docker と moby を分割したのは、docker を開発ツール、moby を infrastructure にするという判断の下、moby を k8s のようなプラットフォームにしたかったはずである。しかし、結果的にその標準化競争に破れ containerd があれば dockerd daemon は不要になったとも解釈できる。docker という名前はコンテナのエコシステムにおいて docker 社が提供するコマンドラインやプロダクトの総称としての名前でしかなくなってしまっていて、一世を風靡した docker というパッケージングシステムの開発元に同情してしまう感もある。いまの docker engine は docker daemon と containerd の2つの daemon を起動していて docker 社としては微妙なアーキテクチャになっているのではないかと推測する。とくに swarm なんか最早削除したいだろう。

こういった docker を取り巻くエコシステムやツールの背景を説明するだけでも1つの記事になりそうなことが1日調べていてわかった。

しくじり先生

たまたま 竹原慎二先生「50歳過ぎてもケンカを売られ続けてバリしんどい先生」 をみたらおもしろかった。ガチンコをリアルタイムでみていた世代なので竹原氏には好感をもっている。少し前に 【あの人の健康法】元プロボクサー・竹原慎二の膀胱(ぼうこう)がんとの闘い のような、闘病生活の記事もみかけていた。病気は克服してがんばっているといるようにみえる。よかった。もう51歳なのか。

この番組の中で若い頃に上京してボクシングジムへ通ったときに根性の定義が変わったという話しが出てくる。

殴られても耐えることを根性だと思っていた。そのときに初めて殴られようが何しようが毎日辛い練習を重ねるのが根性だと気付かされた。

地元で最強だったのが、ボクシングジムのヤンキーでもない先輩にまったく太刀打ちできなかったという。そのとき1番違うのはスタミナだったらしく、竹原氏は1分で息がきれるのに相手は軽く流すといった様相だった。この先輩にボコボコにされた経験を経てそれから心を改めて真面目になったと言う。その後ボクシングと真剣に向き合い、1995年に日本人初のミドル級世界チャンピオンになる。おそらく番組の主旨的に若ものへのメッセージとして「強さ」について話されていた。

「ケンカに強い」だけが「強さ」じゃない、「強さ」の意味を履き違えずに生きよう。大人になると、いろんな強さを知る。大切な人のために仕事をどれだけ頑張れるか、辛い状況でもじっと耐えることができるか。そういう強さもある。

私は誰かのために仕事をがんばったことないし、辛い状況を耐えるみたいなこともほぼやってなくて、嫌になったら仕事を辞めてて、こういう言葉にあうと自分を恥じてしまう。その後、喧嘩自慢で youtube 動画を検索していたらまさにそういうのがあった。竹原氏が「勝てるはずねぇだろ、お前らこんな茶番なことすんなよ」とぼやいていた。先の番組の中でも触れていたが、本当に真剣勝負したいと思って来ているのではなく、記念に戦ってみたいという不純な動機でやってくる人が多いらしい。たしかにそれは相手するのがしんどそう。

docker image のマルチプラットフォーム対応

22時に寝て0時に起きてやや吐き気で苦しんで4時に起きて7時に起きた。夜遅くに食べてないのに調子悪かった。

docker image のマルチプラットフォーム対応

やぎさんが docker buildx でマルチプラットフォームのイメージを作成する の記事を書いてて buildx プラグインがあることを知った。ちょうどいまお仕事でコンテナベースのプロダクトを開発している。まずはオンプレミス向けが対象なので amd64 でビルドしていた。今後はクラウド向けにも提供していくので arm64 ビルドもいずれ追加しないといけないと考えていた。ちょうどリリースを終えて調査時間の余裕があるのでこの機会に buildx について調べてみることにした。

Docker Engine 23.0 release notes をみると次のように書いてある。

  • Set Buildx and BuildKit as the default builder on Linux. moby/moby#43992
    • Alias docker build to docker buildx build. docker/cli#3314
    • The legacy builder can still be used by explicitly setting DOCKER_BUILDKIT=0.
    • There are differences in how BuildKit and the legacy builder handle multi-stage builds. For more information, see Multi-stage builds.

23 からデフォルトのビルダーとして buildx が使われるようになっているらしい。Building multi-platform images を一通り読んで試してみた。

buildx ではカスタムビルダーを定義して複数プラットフォーム向けの docker image を一緒にビルドできる。このとき個々のビルド環境を builder instance と呼び、ビルド環境を抽象した概念として扱われている。マルチプラットフォーム対応の文脈で言えば amd64 や arm64 のビルド環境をそれぞれに作る。例えば amd64 のマシン上で arm64 のビルド環境を作るときは qemu を使ってエミュレーションしたビルド環境を用意したりもできる。builder instance はカスタムビルダーで制御する。

$ docker buildx create --name mybuilder --platform linux/amd64,linux/arm64
$ docker buildx ls
NAME/NODE    DRIVER/ENDPOINT             STATUS   BUILDKIT PLATFORMS
mybuilder    docker-container
  mybuilder0 unix:///var/run/docker.sock inactive          linux/amd64*, linux/arm64*

ここで作ったカスタムビルダーの driver は、デフォルトビルダーの docker ではなく、docker-container になる。おそらく builder instance の実体であるビルド環境がコンテナ上に構築されるのだと思う。これらの builder instance を使ってビルドされる。カスタムビルダーをデフォルトで使うには次のように実行する。

$ docker buildx use mybuilder

このカスタムビルダーを使って docker image をビルドする。カスタムビルダー側にプラットフォームの設定をもっているので --platform は指定しなくてもよいけど、明示した方がわかりやすいだろう。

$ docker buildx build --platform linux/amd64,linux/arm64 -t myimage:latest .

ビルドの処理は進んでいたが、正常終了しなかったので途中でやめた。なにか設定の漏れがあるかもしれないが、だいたいの雰囲気はつかめた。

ここでいま gitlab ci/cd 環境では kaniko というツールを使って docker image をビルドしている。そもそも kaniko と buildx の違いもわからなくなって README を読み返すと次のようなことが書いてあった。

kaniko は Docker デーモンに依存せず、Dockerfile 内の各コマンドを完全にユーザースペースで実行します。これにより、標準的な Kubernetes クラスタのように、Docker デーモンを簡単かつ安全に実行できない環境でもコンテナイメージを構築することができます。

ローカルで検証しているとしばしば忘れてしまうが、docker cli を使うには docker daemon を起動しておかないといけない。ci/cd におけるビルド環境がそもそも dokcer で動いていたりすると docker daemon を使えるかどうか (dind: docker in docker) はセキュリティ上の大きな違いになってくる。ci/cd 環境によっては docker daemon が使えないという状況はありえる。そういった環境でも docker image をビルドできるのが kaniko のメリットと言える。

kaniko はマルチプラットフォーム対応なビルドができるのかどうか?issue でもそういった質問がいくつかみつかる。

結論から言うと、kaniko そのものはマルチプラットフォーム対応のビルド機能をもっていない。アーキテクチャごとのビルド環境があればそれぞれビルドするだけになる。しかし、マルチプラットフォーム対応の docker image というのは manifest list というのを作ってコンテナレジストリに push すればよいという仕組みらしい。この manifest を作るためのツールとして manifest-tool というのがある。このツールと組み合わせれば、kaniko でもマルチプラットフォーム対応の docker image をコンテナレジストリに登録できる。実際に試したわけではないので想像で書くが次のような手順だと推測する。

  1. kaniko でそれぞれのプラットフォームの docker image をビルドする
    1. amd64 向けにビルドする => latest-amd64
    2. arm64 向けにビルドする => latest-arm64
  2. ビルドされた複数プラットフォームの docker image に対して manifest-tool でコンテナレジストリに push する
    1. latest- prefix のタグをもつ docker image の manifest list を作る
    2. manifest list を使ってコンテナレジストリへ push する

ci/cd 環境でこういった手順のパイプライン処理やジョブを定義して実行すれば kaniko でもマルチプラットフォーム対応な docker image を push できると思う。

リリース前テストを完了

0時に寝て6時に起きた。昨日は晩ご飯の買いものに行こうと早めに帰ろうとしたら雨降りでそのまま帰って家でだらだらしてた。

リリース前テスト

来週の火曜日がリリース日になる。4月から3週間ほどかけて行ってきたテストケースを完了した。致命的な不具合もなく、テストの過程でみつかった不具合も修正済みで予定していたテストが完了となった。ここ2週間ほど、テストして issue が登録されると、私が細心の注意を払って内容を精査して、即日で fix して検証したりしていた。それもあってテストでみつけた不具合はほとんど fix した。あとはリリースのためのパッケージングやドキュメント作成、インフラ作業などへ移っていく。アプリケーションの振る舞いに影響を与えるものではないので私の中では肩の荷がおりてプレッシャーも軽減されて少し安心できた。よかった。よかった。

docker hub のプライベートリポジトリ

先日 docker hub の rate limit に引っかかったこともあり、docker hub の有償アカウントを使うことになった。team プランは5人以上必要らしいので pro アカウントで契約してもらった。有償アカウントなら無制限のプライベートリポジトリを提供できる。docker hub でリポジトリを作成していて、いまさらながらにリポジトリ名にスラッシュを含められないことに気付いた。基本的にはハイフン区切りでスラッシュの代替する名前になっていた。これまで自分で docker image を扱ってこなかったので今更ながらに気付いた。

社内のコンテナレジストリから docker hub のプライベートリポジトリへの promotion のスクリプトを書いていた。bash の連想配列を使ってコンテナレジストリのマッピングを定義してあとは pull/push するコードを書くだけ。

declare -A repos
repos["internal-my-repo1"]="external/my-repo1"
repos["internal-my-repo2"]="external/my-repo2"
for internal in "${!repos[@]}"; do
  external="${repos[$key]}"
  echo "pull from: $internal"
  echo "push to  : $external"
done

コワーキングのオンラインイベント

月例のカフーツさんのオンラインイベントに参加した。先月の所感はここ 。promotion のスクリプトを書いていたら20分ほど遅れてから参加。今日の話題は chatgpt だった。少し前に 雑談会 したので歴史や原理的な仕組みなど、調べたことを参加者と共有しながら今後の社会の変化などを議論していた。私も関心のある内容なのでおもしろかった。いまや私は日々の開発業務にも chatgpt を使って調べものをしたりサンプルコードを書いてもらうのが普通になりつつある。そのうち ide と連携してテストコードの叩き台なども書かせるようにしてみたい。

いとうさんは chatgpt をみて、人間は働く意欲がなくなるのではないかといった懸念を表明されていた。いまのところ、ドメイン知識をもっている人がより効率よく働けるようになるツールでしかないという私の認識だが、あと3年ぐらいしたら人間を遥かに超えていって、chatgpt の出力をそのまま業務に応用する日も来るかもしれない。llm というのは次にくるもっともらしい単語を膨大な学習データから統計的に選択しているだけで、実際には内容を理解していないし、人間のように創造的な行為もできない。大雑把に言えば、インターネットの空気を読んで発言すれば人間っぽいというのはすごいことだし、便利で役に立つし、働き方も変わっていくだろうけれど、その延長上で変わる世の中が、私の人生における行動に影響を与えるほどではないと考えている。そういった話をする過程であんちぽさんの言う「価値観を育てる」という文脈が頭の中に残っていて、llm ぐらいでは揺らぎそうにはない気がしている。

ただ it 業界以外にもこんなに話題が拡散しているプロダクトはそうそうないので世の中をどんどん変革していくのだろうというのは、異業種の人たちと話していても実感できた。

一日中リファクタリング

0時に寝て7時に起きた。昨日は夜にホテルで作業しようと思いながらテレビをみているうちに寝落ちしてた。朝から夜までずっとリファクタリングのためにコードを書いたり、コンテナ環境の設定を変更したりしていた。

mongodb のコネクションプール

MongoDB Drivers の Connection Example に次のようなことが書いてある。

Reuse Your Client

We recommend that you reuse your client across sessions and operations. You can use the same Client instance to perform multiple tasks, instead of creating a new one each time. The Client type is safe for concurrent use by multiple goroutines. To learn more about how connection pools work in the driver, see the FAQ page.

mongodb drivers の client は goroutine safe なので再利用することを推奨している。内部的にはコネクションプールをもっていて mongodb とのコネクションを再利用できる。具体的にはライブラリ内部に次のようなコードがみつかる。context にセッション情報があればそれを使い、なければクライアントの sessionPool (コネクションプール) を使ってセッションを取得して mongodb にアクセスする関数の終わりで終了処理を行う。

sess := sessionFromContext(ctx)
if sess == nil && coll.client.sessionPool != nil {
	sess = session.NewImplicitClientSession(coll.client.sessionPool, coll.client.id)
	defer sess.EndSession()
}

既存のコードはコネクションプールのことを考慮していないコードになっていたので大きくリファクタリングして効率化した。

docker hub の pull 制限

午前中はリファクタリング、午後は docker compose 環境の変更と再構築、午後はバグ修正と一日中 docker image を取得する作業をしていた。gitlab ci/cd が動くとテストと docker image 生成の処理が動くのでその過程で関連する docker image を pull する。夕方になって gitlab ci/cd で初めて次のエラーが発生することに気付いた。前にお手伝いしていた職場でもそういう現象が起こると聞いて、docker login するコードを github actions のスクリプトに追加していたので、rate limit がかかることは知っていた。

You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limits.

Understanding Your Docker Hub Rate Limit によると、6時間あたり匿名アクセスは100、 free ユーザーは200を上限としているらしい。匿名アクセスは ip アドレスでカウントしているのだろうから場合によっては会社内からのアクセスをすべてカウントされたりするかもしれない。課金するとこの上限が24時間あたり5000になる。docker hub のプライベートリポジトリを利用する意図で team プランの課金を検討していたが、docker hub のアクセス制限を緩和するために課金する必要があるかもしれない。

ローカルにコンテナレジストリを構築する

出張する日は寝ないで資料を作ったりバグ修正したりして始発の新幹線の中で寝てた。寝てなくて疲れているせいか、新幹線で寝るのに慣れたのか、わりと2-3時間ぐっすり新幹線で眠れるようになってきた。普通にベッドで寝ても3時間ぐらいしか眠れないので睡眠時間はあまり変わらない。

docker registry の構築

先日の調査 の続き。Deploy a registry server に書いてあることを実際にローカルで検証した。

tls の自己証明書の作成。subjectAltName という設定をするように書いてある。

$ openssl req -newkey rsa:4096 -nodes -sha256 -keyout certs/domain.key -addext "subjectAltName = DNS:myhost.mydomain.example.com" -x509 -days 365 -out certs/domain.crt

basic 認証のための htpasswd の設定。htpasswd とか懐かしいなと思いながら実行した。

$ docker run --entrypoint htpasswd httpd:2 -Bbn user1 secret1 >> dot_htpasswd
$ docker run --entrypoint htpasswd httpd:2 -Bbn user2 secret2 >> dot_htpasswd

docker 社が提供する oss な docker registry サーバーを使って起動する。

$ mkdir /mnt/registry  # docker image を永続化する場所
$ sudo docker run -d \
  --restart=always \
  --name registry \
  -v "$(pwd)"/auth:/auth \
  -e "REGISTRY_AUTH=htpasswd" \
  -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
  -e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/dot_htpasswd" \
  -v "$(pwd)"/certs:/certs \
  -e "REGISTRY_HTTP_ADDR=0.0.0.0:443" \
  -e "REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt" \
  -e "REGISTRY_HTTP_TLS_KEY=/certs/domain.key" \
  -p 8443:443 \
  -v /mnt/registry:/var/lib/registry \
  registry:2

これで basic 認証付きで https で通信できる docker registry サーバーができた。

外部のマシンから dokcer login しようとすると次のようなエラーが発生する。

$ docker login myhost.mydomain.example.com:8443
Username: user2
Password: ***
Error response from daemon: Get "https://myhost.mydomain.example.com:8443/v2/": x509: certificate signed by unknown authority

Test an insecure registry によると、自己証明書を使って外部からアクセスできるようにするためには docker client 側にさっき作った domain.crt をコピーする必要がある。

linux だとこんな設定。

$ cp domain.crt /etc/docker/certs.d/myhost.mydomain.example.com:8443/ca.crt 

Docker Desktop for Mac を使っている場合はこんな感じ。

> security add-trusted-cert -d -r trustRoot -k ~/Library/Keychains/login.keychain path/to/certs/domain.crt

これで外部からも docker login して任意の docker image を push/pull できるようになる。docker registry サーバーは Let’s Encrypt をサポートしているそうなので How It Works を参照して設定すればよいと書いてあった。

mdbook の初期設定

mdbook は新しい rust のバージョンだとビルドできなかったりするので rustup を使ってローカルに rustc をインストールするのがよいかもしれない。プラグインとしては mdbook-mermaid を使う。

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
$ ~/.cargo/bin/rustc --version
$ cargo install mdbook mdbook-mermaid

mdbook-mermaid の設定も簡単でドキュメントルート配下に mermaid の js ファイルを配置すると動いた。

$ vi book.toml
[preprocessor.mermaid]
command = "mdbook-mermaid"

[output.html]
additional-js = ["mermaid.min.js", "mermaid-init.js"]

コンテナレジストリをプライベートに運用する

0時に寝て7時に起きた。晩ご飯を食べきれなくて調子悪いと思っていたら夜も吐き気と胃酸で気分が悪くてあまり眠れなかった。

外部向けコンテナレジストリ

いまお仕事で作っているアプリケーションは docker image としてパッケージングしている。エンドユーザーがこのアプリケーションを使うためには docker pull できるためにインターネットを経由してアクセスできる必要がある。普段はイントラネットのコンテナレジストリに push/pull して運用しているのを、外部のエンドユーザー向けにアクセスできるコンテナレジストリ (リポジトリ) を構築しないといけない。パブリックなリポジトリとして提供するのであれば、docker hubGitHub Container registry などを無料で利用できる。しかし、プライベートなリポジトリで運用しようとするとその選択肢は狭まってしまう。おそらく他社のサービスを使うのであれば、実際の運用を考慮するといくらか費用がかかるだろう。

仮に docker image が使うストレージを5GiB、インターネットへの outbound なデータ転送を30GiB/月で見積もってみた。docker hub だと利用量によって課金されないのでその後に利用増加が前提であればよさそうにみえる。

  • github (従量課金)

  • aws (従量課金)

    • region=tokyo: $3.92/month, $47.04/year
  • docker hub (容量無制限)

別の選択肢として自前でコンテナレジストリを運用するという方法もある。docker registry サーバーは oss として公開されていて docker image の push/pull をするだけのサーバーならすぐに構築できる。ベーシック認証に近い v1 の認証でよければ htpasswd を使ってアカウント管理できる。

ドメインと tls の証明書と外部からアクセスできるサーバーがあれば、自前で運用するのもそう大変ではないと思う。実際にこれらの運用コストと他サービスの利用料金とを比べて選択することになるだろう。

docker/compose のモジュールの使い方がわかってきた

0時に寝て7時に起きた。前日はバテてだらだらしていたので寝過ぎた。

案ずるよりもツールできた

先週末に docker/compose 関連のライブラリ調査 を終えて実際のツール作りをしていた。本当は日曜日に作ってしまおうと思いつつ休んでしまった。なぜか今日はメンバーが全員お休みでチームで私しか働いていなかった。年度末で有休消化しているのかな?問い合わせ対応やメンバーのサポートが不要だったので1日中、自分の開発に集中できた。そして開発に集中できた結果、一通りツールの機能の開発を終えられた。火曜日までには仕上げたいと思っていたのでぎりぎり間に合った。

最終的に testcontainers-go の compose モジュールを使うのは断念して compose cli のみ go 標準の os/exec パッケージを使ってプロセスを fork するようにした。また docker image をコンテナレジストリから取得するときに認証が必要な場合、最初の docker login すると credentials store にパスワード (またはトークン) 情報が記録される。設定情報は $HOME/.docker/config.json からも確認できる。この仕組みを使ってコンテナレジストリへのログインを自動化できる。私の環境では macbook に docker desktop をインストールしているが、普通に使っていると次のように credentials が保存されてその内容を確認できる。

$ docker-credential-desktop list | jq .
{
  "https://index.docker.io/v1/": "t2y1979",
  "https://index.docker.io/v1/access-token": "t2y1979",
  "https://index.docker.io/v1/refresh-token": "t2y1979"
}
$ echo "https://index.docker.io/v1/access-token" | docker-credential-desktop get
{"ServerURL":"https://index.docker.io/v1/access-token","Username":"t2y1979","Secret":"***"}

これと同じことを docker のライブラリで行うには次のようにする。取得したい docker image の uri を参照すればコンテナレジストリがわかる。そこからこの cli でやっているようなことを順番にやっていけばよい。これらのユーティリティは3つのリポジトリで管理されていて、この雰囲気をみただけでもこのモジュール分割が本当に適切なんやろか?とか思ったりもする。

func getRegistryAuthFromImage(
	ctx context.Context, imageURI string,
) (string, error) {
	ref, err := reference.ParseNormalizedNamed(imageURI)
	if err != nil {
		return "", fmt.Errorf("failed to parse image uri: %w", err)
	}
	repo, err := registry.ParseRepositoryInfo(ref)
	if err != nil {
		return "", fmt.Errorf("failed to parse repository: %w", err)
	}
	dcli, err := command.NewDockerCli()
	if err != nil {
		return "", fmt.Errorf("failed to create docker cli: %w", err)
	}
	auth := command.ResolveAuthConfig(ctx, dcli, repo.Index)
	encoded, err := command.EncodeAuthToBase64(auth)
	if err != nil {
		return "", fmt.Errorf("failed to encode auth: %w", err)
	}
	return encoded, nil
}

コンテナの運用ツールを作る

1時に寝て明け方に起きて7時に起きた。あまり眠れてない雰囲気がある。

docker/compose の運用

いま作っているアプリケーションは docker compose で構成している。マージ単位で gitlab ci/cd から docker image をビルドしていて、テスト環境のデプロイスクリプトで最新の docker image を取得してコンテナを再作成するようにしている。デプロイスクリプトは docker cli と docker compose cli の2つを組み合わせてシェルスクリプトで実装しているが、複数のアプリケーションやミドルウェアがあるのでそれらを統合的に扱うことはできないし、さまざまな状況を想定して動くようにもなっていない。がんばれば doccker/compose cli と jq とシェルスクリプトで細かい要件を実装することもできるけど、それをお客さんの本番環境においても使うには一定の cli 操作に慣れが必要な上、docker/compose の知識やスキルも要求してしまう。少なくとも頻繁にある運用作業として docker image の更新やコンテナの再作成が想定される。ローリングアップデートまでは実装しないけど、アプリケーションの要件にあわせた docker image の更新とコンテナの再作成 (アプリケーションの再起動) ぐらいはまとめてやってしまってよいと思う。

github.com/docker/docker は github.com/moby/moby にリダイレクトされる。docker は開発ツール、moby はインフラやライブラリという住み分けでそれぞれに関心のあることに注力するようにモジュール構成を分離している。それが2019年に行われていまもおそらくまだ途上だと思う。あと docker のモジュール群は go modules に対応していない。大きなプロジェクトが移行するのが大変なのは理解できるけれど、依存解決のような複雑なところを放置するのはまったく賛成できない。そこが不健全だと依存ライブラリの整理やモジュール分割がうまく進まない気がする。docker の client は https://github.com/moby/moby/tree/master/client に定義されていて、readme のサンプルコードにあるようにすぐに使えるようになっている。一方で compose の spec は github.com/compose-spec/compose-go で定義されていて、github.com/docker/compose はまだライブラリとして使いやすいようにはなっていない。仕様と実装が混在していて動いている状態。そういう issue もあげられている。

testcontainers-go というアプリケーションが compose をライブラリとして使う実装をしている。このコードをみれば compose をどう使えばよいのかはわかる。

自分で compose を実装してもよいけれど、compose の service を扱うための project やオプションの設定が煩雑なことも伺える。仕様と実装が混在しているというのはそこら変の整理ができていないようにみえるからだ。testcontainers-go も自前の client を用意して使いやすいようにしているのでそれを再利用した方が運用ツールを作るのは簡単になる。testcontainers-go の compose client 経由で compose の up/down を制御する。その他のコンテナの操作は docker client を直接使って実装する。この組み合わせで自分たちのアプリケーション向けの運用ツールを作ろうと思う。