Docker topの仕組み

今日は、少しDockerのソースを見てみます。

まだまだ構造がわかっているわけではないので、簡単なdocker topについて調べます。

※ここで対象としているのは、CentOS 7です。他のLinux OSでは少し仕組みが違います。

# docker topコマンド
docker topコマンドのレファレンスは[こちら](https://docs.docker.com/reference/commandline/top/)です。
短いのでコマンドを貼っておきます。

```
Usage: docker top CONTAINER [ps OPTIONS]

Display the running processes of a container
```

docker topコマンドは、コンテナで実行中のプロセスを見ることができるようになっています。
topの後ろには、psのオプションを使うことができます。

```
# docker top test999
UID    PID       PPID      C      STIME      TTY        TIME          CMD
root   2567      1172      0      17:17      pts/1      00:00:00      /bin/bash
root   2786      2567      0      18:12      pts/1      00:00:00      /bin/bash
root   2793      2786      0      18:12      pts/1      00:00:00      /bin/bash

# docker top test999 aux 
USER      PID       %CPU      %MEM      VSZ        RSS       TTY        STAT      START      TIME      COMMAND
root      2567      0.0       0.1       14780      1896      pts/1      Ss        17:17      0:00      /bin/bash
root      2786      0.0       0.0       14780      1728      pts/1      S         18:12      0:00      /bin/bash
root      2793      0.0       0.0       14780      1868      pts/1      S+        18:12      0:00      /bin/bash

```

※「docker run -it --name test999 centos:6 /bin/bash」を実行後、子プロセスを作りたかったので/bin/bashを2回実行しています。

# Docker のソースを見る
## どこに書かれている？

対象としているソースは、[docker-1.6.2-14.el7.centos.src.rpm](http://vault.centos.org/7.1.1503/extras/Source/SPackages/docker-1.6.2-14.el7.centos.src.rpm)です。Dockerは、CentOS 7.1(7.1.1503)のextras内にあります。

docker topコマンドは、dockerコマンドを実行したあと、dockerエンジン(dockerデーモン)にコマンドが渡され、dockerエンジン内で処理が行われます。

daemon/top.goです。

```go:daemon/top.go
...略...
	pids, err := daemon.ExecutionDriver().GetPidsForContainer(container.ID)
	if err != nil {
		return job.Error(err)
	}
	output, err := exec.Command("ps", strings.Split(psArgs, " ")...).Output()
	if err != nil {
		return job.Errorf("Error running ps: %s", err)
	}

...略...

		for _, pid := range pids {
			if pid == p {
				// Make sure number of fields equals number of header titles
				// merging "overhanging" fields
				process := fields[:len(header)-1]
				process = append(process, strings.Join(fields[len(header)-1:], " "))
				processes = append(processes, process)
			}
		}
	}
```

## ソースの説明
上のコードでわかりましたか？
上のコードだけだと、なんとなくやってそうなことがわかりますよね？

1. コンテナIDからPIDのリストを取得する
2. psを実行する
3. psの結果からPIDリストと一致するものだけ処理する

こんな感じですよね。
ちょっと驚いたのは、ただ単にpsコマンドを実行しているだけということにびっくりです。
コンテナIDからPIDリストを取得していることが重要なわけですね。
PIDリストに抜けがあると表示されないことになります。

psのコマンドを実行しているだけなので、docker top [ps OPTIONS]というようにpsのオプションが使えるわけです。
ちなみにオプションを指定しない場合は、ps -ef として実行されています。

psのオプションとして、"-ef"で初期化されていて、引数がある場合は上書かれています。

```go:daemon/top.go
	var (
		name   = job.Args[0]
		psArgs = "-ef"
	)

	if len(job.Args) == 2 && job.Args[1] != "" {
		psArgs = job.Args[1]
	}
```
 
# コンテナIDからPIDのリストを取得する

上で説明したように、docker topコマンドの胆になっているのが、コンテナIDからPIDを取得している部分です。PIDリストをもれなく取得するにはどういうことをしているのでしょうか。
ここでは、その部分を見てみましょう。

GetPidsForContainerをたどっていくと、こういう部分が出てきます。

```
	pids, err := c.cgroupManager.GetPids()
```

そうです。cgroupを使って取得しています。

## cgroupを使ってPID一覧を取得する
ここからはソースを追っていくと記載が面倒なので、概要だけ説明します。

### cgroupの管理ディレクトリを探す
まず、/proc/self/mountinfoを読んで、cgroupの情報を探します。
CentOSの場合、こういうような情報が取れます。
これから、cgroupは/sys/fs/cgroup/を使用すればよいとわかるわけです。

```
# cat /proc/self/mountinfo | grep cgroup
27 24 0:22 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:10 - cgroup cgroup rw,cpuset                                         
28 24 0:23 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:11 - cgroup cgroup rw,cpuacct,cpu  
```

###　cgroupの情報からPID一覧を取得する

/sys/fs/cgroup/にはないがあるかというと、

```
# ls /sys/fs/cgroup/
blkio  cpu  cpu,cpuacct  cpuacct  cpuset  devices  freezer  hugetlb  memory  net_cls  perf_event  systemd
```

この中のcpuを利用します。
cpuの中には、system.sliceがあって、dockerによって作られたcgroupのグループがあります。dockerはコンテナごとにcgroupを作成しており、docker-<コンテナID>.scopeが存在します。（これにより、CPUの制限やメモリの制限を設定します。）

```
# ls /sys/fs/cgroup/cpu
cgroup.clone_children  cgroup.sane_behavior  cpu.rt_period_us   cpu.stat       cpuacct.usage_percpu  system.slice
cgroup.event_control   cpu.cfs_period_us     cpu.rt_runtime_us  cpuacct.stat   notify_on_release     tasks
cgroup.procs           cpu.cfs_quota_us      cpu.shares         cpuacct.usage  release_agent         user.slice
```

docker psで確認したコンテナIDのscopeが出来上がっています。

```
# ls /sys/fs/cgroup/cpu/system.slice/ | grep docker
docker-a3e702b58547880f663b742a7d5650fd275c90dac60720fb2f0c0787e198a8ff.scope
docker.service

# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
a3e702b58547        centos:6            "/bin/bash"         2 hours ago         Up 2 hours                              test999 

# cd docker-a3e702b58547880f663b742a7d5650fd275c90dac60720fb2f0c0787e198a8ff.scope/
# ls
cgroup.clone_children  cgroup.procs       cpu.cfs_quota_us  cpu.rt_runtime_us  cpu.stat      cpuacct.usage         notify_on_release
cgroup.event_control   cpu.cfs_period_us  cpu.rt_period_us  cpu.shares         cpuacct.stat  cpuacct.usage_percpu  tasks
```

このグループで管理されているプロセスは、cgroup.procsに保存されています。
そのファイルに書かれたPIDを使っています。
実際に使ってやってみると同じ結果が出ますね。

```
# cat cgroup.procs                               
2567
2786
2793

# ps -ef | grep -E "(256[7]|278[6]|279[3])" 
root       2567   1172  0 17:17 pts/1    00:00:00 /bin/bash                                                                                    
root       2786   2567  0 18:12 pts/1    00:00:00 /bin/bash                                                                                    
root       2793   2786  0 18:12 pts/1    00:00:00 /bin/bash  

# docker top test999
UID    PID       PPID      C      STIME      TTY        TIME          CMD
root   2567      1172      0      17:17      pts/1      00:00:00      /bin/bash
root   2786      2567      0      18:12      pts/1      00:00:00      /bin/bash
root   2793      2786      0      18:12      pts/1      00:00:00      /bin/bash
```

#最後に
今日は、dockerのソースを見てみました。まずは比較的簡単なtopについて調べてみましたが、単純ではありましたが、大変面白く、参考になりました。