コンテナ
仮想化は、使いたいサービスだけでなくOSも含めてまるごと動かすことが前提の仕組みです。そのため、たとえばゲストOSとホストOSが同じLinuxであればカーネルやシステムのデーモンを重複してロードすることになり、無駄にメモリを消費してしまいます。そこで、「OSのカーネルはホストのものをそのまま使うが、アプリケーションから見て自由に使えるOS環境が手に入る」の実現に特化したのがコンテナと呼ばれる技術です。「アプリケーションが好き勝手にしても全体が壊れないような、他のアプリケーションに干渉しない・されない箱を作る」という機能だけ見ると、仮想化もコンテナも同じであるため、コンテナのことを「OSレベル仮想化」と呼ぶこともあります。 仮想化ではストレージをまるごとファイル化したような仮想イメージを使ってアプリケーションを導入しますが、コンテナでもイメージと呼ばれるものを使います。
コンテナを実現するためのOSカーネルの機能
一口にコンテナ技術といっても、内部では複数の機能を組み合わせて実現されています。たとえばLinuxでは、コンテナを実現するためのOSカーネルの機能として、コントロールグループ(cgroups)および名前空間(Namespaces)があります。これらの機能を組み合わせることで、さまざまなOSのリソースを、仮想メモリを用意するように気軽に分割できます。 コントロールグループ(cgroups)は、次の項目の使用量とアクセスを制限できる ようにするカーネルの機能です。
また、カーネルでは、次のような項目について名前空間(Namespaces)を分離できるようになっています。
-プロセスID - ネットワーク(インタフェース、ルーティングテーブル、ソケットなど) - マウント(ファイルシステム) - UTS(ホスト名) - IPC(セマフォ、MQ、共有メモリなどのプロセス間通信) - ユーザー(UID、GID)
libcontainerでコンテナを自作する
Linux環境でのみ動作する
コンテナのブートに必要な下準備
docker pull alpine docker run --name alpine alpine docker export alpine > alpine.tar docker rm alpine mkdir rootfs tar -C rootfs -xvf alpine.tar go get github.com/opencontainers/runc/libcontainer ⏎ go get golang.org/x/sys/unix
package main import ( "github.com/opencontainers/runc/libcontainer" "github.com/opencontainers/runc/libcontainer/configs" _ "github.com/opencontainers/runc/libcontainer/nsenter" "log" "os" "runtime" "path/filepath" "golang.org/x/sys/unix" ) func init() { if len(os.Args) > 1 && os.Args[1] == "init" { runtime.GOMAXPROCS(1) runtime.LockOSThread() factory, _ := libcontainer.New("") if err != factory.StartInitialization(); err != nil { log.Fatal(err) } panic("--this line should have never been executed, congratulations--") } } func main() { abs, _ := filepath.Abs("./") factory, err := libcontainer.New(abs, libcontainer.Cgroupfs, libcontainer.InitArgs(os.Args[0], "init")) if err != nil { log.Fatal(err) return } capabilities := []string{ "CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FSETID", "CAP_FOWNER", "CAP_MKNOD", "CAP_NET_RAW", "CAP_SETGID", "CAP_SETUID", "CAP_SETFCAP", "CAP_SETPCAP", "CAP_NET_BIND_SERVICE", "CAP_SYS_CHROOT", "CAP_KILL", "CAP_AUDIT_WRITE", } defaultMountFlags := unix.MS_NOEXEC | unix.MS_NOSUID | unix.MS_NODEV config := &configs.Config{ Rootfs: abs+"/rootfs", Capabilities: &configs.Capabilities{ Bounding: capabilities, Effective: capabilities, Inheritable: capabilities, Permitted: capabilities, Ambient: capabilities, }, Namespaces: configs.Namespaces([]configs.Namespace{ {Type: configs.NEWNS}, {Type: configs.NEWUTS}, {Type: configs.NEWIPC}, {Type: configs.NEWPID}, {Type: configs.NEWNET}, }), Cgroups: &configs.Cgroup{ Name: "test-container", Parent: "system", Resources: &configs.Resources{ MemorySwappiness: nil, AllowAllDevices: nil, AllowedDevices: configs.DefaultAllowedDevices, }, }, MaskPaths: []string{ "/proc/kcore", "/sys/firmware", }, ReadonlyPaths: []string{ "/proc/sys", "/proc/sysrq-trigger", "/proc/irq", "/proc/bus", }, Devices: configs.DefaultAutoCreatedDevices, Hostname: "testing", Mounts: []*configs.Mount{ { Source: "proc", Destination: "/proc", Device: "proc", Flags: defaultMountFlags, }, { Source: "tmpfs", Destination: "/dev", Device: "tmpfs", Flags: unix.MS_NOSUID | unix.MS_STRICTATIME, Data: "mode=755", }, { Source: "devpts", Destination: "/dev/pts", Device: "devpts", Flags: unix.MS_NOSUID | unix.MS_NOEXEC, Data: "newinstance,ptmxmode=0666,mode=0620,gid=5", }, { Device: "tmpfs", Source: "shm", Destination: "/dev/shm", Data: "mode=1777,size=65536k", Flags: defaultMountFlags, }, { Source: "mqueue", Destination: "/dev/mqueue", Device: "mqueue", Flags: defaultMountFlags, }, { Source: "sysfs", Destination: "/sys", Device: "sysfs", Flags: defaultMountFlags | unix.MS_RDONLY, }, }, Networks: []*configs.Network{ { Type: "loopback", Address: "127.0.0.1/0", Gateway: "localhost", }, }, Rlimits: []configs.Rlimit{ { Type: unix.RLIMIT_NOFILE, Hard: uint64(1025), Soft: uint64(1025), }, }, container, err := factory.Create("container-id", config) if err != nil { log.Fatal(err) return } process := &libcontainer.Process{ Args: []string{"/bin/sh"}, Env: []string{"PATH=/bin"}, User: "root", Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr, } err = container.Run(process) if err != nil { container.Destroy() log.Fatal(err) return } _, err = process.Wait() if err != nil { log.Fatal(err) } container.Destroy() }