まずはツールの紹介
昨今、注目を浴びているアプリケーションコンテナを作ったり管理したりできるDocker。
そろそろ中身も理解しておきたいところ。特に最近はlibcontainerというのができて、lxc依存すら切り離されているとの噂。
ただ、結構ソースコードの量も大きくなっているので(2014/4月現在 テストコード除いて70000行弱)、
さっと大まかに理解するためのツールを書いてみました。
このツールはgoのastからパッケージの間の依存関係を読んで、graphvizの解釈するdot言語に吐き出すツールです。
たとえば、最近話題のgo-xslateの全体像をつかもうと思ったら、
goviz -i github.com/lestrrat/go-xslate | dot -Tpng -o xslate.png
のようにすることで、
拡大
こんな感じにパッケージ間の依存関係を図示することができます。
これだけでも何となくどんな構造になってるかわかってきそうですね。
不安定性の表示
govizにはdot言語の出力の他にinstabilityの出力ができます。instability(不安定性)とは、詳しい説明はwikipediaに任せるとして、この数値が高い方がアプリケーション本体に近く、低い方が共通ライブラリとしての性質が強いものだとわかります。
goviz -i github.com/dotcloud/docker/docker -m | head (r:0 s:5 j:0)
Inst:1.000 Ca( 0) Ce( 9) github.com/dotcloud/docker/docker
Inst:0.960 Ca( 1) Ce( 24) github.com/dotcloud/docker/pkg/libcontainer/nsinit
Inst:0.956 Ca( 2) Ce( 43) github.com/dotcloud/docker/runtime
Inst:0.950 Ca( 1) Ce( 19) github.com/dotcloud/docker/api/client
Inst:0.950 Ca( 1) Ce( 19) github.com/dotcloud/docker/server
Inst:0.909 Ca( 1) Ce( 10) github.com/dotcloud/docker/api/server
Inst:0.867 Ca( 2) Ce( 13) github.com/dotcloud/docker/runtime/execdriver/native
Inst:0.857 Ca( 1) Ce( 6) github.com/dotcloud/docker/runtime/graphdriver/devmapper
Inst:0.833 Ca( 1) Ce( 5) github.com/dotcloud/docker/runtime/graphdriver/aufs
Inst:0.800 Ca( 1) Ce( 4) github.com/dotcloud/docker/builtins
ソースコードをトップダウンで理解していく場合、これらの上位にあがっているパッケージを理解していけば大まかな挙動を理解することができるでしょう。
全体像
govizですべてのパッケージを解析する場合、複雑になりすぎてしまうので解析する深さを指定します。
goviz -i github.com/dotcloud/docker/docker -s github.com/dotcloud/docker -d 2 | dot -Tpng -o docker2.png
これをみるとclient、buildins、sysinitがそれぞれトップレベルの依存関係にあることがわかります。
実際、docker.goを読んでみると
if selfPath := utils.SelfPath(); strings.Contains(selfPath, ".dockerinit") {
// Running in init mode
sysinit.SysInit()
return
}
/*中略*/
if *flDaemon {
eng, err := engine.New(realRoot)
builtins.Register(eng)
/* ry */
}else {
/*中略*/
cli = client.NewDockerCli(os.Stdin, os.Stdout, os.Stderr, protoAddrParts[0], protoAddrParts[1], &tlsConfig)
/*後略*/
}
となっていて、
- sysinitとして機能する場合
- builtinsとして機能する場合
- clientとして機能する場合
があることがわかります。
sysinit以下をみる
では、まずsysinitから。
goviz -i github.com/dotcloud/docker/sysinit -s github.com/dotcloud/docker -d 2 | dot -Tpng -o sysinit.png
execdriver以下にnativeとlxcがあります。この辺りをプラガブルにすることで、lxcがない環境でも動くようにしているのでしょう。
import (
"github.com/dotcloud/docker/runtime/execdriver"
_ "github.com/dotcloud/docker/runtime/execdriver/lxc"
_ "github.com/dotcloud/docker/runtime/execdriver/native"
)
sysinitが実行されるフェーズでは、
実際にはこのようにlxcとnativeパッケージは読み込まれているだけで、それぞれのinit関数で定義された初期化の処理を行っているようです。
builtins以下をみる
goviz -i github.com/dotcloud/docker/sysinit -s github.com/dotcloud/docker -d 2 | dot -Tpng -o sysinit.png
これをみるとbuiltinの機能としてはnetworkdriver/bridgeとapi/serverとserverの3つの依存を持っているようです。
builtins.goには次のようなコメントがありました。
// daemon: a default execution and storage backend for Docker on Linux,
// with the following underlying components:
//
// * Pluggable storage drivers including aufs, vfs, lvm and btrfs.
// * Pluggable execution drivers including lxc and chroot.
//
// In practice `daemon` still includes most core Docker components, including:
//
// * The reference registry client implementation
// * Image management
// * The build facility
// * Logging
//
// These components should be broken off into plugins of their own.
//
これをよむとdaemonはプラガブルなストレージと実行環境を提供するもので、
それぞれ選べるようにしたいのだけど、まだ依存が切れてないよというようなことらしいです。
client以下をみる
goviz -i github.com/dotcloud/docker/api/client -s github.com/dotcloud/docker -d 2 | dot -Tpng -o c.png
これをみると、様々なコマンドをdocker/engineを通じて、実行しているのではないかと推察できます。
ボトムアップに調べてみる
これまでちょくちょく出てきたdocker/engineが気になってきました。
govizでは特定のモジュールに関して、依存関係を逆にたどった図を作成することができます。
docker/engineがどのように依存されているのかをたどってみましょう。
goviz -i github.com/dotcloud/docker/docker -s github.com/dotcloud/docker/ -f github.com/dotcloud/docker/engine | dot -Tpng -o d.png
このように特定のパッケージがどこから使われているかということをmainまでたどることで、engineがclient、serverをつなぐための仕組みではないかと予想ができます。
// The Engine is the core of Docker.
// It acts as a store for *containers*, and allows manipulation of these
// containers by executing *jobs*.
適当に訳すと、
EngineはDockerのコアです。これはコンテナのストレージとして振る舞ったり、ジョブの実行を通じてそれらのコンテナを操作します。
ということらしいです。
#execdrivers
そういえば、さきほどの初期化されたexecdriverはどのように実行されるのでしょうか。
native、lxcのファクトリとしてexecdriversがあったのでこれを逆順にたどってみましょう。
すごくシンプルです。runtimeを通じて実行されると。
では、nativeとlxcがどのようになっているか見ていきましょう。
func NewDriver(name, root, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
switch name {
case "lxc":
// we want to five the lxc driver the full docker root because it needs
// to access and write config and template files in /var/lib/docker/containers/*
// to be backwards compatible
return lxc.NewDriver(root, sysInfo.AppArmor)
case "native":
return native.NewDriver(path.Join(root, "execdriver", "native"), initPath)
}
return nil, fmt.Errorf("unknown exec driver %s", name)
}
ここのコメントの意味つかみかねてるんで、誰か教えてください。
native( libcontainer )
ようやく話題のlibcontainerが出てきました。では依存関係を逆にたどってみましょう。
lxc
次は現役を退いたlxc。
#まとめ
比較的規模の大きいツールを読むときには、ソースコード解析ツールを作ると便利。