Docker 容器生命周期

精通 Docker 容器生命周期:启动、停止、重启与自定义命令实践

Docker 作为当今最流行的容器化技术之一,深刻改变了软件的开发、交付和运行方式。熟练掌握 Docker 容器的生命周期管理,是每一位开发者和运维工程师的必备技能。本文将结合实际操作示例,深入探讨如何启动、停止、重启 Docker 容器,以及如何在启动时覆盖默认命令,并阐述容器生命周期与其主进程的关键关系。

前提条件

  • 已在您的系统上安装并运行 Docker 服务。
  • 对 Docker 的基本概念(如镜像、容器)有初步了解。

1. 查看容器状态 (docker ps)

在管理容器之前,首先需要了解它们的当前状态。docker ps 命令是您的核心工具。

  • docker ps: 列出当前正在运行的容器。
  • docker ps -a (或 --all): 列出所有容器,包括已停止的容器。

示例:

root@docker102:~# docker ps -a
CONTAINER ID   IMAGE                                                  COMMAND                  CREATED      STATUS                           PORTS                                                    NAMES
93b29258821f   docker.elastic.co/kibana/kibana:8.14.3                 "/bin/tini -- /usr/l…"   2 days ago   Up 2 seconds                     0.0.0.0:5601->5601/tcp, :::5601->5601/tcp                kibana
d346a298551e   docker.elastic.co/elasticsearch/elasticsearch:8.14.3   "/bin/tini -- /usr/l…"   2 days ago   Up 5 seconds                     0.0.0.0:9200->9200/tcp, :::9200->9200/tcp, 9300/tcp      es01
deddaf31973d   mysql:8.3.0-oracle                                     "docker-entrypoint.s…"   2 days ago   Exited (255) About an hour ago   33060/tcp, 0.0.0.0:13306->3306/tcp, :::13306->3306/tcp   mysql-server02

这里的 STATUS 列是关键信息:

  • Up ...: 表示容器正在运行,并显示已运行时间。
  • Exited (code) ...: 表示容器已停止。括号内的退出码 (Exit Code) 提供了容器停止原因的线索(例如,0 通常表示正常退出,非零值表示异常或接收到特定信号)。

2. 停止容器 (docker stop)

docker stop 命令用于优雅地停止一个或多个正在运行的容器。

工作机制:

  1. 该命令首先向容器内的主进程(PID 1)发送 SIGTERM 信号,请求其自行关闭。
  2. 容器内的应用程序应该捕获此信号并执行清理操作(如保存数据、关闭连接)。
  3. 如果在默认的超时时间(10 秒)内,主进程没有退出,Docker 会接着发送 SIGKILL 信号,强制终止该进程。

语法:

docker stop [OPTIONS] CONTAINER [CONTAINER...]
  • 可以通过容器的 NameID (或其唯一前缀) 来指定目标容器。
  • OPTIONS 中常用的有 -t--time:指定发送 SIGTERM 后等待的秒数,超过该时间再发送 SIGKILL。例如,-t 0 表示几乎立即发送 SIGKILL (在 SIGTERM 之后)。

示例:

# 停止名为 'es01' 的容器,设置超时为 0 秒
root@docker102:~# docker stop -t 0 es01
es01

# 通过容器 ID 停止 'kibana' 容器 (使用默认 10 秒超时)
root@docker102:~# docker stop 93b29258821f
93b29258821f

# 验证容器状态,STATUS 变为 Exited
root@docker102:~# docker ps -a
CONTAINER ID   IMAGE                                                  COMMAND                  CREATED      STATUS                           PORTS     NAMES
93b29258821f   docker.elastic.co/kibana/kibana:8.14.3                 "/bin/tini -- /usr/l…"   2 days ago   Exited (0) 9 seconds ago                   kibana # 正常退出
d346a298551e   docker.elastic.co/elasticsearch/elasticsearch:8.14.3   "/bin/tini -- /usr/l…"   2 days ago   Exited (137) 26 seconds ago                  es01     # 可能被 SIGKILL (9) 终止,退出码 137 = 128 + 9
deddaf31973d   mysql:8.3.0-oracle                                     "docker-entrypoint.s…"   2 days ago   Exited (255) About an hour ago             mysql-server02

