今日は、少しDockerのソースを見てみます。
まだまだ構造がわかっているわけではないので、簡単なdocker topについて調べます。
※ここで対象としているのは、CentOS 7です。他のLinux OSでは少し仕組みが違います。
docker topコマンド
docker 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です。Dockerは、CentOS 7.1(7.1.1503)のextras内にあります。
docker topコマンドは、dockerコマンドを実行したあと、dockerエンジン(dockerデーモン)にコマンドが渡され、dockerエンジン内で処理が行われます。
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)
}
}
}
ソースの説明
上のコードでわかりましたか?
上のコードだけだと、なんとなくやってそうなことがわかりますよね?
- コンテナIDからPIDのリストを取得する
- psを実行する
- psの結果からPIDリストと一致するものだけ処理する
こんな感じですよね。
ちょっと驚いたのは、ただ単にpsコマンドを実行しているだけということにびっくりです。
コンテナIDからPIDリストを取得していることが重要なわけですね。
PIDリストに抜けがあると表示されないことになります。
psのコマンドを実行しているだけなので、docker top [ps OPTIONS]というようにpsのオプションが使えるわけです。
ちなみにオプションを指定しない場合は、ps -ef として実行されています。
psのオプションとして、"-ef"で初期化されていて、引数がある場合は上書かれています。
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について調べてみましたが、単純ではありましたが、大変面白く、参考になりました。