3. Docker容器管理:从生到死的全流程掌控
容器管理,说白了就是管好你的应用进程。我见过不少新手,docker run一把梭,出了问题就抓瞎。今天咱们把容器的生命周期、资源限制、日志监控这些核心操作捋一遍。嗯,都是实战中每天要用的东西。
3.1 容器生命周期:创建、启动、停止、删除
容器的生命周期,就像人的一生:创建(出生)、启动(开始工作)、停止(休息)、删除(离开)。每个阶段都有对应的命令,咱们一个一个看。
3.1.1 创建容器(docker create)
docker create 只是创建一个容器,但不启动它。我个人习惯用这个命令做预配置,比如先挂载卷、设置网络,确认无误后再启动。
# 创建一个nginx容器,但不启动
docker create --name my-nginx -p 8080:80 nginx:latest
# 查看容器状态(Created)
docker ps -a | grep my-nginx
docker create 可以提前分配好资源,避免启动时手忙脚乱。我在项目中经常用它来批量创建测试环境。
3.1.2 启动容器(docker start)
容器创建后,用 docker start 启动它。注意,这和 docker run 不同——docker run 是创建+启动一步到位。
# 启动刚才创建的容器
docker start my-nginx
# 查看运行状态
docker ps
为什么会有人用 docker create + docker start 而不是直接 docker run?因为有时候你需要精细控制启动时机。比如在CI/CD流水线中,先拉取镜像、创建容器,等所有依赖就绪后再统一启动。
3.1.3 停止容器(docker stop)
停止容器时,Docker会先发送SIGTERM信号,给进程10秒的优雅退出时间。如果进程没反应,再发SIGKILL强制杀死。
# 优雅停止
docker stop my-nginx
# 强制停止(不推荐)
docker kill my-nginx
docker kill 停了一个数据库容器,结果导致数据损坏。后来我养成了习惯:优先用 docker stop,给应用足够的清理时间。如果10秒不够,可以用 -t 参数延长等待时间,比如 docker stop -t 30 my-nginx。
3.1.4 删除容器(docker rm)
容器停止后,它的文件系统还在。要彻底清理,用 docker rm。
# 删除已停止的容器
docker rm my-nginx
# 强制删除运行中的容器
docker rm -f my-nginx
# 清理所有已停止的容器
docker container prune
你想想看,如果容器一直不删,磁盘空间迟早会爆。我在CI流水线中,每次构建完都会自动清理临时容器,不然跑几次磁盘就满了。
3.2 容器资源限制:别让一个容器吃掉所有资源
默认情况下,容器可以使用宿主机的全部CPU和内存。这很危险——一个出问题的容器可能拖垮整个机器。所以,资源限制是必须的。
3.2.1 CPU限制
Docker通过cgroup来控制CPU使用。常用参数有 --cpus 和 --cpu-shares。
| 参数 | 说明 | 示例 |
|---|---|---|
--cpus |
限制容器使用的CPU核心数(精确到小数点) | --cpus=1.5 表示最多用1.5个核心 |
--cpu-shares |
设置CPU权重(相对值,默认1024) | --cpu-shares=512 表示只有默认一半的优先级 |
--cpuset-cpus |
绑定到特定CPU核心 | --cpuset-cpus=0,2 只使用第0和第2个核心 |
# 限制容器最多使用1.5个CPU核心
docker run -d --name web --cpus=1.5 nginx
# 设置CPU权重,让重要容器优先获得CPU
docker run -d --name critical-app --cpu-shares=2048 myapp
--cpu-shares 设成2048,非核心服务设成512。这样即使CPU争抢,核心服务也不会被饿死。
3.2.2 内存限制
内存限制比CPU更严格——超过限制会触发OOM(Out Of Memory)杀死容器。
| 参数 | 说明 | 示例 |
|---|---|---|
--memory 或 -m |
限制最大内存使用量 | -m=512m 最多使用512MB |
--memory-swap |
限制内存+交换分区总量 | --memory-swap=1g 内存+swap最多1GB |
--memory-reservation |
软限制,当内存充足时允许超过 | --memory-reservation=256m 建议保留256MB |
# 限制内存为512MB,swap为1GB
docker run -d --name app -m=512m --memory-swap=1g myapp
# 设置软限制,内存紧张时才会限制
docker run -d --name relaxed-app --memory-reservation=256m myapp
--memory-swap,默认情况下容器可以使用双倍内存(比如 -m=512m 时,swap也是512m)。我曾经有个Java应用因为没设swap限制,内存飙升后把宿主机swap打满了,导致其他容器响应变慢。所以,建议明确设置 --memory-swap。
3.3 容器日志与监控:出了问题别慌
容器跑起来后,日志和监控就是你的眼睛。没有它们,你就像在黑暗中开车。
3.3.1 查看日志(docker logs)
docker logs 是最常用的调试命令。它默认输出容器的标准输出和标准错误流。
# 查看最近100行日志
docker logs --tail 100 my-nginx
# 实时跟踪日志(类似tail -f)
docker logs -f my-nginx
# 显示时间戳
docker logs -t my-nginx
为什么有时候 docker logs 看不到日志?因为应用可能把日志写到了文件里,而不是标准输出。我建议在Dockerfile里把日志重定向到stdout,比如 RUN ln -sf /dev/stdout /var/log/nginx/access.log。
3.3.2 日志驱动配置
Docker支持多种日志驱动,默认是 json-file。生产环境建议用 journald 或 syslog,方便集中管理。
| 日志驱动 | 说明 | 适用场景 |
|---|---|---|
json-file |
默认,日志以JSON格式存文件 | 开发环境、单机测试 |
journald |
写入systemd日志 | 与systemd集成的系统 |
syslog |
发送到syslog服务器 | 集中式日志收集 |
gelf |
Graylog Extended Log Format | 与Graylog等工具集成 |
# 使用journald驱动启动容器
docker run -d --log-driver journald --name app myapp
# 限制日志文件大小和数量(json-file驱动)
docker run -d --log-opt max-size=10m --log-opt max-file=3 myapp
--log-opt max-size=10m --log-opt max-file=3,确保日志不会无限增长。
3.3.3 实时监控(docker stats)
docker stats 可以实时查看容器的CPU、内存、网络、磁盘IO等指标。它就像容器的任务管理器。
# 查看所有运行中容器的资源使用
docker stats
# 查看特定容器
docker stats my-nginx my-app
# 只显示一次(不持续刷新)
docker stats --no-stream
输出示例:
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O
abc123def456 my-nginx 0.05% 12.3MiB / 512MiB 2.40% 1.2kB / 1.5kB 0B / 0B
你想想看,如果某个容器CPU一直100%,或者内存持续增长,那肯定有问题。我在生产环境会用 docker stats 配合脚本做告警,比如内存超过80%就发通知。
3.4 进入容器与执行命令:调试的终极武器
有时候容器出了问题,日志看不出来,你需要直接进到容器里面看看。这时候 docker exec 就派上用场了。
3.4.1 进入容器(docker exec -it)
# 进入容器的bash shell
docker exec -it my-nginx /bin/bash
# 如果容器没有bash,用sh
docker exec -it my-nginx /bin/sh
-it 参数很关键:-i 保持标准输入打开,-t 分配一个伪终端。少了任何一个,你可能无法正常交互。
docker exec -it my-container /bin/busybox sh。如果连busybox都没有,那就只能挂载卷进去看了。
3.4.2 在容器中执行单条命令
有时候你不需要交互式shell,只想执行一条命令看看结果。
# 查看容器内的环境变量
docker exec my-nginx env
# 检查进程
docker exec my-nginx ps aux
# 测试网络连通性
docker exec my-nginx ping google.com
为什么不用 docker attach?attach 是连接到容器的主进程,如果你退出,容器也会停止。而 exec 是启动一个新进程,退出不影响容器运行。我个人几乎不用 attach,除非要调试容器的初始化过程。
3.4.3 从宿主机复制文件到容器
有时候你需要把配置文件或调试工具拷进容器。
# 从宿主机复制到容器
docker cp /path/to/local/file my-nginx:/path/in/container/
# 从容器复制到宿主机
docker cp my-nginx:/var/log/nginx/access.log ./
docker cp 在容器停止时也能工作,但只能复制文件,不能复制目录的权限信息。我曾经用 docker cp 覆盖了容器里的配置文件,结果因为权限不对导致应用启动失败。所以,复制后最好检查一下文件权限。
3.5 综合实战:一个完整的容器管理流程
咱们把今天学的串起来,模拟一个典型的CI/CD场景:
# 1. 拉取镜像
docker pull myapp:latest
# 2. 创建容器(预配置资源限制)
docker create \
--name myapp-prod \
--cpus=2 \
-m=1g \
--memory-swap=2g \
--log-opt max-size=10m \
--log-opt max-file=3 \
-p 8080:8080 \
myapp:latest
# 3. 启动容器
docker start myapp-prod
# 4. 检查运行状态
docker ps | grep myapp-prod
# 5. 查看资源使用
docker stats --no-stream myapp-prod
# 6. 查看日志
docker logs --tail 50 myapp-prod
# 7. 如果需要调试,进入容器
docker exec -it myapp-prod /bin/bash
# 8. 停止容器(优雅退出)
docker stop -t 30 myapp-prod
# 9. 删除容器
docker rm myapp-prod
这个流程我在自动化部署脚本里用了无数次。你想想看,每一步都有对应的命令,出了问题也能快速定位。嗯,这就是Docker容器管理的精髓——把复杂的事情拆成一个个可控的步骤。