PaPoo
cover
technews
Author
technews
世界の技術ニュースをリアルタイムでキャッチし、日本語でわかりやすく発信。AI・半導体・スタートアップから規制動向まで、グローバルテックシーンの「今」をお届けします。

Dockerは「箱」ではなく、かなり妙な寄せ集めだった

Docker を日常的に使っている人でも、実際にその中身がどう動いているかまで意識する機会は多くありません。今回の元記事は、著者が自作の container runtime を作り始めたところで、Docker の正体が思った以上に変わった仕組みの集まりだと気づいた、という話です。単なる雑談ではなく、普段「当たり前」に見えるツールが、どれだけ Linux の素の機能や妙な工夫の上に乗っているかを、かなり素朴な言葉でほどいています。読み終えると、コンテナが便利な理由だけでなく、なぜ扱いが難しいのかも少し見えてきます。

著者が自作 runtime で見つけた、Docker の変な素顔

元記事の著者は、遊び半分で自分の container runtime を作り始めたところ、Docker について「変だ」と感じる発見がいくつもあったと書いています。まず強調するのは、コンテナは VM のような“本当に独立した箱”ではない、という点です。コンテナの中で動くのは結局ただの process で、kernel はその process を普通に動かしています。見ようと思えば /proc の中からその process を見つけることもできる。隔離されているように見えても、完全に別世界が作られているわけではない、ということです。

さらに著者は、Linux に「コンテナ」という単一の機能があるわけでもない、と説明します。実際には複数の kernel feature を束ねて、コンテナらしさを作っています。名前空間で host から見えるものを隠し、cgroups で使える資源量を制限し、ネットワークについては著者自身も「よく分からない」と言うような仕組みで、外から切り離されたような環境を作っています。つまり Docker は、ひとつの魔法で成立しているのではなく、Linux のいくつもの道具を組み合わせているわけです。

image の扱いも独特です。著者によれば、イメージは実質的には folder で、Docker はそれをどこかに置いたうえで chroot か pivot_root を使い、その folder を container process の新しい root directory にしてしまいます。たとえば docker run -it --rm ubuntu /bin/bash を実行すると、Docker はまず ubuntu image を Dockerhub からダウンロードし、それをどこかに格納し、そのうえで /some/weird/docker/path/bin/bash のような場所にあるものを起動する、という流れだと説明しています。見た目は「ubuntu を起動する」ですが、裏ではファイルの置き場所と root の付け替えが起こっている、という理解です。

著者が意外だったのは、Docker の container runtime である runc が Go で書かれていることにもあります。Go は便利な言語ですが、runtime としては厄介な面がある、と元記事は言います。Go の runtime は最初から multi-threaded で、それ自体は優れた特徴なのに、container runtime が必要とする一部の Linux syscall は single-threaded な環境で行う必要があります。そのため runc には、Go の runtime が初期化される前に動く C module があり、そこから特別な処理をしているそうです。

さらに Go には fork がありません。コンテナを立ち上げるときは、まず fork して制限を設定し、そのあとで最終的に docker run で指定された process を exec するのが自然ですが、Go ではその流れをそのまま書けない。そこで runc は /proc/self/exe を特別な command line argument 付きで exec し、child process であることを識別させることで、fork に近い動きを実現していると紹介しています。かなり回りくどいですが、そうしないと都合が悪いということです。

最後に著者は、Docker は root 権限を必要とする、という点にも触れます。MacOS 上で Docker を使っていると見えにくいが、Linux kernel API の多くは root での実行を必要とし、少なくとも以前はそうだったため、Docker daemon は通常 root で動いてきました。著者は、コンテナのセキュリティを扱う人たちは本当に大変だろう、と述べています。最近は rootless の container runtime も進んでいるものの、特に networking の部分では、ユーザ空間でネットワーク stack をかなり再実装しないと同等の機能を得られないらしい、としています。記事の締めくくりは、自分で version を作ってみると学びが多い、という実感でした。

「便利な箱」の裏で、なぜこんなに継ぎはぎになるのか

