引言
因为Docker技术的火热,因此在工作中我们经常会以容器的方式来运行一个应用。每当容器无法成功运行或者想要对容器中的应用参数、应用配置以及应用启动进行深入研究时,当然希望能够像在宿主机上调试程序一样在容器中调试应用。容器的本质包括应用与应用运行所依赖的环境, 因此首先需要创建一个空壳容器(没有运行应用的应用容器),然后进入容器中调试应用。此处的空壳容器提供了应用运行所需的环境,进而可方便的在其中调试应用。实践环境:Centos7.2+Docker1.12.6。
容器启动命令介绍
比较规范的镜像的Dockerfile中通常会有ENTRYPOINT与CMD的定义(Docker官方推荐这样做)。因此容器的启动命令则为ENTRYPOINT所对应的脚本或可执行程序加上CMD中定义的内容。比如elasticsearch的Dockerfile定义的ENTRYPOINT与CMD分别为:ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["elasticsearch"],则创建的容器的启动命令为:/docker-entrypoint.sh elasticsearch
;mysql的Dockerfile:ENTRYPOINT ["docker-entrypoint.sh"] CMD ["mysqld"],则创建的容器的启动命令为:/docker-entrypoint.sh mysqld
。所以想要知道一个容器的启动命令需要首先了解其镜像的Dockerfile中ENTRYPOINT与CMD的定义。如何查看一个镜像的ENTRYPONT与CMD的值呢?一般采用如下两种方式:
-
查看镜像的Dockerfile
比如查看zookeeper的Dockerfile,先在Docker仓库中找到zookeeper镜像,然后找到特定版本的Dockerfile
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["zkServer.sh", "start-foreground"]
-
通过 docker history 或 docker inspect 命令查看
比如查看prom/prometheus:v1.8.0镜像的ENTRYPOINT与CMD信息
docker history --no-trunc prom/prometheus:v1.8.0
(--no-trunc表示完整输出)
"-config.file=/etc/prometheus/prometheus.yml"
"-storage.local.path=/prometheus"
"-web.console.libraries=/usr/share/prometheus/console_libraries"
"-web.console.templates=/usr/share/prometheus/consoles"docker inspect prom/prometheus:v1.8.0
CMD为:"-config.file=/etc/prometheus/prometheus.yml",
"-storage.local.path=/prometheus",
"-web.console.libraries=/usr/share/prometheus/console_libraries",
"-web.console.templates=/usr/share/prometheus/consoles"
上述第一种方式适用于比较规范的镜像,这类镜像通常会提供清晰、具体的Dockerfile。第二种方式适用于各种镜像,尽管是不规范的镜像。通过history、inspect两个命令的任一个均可快速、方便的查看镜像的ENTRYPOINT与CMD的值。
不同类型镜像的调试举例
如果不作额外的设置,容器的启动命令则是镜像的ENTRYPOINT+CMD的方式。比如:若要调试容器中的应用程序,则需额外的设置实现。docker run命令提供的--entrypoint参数能够覆盖Dockerfile中默认定义的ENTRYPOINT;docker run [OPTIONS] IMAGE [COMMAND] [ARG...]的COMMAND能够替换Dockerfile中定义的CMD。通过上面的示例可以发现,有的镜像的Dockerfile中ENTRYPOINT值为:/docker-entrypoint.sh,CMD为应用的可执行程序;有的镜像的Dockerfile中ENTRYPOINT值为应用的可执行程序,CMD为可执行程序的参数。因此针对不同的镜像想要创建空壳容器其方式是不同的。
-
Dockerfile的ENTRYPOINT为/docker-entrypoint.sh,CMD为应用的可执行程序
通常比较正式的官方镜像均会提供docker-entrypoint.sh脚本作为ENTRYPOINT,docker-entrypoint.sh脚本一般能够接收类似bash、sh的参数,因此对于这类镜像可以指定容器的COMMAND覆盖应用的二进制程序创建空壳容器。这里以elasticsearch:5.0.2镜像为例创建空壳容器es:
查看空壳容器es中运行的进程:
docker run -itd --name es --network host elasticsearch:5.0.2 bash
(alpine系统指定sh,非alpine系统可以指定bash or sh)可见,es容器中运行的进程变为bash,并不是elasticsearch应用;此时es就为空壳容器,进入es容器调试elasticsearch应用就像在宿主机上调试程序
进入es容器: -
Dockerfile的ENTRYPOINT为应用的可执行程序,CMD为可执行程序的参数
这类镜像不能再简单的通过覆盖镜像的CMD来创建空壳容器了,因为docker run 的COMMAND是作为ENTRYPOINT指定的二进制程序的参数传入的,所以简单的指定COMMAND为bash或sh多数情况下是不行的,最好的方式是通过docker run的--entrypoint参数来覆盖镜像中指定的二进制可执行程序,使得创建的容器其内部的进程为entrypoint参数指定的bash or sh。这里以google/cadvisor:v0.27.2镜像(ENTRYPOINT: /usr/bin/cadvisor -logtostderr, CMD: null)为例创建空壳容器cadvisor:
查看cadvisor容器的进程:
docker run -itd --name cadvisor --network host --entrypoint sh -v /var/lib/docker:/var/lib/docker:ro google/cadvisor:v0.27.2
可见,这种方式不能成功创建空壳容器
上面是对两种类型的ENTRYPOINT创建空壳容器的举例说明,其实也可以对ENTRYPOINT不加以区分,即均通过docker run 的--entrypoint参数覆盖镜像中的ENTRYPOINT创建空壳容器。比如示例1中的elasticsearch:5.0.2镜像,指定--entrypoint bash创建空壳容器:
总结
容器其实是应用与应用运行所依赖的环境,创建空壳容器即提供了应用所需要的环境,进入此环境中可以调试应用,可以验证应用的各个参数,同样更可以像在宿主机中运行程序一样在此环境中运行应用,区别仅是容器与宿主机的两个环境。上面是对如何在容器中调试应用程序做的一些记录,希望与大家一起讨论、交流,一起学习。