Skip to content

Instantly share code, notes, and snippets.

@hayajo
Last active May 7, 2018 09:44
Show Gist options
  • Save hayajo/1f45ee6f8df0e6ef392b279908a67c68 to your computer and use it in GitHub Desktop.
Save hayajo/1f45ee6f8df0e6ef392b279908a67c68 to your computer and use it in GitHub Desktop.
[WIP]runCでDockerイメージを素朴に使う

runCでDockerイメージを素朴に使う

WIP!WIP!

Docker 1.11になってバックエンドがrunC(docker-runc)になったので、drootのように素朴に運用できないか検討してみました。

DockerのイメージをrunCでコンテナ起動する

検証環境のVagrantfileはつぎのとおりです。

developmentとproductionの2つのホストを作成します。 development環境ではdocker利用してコンテナイメージを作成、production環境では作成したコンテナイメージをrunCで実行します。

# -*- mode: ruby -*-
# vi: set ft=ruby :

RUNC_VERSION = "v0.1.1"

Vagrant.configure(2) do |config|
  config.vm.box = "boxcutter/centos72"

  config.vm.define "development" do |development|
    development.vm.hostname = "development"

    development.vm.provision "shell", inline: <<-SHELL
      sudo yum install -y epel-release

      # dockerをインストール
      sudo yum install -y docker-io

      # vagrantユーザーでdockerコマンドを実行できるようにグループを設定
      sudo groupadd docker
      sudo gpasswd -a vagrant docker

      # dockerを起動
      sudo systemctl enable docker
      sudo systemctl restart docker
    SHELL
  end

  config.vm.define "production" do |production|
    production.vm.hostname = "production"

    production.vm.provision "shell", inline: <<-SHELL
      sudo yum install -y epel-release
      sudo yum install -y lxc libseccomp-devel golang git # runCビルドのためのツール/ライブラリ一式をインストール

      # runcをインストール(/usr/local/sbin/runc)
      sudo git clone https://github.com/opencontainers/runc.git /usr/local/src/runc
      sudo /bin/sh -c '
        cd /usr/local/src/runc &&
        git fetch &&
        git checkout tags/#{RUNC_VERSION} &&
	export GOPAHT=$HOME &&
        make &&
        make install
      '
    SHELL
  end

end

vagrant upで検証環境を構築します。

$ vagrant up

development環境でDockerイメージからファイルシステムアーカイブを作成する

$ vagrant ssh development

コンテナイメージを/vagrant/image_alpine.tar.gzに作成します。

development$ docker pull alpine
development$ docker export $(docker create alpine /bin/sh) | gzip > /vagrant/image_alpine.tar.gz # alpineのイメージはCOMMANDが指定されていないので`/bin/sh`を指定する
development$ exit

production環境でファイルシステムアーカイブを元にコンテナを起動する

$ vagrant ssh production
production$ sudo mkdir -p /var/container/alpine/rootfs
production$ sudo tar xzfv /vagrant/image_alipne.tar.gz -C /var/container/alpine/rootfs
production$ cd /var/container/alpine
production$ sudo /usr/local/sbin/runc spec
production$ sudo /usr/local/sbin/runc start alpine # コンテナ内でshが実行される
/ # ls
bin/     etc/     lib/     media/   proc/    run/     sys/     usr/
dev/     home/    linuxrc@ mnt/     root/    sbin/    tmp/     var/

dockerデーモンのような中央集権なデーモンいらずで実行できます。

production$ pa auxfww
...
root      3770  0.0  0.5 193432  2772 pts/0    S+   06:27   0:00  |           \_ sudo /usr/local/sbin/runc start alpine
root      3771  0.3  0.9 198028  4676 pts/0    Sl+  06:27   0:00  |               \_ /usr/local/sbin/runc start alpine
root      3778  0.0  0.0   1512   256 pts/1    Ss+  06:27   0:00  |                   \_ sh
...

config.json

コンテナの挙動を定義した設定ファイル。

docker 1.11.0からバックエンドがrunCになったため、コンテナ起動時に/var/run/docker/libcontainerd/<CONTAINER_ID>/config.json生成される。 (以前の/var/lib/docker/containers/<CONTAINER_ID>/config.jsonとは違うので注意)