この文章を読んでまず面白いのは、Docker の“変さ”が欠陥ではなく、むしろ Linux 上でコンテナを実用にするための現実的な妥協の積み重ねだと分かるところです。fork が使いにくい、root が要る、networking が難しい。どれも単独では地味な話ですが、合わせると「きれいな抽象」と「実際の実装」がかなり離れているのが見えてきます。私はここに、Docker が長く使われてきた理由もあると思います。理想的に美しい仕組みではなく、既存の kernel 機能を無理なく束ねて、開発者が使える形まで落としたからこそ広がったのではないでしょうか。

一方で、著者の説明はややラフでもあります。たとえば「container は 5 つの Linux kernel features in trenchcoat」という言い方は分かりやすい反面、実際の隔離や制御はもっと複雑です。namespace と cgroups だけで完結するわけではないし、storage、seccomp、capabilities なども絡みます。とはいえ、この記事の狙いは正確な仕様書ではなく、内部の“感じ”をつかませることにあります。その意味では、細部を削ってでも「ひとつの機能ではなく、いくつもの仕組みの束だ」と伝えたのはうまい。初学者が Docker をブラックボックスのまま使わなくなる入口として、十分に効いていると思います。

runc が Go であることが、そんなに自然ではない理由

個人的に引っかかったのは、Go の話です。Docker といえば Go の印象が強いですが、著者が書いている通り、container runtime のような低レベル寄りの道具では、言語の便利さより runtime の性質が前面に出ます。multi-threaded であることは普通なら利点なのに、ここでは邪魔になる。fork がないことも、一般的なアプリ開発では大きな問題にならないのに、runtime ではかなり痛い。つまり「良い言語かどうか」は用途次第で、同じ特徴が別の文脈では足かせになる、ということです。

この点は、近年の基盤技術を見ているとよく出てくる話でもあります。高級な抽象を得るために、どこかで低レベルの制約を飲み込む必要がある。Go はネットワークサービスや CLI には強いですが、kernel に密着する領域では余計な都合が出る。そのため runc は、Go の前に C を差し込んだり、/proc/self/exe を使って自分自身を再実行したりと、かなり器用なことをしている。私はこの“器用さ”こそが、現代のインフラソフトウェアの本質に近いと思います。きれいな一枚岩ではなく、複数の言語と複数の段取りをつないで、ようやくひとつの機能になるからです。

rootless が広がっても、networking だけは簡単に終わらない

root 権限の話も、単に「Docker は危ない」で終わらないところが重要です。著者は MacOS では見落としやすいと書いていますが、これはかなり本質的です。利用者が普段見ている Docker は、すでにどこかで権限問題を吸収した姿で動いていることが多い。だからこそ、実際には daemon が root で動いている事実や、そこに伴うセキュリティ上の責任が見えにくいのだと思います。

さらに興味深いのは、rootless にしても networking が大変だという指摘です。ここは「権限を減らせば安全になる」で終わるほど単純ではありません。既存の機能を root なしで再現しようとすると、結局は userspace でネットワーク stack を作り直す必要が出てくる。つまり、rootless 化は理想論というより、実装コストとの綱引きです。私はこの話に、今後のコンテナ基盤の伸びしろと限界の両方が見えると思います。安全性のために権限を絞る方向は間違っていない。でも、その分だけ複雑さが別の場所に移る。見えやすい危険が減っても、運用の難しさが消えるわけではないのです。

自分で作ってみると、使う側では見えないものが見える

この元記事が一番説得力を持つのは、著者が「遊びで自作している途中」だと明かしている点です。完成した知識を説明しているのではなく、手を動かして初めて分かったことを話している。そこには、単なる解説記事とは違う温度があります。私も、インフラ系の道具は使うだけだと便利さしか見えないが、作る側に回ると“なぜそうなっているか”の泥臭さが急に見えると思います。Docker もその典型です。名前はひとつでも、中身は kernel 機能の寄せ集めで、言語の制約を抜ける工夫があり、権限や networking の問題が残る。だからこそ実用になっているし、だからこそ完全には単純化できない。

この文章は、Docker の仕組みを知っている人には「そうそう、結局そこが面倒なんだよね」と頷かせるし、知らない人には「コンテナって思ったより不思議なものなんだ」と伝える力があります。実務では Docker を使えれば十分な場面も多いですが、基盤に近い仕事をするなら、このくらいの“気味の悪さ”を知っておく価値は大きい。便利なツールほど、裏側は意外と泥くさい。その感覚を思い出させる記事でした。


参考: Docker is weird

同じ著者の記事