フラミナル

考え方や調べたことを書き殴ります。IT技術系記事多め

docker execを正しく理解する【Namespace】

docker

docker exec使ってますか?

起動したコンテナを操作できる素晴らしいコマンドですね。とてもお世話になっています。

ただ、その裏側で何が起こっているのかがわかりづらいので説明します。

コンテナにログインしているわけではない

ネットの情報を調べるとコンテナにログインしているという情報がありますね。これは誤りです。

コンテナはあくまでプロセスです。プロセスにログインっておかしいですよね。ただ、概念が少しややこしいのでそういった簡単な説明をしているのだと思います。

では一体どうなっているのでしょうか?

基礎知識としてNamespaceをおさえよう

コンテナを成立させるための技術要素には色々なものがあります。

その中の1つのNamespaceを特に抽出して紹介します。

誤解を恐れず書きますが、LinuxOSではプロセスはNamespaceと呼ばれる空間に所属しています。Namespaceは複数存在しており、自由に作成・削除することができます。

f:id:lirlia:20200408231019p:plain

この例ではsshdbashdockerプロセスが存在していますね。

さて、ここにapacheコンテナを作成するとどうなるのかを見ていきましょう。

するとこのように別のNamespaceにコンテナプロセスが作成されます。

f:id:lirlia:20200408222245p:plain

このようにすることでこのコンテナプロセスは独自のPIDの仕組みを持てるようになり、1つのOSの中で分離した環境が用意できるのです。(厳密にはルート領域はchrootだったり、リソースの制限はcgroupだったりしますが割愛します)

別のNamespaceで動くコンテナにアクセスしてみよう

さて、別のNamespaceは別空間であるという話をしました。

実は別空間を触るには特殊なコマンドが必要です。そこで登場するのがnsenterコマンドです。

適当にapacheコンテナを起動してみます。

root@ubuntu-bionic:~# docker run -d httpd
Unable to find image 'httpd:latest' locally
latest: Pulling from library/httpd
c499e6d256d6: Pull complete 
76155f771be0: Pull complete 
48b718b71719: Pull complete 
d65ae7a4c211: Pull complete 
8d17dee838ad: Pull complete 
Digest: sha256:13aa010584cb3d79d66adf52444494ae5db67faa28d65a1a25e6ddc57f7c0e2a
Status: Downloaded newer image for httpd:latest
31e8465ec3a581d64cbcc4210bdc8fe77bd6f60d63daf227ddc091991ed6611d
root@ubuntu-bionic:~# docker ps 
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS               NAMES
31e8465ec3a5        httpd               "httpd-foreground"   7 seconds ago       Up 5 seconds        80/tcp              quirky_grothendieck
root@ubuntu-bionic:~# 

そしてPIDを確認します。

root@ubuntu-bionic:~# ps -ef |grep httpd
root     19149 19122  0 12:47 ?        00:00:00 httpd -DFOREGROUND
daemon   19200 19149  0 12:47 ?        00:00:00 httpd -DFOREGROUND
daemon   19201 19149  0 12:47 ?        00:00:00 httpd -DFOREGROUND
daemon   19202 19149  0 12:47 ?        00:00:00 httpd -DFOREGROUND
root     19295 18757  0 12:48 pts/0    00:00:00 grep --color=auto httpd

どうやら19149で動いているようですね。

そうしたらnsenterコマンドを実行してみます。するとコンテナにアクセスできました。

root@ubuntu-bionic:~# nsenter -t 19149 -m -u -i -n -p -- bash
root@31e8465ec3a5:/# hostname
31e8465ec3a5

さて一体これは何をしているのでしょうか? これはnsenterコマンドを使って、NamespaceAのbashプロセスからNamespaceBにてbashプロセスを起動しています。

f:id:lirlia:20200408223302p:plain

同じNamespace内に存在するプロセス同士であればお互いに共通しているリソース情報を確認できるため、コンテナが持っている情報が見れるというわけです。

f:id:lirlia:20200408223531p:plain

実際にコンテナの/proc/{コンテナPID}/nsをみてみるとこの通りです。

lrwxrwxrwx 1 root root 0 Apr  8 12:48 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Apr  8 12:48 ipc -> 'ipc:[4026532183]'
lrwxrwxrwx 1 root root 0 Apr  8 12:48 mnt -> 'mnt:[4026532181]'
lrwxrwxrwx 1 root root 0 Apr  8 12:47 net -> 'net:[4026532186]'
lrwxrwxrwx 1 root root 0 Apr  8 12:48 pid -> 'pid:[4026532184]'
lrwxrwxrwx 1 root root 0 Apr  8 12:49 pid_for_children -> 'pid:[4026532184]'
lrwxrwxrwx 1 root root 0 Apr  8 12:48 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Apr  8 12:48 uts -> 'uts:[4026532182]'

そして先ほどのnsenterコマンドはこちら

lrwxrwxrwx 1 root root 0 Apr  8 13:11 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Apr  8 13:11 ipc -> 'ipc:[4026532183]'
lrwxrwxrwx 1 root root 0 Apr  8 13:11 mnt -> 'mnt:[4026532181]'
lrwxrwxrwx 1 root root 0 Apr  8 13:11 net -> 'net:[4026532186]'
lrwxrwxrwx 1 root root 0 Apr  8 13:11 pid -> 'pid:[4026532184]'
lrwxrwxrwx 1 root root 0 Apr  8 13:11 pid_for_children -> 'pid:[4026532184]'
lrwxrwxrwx 1 root root 0 Apr  8 13:11 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Apr  8 13:11 uts -> 'uts:[4026532182]'

4026531835などの数字が全く同じですね。これはnsenter -t 19149 -m -u -i -n -pのオプションで指定されたmntutsipcnetpidはPID:19149で指定されたものと同じ奴を使えという意味です。

ということでこんなこともできちゃいます。

# コンテナ起動(port 8000)
root@ubuntu-bionic:~# docker run -p 8000:80 -d httpd
  
# curl実行
root@ubuntu-bionic:~# curl localhost:8000
<html><body><h1>It works!</h1></body></html>
  
# コンテナのNamespaceにアクセスして別のファイル作ってみる
root@ubuntu-bionic:~# nsenter -t 20184 -m -u -i -n -p --  sh -c "sed -e 's/It works/Hello World/g' usr/local/apache2/htdocs/index.html > usr/local/apache2/htdocs/index2.html"
  
# curl実行
root@ubuntu-bionic:~# curl localhost:8000/index2.html
<html><body><h1>Hello World!</h1></body></html>

docker execとはなんなのか?

docker execはnsenterをラッピングしたようなツールです。

コンテナにログインしたように見せかけて、実際は同一のNamespaceに指定されたコマンドのプロセスを起動しそのプロセスから制御ができるということです。

実際はこのようなフローでdocker execからdocker daemonに対して(ローカル通信の場合は)UNIX domain socketを経由したHTTP通信を行い対象のNamespace上のbashプロセスに命令を送っています。

f:id:lirlia:20200409000946p:plain