容器运行与生命周期
镜像是只读模板,容器是由镜像创建的运行实例。本篇使用 Nginx 和 Alpine 做实验,学习容器从创建到删除的完整生命周期。
完成后,你将能够:
- 区分
run、start、stop、restart和rm; - 选择前台、后台或交互式运行方式;
- 查看日志、进程、状态和退出码;
- 注入配置并限制 CPU、内存;
- 理解主进程、停止信号和健康检查。
1. 容器状态与主进程
每个容器都有一个主进程,也叫 PID 1:
- 主进程运行,容器就是
running; - 主进程正常结束,容器变成
exited (0); - 主进程异常结束,通常会得到非零退出码;
- 容器停止后,其可写层和配置仍存在,删除容器才会移除它们。
Docker 容器不是一台需要一直“开机”的小型虚拟机。不要通过在后台硬塞一个无意义进程来保持容器运行,应该让应用本身作为主进程运行。
2. 三种常见运行方式
2.1 前台运行
# 前台启动 Nginx;日志占用当前终端
docker run --name web-foreground \
--publish 8080:80 \
nginx:1.27-alpine
按 Ctrl+C 会向容器发送中断信号并停止它。检查状态并删除:
# -a 同时显示已经停止的容器
docker ps -a --filter name=web-foreground
# 删除已停止的实验容器
docker rm web-foreground
2.2 后台运行
Web 服务通常在后台运行:
# -d 让容器在后台运行,命令返回容器 ID
docker run --detach \
--name web \
--publish 8080:80 \
nginx:1.27-alpine
# 查看正在运行的 web 容器
docker ps --filter name=web
2.3 交互式临时容器
学习命令或临时检查网络时,常用 -it --rm:
# -i 保持标准输入,-t 分配终端,--rm 在退出后自动删除容器
docker run --interactive --tty --rm \
alpine:3.20 sh
# 下面两条在容器 shell 中执行;查看系统信息后退出
cat /etc/os-release
exit
退出后执行 docker ps -a,不会看到这个 Alpine 容器,因为 --rm 已自动删除它。
3. run、create 和 start 的区别
docker run 实际组合了“创建”和“启动”两步:
# 只创建容器,不启动;此时状态为 Created
docker create --name created-demo alpine:3.20 \
sh -c 'echo "container started"; sleep 10'
# 启动已存在的容器,并附着输出;不会创建第二个容器
docker start --attach created-demo
# 删除已经退出的实验容器
docker rm created-demo
记住以下规则:
docker run IMAGE:创建一个新容器并立即启动;docker start NAME:启动一个已存在且已停止的容器;- 同名容器存在时,再次
docker run --name 同名 ...会报名称冲突; - 想用新镜像或新参数替换容器时,通常删除旧容器再创建。
4. 查看状态、日志和进程
确保前面的 web 容器正在运行,然后执行:
# 查看容器列表;--no-trunc 可以显示完整命令
docker ps --no-trunc --filter name=web
# 查看最近 20 行日志,并持续输出后续日志;按 Ctrl+C 只退出查看
docker logs --follow --tail 20 web
# 显示容器内当前进程,不要求镜像中安装 ps
docker top web
# 提取状态、启动时间和退出码;运行中退出码通常为 0
docker inspect web \
--format 'status={{.State.Status}} started={{.State.StartedAt}} exit={{.State.ExitCode}}'
# 查看一次 CPU、内存、网络和块设备 I/O 快照
docker stats --no-stream web
docker logs 显示的是容器主进程写到标准输出和标准错误的内容。应用应把常规日志输出到这两个流,而不是只写容器内文件。
5. 停止、重启和删除
# 先发送 SIGTERM,最多等待 10 秒,再发送 SIGKILL
docker stop --time 10 web
# 确认容器仍存在,但状态变成 Exited
docker ps -a --filter name=web
# 按原有端口、环境变量和挂载配置重新启动
docker start web
# 停止后立即重新启动;常用于让进程重新读取配置
docker restart --time 10 web
# 强制删除运行中的实验容器:先停止,再删除
docker rm --force web
应用应该处理 SIGTERM,停止接收新请求、完成正在处理的任务并退出。只有应用没有及时退出时,Docker 才会在超时后发送 SIGKILL。
6. 传入环境变量
先用命令行传入一个非敏感配置:
# 容器输出环境变量后退出;--rm 自动清理
docker run --rm \
--env APP_ENV=development \
alpine:3.20 \
sh -c 'echo "APP_ENV=$APP_ENV"'
变量较多时使用环境文件。创建一个仅用于实验的 demo.env:
# 环境文件采用 KEY=VALUE 格式,不写 export
APP_ENV=development
LOG_LEVEL=debug
# 从 demo.env 注入变量,并在容器中打印指定项
docker run --rm \
--env-file ./demo.env \
alpine:3.20 \
sh -c 'env | grep -E "^(APP_ENV|LOG_LEVEL)="'
环境变量会出现在容器配置中,不适合承载长期密钥。不要提交包含密码的 .env;生产环境使用平台提供的 secret 机制。
7. 覆盖默认命令
镜像通常定义默认命令。把命令写在镜像名后面,可以覆盖镜像的 CMD:
# alpine 默认启动 shell;这里改为输出一句话后退出
docker run --rm alpine:3.20 \
echo 'hello from container'
# 查看镜像默认的 Entrypoint 和 Cmd
docker image inspect alpine:3.20 \
--format 'Entrypoint={{json .Config.Entrypoint}} Cmd={{json .Config.Cmd}}'
不要混淆两类参数:镜像名前的是 Docker 参数,镜像名后的是容器内命令或该命令的参数。
8. 进入运行中的容器
重新启动一个 Nginx 实验容器:
# 后台启动容器,本节不发布端口
docker run --detach --name inspect-web nginx:1.27-alpine
# 在运行中的容器里额外启动 sh;exit 不会停止 Nginx 主进程
docker exec --interactive --tty inspect-web sh
# 以下命令在容器 shell 中执行,然后退出
nginx -v
ls -la /usr/share/nginx/html
exit
还可以从外部执行单条命令或复制文件:
# 在容器内执行只读检查,不进入交互 shell
docker exec inspect-web cat /etc/nginx/conf.d/default.conf
# 把默认首页复制到当前宿主机目录
docker cp inspect-web:/usr/share/nginx/html/index.html ./nginx-index.html
# 查看容器可写层相对镜像有哪些新增、修改或删除
docker diff inspect-web
# 清理实验容器
docker rm --force inspect-web
docker exec 中的临时修改只属于当前容器,重建后会丢失。正式修改应进入代码、Dockerfile 或挂载配置,而不是手工改线上容器。
9. 限制资源
默认情况下,容器可能使用宿主机的大部分资源。为服务设置合理上限:
# 限制最多使用 0.5 个 CPU、128 MiB 内存和 100 个进程
docker run --detach \
--name limited-web \
--cpus 0.5 \
--memory 128m \
--pids-limit 100 \
nginx:1.27-alpine
# 查看 Docker 记录的内存与 CPU 限制
docker inspect limited-web \
--format 'memory={{.HostConfig.Memory}} nano_cpus={{.HostConfig.NanoCpus}} pids={{.HostConfig.PidsLimit}}'
# 清理实验容器
docker rm --force limited-web
内存上限过低会导致进程被 OOM Kill。限制值应根据实际观测设置,而不是盲目照抄示例。
10. 自动重启策略
常用策略如下:
| 策略 | 行为 |
|---|---|
no | 默认值,不自动重启 |
on-failure[:次数] | 仅在非零退出码时重启,可限制次数 |
always | 无论退出原因都重启;手动停止后,Engine 重启时还会启动 |
unless-stopped | 自动重启,但手动停止后不会因 Engine 重启而启动 |
# 为长期服务设置 unless-stopped;它不等同于健康检查或高可用
docker run --detach \
--name restart-demo \
--restart unless-stopped \
nginx:1.27-alpine
# 查看容器当前重启策略
docker inspect restart-demo \
--format '{{.HostConfig.RestartPolicy.Name}}'
# 清理容器;删除后重启策略自然不再生效
docker rm --force restart-demo
11. 健康检查
“进程仍在运行”不代表“服务能正常响应”。Dockerfile 可以定义健康检查:
# 每 30 秒请求一次本机健康端点,连续 3 次失败标记为 unhealthy
HEALTHCHECK \
CMD wget -q -O /dev/null http://127.0.0.1/ || exit 1
也可以在运行时为支持 wget 的 Nginx Alpine 镜像添加检查:
# 启动带健康检查的容器;start-period 给服务预留启动时间
docker run --detach \
--name healthy-web \
--health-cmd='wget -q -O /dev/null http://127.0.0.1/ || exit 1' \
--health-interval=10s \
--health-timeout=3s \
--health-start-period=5s \
--health-retries=3 \
nginx:1.27-alpine
# 等待几秒后查看 Status 列中的 healthy 状态
docker ps --filter name=healthy-web
# 查看每次健康检查的退出码和输出
docker inspect healthy-web \
--format '{{json .State.Health}}'
# 清理实验容器
docker rm --force healthy-web
Docker Engine 会记录 healthy 或 unhealthy,但不会仅因为容器不健康就自动重启它。应用仍需合理的超时、重试和外部监控。
12. 本篇练习
- 创建一个后台 Nginx 容器,停止后再用
docker start启动,确认容器 ID 不变; - 运行一个命令以退出码 7 结束,再从
docker inspect中找到这个退出码; - 为 Nginx 设置 64 MiB 内存限制,并用
docker stats --no-stream查看; - 比较
docker attach和docker exec的帮助信息,思考为什么日常调试通常使用后者。
下一篇将解释容器网络、端口映射,以及两个容器如何通过名称互相访问。