注意: 退出码 137 通常表示进程是被 SIGKILL (信号编号 9) 杀死的,因为 Shell 的约定是将退出码设置为 128 + 信号编号

3. 启动已停止的容器 (docker start)

对于处于 Exited 状态的容器,可以使用 docker start 命令将其重新启动。

关键特性:

  • docker start保留容器创建时的所有配置(包括命令、环境变量、挂载卷、端口映射等)。
  • 它只是将容器的主进程重新拉起。

语法:

docker start [OPTIONS] CONTAINER [CONTAINER...]
  • 同样可以通过容器的 NameID 指定。
  • 可以一次启动多个容器。

示例:

# 查看当前状态 (es01 和 kibana 已停止)
root@docker102:~# docker ps -a
CONTAINER ID   IMAGE                                                  COMMAND                  CREATED      STATUS                           PORTS                                                    NAMES
93b29258821f   docker.elastic.co/kibana/kibana:8.14.3                 "/bin/tini -- /usr/l…"   2 days ago   Exited (255) About an hour ago   0.0.0.0:5601->5601/tcp, :::5601->5601/tcp                kibana
d346a298551e   docker.elastic.co/elasticsearch/elasticsearch:8.14.3   "/bin/tini -- /usr/l…"   2 days ago   Exited (130) 2 days ago                                                                   es01
deddaf31973d   mysql:8.3.0-oracle                                     "docker-entrypoint.s…"   2 days ago   Exited (255) About an hour ago   33060/tcp, 0.0.0.0:13306->3306/tcp, :::13306->3306/tcp   mysql-server02

# 通过名称启动 'es01'
root@docker102:~# docker start es01
es01

# 通过 ID 启动 'kibana'
root@docker102:~# docker start 93b29258821f
93b29258821f

# 验证容器状态,STATUS 变为 Up,运行时间重新开始计数
root@docker102:~# docker ps -a
CONTAINER ID   IMAGE                                                  COMMAND                  CREATED      STATUS                           PORTS                                                    NAMES
93b29258821f   docker.elastic.co/kibana/kibana:8.14.3                 "/bin/tini -- /usr/l…"   2 days ago   Up 4 seconds                     0.0.0.0:5601->5601/tcp, :::5601->5601/tcp                kibana
d346a298551e   docker.elastic.co/elasticsearch/elasticsearch:8.14.3   "/bin/tini -- /usr/l…"   2 days ago   Up 26 seconds                    0.0.0.0:9200->9200/tcp, :::9200->9200/tcp, 9300/tcp      es01
deddaf31973d   mysql:8.3.0-oracle                                     "docker-entrypoint.s…"   2 days ago   Exited (255) About an hour ago   33060/tcp, 0.0.0.0:13306->3306/tcp, :::13306->3306/tcp   mysql-server02

4. 重启容器 (docker restart)

docker restart 命令是一个便捷操作,它相当于对指定的容器执行 docker stop,然后再执行 docker start

特性:

  • 如果容器正在运行,它会先停止再启动。
  • 如果容器已停止,它会直接启动。
  • 同样支持 -t (或 --time) 选项,用于控制停止阶段的超时时间。
  • 重启后,容器的 STATUS 中的运行时间会重新开始计数

语法:

docker restart [OPTIONS] CONTAINER [CONTAINER...]

示例:

# 查看 es01 状态 (已运行 25 秒)
root@docker102:~# docker ps -a
CONTAINER ID   IMAGE                                                  COMMAND                  CREATED      STATUS                           PORTS                                                    NAMES
...
d346a298551e   docker.elastic.co/elasticsearch/elasticsearch:8.14.3   "/bin/tini -- /usr/l…"   2 days ago   Up 25 seconds                    0.0.0.0:9200->9200/tcp, :::9200->9200/tcp, 9300/tcp      es01
...

# 重启 es01
root@docker102:~# docker restart es01
es01

# 再次查看状态,es01 仍在运行,但 Up 时间已重置
root@docker102:~# docker ps -a
CONTAINER ID   IMAGE                                                  COMMAND                  CREATED      STATUS                           PORTS                                                    NAMES
...
d346a298551e   docker.elastic.co/elasticsearch/elasticsearch:8.14.3   "/bin/tini -- /usr/l…"   2 days ago   Up 2 seconds                     0.0.0.0:9200->9200/tcp, :::9200->9200/tcp, 9300/tcp      es01
...

5. 强制终止容器 (docker kill)

docker stop 不同,docker kill 命令直接向容器的主进程发送 SIGKILL 信号(或其他指定的信号),强制终止容器,给应用程序优雅关闭的机会。

使用场景:

  • docker stop 无法停止容器时。
  • 需要立即停止容器,不关心数据是否丢失或状态是否一致时(生产环境慎用)。

语法:

docker kill [OPTIONS] CONTAINER [CONTAINER...]
  • 默认发送 SIGKILL
  • 可以通过 -s--signal 选项指定发送其他信号。

示例:

# 启动 kibana 和 es01
root@docker102:~# docker start kibana es01
kibana
es01

# 验证它们正在运行
root@docker102:~# docker ps -a | grep -E 'kibana|es01'
93b29258821f   docker.elastic.co/kibana/kibana:8.14.3                 "/bin/tini -- /usr/l…"   2 days ago   Up 7 seconds                     0.0.0.0:5601->5601/tcp, :::5601->5601/tcp                kibana
d346a298551e   docker.elastic.co/elasticsearch/elasticsearch:8.14.3   "/bin/tini -- /usr/l…"   2 days ago   Up 5 seconds                     0.0.0.0:9200->9200/tcp, :::9200->9200/tcp, 9300/tcp      es01

# 强制终止 kibana
root@docker102:~# docker kill kibana
kibana

# 验证 kibana 状态变为 Exited (通常退出码为 137)
root@docker102:~# docker ps -a | grep kibana
93b29258821f   docker.elastic.co/kibana/kibana:8.14.3                 "/bin/tini -- /usr/l…"   2 days ago   Exited (137) 2 seconds ago                                                                kibana

重要提示: docker stop 本质上是尝试优雅关闭(SIGTERM),超时后再强制关闭(SIGKILL)。而 docker kill 默认直接强制关闭(SIGKILL)。在生产环境中,应优先使用 docker stop,以允许应用程序完成必要的清理工作。

6. 容器与宿主机进程的关系

理解容器的本质非常重要:一个运行中的 Docker 容器,其核心是宿主机上的一个或多个进程,这些进程被 Linux 内核的 Namespace 和 Cgroups 技术隔离和限制资源。

  • 容器启动:相当于在宿主机上启动了容器的主进程(以及可能的子进程)。
  • 容器停止/终止:意味着宿主机上对应的进程被终止。

示例:查找并终止 Elasticsearch 容器的宿主机进程

# 确认 es01 容器正在运行
root@docker102:~# docker ps | grep es01
d346a298551e   docker.elastic.co/elasticsearch/elasticsearch:8.14.3   "/bin/tini -- /usr/l…"   2 days ago   Up 55 seconds                    0.0.0.0:9200->9200/tcp, :::9200->9200/tcp, 9300/tcp      es01

# 在宿主机上查找与 Elasticsearch 相关的 Java 进程
# (注意:Elasticsearch 是 Java 应用,进程名通常包含 'java' 或 'elasticsearch')
root@docker102:~# ps -ef | grep java
# 输出可能包含多个进程,找到与 es01 容器相关的 (通常由特定用户如 'elasticsearch' 或 'yinzhen+' 运行,且路径包含 /usr/share/elasticsearch)
yinzhen+    9280    9253  5 09:44 ?        00:00:03 /usr/share/elasticsearch/jdk/bin/java ... # 这是 Elasticsearch 的启动器/父进程
yinzhen+    9341    9280 77 09:44 ?        00:00:50 /usr/share/elasticsearch/jdk/bin/java ... # 这是 Elasticsearch 的主服务进程

# 在宿主机上直接杀死容器的主进程或其父进程 (这里以父进程 9280 为例)
root@docker102:~# kill 9280

# 等待片刻,再次查看容器状态
root@docker102:~# docker ps -a | grep es01
d346a298551e   docker.elastic.co/elasticsearch/elasticsearch:8.14.3   "/bin/tini -- /usr/l…"   2 days ago   Exited (143) 6 seconds ago                                                                es01

可以看到,当宿主机上的对应进程被杀死后,Docker 检测到主进程退出,容器的状态也随之变为 Exited。退出码 143 通常表示进程是被 SIGTERM (信号编号 15) 终止的 (128 + 15 = 143),kill 命令默认发送 SIGTERM

7. 容器启动时指定自定义命令

使用 docker run 创建并启动容器时,可以在镜像名称后面指定要运行的命令。这个指定的命令会覆盖镜像 Dockerfile 中定义的默认 CMDENTRYPOINT

1. 查看容器的默认启动命令:
使用 docker ps 查看正在运行的容器时,COMMAND 列会显示容器启动时执行的命令。有时命令过长会被截断,可以使用 --no-trunc 选项查看完整命令。

# 运行一个基于 nginx 的示例容器 c1
root@docker102:~# docker run -d --name c1 -p 81:80 registry.cn-hangzhou.aliyuncs.com/yinzhengjie-k8s/apps:v1
9f0419692262...

# 查看 c1 的启动命令 (可能会被截断)
root@docker102:~# docker ps -l
CONTAINER ID   IMAGE                                                       COMMAND                  CREATED         STATUS        PORTS                               NAMES
9f0419692262   registry.cn-hangzhou.aliyuncs.com/yinzhengjie-k8s/apps:v1   "/docker-entrypoint.…"   3 seconds ago   Up 1 second   0.0.0.0:81->80/tcp, :::81->80/tcp   c1

# 查看 c1 完整的启动命令
root@docker102:~# docker ps -l --no-trunc
CONTAINER ID                                                       IMAGE                                                       COMMAND                                          CREATED          STATUS          PORTS                               NAMES
9f04196922621ea548d4cd9058f4fa77674ef380ac62156663bd066018f9e66a   registry.cn-hangzhou.aliyuncs.com/yinzhengjie-k8s/apps:v1   "/docker-entrypoint.sh nginx -g 'daemon off;'"   11 seconds ago   Up 10 seconds   0.0.0.0:81->80/tcp, :::81->80/tcp   c1

默认情况下,这个镜像启动了 Nginx 服务。

2. 启动容器并指定自定义命令:
docker run 命令的最后,直接跟上你想要执行的命令及其参数。

# 启动一个名为 c2 的容器,使用相同的镜像,但覆盖默认命令,让它执行 'sleep 120'
root@docker102:~# docker run -d --name c2 -p 82:80 registry.cn-hangzhou.aliyuncs.com/yinzhengjie-k8s/apps:v1 sleep 120
a266625de6d9...

# 查看 c2 的完整启动命令,确认它执行的是 sleep 命令
root@docker102:~# docker ps -l --no-trunc
CONTAINER ID                                                       IMAGE                                                       COMMAND                             CREATED         STATUS         PORTS                               NAMES
a266625de6d9fa010c90f4a3af175b0d4e90eaa5be95ea3292c7b2f1dc7d547e   registry.cn-hangzhou.aliyuncs.com/yinzhengjie-k8s/apps:v1   "/docker-entrypoint.sh sleep 120"   8 seconds ago   Up 7 seconds   0.0.0.0:82->80/tcp, :::82->80/tcp   c2

这个 c2 容器现在的主进程是 sleep 120,它不会启动 Nginx 服务。

8. 连接到运行中的容器 (docker exec)