config.jsonの仕様はこちら。

runc specconfig.json生成し、これを編集してコンテナの挙動を定義する。

config.jsonの簡単な解説

runc startでコンテナ内で実行されるコマンドはprocess.argsで定義する(例ではshコマンドを実行する)。 また、ターミナルにアタッチする場合はprocess.terminal、環境変数はprocess.env、カレントディレクトリはproces.cwdで定義する。

  "process": {
    ...
    "args": [
      "sh"
    ],
    ...

デフォルトではroot.readonlyはtrueなのでコンテナ内のプロセスはファイルシステムに書き込むことができない。書き込みを行う場合はfalseする。

  "root": {
    "path": "rootfs",
    "readonly": false
  },

プロセスの動作にケーパビリティ(capabilities(7))が不足している場合はprocess.capabilitiesを修正する。

  "process": {
    ...
    "capabilities": [
      "CAP_AUDIT_WRITE"
      "CAP_KILL",
      "CAP_NET_BIND_SERVICE",
      "CAP_NET_RAW" # pingを使いたい場合は必要
    ]

ちなみにDocker 1.11におけるコンテナのデフォルトケーパビリティは下記のものとなります。

  • 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

またデフォルトではnetwork namespaceが作成されるためコンテナ内で独立したネットワークになってしまい、外->内、内->外への通信を行うことはできない。 コンテナ内でホストのネットワークを直接利用する(docker run --net=hostと同等)場合はnetwork namespaceを無効にし、ホストの/etc/hosts/etc/resolv.confなどをコンテナ内で参照できるよう定義する。 具体的にはconfig.jsonを下記のように修正してコンテナを起動する。

  • linux.namespacesからtypenetworkの要素を削除する

  • /etc/hosts, /etc/resolv.conf, /etc/hostnameをマウントするようmountsに追記する(dockerではこれらのファイルをテンポラリ領域にコピーして、それらをマウントしている)

    "mounts": [
      ...
      {
        "destination": "/etc/resolv.conf",
        "type": "bind",
        "source": "/etc/resolv.conf",
        "options": [
          "rbind",
          "rprivate",
          "ro"
        ]
      },
      {
        "destination": "/etc/hostname",
        "type": "bind",
        "source": "/etc/hostname",
        "options": [
          "rbind",
          "rprivate",
          "ro"
        ]
      },
      {
        "destination": "/etc/hosts",
        "type": "bind",
        "source": "/etc/hosts",
        "options": [
          "rbind",
          "rprivate",
          "ro"
        ]
      }
    ],
    

Examples: config.json

runc specで生成されるconfig.jsonはこちら。

{
	"ociVersion": "0.5.0",
	"platform": {
		"os": "linux",
		"arch": "amd64"
	},
	"process": {
		"terminal": true,
		"user": {},
		"args": [
			"sh"
		],
		"env": [
			"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
			"TERM=xterm"
		],
		"cwd": "/",
		"capabilities": [
			"CAP_AUDIT_WRITE",
			"CAP_KILL",
			"CAP_NET_BIND_SERVICE"
		],
		"rlimits": [
			{
				"type": "RLIMIT_NOFILE",
				"hard": 1024,
				"soft": 1024
			}
		],
		"noNewPrivileges": true
	},
	"root": {
		"path": "rootfs",
		"readonly": true
	},
	"hostname": "runc",
	"mounts": [
		{
			"destination": "/proc",
			"type": "proc",
			"source": "proc"
		},
		{
			"destination": "/dev",
			"type": "tmpfs",
			"source": "tmpfs",
			"options": [
				"nosuid",
				"strictatime",
				"mode=755",
				"size=65536k"
			]
		},
		{
			"destination": "/dev/pts",
			"type": "devpts",
			"source": "devpts",
			"options": [
				"nosuid",
				"noexec",
				"newinstance",
				"ptmxmode=0666",
				"mode=0620",
				"gid=5"
			]
		},
		{
			"destination": "/dev/shm",
			"type": "tmpfs",
			"source": "shm",
			"options": [
				"nosuid",
				"noexec",
				"nodev",
				"mode=1777",
				"size=65536k"
			]
		},
		{
			"destination": "/dev/mqueue",
			"type": "mqueue",
			"source": "mqueue",
			"options": [
				"nosuid",
				"noexec",
				"nodev"
			]
		},
		{
			"destination": "/sys",
			"type": "sysfs",
			"source": "sysfs",
			"options": [
				"nosuid",
				"noexec",
				"nodev",
				"ro"
			]
		},
		{
			"destination": "/sys/fs/cgroup",
			"type": "cgroup",
			"source": "cgroup",
			"options": [
				"nosuid",
				"noexec",
				"nodev",
				"relatime",
				"ro"
			]
		}
	],
	"hooks": {},
	"linux": {
		"resources": {
			"devices": [
				{
					"allow": false,
					"access": "rwm"
				}
			]
		},
		"namespaces": [
			{
				"type": "pid"
			},
			{
				"type": "network"
			},
			{
				"type": "ipc"
			},
			{
				"type": "uts"
			},
			{
				"type": "mount"
			}
		],
		"maskedPaths": [
			"/proc/kcore",
			"/proc/latency_stats",
			"/proc/timer_stats",
			"/proc/sched_debug"
		],
		"readonlyPaths": [
			"/proc/asound",
			"/proc/bus",
			"/proc/fs",
			"/proc/irq",
			"/proc/sys",
			"/proc/sysrq-trigger"
		]
	}
}

runcコマンド

いくつかのサブコマンドを解説します。

spec

config.jsonを生成します。

$ runc spec

run

config.jsonを元にコンテナを起動します。 デフォルトではカレントディレクトリのconfig.jsonを元にコンテナを起動します。

$ cd /path/to/container/myapp
$ ls -F
config.json rootfs/
$ sudo runc run --detach myapp # コンテナ内からデタッチ

list, state

実行中のコンテナ一覧やステータスを表示します。 listでは--fromat jsonでJSONで取得できます。

稼働中のコンテナの情報は/run/runc/<CONTAINER_ID>/state.jsonに格納されています。

$ sudo runc list
ID          PID         STATUS      BUNDLE                  CREATED
alpine      10958       running     /var/container/alpine   2016-04-15T06:15:54.433918254Z
alpine_2    11043       running     /var/container/alpine   2016-04-15T06:17:48.369802219Z
# cat /run/runc/alpine/state.json | jq -C . | less -R
{
  "id": "alpine",
  "init_process_pid": 3963,
  "init_process_start": "272024",
  "created": "2016-09-26T06:48:57.070792327Z",
  ...
  ...
  ...
}
$ sudo runc state alpine
{
  "ociVersion": "0.5.0",
  "id": "alpine",
  "pid": 10958,
  "bundlePath": "/var/container/alpine",
  "rootfsPath": "/var/container/alpine/rootfs",
  "status": "running",
  "created": "2016-04-15T06:15:54.433918254Z"
}

exec

起動中のコンテナ内で新しくコマンドを実行します。 docker execと同等。

$ sudo runc exec alpine sh # 稼働中のaplineコンテナで`sh`を実行
/ # ps
PID   USER     TIME   COMMAND
    1 root       0:00 sh # コンテナのinitプロセス(`sh`)
   15 root       0:00 sh # execで実行した`sh`プロセス
   19 root       0:00 ps # 現在実行した`ps`プロセス

kill

コンテナのinitプロセスにシグナル(デフォルトではSIGTERM)を送ります。

$ sudo runc kill alpine KILL

ちなみにinit(PID1)プロセスに送ることができるシグナルは、initが明示的にシグナルハンドラを設定したシグナルだけとなる (alpineコンテナのinitプロセスが/sbin/shの場合はTERMシグナルはハンドルされない)ので注意が必要。

https://linuxjm.osdn.jp/html/LDP_man-pages/man2/kill.2.html

delete

ステータスがdestroyedなコンテナのリソースを削除します。 主にでタッチされたコンテナに利用します。

$ sudo runc delete myapp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment