TenForward

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

(内容おかしいので書き直し中) docker 0.9 で導入された libcontainer の Namespace 関連の処理

(2014-03-25) 書き直しました.このエントリも参考になる部分はあるので置いときます ^^;(改訂版) docker 0.9 で導入された libcontainer の Namespace 関連の処理 - TenForwardの日記
(2014-03-25) このエントリは色々おかしいので書きなおし中です
(2014-03-16) 再度,Clone と Setns の部分の表現を修正しています.
(2014-03-11) 公開後,細かい所を修正しています.

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 はコードもシンプルで,他に Namespace 関係の処理がなされている所はなさそうです.

そこで,リファレンス実装である nsinit (docker/pkg/libcontainer/nsinit) を見てみると... docker/pkg/libcontainer/nsinit/execin.go 内で

func (ns *linuxNs) ExecIn(container *libcontainer.Container, nspid int, args []string) (int, error) {
        for _, ns := range container.Namespaces {
                if err := system.Unshare(ns.Value); err != nil {
                        return -1, err
                }
        }
    :(snip)

他にも

if err := system.Setns(fd, 0); err != nil {

とか

if err := system.Unshare(syscall.CLONE_NEWNS); err != nil {

とかいう行がありますね.libcontainer を使って,ライブラリの Namespace 関係の関数を呼ぶというよりは,libcontainer でコンテナ環境を定義して,その定義を使って自前で docker/pkg/system の system.Unshare / system.Setns を使ってコンテナを作るという感じのようですね.

system.Unshare と system.Setns

system.Unshare は go の持つ syscall パッケージにある syscall.Unshare を呼んでるだけです.docker/pkg/system/calls_linux.go

func Unshare(flags int) error {
        return syscall.Unshare(flags)
}

というのがあります.おそらくそのまま unshare(2) を呼ぶだけでしょう.

system.Setns の場合は syscall パッケージにはありませんので,RawSyscall ってのを使って定義されているようです.docker/pkg/system/setns_linux.go に

var setNsMap = map[string]uintptr{
        "linux/amd64": 308,
}

func Setns(fd uintptr, flags uintptr) error {
        ns, exists := setNsMap[fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)]
        if !exists {
                return ErrNotSupportedPlatform
        }
        _, _, err := syscall.RawSyscall(ns, fd, flags, 0)
        if err != 0 {
                return err
        }
        return nil
}

setNsMap 変数はマップで,現時点では『"linux/amd64" 環境で 308 番のシステムコール』という値だけが登録されていますね.で linuxamd64 環境で実行すると runtime.GOOS, runtime.GOARCH がそれぞれ linux, amd64 となって,ns には 308 番が入るんですかね (文法知らないので当てずっぽう).

linux/amd64 で 308 番はというと... Linux 環境で

$ grep 308 /usr/include/asm/unistd_64.h 
#define __NR_setns 308

setns になってます (たぶん^^;).

結論

長々と書きましたが...

libcontainer を使ってコンテナを定義したあとに自分で system.Unshare を呼び出してコンテナを作ったり,system.Setns を呼び出してコンテナに入ったりしましょう (2014-03-16 一部修正).

整理されてませんが,とりあえず.

(補足)

nsinit では上記で説明した Namespace 関連の処理をやる前にマウント名前空間関連の処理を行っています.色々マウントして,pivot_root するという,lxc と同じ処理の流れですね (多分).docker/pkg/libcontainer/nsinit/mount.go を参照してみてください.(新しい mount namespace に入るのはやはり execin.go 内の ExecIn 内でです.)