docker exec
使ってますか?
起動したコンテナを操作できる素晴らしいコマンドですね。とてもお世話になっています。
ただ、その裏側で何が起こっているのかがわかりづらいので説明します。
コンテナにログインしているわけではない
ネットの情報を調べるとコンテナにログインしているという情報がありますね。これは誤りです。
コンテナはあくまでプロセスです。プロセスにログインっておかしいですよね。ただ、概念が少しややこしいのでそういった簡単な説明をしているのだと思います。
では一体どうなっているのでしょうか?
基礎知識としてNamespaceをおさえよう
コンテナを成立させるための技術要素には色々なものがあります。
その中の1つのNamespaceを特に抽出して紹介します。
誤解を恐れず書きますが、LinuxOSではプロセスはNamespaceと呼ばれる空間に所属しています。Namespaceは複数存在しており、自由に作成・削除することができます。
この例ではsshd
・bash
・docker
プロセスが存在していますね。
さて、ここにapacheコンテナを作成するとどうなるのかを見ていきましょう。
するとこのように別のNamespaceにコンテナプロセスが作成されます。
このようにすることでこのコンテナプロセスは独自の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プロセスを起動しています。
同じNamespace内に存在するプロセス同士であればお互いに共通しているリソース情報を確認できるため、コンテナが持っている情報が見れるというわけです。
実際にコンテナの/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
のオプションで指定されたmnt
・uts
・ipc
・net
・pid
は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プロセスに命令を送っています。