调试运行中的容器

对于普通的服务器进程,我们可以很方便的使用宿主机上的各种工具来调试;但容器经常是仅包含必要的应用程序,一般不包含常用的调试工具,那如何在线调试容器中的进程呢?最简单的方法是再起一个新的包含了调试工具的容器。

来看一个最简单的 web 容器如何调试。

webserver 容器

用 Go 编写一个最简单的 webserver:

  1. // go-examples/basic/webserver
  2. package main
  3. import "net/http"
  4. import "fmt"
  5. import "log"
  6. func index(w http.ResponseWriter, r *http.Request) {
  7. fmt.Fprintln(w, "Hello World")
  8. }
  9. func main() {
  10. http.HandleFunc("/", index)
  11. err := http.ListenAndServe(":80", nil)
  12. if err != nil {
  13. log.Println(err)
  14. }
  15. }

以 linux 平台方式编译

  1. GOOS=linux go build -o webserver

然后用下面的 Docker build 一个 docker 镜像:

  1. FROM scratch
  2. COPY ./webserver /
  3. CMD ["/webserver"]
  1. # docker build -t feisky/hello-world .
  2. Sending build context to Docker daemon 5.655 MB
  3. Step 1/3 : FROM scratch
  4. --->
  5. Step 2/3 : COPY ./webserver /
  6. ---> 184eb7c074b5
  7. Removing intermediate container abf107844295
  8. Step 3/3 : CMD /webserver
  9. ---> Running in fe9fa4841e70
  10. ---> dca5ec00b3e7
  11. Removing intermediate container fe9fa4841e70
  12. Successfully built dca5ec00b3e7

最后启动 webserver 容器

  1. docker run -itd --name webserver -p 80:80 feisky/hello-world

访问映射后的 80 端口,webserver 容器正常返回 “Hello World”

  1. # curl http://$(hostname):80
  2. Hello World

新建一个容器调试 webserver

用一个包含调试工具或者方便安装调试工具的镜像(如 alpine)创建一个新的 container,为了便于获取 webserver 进程的状态,新的容器共享 webserver 容器的 pid namespace 和 net namespace,并增加必要的 capability:

  1. docker run -it --rm --pid=container:webserver --net=container:webserver --cap-add sys_admin --cap-add sys_ptrace alpine sh
  2. / # ps -ef
  3. PID USER TIME COMMAND
  4. 1 root 0:00 /webserver
  5. 13 root 0:00 sh
  6. 18 root 0:00 ps -ef

这样,新的容器可以直接 attach 到 webserver 进程上来在线调试,比如 strace 到 webserver 进程

  1. # 继续在刚创建的新容器 sh 中执行
  2. / # apk update && apk add strace
  3. fetch http://dl-cdn.alpinelinux.org/alpine/v3.5/main/x86_64/APKINDEX.tar.gz
  4. fetch http://dl-cdn.alpinelinux.org/alpine/v3.5/community/x86_64/APKINDEX.tar.gz
  5. v3.5.1-34-g1d3b13bd53 [http://dl-cdn.alpinelinux.org/alpine/v3.5/main]
  6. v3.5.1-29-ga981b1f149 [http://dl-cdn.alpinelinux.org/alpine/v3.5/community]
  7. OK: 7958 distinct packages available
  8. (1/1) Installing strace (4.14-r0)
  9. Executing busybox-1.25.1-r0.trigger
  10. OK: 5 MiB in 12 packages
  11. / # strace -p 1
  12. strace: Process 1 attached
  13. epoll_wait(4,
  14. ^Cstrace: Process 1 detached
  15. <detached ...>

也可以获取 webserver 容器的网络状态

  1. # 继续在刚创建的新容器 sh 中执行
  2. / # apk add lsof
  3. (1/1) Installing lsof (4.89-r0)
  4. Executing busybox-1.25.1-r0.trigger
  5. OK: 5 MiB in 13 packages
  6. / # lsof -i TCP
  7. COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
  8. webserver 1 root 3u IPv6 14233 0t0 TCP *:http (LISTEN)

当然,也可以访问 webserver 容器的文件系统

  1. / # ls -l /proc/1/root/
  2. total 5524
  3. drwxr-xr-x 5 root root 360 Feb 14 13:16 dev
  4. drwxr-xr-x 2 root root 4096 Feb 14 13:16 etc
  5. dr-xr-xr-x 128 root root 0 Feb 14 13:16 proc
  6. dr-xr-xr-x 13 root root 0 Feb 14 13:16 sys
  7. -rwxr-xr-x 1 root root 5651357 Feb 14 13:15 webserver

Kubernetes 社区也在提议增加一个 kubectl debug 命令,用类似的方式在 Pod 中启动一个新容器来调试运行中的进程,可以参见 https://github.com/kubernetes/community/pull/649