docker exec 命令允许你在一个已经运行的容器内部执行新的命令。这对于调试、检查容器状态或执行临时任务非常有用。

常用选项:

  • -i (interactive): 保持标准输入 (STDIN) 打开。
  • -t (tty): 分配一个伪终端。
  • -it: 通常一起使用,提供一个交互式的 Shell 环境。

示例:进入 c2 容器并查看进程

# 进入名为 c2 的容器,并启动一个 shell (/bin/sh 或 sh,取决于基础镜像)
root@docker102:~# docker exec -it c2 sh

# 现在你位于容器 c2 内部的 shell 环境
/ #

# 查看容器内的进程,可以看到 PID 1 是 sleep 120
/ # ps -ef
PID   USER     TIME  COMMAND
    1 root      0:00 sleep 120  # 这是容器的主进程 (PID 1)
    6 root      0:00 sh         # 这是 docker exec 启动的 shell 进程
   12 root      0:00 ps -ef     # ps 命令本身

# 可以在容器内手动启动其他服务,比如 nginx (注意:这不会成为主进程)
/ # nginx
... (nginx 启动日志) ...

# 再次查看进程,nginx 进程已启动,但 PID 1 仍然是 sleep
/ # ps -ef
PID   USER     TIME  COMMAND
    1 root      0:00 sleep 120
    6 root      0:00 sh
   20 root      0:00 nginx: master process nginx
   21 nginx     0:00 nginx: worker process
   22 nginx     0:00 nginx: worker process
   23 root      0:00 ps -ef

# 退出容器的 shell 环境
/ # exit
root@docker102:~#

9. 容器生命周期与其主进程的绑定关系

这是理解容器运行机制的核心要点:Docker 容器的生命周期与其内部的主进程(PID 1)紧密绑定。当容器的主进程退出时,无论是什么原因(正常完成、出错、被杀死),容器自身也会停止。

回顾上面的 c2 容器示例:

  • 我们使用 docker run ... sleep 120 启动了 c2,所以 sleep 120 是它的主进程 (PID 1)。
  • 虽然我们通过 docker exec 进入容器并手动启动了 nginx,但这只是容器内部的附加进程,并非主进程。
  • sleep 120 命令会在 120 秒后正常退出。

结果:

# 等待大约 2 分钟后,再次查看 c2 的状态
root@docker102:~# docker ps -l
CONTAINER ID   IMAGE                                                       COMMAND                  CREATED         STATUS                      PORTS     NAMES
a266625de6d9   registry.cn-hangzhou.aliyuncs.com/yinzhengjie-k8s/apps:v1   "/docker-entrypoint.…"   2 minutes ago   Exited (0) 38 seconds ago             c2

可以看到,c2 容器的状态变成了 Exited (0)。这是因为它的主进程 sleep 120 在 120 秒后执行完毕并正常退出(退出码 0),即使我们在容器内手动启动的 nginx 可能还在运行,但由于主进程已结束,Docker 判定该容器的生命周期结束,因此将其停止。

总结与关键要点

  • docker ps -a: 查看所有容器状态。
  • docker stop: 优雅停止容器(SIGTERM -> SIGKILL),是首选的停止方式。
  • docker start: 启动已停止的容器,保留原有配置。
  • docker restart: 方便地停止并重新启动容器。
  • docker kill: 强制终止容器(SIGKILL),用于 stop 无效或紧急情况。
  • 容器是进程: 运行的容器对应宿主机上的一个或多个进程。
  • 自定义命令: docker run [image] [command] 可以覆盖镜像的默认启动命令。
  • docker exec: 在运行的容器内执行命令,用于调试和交互。
  • 生命周期核心: 容器与其主进程(PID 1)共存亡。主进程退出,容器即停止。

掌握这些命令和概念,将使您在日常的 Docker 使用中更加得心应手,能够有效地管理和维护您的容器化应用。


来源链接:https://www.cnblogs.com/leojazz/p/18807707

请登录后发表评论

    没有回复内容