TenForward

技術ブログ。はてなダイアリーから移転しました

(改訂版) docker 0.9 で導入された libcontainer の Namespace 関連の処理

先に書いたエントリ はそれなりにアクセスがあるようですが,実は結構デタラメで間違いがあることに気づいたので,改めて調べて書きなおしてみました.相変わらず Go シロートですので,このエントリも間違ってる可能性はありますので,興味のある方はご自分でお調べになることをおすすめします.

というわけで...

docker 0.9 がリリースされて,なんかすごいことになっていますね.その辺りの詳しくは

をどうぞ.

さて,このバージョンの目玉のドライバさえ書けばどんなコンテナツール・ライブラリでも docker 動かせるよ,ってのは置いといて,特定のツール・ライブラリに依存しない libcontainer というものが導入されてます.ここでどうコンテナというか,Namespace を作成しているのかというのが知りたかったので,ちょっと調べてみました. ちなみに Go 言語はさっぱりわかりませんので用語とか間違ってるかも? ^^;

docker リポジトリを clone して調べてみると...

docker/pkg/libcontainer/types.go 内で Namespace という構造体 (?) とその配列の Namespaces が定義されています.

type (
        Namespace struct {
                Key   string
                Value int
                File  string
        }
        Namespaces []*Namespace
)

その他,Namespace 関係の処理が色々書かれてますね.

docker/pkg/libcontainer/container.go に Container という構造体が定義されていて Namespaces というメンバ (?) がいます.

type Container struct {
    :(snip)
        Namespaces   Namespaces      `json:"namespaces,omitempty"`    // namespaces to apply
    :(snip)
}

と,調べたものの,libcontainer 直下 (pkg/libcontainer) はコードもシンプルで,他に Namespace 関係の処理がなされている所はなさそうです.

nsinit 以下を見てみる

そこで "nsinit is a cli application used as the reference implementation of libcontainer." とある nsinit 以下を見てみました.

(言い訳) ここでリファレンス参照となっているのは pkg/libcontainer/nsinit/nsinit 以下のモノで pkg/libcontainer/nsinit 以下は libcontainer 内部から使われている libcontainer の一部みたいですね.最初誤解していたのも,先のブログエントリのような事になってしまった原因の一つです.(言い訳ここまで)

nsinit は "nsinit exec /bin/bash" みたいに実行して,事前に json ファイルで定義した Namespace の中でコマンドを実行するようです (→ https://github.com/dotcloud/docker/tree/master/pkg/libcontainer )

さて,ここからまたアテにならないコードの解説が始まります.間違いの指摘歓迎です.
pkg/libcontainer/nsinit/nsinit/main.go を見ると

		if nspid > 0 {
			exitCode, err = ns.ExecIn(container, nspid, flag.Args()[1:])
		} else {
			term := nsinit.NewTerminal(os.Stdin, os.Stdout, os.Stderr, container.Tty)
			exitCode, err = ns.Exec(container, term, flag.Args()[1:])
		}

という部分があります.このうち "ExecIn" は既にコンテナが存在する場合にコンテナ内で nsinit を実行した場合の処理のようですから,"Exec" の方を見ます.これは pkg/libcontainer/nsinit/exec.go で定義されており,キーとなる部分は以下です.

func (ns *linuxNs) Exec(container *libcontainer.Container, term Terminal, args []string) (int, error) {
    :(snip)
	command := ns.commandFactory.Create(container, console, syncPipe.child, args)
    :(snip)
	if err := command.Start(); err != nil {
		return -1, err
	}
    :(snip)

これだけじゃ何がなんだかわからんのですが,とりあえず command っぽいものを作って,command.Start() で実行してるっぽいという想像はつきます.

Clone フラグの設定

まず,Clone フラグについて簡単に触れておきますが,これは Linuxシステムコール clone(2) を実行する時に与えるフラグの事です.詳しくはマニュアル(man 2 clone)をご覧ください.このフラグで Namespace を作成できます.

まずは ns.commandFactroy.Create の部分ですが,これは pkg/libcontainer/nsinit/command.go にあります.

func (c *DefaultCommandFactory) Create(container *libcontainer.Container, console string, pipe *os.File, args []string) *exec.Cmd {
    :(snip)
	system.SetCloneFlags(command, uintptr(GetNamespaceFlags(container.Namespaces)))
    :(snip)
	return command
}

定義されている Namespace の clone フラグを取得して,system.SetCloneFlags で設定しています (多分ね).pkg/system/calls_linux.go 内で

func SetCloneFlags(cmd *exec.Cmd, flag uintptr) {
	if cmd.SysProcAttr == nil {
		cmd.SysProcAttr = &syscall.SysProcAttr{}
	}
	cmd.SysProcAttr.Cloneflags = flag
}

システムコールを実行するときの attribute に clone フラグを設定してる感じですかね (?)

コマンド実行

あとは command.Start() が実行されているだけなので,きっと先に設定した clone フラグが設定されてコマンドが実行されているだろう,と思うのですが,一応 clone(2) の実行っぽい所まで確認してみました.ここからは Go 言語自身のコードを追ってます.

という風に Go 標準の機能を使って clone が実行されてるようですね (かなり当てずっぽう).

まとめ

というわけで 先に書いた変なエントリ を書きなおしてみました.

とりあえず Clone フラグを渡して clone(2) が実行されてるっぽいのが確認出来て納得です.とはいえ,シロートの調査ですので,このエントリを信じないで,ご自分で Linuxシステムコールが呼ばれて,コンテナ (Namespace) が作られている事を確認されることをオススメします.

先のエントリで書いた Setns や Unshare は,libcontainer の機能として,既に存在するプロセスやコンテナに対する処理を行うために存在する機能であると思われます.この辺りをおっかける参考にはなるかもしれないので,恥ずかしい間違いだらけの先のエントリも残しておきますね.