🐳

Dockerのソースコードを読む

2021/02/03に公開
※本記事は2020年のアドベントカレンダーで書いた内容です。
技術的に誤った情報があった場合はコメント欄等で訂正入れていただけると助かります。

「私にどうか……Dockerを理解できる力をください」

私達が普段Dockerを初学者に教えたり教えてもらったりする場合、人にもよるかと思うが大半は抽象的な概念としてのコンテナ技術についてと実際にDockerを使う際のコマンドなどを教えるみたいなことが多いと思います。
また、実際にDockerを使う際にバグに当たったりしない限りはDockerのソースコードを読むことはないというのが実際だと思います。(気が触れたりしない限りは)
本記事では、実際にDockerがどうやってネットワークを作成しているのかを例として追ってみます。

「エンジニア君 これは命令です 今後は変なまとめブログではなく正しい情報を見るといいなさい」

まず、Dockerのソースコードについてです。
現在、Dockerのバージョンは2020年12月8日にDocker 20.10がリリースされたばかりです。
Dockerのソースコードは以前は以下のリポジトリを参照することになっていました。
https://github.com/docker/docker-ce
しかし、↑のリポジトリのREADME.mdにも書いてあるとおり、Docker20.10リリース以降はDockerEngineとDockerCLIのパッケージは以下のそれぞれのリポジトリで開発が進められることになりました。
今後は上記の docker/docker-ce リポジトリはアーカイブされ、非推奨な情報となるらしいです。
↓今後のDockerEngineのリポジトリ↓
https://github.com/moby/moby
↓今後のDockerCLIのリポジトリ↓
https://github.com/docker/cli
また、今後のリリースノートはCHANGELOGではなく、以下のrelease notesに書かれることになりました。
https://docs.docker.com/engine/release-notes/

「オマエもGo言語最高と叫びなさい!」

今回はDockerEngineのソースコードを読むことになるので、以下のリポジトリを見ていくことになります。
https://github.com/moby/moby
GithubのLanguagesを見て分かる通り、DockerEngineは主にGo言語で書かれています。
話は脱線しますが、個人的にGo言語は可読性が高くなるように書かれがちなので、ソースコードを読むのはそこまで難しくないと思います。
実際に読むにあたってGo言語に入門したい方はメルカリの@tenntennさんが公開されている資料などを読むのが良いでしょう(自分はそれを読ませていただきました。)
https://engineering.mercari.com/blog/entry/goforbeginners/
まず、今回の目的はネットワーク関係の関数なりなんなりを見つけることで、DockerEngineがどのようにしてネットワークの作成をしているかを調べることです。
ですが、突然DockerEngineのソースコードを見ても私達はDockerEngineを直接叩いているわけではないので、いまいちイメージが湧きづらいと思います。(分かる人はわかるかと思いますが…)
なので、最初はCLIのコマンドから糸を手繰っていくのが良いでしょう。
前項ではDockerCLIは docker/cliといったリポジトリに存在しているということですが、今回は moby/moby/client に存在する Go client for the Docker Engine API を参照します。
ここには色々とコマンド名チックなファイルがたくさんあり、見渡すと network_create.go が存在していることがわかります。(名前的にそれっぽいのがありますね)
https://github.com/moby/moby/blob/v20.10.0/client/network_create.go
ココを見ると cli.post() を使ってDockerEngineに対してPOSTを使ってnetworkを作るように送っているようです。
ということは、DockerEngineでそのデータを受け取っているはずなので、それらしいファイルを探してみます。
探す際は適当に探索してもいいんですが、それだとツライ気持ちもあるのでリポジトリ内検索機能を使います。
https://github.com/moby/moby/search?q=network
すると、こういった感じなのがでてきます。
その中を見ていくと daemon/xxxx みたいな怪しい名前が見つかります。
なので、 moby/moby/daemon に行くとこのようなファイルが見つかります。
https://github.com/moby/moby/blob/v20.10.0/daemon/network.go
名前的にもどうやらdaemonのnetwork周りを実装してそうな雰囲気です。
とりあえずザッと流し読みをしていくと createNetwork という名前の如何にもな関数を見つけることが出来ます。
https://github.com/moby/moby/blob/v20.10.0/daemon/network.go#L293

これがア!!libnetworkの力だああああ!!

どうやら前項の createNetwork関数内を見てみるとlibnetworkを参照しているようなので moby/libnetwork が色々やっているようです。
では、このリポジトリがどんなものなのか見てみましょう。
https://github.com/moby/libnetwork
README.mdを見てみると、どうやらこのリポジトリは「コンテナネットワーク全般をうまいことやってやるぜ!」みたいなことを言っています。
ここからは気合なのですが、oslというディレクトリがあります。
そこを見てみると、 xxxx_[os名] といったファイルが沢山あります。
なので、例えばDockerのネットワークの説明などでよく聞く ip netns のようなコマンドに相当しそうな namespace_linux.go を見てみます。
すると、 createNetworkNamespace といった名前の関数があります。
https://github.com/moby/libnetwork/blob/master/osl/namespace_linux.go#L316
その関数内をよく見てみると
CLONE_NEWNET という名前のsyscallを叩いているようです。
これが何をするsyscallなのかを調べると、以下のようなサイトが見つかります。
https://linuxjm.osdn.jp/html/LDP_man-pages/man2/unshare.2.html
このサイトを見るとどうやら CLONE_NEWNET はネットワークの名前空間を分離することで、ホストのネットワークインターフェースと干渉したりしないようにするといったことをしているようです。
よって、このようにDockerのネットワーク生成時にネームスペースを切るという動作をするプログラムを見ることができました 。

「コンテナの悪魔よ」「私の寿命を数週間与える」「代わりにどうかDockerを…いや……libnetworkを理解させて欲しい」

今回はDockerEngineがどうやってNetworkのNamespaceを切っているかまで見ることが出来ました。
実際、今回の本来の目的のDockerのNetwork周りをソースコードレベルで理解するためには数週間、数ヶ月レベルで時間がかかる可能性もあります。
ですが、本記事を読んで少しでもDockerなどのソースコードリーディングに興味を持ってくれる方がいれば幸いです。
「Dockerのネットワークのソースコードってこんな味かぁ…」

Discussion