Docker容器生命周期管理

本文是Docker专栏系列的第四篇,将全面深入地讲解Docker容器生命周期管理的所有知识。从容器的创建、运行、暂停、停止到删除,涵盖每一个状态转换的底层原理、命令参数详解、资源限制机制、日志管理、健康检查、批量运维以及生产环境故障排查实战。文章面向有一定Docker基础的开发者和运维工程师,力求做到既有理论深度又有实战价值。



第一章 容器生命周期概述

1.1 容器的完整生命周期(创建→运行→暂停→停止→删除)

Docker容器的生命周期是指容器从被创建到最终被删除的整个过程。理解容器生命周期的每一个阶段,是进行高效容器运维的基础。一个容器的完整生命周期包含以下几个核心阶段:

创建阶段(Created)

当执行docker create命令时,Docker引擎会在镜像的基础上创建一个可写的容器层,但此时容器并未启动,容器内的进程也尚未运行。这个阶段的主要工作包括:

  • 在镜像层之上创建一个可读写的容器层(Container Layer)
  • 分配容器ID(64位十六进制字符串)
  • 根据参数配置网络、存储、资源限制等
  • 将容器的元数据写入/var/lib/docker/containers/<container-id>/目录
# 创建一个nginx容器,但不启动它
docker create --name my-nginx -p 8080:80 nginx:latest

# 输出示例:
# 3a7c9f2b8e1d4f5a6c7b8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b

运行阶段(Running)

通过docker startdocker run命令,容器从created状态进入running状态。此时容器内的主进程(PID 1)被启动,容器开始执行其设计的工作:

# 启动已创建的容器
docker start my-nginx

# 或者直接创建并启动
docker run --name my-nginx -p 8080:80 -d nginx:latest

暂停阶段(Paused)

通过docker pause命令,可以冻结容器内的所有进程。此时进程并未被杀死,而是被挂起(suspend),不再消耗CPU资源,但内存中的数据仍然保留:

# 暂停容器
docker pause my-nginx

# 恢复容器
docker unpause my-nginx

停止阶段(Stopped)

通过docker stop命令,容器收到SIGTERM信号,等待一段时间后(默认10秒)如果仍未退出则发送SIGKILL强制终止。容器进入stopped状态后,其文件系统和配置仍然保留,可以随时重新启动:

# 停止容器(优雅停止)
docker stop my-nginx

# 强制停止容器
docker kill my-nginx

删除阶段(Removed)

通过docker rm命令,容器被永久删除。此时容器的文件系统、配置、日志等所有数据都会被清除,无法恢复:

# 删除已停止的容器
docker rm my-nginx

# 强制删除运行中的容器
docker rm -f my-nginx

下面是一个完整的容器生命周期演示脚本:

#!/bin/bash
# 完整的容器生命周期演示

echo "=== 1. 创建容器 ==="
docker create --name lifecycle-demo alpine:latest sleep 3600
echo "容器状态: $(docker inspect -f '{{.State.Status}}' lifecycle-demo)"
# 输出: created

echo "=== 2. 启动容器 ==="
docker start lifecycle-demo
echo "容器状态: $(docker inspect -f '{{.State.Status}}' lifecycle-demo)"
# 输出: running

echo "=== 3. 暂停容器 ==="
docker pause lifecycle-demo
echo "容器状态: $(docker inspect -f '{{.State.Status}}' lifecycle-demo)"
# 输出: paused

echo "=== 4. 恢复容器 ==="
docker unpause lifecycle-demo
echo "容器状态: $(docker inspect -f '{{.State.Status}}' lifecycle-demo)"
# 输出: running

echo "=== 5. 停止容器 ==="
docker stop lifecycle-demo
echo "容器状态: $(docker inspect -f '{{.State.Status}}' lifecycle-demo)"
# 输出: exited

echo "=== 6. 再次启动 ==="
docker start lifecycle-demo
echo "容器状态: $(docker inspect -f '{{.State.Status}}' lifecycle-demo)"
# 输出: running

echo "=== 7. 停止并删除 ==="
docker stop lifecycle-demo
docker rm lifecycle-demo
echo "容器已删除"

echo "=== 生命周期演示完成 ==="

1.2 容器状态机详解(created/running/paused/stopped/dead)

Docker容器在生命周期中可以处于以下几种状态,每种状态都有明确的含义和允许的状态转换:

状态 英文名称 描述 可转换到
已创建 created 容器已创建但未启动,进程未运行 running, removed
运行中 running 容器正在运行,主进程活跃 paused, stopped, dead
已暂停 paused 容器进程被冻结,内存保留 running, stopped
已停止 exited/stopped 容器进程已退出,文件系统保留 running, removed
已死亡 dead 容器出现异常,无法正常操作 removed

各状态详细说明:

created状态:
容器刚被docker create创建后所处的状态。此时容器已经分配了文件系统、网络接口等资源,但容器内的进程尚未启动。在这个状态下,你可以对容器进行一些预配置操作,比如使用docker cp向容器中拷贝文件。

# 查看created状态的容器
docker ps -a --filter "status=created"

# 在created状态下向容器拷贝文件
docker create --name pre-config nginx:latest
docker cp ./custom.conf pre-config:/etc/nginx/nginx.conf
docker start pre-config

running状态:
容器的主进程(PID 1)正在运行。这是容器最常见的工作状态。在running状态下,你可以使用docker exec进入容器执行命令,使用docker logs查看日志,使用docker top查看容器内进程等。

# 查看运行中的容器
docker ps

# 进入运行中的容器
docker exec -it my-nginx /bin/bash

# 查看容器内进程
docker top my-nginx

paused状态:
通过cgroup的freezer子系统将容器内所有进程冻结。进程暂停执行,不消耗CPU时间,但内存中的数据完整保留。这个状态常用于调试、资源临时释放或容器迁移前的准备。

# 暂停容器
docker pause my-nginx

# 查看暂停状态的容器
docker ps --filter "status=paused"

# 验证进程被冻结(进程状态会显示为D或T)
docker top my-nginx

exited状态(也叫stopped状态):
容器的进程已经退出。退出码0表示正常退出,非0表示异常退出。在此状态下,容器的可写层仍然存在,可以通过docker start重新启动容器。

# 查看已停止的容器
docker ps -a --filter "status=exited"

# 查看容器退出码
docker inspect -f '{{.State.ExitCode}}' my-nginx

# 查看容器退出时间
docker inspect -f '{{.State.FinishedAt}}' my-nginx

dead状态:
这是一个异常状态,通常表示容器在停止过程中出现了问题(比如无法删除容器的存储层、设备忙等)。处于dead状态的容器通常无法再启动,只能被强制删除。

# 查看dead状态的容器
docker ps -a --filter "status=dead"

# 强制删除dead状态的容器
docker rm -f dead-container

1.3 容器状态转换图解(详细文字描述)

容器的状态转换遵循一定的规则,并非所有状态之间都能自由切换。下面用文字详细描述容器状态机的转换路径:

                        docker create
                             |
                             v
                        +--------+
                        |created |
                        +--------+
                        /        \
              docker start      docker rm
                   /              \
                  v                v
            +---------+      +---------+
     +----->| running |      | removed |
     |      +---------+      +---------+
     |      /  |  |  \
     |     /   |  |   \
     |    /    |  |    \
     |   v     |  |     v
     | +------+ |  |  +------+
     | |paused| |  |  | dead |
     | +------+ |  |  +------+
     |   |      |  |
     |   |docker|  |docker
     |   |unpause| |kill/stop
     |   |      |  |
     +---+      |  |
                v  v
            +---------+
            | exited  |
            +---------+
              |     |
      docker start  docker rm
              |     |
              v     v
         +---------+ +---------+
         | running | | removed |
         +---------+ +---------+

状态转换路径详解:

  1. created → running: 执行docker start命令,容器从已创建状态进入运行状态。

  2. created → removed: 执行docker rm命令,直接删除未启动的容器。

  3. running → paused: 执行docker pause命令,冻结容器内所有进程。

  4. running → exited: 容器内主进程主动退出,或执行docker stop/docker kill命令。

  5. running → dead: 容器运行过程中出现严重异常(如存储驱动错误)。

  6. paused → running: 执行docker unpause命令,恢复被冻结的进程。

  7. paused → exited: 执行docker stop命令,停止暂停状态的容器。

  8. exited → running: 执行docker start命令,重新启动已停止的容器。

  9. exited → removed: 执行docker rm命令,删除已停止的容器。

  10. dead → removed: 执行docker rm -f命令,强制删除异常状态的容器。

需要特别注意的是,以下状态转换是不允许的:

  • paused状态不能直接转换为removed(需要先stop)
  • exited状态不能直接转换为paused(需要先start)
  • dead状态不能转换为running(只能被删除)

1.4 容器与进程的关系

理解容器与进程的关系是深入掌握Docker的关键。容器本质上就是一组被隔离的进程,它们运行在宿主机上,但通过Linux内核的命名空间(Namespace)和控制组(cgroups)技术实现了隔离和资源限制。

容器中的PID 1:

每个容器都有一个主进程,其进程号为1(PID 1)。这个进程是容器内所有其他进程的祖先。容器的生命周期与PID 1紧密绑定——当PID 1退出时,容器就会停止。

# 查看容器内的进程树
docker exec my-nginx ps aux
# USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
# root         1  0.0  0.0  10656  3248 ?        Ss   10:00   0:00 nginx: master process
# nginx       29  0.0  0.0  11104  1680 ?        S    10:00   0:00 nginx: worker process

# 在宿主机上查看容器进程(实际上是宿主机上的进程)
docker inspect -f '{{.State.Pid}}' my-nginx
# 输出: 12345(宿主机上的进程号)

# 在宿主机上查看该进程
ps aux | grep 12345
# root  12345  0.0  0.0  10656  3248 ?  Ss  10:00  0:00 nginx: master process

命名空间隔离:

容器使用Linux命名空间技术实现进程隔离。Docker使用了以下几种命名空间:

命名空间 作用 说明
PID 进程隔离 容器内的进程看不到宿主机上的其他进程
NET 网络隔离 容器有独立的网络栈(网卡、IP、路由表等)
IPC 进程间通信隔离 容器有独立的信号量、消息队列和共享内存
MNT 文件系统挂载隔离 容器有独立的文件系统视图
UTS 主机名隔离 容器有独立的主机名和域名
USER 用户隔离 容器内的用户与宿主机用户映射
# 查看容器使用的命名空间
docker inspect -f '{{.State.Pid}}' my-nginx
# 假设输出 12345

ls -la /proc/12345/ns/
# lrwxrwxrwx 1 root root 0 Aug  6 10:00 cgroup -> cgroup:[4026531835]
# lrwxrwxrwx 1 root root 0 Aug  6 10:00 ipc -> ipc:[4026532208]
# lrwxrwxrwx 1 root root 0 Aug  6 10:00 mnt -> mnt:[4026532206]
# lrwxrwxrwx 1 root root 0 Aug  6 10:00 net -> net:[4026532210]
# lrwxrwxrwx 1 root root 0 Aug  6 10:00 pid -> pid:[4026532211]
# lrwxrwxrwx 1 root root 0 Aug  6 10:00 user -> user:[4026531837]
# lrwxrwxrwx 1 root root 0 Aug  6 10:00 uts -> uts:[4026532209]

信号处理与容器生命周期:

容器停止时,Docker守护进程会向容器的PID 1进程发送SIGTERM信号。如果进程在指定的超时时间内(默认10秒)没有退出,则发送SIGKILL信号强制终止。因此,容器内的主进程必须能够正确处理信号才能实现优雅停止。

# docker stop 发送SIGTERM,等待10秒后发送SIGKILL
docker stop my-nginx

# docker kill 直接发送SIGKILL(或指定信号)
docker kill my-nginx
docker kill --signal=SIGTERM my-nginx

# 自定义停止超时时间(单位:秒)
docker stop -t 30 my-nginx

# 在Dockerfile中使用exec形式确保信号正确传递
# 错误写法(shell形式,信号无法传递):
# CMD nginx -g 'daemon off;'
# 正确写法(exec形式):
# CMD ["nginx", "-g", "daemon off;"]

一个典型的信号处理不当导致的问题:

# 问题:使用shell形式启动,信号无法传递给实际进程
# Dockerfile:
# FROM alpine:latest
# CMD sleep 3600

# 启动后尝试停止
docker run -d --name signal-test alpine:latest sleep 3600
docker stop signal-test
# 由于shell形式启动,sh进程作为PID 1,它不会将SIGTERM转发给sleep进程
# 导致docker stop需要等待10秒超时后才能强制杀死

# 正确做法:使用exec形式
# Dockerfile:
# FROM alpine:latest
# CMD ["sleep", "3600"]

1.5 容器生命周期中的资源变化

容器在不同生命周期阶段,其资源占用情况会发生变化。理解这些变化有助于排查资源泄漏和性能问题。

各阶段资源状态对比:

生命周期阶段 CPU 内存 磁盘(可写层) 网络
created 不消耗 不消耗 已分配 已配置但未激活
running 按需消耗 按需消耗 持续写入 活跃
paused 不消耗 保留(不释放) 不变 连接保留
exited 不消耗 已释放 保留 已断开
removed 不消耗 不消耗 已删除 已删除

磁盘空间管理:

每个容器都有一个可写层,容器运行过程中产生的所有文件修改都存储在这个层中。当容器被删除时,可写层也被删除。如果不删除容器,可写层会持续增长。

# 查看容器可写层大小
docker inspect -f '{{.SizeRootFs}}' my-nginx

# 查看容器可写层的实际使用大小
docker ps -s
# CONTAINER ID   IMAGE   COMMAND   ...   SIZE
# 3a7c9f2b8e1d   nginx   "/docker-entrypoint.…"   ...   2MB (virtual 142MB)
# SIZE列显示的是可写层大小,virtual是镜像+可写层的总大小

# 查看Docker磁盘使用情况
docker system df
# TYPE     TOTAL   ACTIVE  SIZE      RECLAIMABLE
# Images   5       3       1.2GB     400MB (33%)
# Containers  8    3       120MB     80MB (66%)
# Local Volumes  2  2     500MB     0B (0%)

内存资源管理:

容器运行时,其内存使用包括进程代码段、数据段、堆、栈以及共享库等。当容器停止时,这些内存被完全释放。但如果容器被pause而非stop,内存仍然被占用。

# 实时监控容器资源使用
docker stats my-nginx
# CONTAINER ID   NAME       CPU %   MEM USAGE / LIMIT   MEM %   NET I/O
# 3a7c9f2b8e1d   my-nginx   0.50%   25MiB / 512MiB      4.88%   1.2kB/648B

# 查看容器内存限制
docker inspect -f '{{.HostConfig.Memory}}' my-nginx
# 输出: 536870912 (即512MB)

# 查看容器的内存使用详情
cat /sys/fs/cgroup/memory/docker/<container-id>/memory.usage_in_bytes

网络资源管理:

容器创建时会分配网络接口(veth pair),容器运行时网络接口处于UP状态,容器停止时网络接口被移除,容器删除时相关的网络配置也被清除。

# 查看容器网络配置
docker inspect -f '{{json .NetworkSettings.Networks}}' my-nginx | python -m json.tool

# 查看宿主机上的veth接口(容器运行时)
ip link show
# ... veth1234567@if12: <BROADCAST,MULTICAST,UP,LOWER_UP> ...

# 容器停止后veth接口会消失
docker stop my-nginx
ip link show
# veth接口已不存在

实际运维中的资源泄漏排查:

在生产环境中,经常遇到停止的容器占用磁盘空间导致存储不足的问题。以下是一个排查和清理的示例:

#!/bin/bash
# 容器资源泄漏排查脚本

echo "=== Docker磁盘使用情况 ==="
docker system df -v

echo ""
echo "=== 已停止容器列表(按可写层大小排序) ==="
docker ps -a --filter "status=exited" --format "{{.ID}}\t{{.Names}}\t{{.Size}}" | sort -t$'\t' -k3 -rh

echo ""
echo "=== 清理已停止超过7天的容器 ==="
# 获取7天前停止的容器并删除
docker ps -a --filter "status=exited" --format "{{.ID}}\t{{.Names}}\t{{.Status}}" | while read line; do
    container_id=$(echo "$line" | cut -f1)
    container_name=$(echo "$line" | cut -f2)
    exit_time=$(docker inspect -f '{{.State.FinishedAt}}' "$container_id")
    # 比较时间并删除7天前的容器
    days_ago=$(( ($(date +%s) - $(date -d "$exit_time" +%s)) / 86400 ))
    if [ "$days_ago" -gt 7 ]; then
        echo "删除容器: $container_name (已停止 $days_ago 天)"
        docker rm "$container_id"
    fi
done

第二章 创建与启动容器

2.1 docker create命令详解(所有参数)

docker create命令用于创建一个新容器但不启动它。这是容器生命周期的第一步。其基本语法如下:

docker create [OPTIONS] IMAGE [COMMAND] [ARG...]

docker create命令支持大量参数,下面按功能分类详解:

基础参数:

# --name: 为容器指定名称(不指定则Docker自动生成)
docker create --name my-web nginx:latest

# --hostname: 设置容器的主机名
docker create --name my-web --hostname webserver nginx:latest

# --domainname: 设置容器的域名
docker create --name my-web --domainname example.com nginx:latest

# -i, --interactive: 保持标准输入打开(即使未附加)
docker create -i --name my-web nginx:latest

# -t, --tty: 分配一个伪终端
docker create -t --name my-web nginx:latest

# --init: 在容器中运行一个init进程,负责信号转发和回收僵尸进程
docker create --init --name my-app my-image:latest

运行环境参数:

# -e, --env: 设置环境变量
docker create -e MYSQL_ROOT_PASSWORD=secret --name mysql mysql:8.0

# --env-file: 从文件读取环境变量
docker create --env-file /path/to/env.list --name my-app my-image:latest

# -w, --workdir: 设置工作目录
docker create -w /app --name my-app my-image:latest

# -u, --user: 指定运行用户
docker create -u 1000:1000 --name my-app my-image:latest
docker create -u appuser --name my-app my-image:latest

# --entrypoint: 覆盖镜像的ENTRYPOINT
docker create --entrypoint /bin/bash --name my-app my-image:latest

网络参数:

# --network: 指定网络模式
docker create --network my-net --name my-app my-image:latest

# --network-alias: 为容器设置网络别名
docker create --network my-net --network-alias web --name my-app my-image:latest

# -p, --publish: 端口映射
docker create -p 8080:80 --name my-web nginx:latest

# -P, --publish-all: 随机映射所有暴露端口
docker create -P --name my-web nginx:latest

# --dns: 设置DNS服务器
docker create --dns 8.8.8.8 --name my-app my-image:latest

# --add-host: 添加host映射
docker create --add-host "db.local:192.168.1.100" --name my-app my-image:latest

# --mac-address: 设置MAC地址
docker create --mac-address "02:42:ac:11:00:01" --name my-app my-image:latest

# --ip: 指定容器IP地址(仅适用于自定义网络)
docker create --network my-net --ip 172.20.0.10 --name my-app my-image:latest

存储参数:

# -v, --volume: 挂载数据卷
docker create -v /data --name my-app my-image:latest
docker create -v /host/data:/container/data --name my-app my-image:latest
docker create -v my-volume:/data --name my-app my-image:latest

# --mount: 更灵活的挂载方式
docker create --mount type=bind,source=/host/data,target=/container/data \
  --name my-app my-image:latest

docker create --mount type=volume,source=my-volume,target=/data \
  --name my-app my-image:latest

docker create --mount type=tmpfs,destination=/tmp \
  --name my-app my-image:latest

# --tmpfs: 挂载tmpfs文件系统
docker create --tmpfs /tmp --name my-app my-image:latest

# --read-only: 将容器文件系统设为只读
docker create --read-only --name my-app my-image:latest

# --storage-opt: 存储驱动选项
docker create --storage-opt size=10G --name my-app my-image:latest

资源限制参数:

# --cpus: 限制CPU使用量(核数)
docker create --cpus 1.5 --name my-app my-image:latest

# --cpu-shares: CPU权重(相对值,默认1024)
docker create --cpu-shares 512 --name my-app my-image:latest

# --cpuset-cpus: 绑定到指定CPU核心
docker create --cpuset-cpus 0,1 --name my-app my-image:latest

# -m, --memory: 限制内存使用量
docker create -m 512m --name my-app my-image:latest

# --memory-swap: 限制内存+交换空间
docker create -m 512m --memory-swap 1g --name my-app my-image:latest

# --memory-reservation: 内存软限制
docker create --memory-reservation 256m --name my-app my-image:latest

# --pids-limit: 限制容器内最大进程数
docker create --pids-limit 200 --name my-app my-image:latest

# --oom-kill-disable: 禁止OOM killer杀掉容器进程
docker create --oom-kill-disable -m 512m --name my-app my-image:latest

重启策略参数:

# --restart: 设置重启策略
docker create --restart no --name my-app my-image:latest
docker create --restart on-failure:3 --name my-app my-image:latest
docker create --restart always --name my-app my-image:latest
docker create --restart unless-stopped --name my-app my-image:latest

其他重要参数:

# --cap-add/--cap-drop: 添加/删除Linux capabilities
docker create --cap-add NET_ADMIN --name my-app my-image:latest
docker create --cap-drop ALL --cap-add CHOWN --name my-app my-image:latest

# --privileged: 给容器特权模式(危险,不推荐)
docker create --privileged --name my-app my-image:latest

# --security-opt: 安全选项
docker create --security-opt seccomp=unconfined --name my-app my-image:latest
docker create --security-opt label=type:container_runtime_t --name my-app my-image:latest

# --device: 添加宿主机设备
docker create --device /dev/sda:/dev/sda --name my-app my-image:latest

# --ulimit: 设置ulimit限制
docker create --ulimit nofile=65536:65536 --name my-app my-image:latest
docker create --ulimit nproc=4096:4096 --name my-app my-image:latest

# --healthcheck: 设置健康检查
docker create --health-cmd "curl -f http://localhost/ || exit 1" \
  --health-interval 5s --health-timeout 3s --health-retries 3 \
  --name my-web nginx:latest

# --shm-size: 设置/dev/shm大小
docker create --shm-size 256m --name my-app my-image:latest

# --sysctl: 设置内核参数
docker create --sysctl net.core.somaxconn=1024 --name my-app my-image:latest

2.2 docker run命令完整解析(50+参数详解)

docker run命令是Docker中最常用的命令之一,它等价于docker create + docker start。其语法为:

docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

下面详细解析50+个参数,按类别分组:

运行模式参数:

# -d, --detach: 后台运行容器
docker run -d --name my-web nginx:latest

# -it: 交互式运行(常用于调试)
docker run -it --name my-shell alpine:latest /bin/sh

# --rm: 容器退出时自动删除
docker run --rm alpine:latest echo "Hello Docker"

# --restart: 重启策略(详见2.6节)
docker run -d --restart always --name my-web nginx:latest

# --detach-keys: 自定义detach键序列(默认Ctrl+p, Ctrl+q)
docker run -it --detach-keys="ctrl-z" --name my-app my-image:latest

命名与标识参数:

# --name: 容器名称
docker run -d --name web-server nginx:latest

# --hostname: 容器内主机名
docker run -d --hostname web01 --name web-server nginx:latest

# --mac-address: 指定MAC地址
docker run -d --mac-address "02:42:ac:11:00:ab" --name my-app my-image:latest

环境配置参数:

# -e, --env: 单个环境变量
docker run -d -e "ENV=production" -e "DEBUG=false" --name my-app my-image:latest

# --env-file: 从文件加载环境变量
# env.list文件内容:
# MYSQL_ROOT_PASSWORD=secret
# MYSQL_DATABASE=mydb
# MYSQL_USER=appuser
# TZ=Asia/Shanghai
docker run -d --env-file ./env.list --name mysql mysql:8.0

# -w, --workdir: 工作目录
docker run -d -w /opt/app --name my-app my-image:latest

# -u, --user: 运行用户
docker run -d -u 1000:1000 --name my-app my-image:latest

# --init: 使用init进程
docker run -d --init --name my-app my-image:latest

网络配置参数:

# --network: 网络模式(bridge/none/host/container/<自定义网络>)
docker run -d --network host --name my-app my-image:latest
docker run -d --network my-net --name my-app my-image:latest
docker run -d --network container:other-container --name my-app my-image:latest

# --network-alias: 网络别名
docker run -d --network my-net --network-alias "api" --name my-app my-image:latest

# -p, --publish: 端口映射 [宿主机端口:]容器端口[/协议]
docker run -d -p 8080:80 --name my-web nginx:latest           # TCP
docker run -d -p 53:53/udp --name dns-server my-dns:latest    # UDP
docker run -d -p 8080-8090:8080-8090 --name my-app my-image   # 范围
docker run -d -p 127.0.0.1:8080:80 --name my-web nginx:latest # 绑定IP
docker run -d -P --name my-web nginx:latest                   # 随机端口

# --dns: 自定义DNS
docker run -d --dns 8.8.8.8 --dns 8.8.4.4 --name my-app my-image:latest

# --dns-search: DNS搜索域
docker run -d --dns-search example.com --name my-app my-image:latest

# --add-host: 添加/etc/hosts条目
docker run -d --add-host "db.local:192.168.1.100" \
  --add-host "cache.local:192.168.1.101" --name my-app my-image:latest

# --ip: 指定IP(自定义网络)
docker run -d --network my-net --ip 172.20.0.10 --name my-app my-image:latest

# --ip6: 指定IPv6地址
docker run -d --network my-net --ip6 2001:db8::10 --name my-app my-image:latest

# --link: 链接到另一个容器(已弃用,推荐使用自定义网络)
docker run -d --link redis:redis --name my-app my-image:latest

存储与卷参数:

# -v, --volume: 数据卷挂载
# 匿名卷
docker run -d -v /data --name my-app my-image:latest
# 命名卷
docker run -d -v my-data:/data --name my-app my-image:latest
# 绑定挂载
docker run -d -v /host/path:/container/path --name my-app my-image:latest
# 只读挂载
docker run -d -v /host/config:/config:ro --name my-app my-image:latest

# --mount: 更详细的挂载方式
docker run -d --mount type=bind,source=/host/data,target=/data,readonly \
  --name my-app my-image:latest

docker run -d --mount type=volume,source=my-vol,target=/data,volume-nocopy \
  --name my-app my-image:latest

docker run -d --mount type=tmpfs,destination=/tmp,tmpfs-size=64m \
  --name my-app my-image:latest

# --tmpfs: 挂载tmpfs
docker run -d --tmpfs /tmp:rw,size=64m --name my-app my-image:latest

# --read-only: 只读文件系统
docker run -d --read-only --tmpfs /tmp --tmpfs /var/run --name my-app my-image:latest

# --volumes-from: 从另一个容器挂载卷
docker run -d --volumes-from data-container --name my-app my-image:latest

# --shm-size: /dev/shm大小
docker run -d --shm-size 256m --name my-app my-image:latest

# --storage-opt: 存储选项
docker run -d --storage-opt size=20G --name my-app my-image:latest

资源限制参数:

# --cpus: CPU核数限制
docker run -d --cpus 2.0 --name my-app my-image:latest

# --cpu-shares: CPU权重(相对值)
docker run -d --cpu-shares 512 --name my-app my-image:latest

# --cpuset-cpus: 绑定CPU核心
docker run -d --cpuset-cpus "0-3" --name my-app my-image:latest
docker run -d --cpuset-cpus "0,2,4" --name my-app my-image:latest

# --cpuset-mems: 绑定NUMA内存节点
docker run -d --cpuset-mems "0" --name my-app my-image:latest

# -m, --memory: 内存限制
docker run -d -m 1g --name my-app my-image:latest

# --memory-swap: 交换空间限制
docker run -d -m 512m --memory-swap 1g --name my-app my-image:latest
# --memory-swap设置为-1表示不限制swap
docker run -d -m 512m --memory-swap -1 --name my-app my-image:latest

# --memory-reservation: 内存软限制
docker run -d -m 1g --memory-reservation 512m --name my-app my-image:latest

# --memory-swappiness: 调整swappiness(0-100)
docker run -d --memory-swappiness 60 --name my-app my-image:latest

# --kernel-memory: 内核内存限制
docker run -d --kernel-memory 100m --name my-app my-image:latest

# --oom-kill-disable: 禁止OOM Killer
docker run -d --oom-kill-disable -m 512m --name my-app my-image:latest

# --oom-score-adj: 调整OOM优先级(-1000到1000)
docker run -d --oom-score-adj -500 --name my-app my-image:latest

# --pids-limit: 进程数限制
docker run -d --pids-limit 200 --name my-app my-image:latest

# --device-read-bps / --device-write-bps: 读写速率限制
docker run -d --device-read-bps /dev/sda:10mb --name my-app my-image:latest

# --device-read-iops / --device-write-iops: 读写IOPS限制
docker run -d --device-write-iops /dev/sda:100 --name my-app my-image:latest

# --blkio-weight: 块IO权重(10-1000)
docker run -d --blkio-weight 500 --name my-app my-image:latest

# --ulimit: ulimit设置
docker run -d --ulimit nofile=65536:65536 --ulimit nproc=4096:4096 \
  --name my-app my-image:latest

安全与权限参数:

# --cap-add: 添加Linux capabilities
docker run -d --cap-add NET_ADMIN --name my-app my-image:latest
docker run -d --cap-add SYS_PTRACE --name my-app my-image:latest

# --cap-drop: 删除capabilities
docker run -d --cap-drop ALL --cap-add CHOWN --cap-add SETGID --cap-add SETUID \
  --name my-app my-image:latest

# --privileged: 特权模式
docker run -d --privileged --name my-app my-image:latest

# --security-opt: 安全选项
docker run -d --security-opt seccomp=unconfined --name my-app my-image:latest
docker run -d --security-opt apparmor=unconfined --name my-app my-image:latest
docker run -d --security-opt no-new-privileges --name my-app my-image:latest

# --userns: 用户命名空间模式
docker run -d --userns=host --name my-app my-image:latest

# --read-only: 只读根文件系统
docker run -d --read-only --name my-app my-image:latest

# --cgroup-parent: 指定父cgroup
docker run -d --cgroup-parent /docker/myapp --name my-app my-image:latest

日志参数:

# --log-driver: 日志驱动
docker run -d --log-driver json-file --name my-app my-image:latest
docker run -d --log-driver syslog --name my-app my-image:latest
docker run -d --log-driver fluentd --log-opt fluentd-address=localhost:24224 \
  --name my-app my-image:latest

# --log-opt: 日志选项
docker run -d --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  --name my-app my-image:latest

健康检查参数:

# --health-cmd: 健康检查命令
# --health-interval: 检查间隔
# --health-timeout: 超时时间
# --health-retries: 重试次数
# --health-start-period: 启动宽限期
docker run -d \
  --health-cmd "curl -f http://localhost:8080/health || exit 1" \
  --health-interval 10s \
  --health-timeout 5s \
  --health-retries 3 \
  --health-start-period 30s \
  --name my-web nginx:latest

# --no-healthcheck: 禁用健康检查
docker run -d --no-healthcheck --name my-web nginx:latest

2.3 交互式容器运行(-it参数)

交互式容器是开发和调试中常用的运行模式。-i-t两个参数通常组合使用:

  • -i(–interactive): 保持标准输入(STDIN)打开,即使没有附加到容器
  • -t(–tty): 分配一个伪终端( pseudo-TTY)
# 交互式运行Alpine Shell
docker run -it alpine:latest /bin/sh
# 进入容器后可以执行命令:
# / # ls
# / # cat /etc/os-release
# / # apk add curl
# / # exit

# 交互式运行Ubuntu Bash
docker run -it ubuntu:22.04 /bin/bash
# root@container_id:/# apt update
# root@container_id:/# apt install -y vim
# root@container_id:/# exit

单独使用-i vs -it的区别:

# 只用-i: 标准输入打开,但没有终端
echo "hello" | docker run -i alpine cat
# 输出: hello

# 使用-it: 宇宙标准输入打开,又分配了伪终端
docker run -it alpine /bin/sh
# 进入交互式Shell

# 只用-t: 分配了终端但标准输入未连接(较少使用)
docker run -t alpine echo "hello"

交互式容器的实际应用场景:

# 1. 在容器中临时调试
docker run -it --rm --network my-net my-image:latest /bin/sh

# 2. 运行一次性命令
docker run -it --rm python:3.11 python3 -c "print('Hello from Python')"

# 3. 交互式运行数据库客户端
docker run -it --rm --network my-net mysql:8.0 \
  mysql -h mysql-server -u root -p

# 4. 挂载代码到容器中开发
docker run -it --rm \
  -v $(pwd):/app \
  -w /app \
  node:18-alpine \
  sh

# 5. 从容器内部连接到其他容器
docker run -it --rm \
  --network app-network \
  alpine sh -c "apk add curl && curl http://web-server"

退出交互式容器但不停止:

在交互式容器中,使用Ctrl+P, Ctrl+Q键序列可以从容器中分离(detach)而不停止容器:

# 启动交互式容器
docker run -it --name interactive-demo alpine:latest /bin/sh
# / # 

# 按下 Ctrl+P, Ctrl+Q 分离
# 容器仍在运行

# 验证容器状态
docker ps
# 3a7c9f2b8e1d   alpine:latest   "/bin/sh"   Up 2 minutes   interactive-demo

# 重新连接到容器
docker attach interactive-demo
# / #

2.4 后台容器运行(-d参数)

-d(–detach)参数让容器在后台运行,容器启动后Docker会返回容器ID,终端不会被占用。这是生产环境中最常用的运行模式。

# 后台运行Nginx
docker run -d --name my-web -p 8080:80 nginx:latest
# 输出: 3a7c9f2b8e1d4f5a6c7b8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b

# 查看后台运行的容器
docker ps

后台运行与日志:

# 后台运行的容器,使用docker logs查看输出
docker run -d --name my-app my-image:latest

# 实时查看日志
docker logs -f my-app

# 查看最近100行日志
docker logs --tail 100 my-app

# 查看指定时间之后的日志
docker logs --since "2024-01-01T00:00:00" my-app

后台容器与交互式结合:

# 后台运行但保留交互能力
docker run -dit --name dev-env ubuntu:22.04

# 需要时使用exec进入
docker exec -it dev-env /bin/bash

常见的后台运行模式:

# 1. Web服务器
docker run -d --name web \
  -p 80:80 -p 443:443 \
  -v ./html:/usr/share/nginx/html \
  --restart unless-stopped \
  nginx:latest

# 2. 数据库
docker run -d --name mysql \
  -e MYSQL_ROOT_PASSWORD=secret \
  -e MYSQL_DATABASE=mydb \
  -v mysql-data:/var/lib/mysql \
  -p 3306:3306 \
  --restart unless-stopped \
  mysql:8.0

# 3. Redis缓存
docker run -d --name redis \
  -p 6379:6379 \
  -v redis-data:/data \
  --restart unless-stopped \
  redis:7-alpine redis-server --appendonly yes

# 4. 消息队列
docker run -d --name rabbitmq \
  -p 5672:5672 -p 15672:15672 \
  -e RABBITMQ_DEFAULT_USER=admin \
  -e RABBITMQ_DEFAULT_PASS=secret \
  --restart unless-stopped \
  rabbitmq:3-management

2.5 容器命名与主机名设置(–name, --hostname)

容器名称和主机名是容器身份标识的重要组成部分,合理设置有助于容器管理和网络通信。

容器名称(–name):

# 不指定名称时Docker自动生成
docker run -d nginx:latest
# docker ps 显示名称如: happy_einstein

# 指定有意义的名称
docker run -d --name web-server-01 nginx:latest
docker run -d --name web-server-02 nginx:latest
docker run -d --name db-master mysql:8.0

# 容器名称必须是唯一的
docker run -d --name web-server nginx:latest
docker run -d --name web-server nginx:latest  # 报错:名称冲突
# docker: Error response from daemon: Conflict. The container name "/web-server" is already in use

主机名(–hostname):

# 设置容器内主机名
docker run -d --hostname web01 --name web01 nginx:latest

# 验证
docker exec web01 hostname
# 输出: web01

docker exec web01 cat /etc/hostname
# 输出: web01

命名最佳实践:

# 采用统一命名规范: <项目>-<服务>-<序号>
docker run -d --name shop-web-01 nginx:latest
docker run -d --name shop-web-02 nginx:latest
docker run -d --name shop-db-master mysql:8.0
docker run -d --name shop-db-slave-01 mysql:8.0
docker run -d --name shop-redis-01 redis:7-alpine

# 命名规范好处:
# 1. 便于识别容器所属项目和角色
# 2. 便于批量操作(按名称过滤)
# 3. 便于自动化脚本管理

2.6 容器重启策略(–restart: no/on-failure/always/unless-stopped)

重启策略决定了容器在退出后是否以及如何被自动重启。这是保障服务可用性的重要机制。

四种重启策略对比:

策略 说明 退出码0时重启 异常退出时重启 守护进程重启时
no(默认) 不自动重启
on-failure[:N] 仅异常退出时重启 是(最多N次) 视情况而定
always 总是重启
unless-stopped 除手动停止外总是重启 视之前状态
# 1. no: 不自动重启(默认行为)
docker run -d --restart no --name my-app my-image:latest

# 2. on-failure: 仅在非0退出码时重启
docker run -d --restart on-failure --name my-app my-image:latest
# 最多重启5次
docker run -d --restart on-failure:5 --name my-app my-image:latest

# 3. always: 总是重启(包括正常退出)
docker run -d --restart always --name my-app my-image:latest

# 4. unless-stopped: 除了手动停止外总是重启
docker run -d --restart unless-stopped --name my-app my-image:latest

各策略详细行为分析:

# 演示on-failure策略
docker run -d --restart on-failure:3 --name restart-test \
  alpine:latest sh -c "exit 1"

# 查看重启次数
docker inspect -f '{{.RestartCount}}' restart-test

# 演示always策略——即使正常退出也会重启
docker run -d --restart always --name always-test \
  alpine:latest echo "Hello"

# 查看状态(会不断重启)
docker ps -a | grep always-test

# 演示unless-stopped与always的区别
# 启动两个容器
docker run -d --restart always --name always-app nginx:latest
docker run -d --restart unless-stopped --name unless-app nginx:latest

# 手动停止两个容器
docker stop always-app unless-app

# 重启Docker守护进程
sudo systemctl restart docker

# 检查结果:
# always-app会自动启动(always策略忽略手动停止)
# unless-app不会自动启动(手动停止的容器不会在Docker重启后启动)
docker ps

on-failure重启延迟机制:

on-failure策略使用指数退避算法,每次重启的延迟时间逐渐增加:

  • 第1次重启: 等待100ms
  • 第2次重启: 等待200ms
  • 第3次重启: 等待400ms
  • 上限为60秒
# 查看重启策略配置
docker inspect -f '{{.HostConfig.RestartPolicy}}' my-app
# {Name:on-failure MaximumRetryCount:3}

2.7 环境变量传递(-e, --env-file)

环境变量是容器配置的重要方式,实现了配置与镜像的解耦。

使用-e传递单个环境变量:

# 传递单个变量
docker run -d -e MYSQL_ROOT_PASSWORD=secret --name mysql mysql:8.0

# 传递多个变量
docker run -d \
  -e MYSQL_ROOT_PASSWORD=secret \
  -e MYSQL_DATABASE=mydb \
  -e MYSQL_USER=appuser \
  -e MYSQL_PASSWORD=apppass \
  --name mysql mysql:8.0

# 使用宿主机已有的环境变量
export API_KEY=abc123
docker run -d -e API_KEY --name my-app my-image:latest

# 验证环境变量
docker exec my-app env | grep API_KEY
# API_KEY=abc123

使用–env-file批量传递环境变量:

# 创建环境变量文件
cat > /opt/app/env.list << 'EOF'
# 数据库配置
DB_HOST=mysql-server
DB_PORT=3306
DB_NAME=mydb
DB_USER=appuser
DB_PASSWORD=secret

# 应用配置
APP_ENV=production
APP_PORT=8080
DEBUG=false
LOG_LEVEL=info

# 时区设置
TZ=Asia/Shanghai
EOF

# 使用环境变量文件
docker run -d \
  --env-file /opt/app/env.list \
  --name my-app \
  my-image:latest

# 可以使用多个env文件(后面的覆盖前面的)
docker run -d \
  --env-file /opt/app/base.env \
  --env-file /opt/app/override.env \
  --name my-app \
  my-image:latest

环境变量文件格式注意事项:

# env.list文件格式规则:
# 1. 每行一个变量,格式为 KEY=VALUE
# 2. 以#开头的行被忽略
# 3. 值中的空格不需要引号(但推荐使用引号)
# 4. 值中的特殊字符需要适当处理

# 正确的env文件示例
cat > env.list << 'EOF'
# 这是注释,被忽略
KEY1=value1
KEY2=hello world
KEY3="quoted value"
KEY4='single quoted'
KEY5=path/to/file
EOF

# 验证
docker run --rm --env-file env.list alpine env

2.8 工作目录设置(-w)

-w(–workdir)参数用于设置容器启动时的工作目录,相当于在容器内执行了cd命令。

# 设置工作目录为/app
docker run -it --rm -w /app -v $(pwd):/app node:18-alpine sh
# /app # npm install
# /app # npm start

# 在Dockerfile中使用WORKDIR与docker run中使用-w的区别:
# Dockerfile中的WORKDIR是镜像层面的设置
# docker run中的-w会覆盖Dockerfile中的WORKDIR

# 示例: 覆盖镜像默认工作目录
docker run -it --rm -w /tmp ubuntu:22.04 pwd
# 输出: /tmp

2.9 用户设置(-u)

-u(–user)参数指定容器内运行进程的用户,是容器安全的重要措施。

# 使用UID运行
docker run -it --rm -u 1000 alpine id
# 输出: uid=1000 gid=0(root) groups=0(root)

# 使用UID:GID运行
docker run -it --rm -u 1000:1000 alpine id
# 输出: uid=1000 gid=1000 groups=1000

# 使用用户名运行(用户需在容器内存在)
docker run -it --rm -u nginx nginx:latest id
# 输出: uid=101(nginx) gid=101(nginx) groups=101(nginx)

# 使用user:group格式
docker run -it --rm -u appuser:appgroup my-image:latest id

2.10 容器退出自动删除(–rm)

--rm参数让容器在退出时自动删除,非常适合临时容器和一次性任务。

# 一次性命令执行
docker run --rm alpine echo "Hello Docker"
# 执行完毕后容器自动删除

# 临时调试容器
docker run --rm -it --network my-net alpine sh

# 数据库迁移一次性执行
docker run --rm \
  --network app-net \
  -e DATABASE_URL=mysql://user:pass@db:3306/mydb \
  migration-image:latest \
  python manage.py migrate

# 使用--rm的好处:
# 1. 不产生遗留的停止容器
# 2. 自动清理容器文件系统
# 3. 自动清理匿名数据卷(配合-v参数)

2.11 完整启动参数实战示例

下面通过一个完整的生产级Web应用容器启动示例,综合运用所有参数:

#!/bin/bash
# 生产级容器启动脚本

docker run -d \
  --name shop-web-01 \
  --hostname web01 \
  --restart unless-stopped \
  \
  `# 网络配置` \
  --network shop-network \
  --network-alias web \
  -p 80:80 \
  -p 443:443 \
  --dns 8.8.8.8 \
  --add-host "db.internal:172.20.0.10" \
  \
  `# 存储配置` \
  -v shop-web-html:/usr/share/nginx/html:ro \
  -v /etc/nginx/conf.d:/etc/nginx/conf.d:ro \
  -v shop-web-logs:/var/log/nginx \
  -v /etc/ssl/certs:/etc/ssl/certs:ro \
  --tmpfs /tmp \
  --shm-size 128m \
  \
  `# 环境配置` \
  -e TZ=Asia/Shanghai \
  -e NGINX_WORKER_PROCESSES=4 \
  -e UPSTREAM_API=http://api-server:8080 \
  --env-file /opt/shop/web.env \
  -w /usr/share/nginx/html \
  \
  `# 资源限制` \
  --cpus 2.0 \
  --cpu-shares 1024 \
  --cpuset-cpus "0-3" \
  -m 2g \
  --memory-swap 2g \
  --memory-reservation 1g \
  --memory-swappiness 30 \
  --pids-limit 500 \
  --ulimit nofile=65536:65536 \
  \
  `# 健康检查` \
  --health-cmd "curl -f http://localhost/health || exit 1" \
  --health-interval 10s \
  --health-timeout 5s \
  --health-retries 3 \
  --health-start-period 30s \
  \
  `# 日志配置` \
  --log-driver json-file \
  --log-opt max-size=50m \
  --log-opt max-file=5 \
  \
  `# 安全配置` \
  --cap-drop ALL \
  --cap-add CHOWN \
  --cap-add SETGID \
  --cap-add SETUID \
  --cap-add NET_BIND_SERVICE \
  --security-opt no-new-privileges \
  --read-only \
  --tmpfs /var/run \
  --tmpfs /var/cache/nginx \
  \
  `# 初始化进程` \
  --init \
  \
  nginx:1.25-alpine

echo "容器启动完成"
docker ps --filter "name=shop-web-01"

第三章 容器运行管理

3.1 docker start/stop/restart命令详解

这三个命令是容器生命周期管理的核心操作,分别用于启动、停止和重启容器。

docker start命令:

docker start用于启动一个已存在的容器(处于created或exited状态)。

# 基本用法:启动已停止的容器
docker start my-nginx

# 启动并附加到容器(类似docker run -it的效果)
docker start -a my-nginx

# 启动并附加+交互模式
docker start -ai my-nginx

# 启动多个容器
docker start web01 web02 web03

# 使用容器ID启动
docker start 3a7c9f2b8e1d

# 启动所有已停止的容器
docker start $(docker ps -a -q --filter "status=exited")

start命令的参数:

参数 说明
-a, --attach 附加到容器的STDOUT/STDERR,并转发信号
-i, --interactive 附加到容器的STDIN
–detach-keys 覆盖detach的快捷键序列

docker stop命令:

docker stop用于优雅地停止运行中的容器。它会先发送SIGTERM信号,等待超时后发送SIGKILL。

# 基本用法:停止容器(默认等待10秒)
docker stop my-nginx

# 自定义超时时间(单位:秒)
docker stop -t 30 my-nginx

# 停止多个容器
docker stop web01 web02 web03

# 停止所有运行中的容器
docker stop $(docker ps -q)

# 按名称模式停止容器
docker stop $(docker ps -q --filter "name=web")

stop命令的工作流程:

  1. 向容器PID 1发送SIGTERM信号
  2. 等待指定的超时时间(默认10秒)
  3. 如果进程仍未退出,发送SIGKILL信号强制终止
# 查看容器被停止时的信号和退出码
docker inspect -f 'Signal: {{.State.StopSignal}} ExitCode: {{.State.ExitCode}}' my-nginx
# 输出: Signal: 15 ExitCode: 0
# Signal 15 = SIGTERM, ExitCode 0 = 正常退出

docker restart命令:

docker restart先停止容器再重新启动,相当于docker stop + docker start

# 重启容器
docker restart my-nginx

# 自定义超时时间
docker restart -t 5 my-nginx

# 重启多个容器
docker restart web01 web02 web03

# 重启所有运行中的容器
docker restart $(docker ps -q)

实际运维中的start/stop/restart场景:

#!/bin/bash
# 优雅的重启服务流程

CONTAINER_NAME="my-web-app"

# 1. 检查容器是否存在
if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
    echo "容器 ${CONTAINER_NAME} 不存在"
    exit 1
fi

# 2. 检查容器当前状态
STATUS=$(docker inspect -f '{{.State.Status}}' "$CONTAINER_NAME")
echo "容器当前状态: $STATUS"

# 3. 记录重启前的运行时间
if [ "$STATUS" = "running" ]; then
    STARTED_AT=$(docker inspect -f '{{.State.StartedAt}}' "$CONTAINER_NAME")
    echo "上次启动时间: $STARTED_AT"
fi

# 4. 优雅停止
echo "正在停止容器..."
docker stop -t 30 "$CONTAINER_NAME"

# 5. 验证已停止
STATUS=$(docker inspect -f '{{.State.Status}}' "$CONTAINER_NAME")
if [ "$STATUS" != "exited" ]; then
    echo "容器停止失败,当前状态: $STATUS"
    exit 1
fi

# 6. 重新启动
echo "正在启动容器..."
docker start "$CONTAINER_NAME"

# 7. 等待并验证
sleep 3
STATUS=$(docker inspect -f '{{.State.Status}}' "$CONTAINER_NAME")
if [ "$STATUS" = "running" ]; then
    echo "容器重启成功"
    docker ps --filter "name=$CONTAINER_NAME"
else
    echo "容器启动失败,当前状态: $STATUS"
    docker logs --tail 50 "$CONTAINER_NAME"
fi

3.2 docker pause/unpause命令(冻结进程)

docker pausedocker unpause用于冻结和解冻容器内的进程,这是一种比stop更"温和"的操作方式。

pause的工作原理:

docker pause利用Linux cgroup的freezer子系统,将容器内所有进程的执行冻结。被冻结的进程:

  • 不消耗CPU资源
  • 内存中的数据完整保留
  • 网络连接保持(但无法响应请求)
  • 文件描述符保持打开
# 暂停容器
docker pause my-nginx

# 查看暂停状态的容器
docker ps --filter "status=paused"
# CONTAINER ID   IMAGE   ...   STATUS
# 3a7c9f2b8e1d   nginx   ...   Up 5 minutes (Paused)

# 恢复容器
docker unpause my-nginx

# 暂停多个容器
docker pause web01 web02 web03

# 恢复所有暂停的容器
docker unpause $(docker ps -q --filter "status=paused")

pause与stop的区别:

特性 docker pause docker stop
进程状态 冻结(suspended) 终止(terminated)
内存 保留 释放
启动速度 极快(立即恢复) 需要重新启动进程
网络连接 保持 断开
文件描述符 保持 关闭
退出码
适用场景 临时暂停、调试、迁移 正常停止

pause的实际应用场景:

# 场景1: 容器迁移前暂停,确保数据一致性
docker pause db-master
# 执行数据快照
docker run --rm -v db-data:/data alpine tar czf - /data > db-backup.tar.gz
docker unpause db-master

# 场景2: 调试时暂停容器,检查内存状态
docker pause debug-container
# 在宿主机上检查容器的内存映射
cat /proc/$(docker inspect -f '{{.State.Pid}}' debug-container)/maps
docker unpause debug-container

# 场景3: 临时释放CPU资源给其他容器
docker pause batch-processor
# 运行高优先级任务
docker run --rm high-priority-task
docker unpause batch-processor

3.3 docker exec进入运行中容器(exec vs attach区别)

docker exec是在运行中的容器内执行新进程的命令,是日常运维中最常用的容器交互方式。

docker exec基本用法:

# 在容器中执行命令
docker exec my-nginx ls /etc/nginx

# 交互式进入容器Shell
docker exec -it my-nginx /bin/bash
docker exec -it my-nginx /bin/sh

# 以指定用户执行命令
docker exec -u root my-nginx whoami

# 指定工作目录
docker exec -w /var/log my-nginx ls -la

# 设置环境变量
docker exec -e DEBUG=true my-nginx env

# 后台执行命令
docker exec -d my-nginx touch /tmp/maintenance.flag

# 脱离模式下执行命令并获取输出
docker exec my-nginx nginx -t

exec命令参数详解:

参数 说明
-d, --detach 在后台运行命令
-e, --env 设置环境变量
-i, --interactive 保持STDIN打开
-t, --tty 分配伪终端
-u, --user 指定用户
-w, --workdir 指定工作目录
–privileged 给命令特权模式
–env-file 从文件读取环境变量

docker exec vs docker attach:

这是两个经常被混淆的命令,它们有本质区别:

# docker exec: 在容器中启动新进程
docker exec -it my-nginx /bin/bash
# 新开一个bash进程,与容器主进程并行运行
# 退出bash(执行exit)不影响容器运行

# docker attach: 连接到容器的主进程(STDIN/STDOUT/STDERR)
docker attach my-nginx
# 直接连接到容器PID 1的标准输入输出
# 退出(Ctrl+C)会发送信号给主进程,可能导致容器停止
特性 docker exec docker attach
进程 创建新进程 连接到现有进程(PID 1)
退出影响 不影响容器 可能导致容器停止
多人使用 支持多个exec 共享同一终端
使用场景 调试、执行命令 查看主进程输出、交互
推荐程度 日常推荐使用 谨慎使用

exec的常见运维场景:

# 1. 查看容器内进程
docker exec my-nginx ps aux

# 2. 查看容器内网络配置
docker exec my-nginx cat /etc/hosts
docker exec my-nginx ip addr
docker exec my-nginx netstat -tlnp

# 3. 安装调试工具
docker exec my-nginx apt-get update && docker exec my-nginx apt-get install -y vim curl

# 4. 执行数据库操作
docker exec -it mysql mysql -u root -p -e "SHOW DATABASES;"

# 5. 查看应用配置
docker exec my-nginx cat /etc/nginx/nginx.conf

# 6. 修改运行中容器的配置(临时)
docker exec my-nginx sh -c "echo 'server_tokens off;' >> /etc/nginx/nginx.conf"
docker exec my-nginx nginx -s reload

# 7. 查看容器内文件系统使用情况
docker exec my-nginx df -h

# 8. 收集诊断信息
docker exec my-nginx sh -c "
  echo '=== 系统信息 ==='
  uname -a
  echo '=== 内存信息 ==='
  free -m
  echo '=== 磁盘信息 ==='
  df -h
  echo '=== 进程列表 ==='
  ps aux
  echo '=== 网络连接 ==='
  netstat -tlnp
"

3.4 docker attach连接容器

docker attach用于连接到正在运行的容器的标准输入、输出和错误流,直接与容器的主进程交互。

# 基本用法
docker attach my-nginx

# 不分配STDIN(只看输出,不能输入)
docker attach --no-stdin my-nginx

# 使用自定义detach键(默认Ctrl+p, Ctrl+q)
docker attach --detach-keys="ctrl-z" my-nginx

attach的安全退出方式:

# 方式1: 使用detach键序列退出(不影响容器)
# 按下 Ctrl+p, 然后 Ctrl+q
# 容器继续运行

# 方式2: 使用--sig-proxy=false禁用信号代理
docker attach --sig-proxy=false my-nginx
# 这样Ctrl+C只会断开attach连接,不会发送SIGINT给容器主进程

# 危险操作: 直接Ctrl+C(默认会转发SIGINT给容器)
docker attach my-nginx
# ^C
# 这会导致容器收到SIGINT信号,可能停止!

attach的使用场景:

# 场景1: 查看容器主进程的实时输出(类似于控制台)
docker attach my-app
# 可以看到应用的实时日志输出

# 场景2: 交互式应用(容器以-it启动时)
# 例如:一个交互式Python REPL
docker run -it --name python-repl python:3.11 python3
# Ctrl+p, Ctrl+q 分离
# 稍后重新连接
docker attach python-repl
# 回到Python REPL

# 场景3: 调试前台运行的容器
docker run -d --name debug-app my-image:latest
docker attach debug-app
# 观察应用的stdout/stderr输出

3.5 docker top查看容器进程

docker top命令用于查看容器内正在运行的进程,类似于在Linux中使用ps命令。

# 基本用法
docker top my-nginx

# 输出示例:
# UID    PID    PPID   C   STIME   TTY   TIME       CMD
# root   12345  1230   0   10:00   ?     00:00:00   nginx: master process nginx -g 'daemon off;'
# nginx  12346  12345  0   10:00   ?     00:00:00   nginx: worker process
# nginx  12347  12345  0   10:00   ?     00:00:00   nginx: worker process

# 使用ps选项格式化输出
docker top my-nginx ax -o pid,user,pcpu,pmem,cmd
# PID    USER    %CPU  %MEM  CMD
# 12345  root    0.0   0.1   nginx: master process
# 12346  nginx   0.5   0.2   nginx: worker process

# 查看容器内进程的完整信息
docker top my-nginx -ef

docker top的实际应用:

# 1. 检查容器内是否有僵尸进程
docker top my-app | awk '{print $8}' | sort | uniq -c | sort -rn
# 如果看到Z状态(zombie)的进程,说明有僵尸进程

# 2. 查看容器内CPU占用最高的进程
docker top my-app ax -o pid,pcpu,cmd --sort -pcpu | head -5

# 3. 查看容器内内存占用最高的进程
docker top my-app ax -o pid,pmem,cmd --sort -pmem | head -5

# 4. 检查容器内是否有异常进程
docker top my-app ax
# 对比预期进程列表,检查是否有未预期的进程运行

# 5. 查看进程的启动时间,辅助判断是否重启过
docker top my-app ax -o pid,lstart,cmd

3.6 docker stats实时监控资源使用

docker stats命令用于实时显示容器的资源使用统计信息,包括CPU、内存、网络和磁盘IO。

# 监控所有运行中的容器(实时刷新)
docker stats

# 监控特定容器
docker stats my-nginx my-redis

# 只输出一次(不实时刷新)
docker stats --no-stream

# 自定义输出格式
docker stats --format "{{.Container}}: {{.CPUPerc}} CPU, {{.MemUsage}} RAM"

# 以表格形式显示自定义列
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}"

# 输出示例:
# NAME          CPU %     MEM USAGE / LIMIT     MEM %     NET I/O          BLOCK I/O
# my-nginx      0.50%     25.5MiB / 512MiB      4.98%     1.2kB / 648B     8.19kB / 0B
# my-redis      0.20%     8.2MiB / 256MiB       3.20%     968B / 0B        0B / 0B
# my-app        15.30%    512MiB / 1GiB         50.00%    45kB / 23kB      2.1MB / 512kB

stats输出各列含义:

列名 说明
CONTAINER 容器ID
NAME 容器名称
CPU % CPU使用百分比
MEM USAGE / LIMIT 内存使用量/限制
MEM % 内存使用百分比
NET I/O 网络收发字节数
BLOCK I/O 磁盘读写字节数
PIDS 容器内进程数

编写资源监控脚本:

#!/bin/bash
# 容器资源使用监控告警脚本

CPU_THRESHOLD=80    # CPU使用率阈值(%)
MEM_THRESHOLD=80    # 内存使用率阈值(%)

# 获取所有运行中容器的资源使用情况(单次)
docker stats --no-stream --format "{{.Name}}|{{.CPUPerc}}|{{.MemPerc}}|{{.MemUsage}}|{{.PIDs}}" | while IFS='|' read name cpu mem mem_usage pids; do
    # 去除百分号并转为数字
    cpu_num=$(echo "$cpu" | tr -d '%')
    mem_num=$(echo "$mem" | tr -d '%')
    
    # CPU告警
    if (( $(echo "$cpu_num > $CPU_THRESHOLD" | bc -l) )); then
        echo "[ALERT] 容器 $name CPU使用率过高: ${cpu}"
    fi
    
    # 内存告警
    if (( $(echo "$mem_num > $MEM_THRESHOLD" | bc -l) )); then
        echo "[ALERT] 容器 $name 内存使用率过高: ${mem} (${mem_usage})"
    fi
    
    # 进程数告警
    if [ "$pids" -gt 500 ]; then
        echo "[ALERT] 容器 $name 进程数过多: ${pids}"
    fi
done

3.7 docker wait等待容器退出

docker wait命令会阻塞当前终端,直到指定的容器停止,然后输出容器的退出码。

# 等待单个容器退出
docker wait my-batch-job
# 输出: 0 (退出码)

# 等待多个容器退出
docker wait container1 container2 container3
# 输出每行一个退出码

# 实际应用: 等待批量任务完成
docker run -d --name batch-01 my-batch-image
docker run -d --name batch-02 my-batch-image
docker run -d --name batch-03 my-batch-image

# 等待所有批量任务完成
echo "等待批量任务完成..."
docker wait batch-01 batch-02 batch-03
echo "所有任务已完成"

结合wait的自动化脚本:

#!/bin/bash
# 并行执行多个数据处理任务,等待全部完成

TASKS=("task-data-clean" "task-data-transform" "task-data-export")
IMAGE="data-processor:latest"

# 启动所有任务
for task in "${TASKS[@]}"; do
    docker run -d --name "$task" \
        -v /data:/data \
        -e TASK_NAME="$task" \
        "$IMAGE" \
        python process.py "$task"
    echo "启动任务: $task"
done

# 等待所有任务完成
echo "等待所有任务完成..."
FAILED=0
for task in "${TASKS[@]}"; do
    EXIT_CODE=$(docker wait "$task")
    if [ "$EXIT_CODE" -ne 0 ]; then
        echo "[FAILED] 任务 $task 失败,退出码: $EXIT_CODE"
        FAILED=$((FAILED + 1))
    else
        echo "[OK] 任务 $task 完成"
    fi
    # 清理容器
    docker rm "$task"
done

echo "全部完成,失败任务数: $FAILED"

3.8 docker rename重命名容器

docker rename命令用于修改容器的名称,这是一个简单的操作但有时非常实用。

# 重命名容器
docker rename old-name new-name

# 示例
docker run -d --name temp-web nginx:latest
docker rename temp-web web-server-01

# 验证
docker ps --filter "name=web-server-01"

实际应用场景:

# 场景: 灰度发布时交换容器名称
# 1. 新版本容器以临时名称启动
docker run -d --name web-v2-new nginx:v2

# 2. 验证新版本正常后,旧版本改名
docker rename web-server web-server-old

# 3. 新版本改名为主名称
docker rename web-v2-new web-server

# 4. 确认无误后删除旧版本
docker rm web-server-old

# 场景: 自动化部署中的容器名称规范化
for container in $(docker ps -a --format '{{.Names}}' | grep -E '^tmp_'); do
    new_name=$(echo "$container" | sed 's/^tmp_/prod_/')
    docker rename "$container" "$new_name"
    echo "重命名: $container -> $new_name"
done

3.9 docker update动态更新容器配置

docker update命令可以在容器运行时动态修改资源限制,无需停止和重新创建容器。

# 更新CPU限制
docker update --cpus 2.0 my-nginx

# 更新CPU shares
docker update --cpu-shares 1024 my-nginx

# 更新CPU核心绑定
docker update --cpuset-cpus "0,1" my-nginx

# 更新内存限制
docker update -m 1g my-nginx

# 更新内存交换空间
docker update --memory-swap 2g my-nginx

# 更新内存软限制
docker update --memory-reservation 512m my-nginx

# 更新重启策略
docker update --restart unless-stopped my-nginx

# 更新进程数限制
docker update --pids-limit 300 my-nginx

# 更新BlkIO权重
docker update --blkio-weight 500 my-nginx

# 同时更新多个参数
docker update \
  --cpus 4.0 \
  -m 4g \
  --memory-swap 4g \
  --restart always \
  my-nginx

# 批量更新多个容器
docker update --cpus 2.0 -m 2g web01 web02 web03

update的注意事项和限制:

# 1. 只有部分配置可以动态更新,以下配置无法动态修改:
#    - 网络(--network)
#    - 端口映射(-p)
#    - 挂载卷(-v)
#    - 环境变量(-e)
#    - 用户(-u)
#    - 工作目录(-w)

# 2. 内存限制不能低于当前实际使用量
docker run -d -m 1g --name my-app my-image
# 如果应用已使用800MB内存
docker update -m 500m my-app
# 可能失败,因为500MB低于已使用的800MB

# 3. 可以在容器停止状态下更新
docker stop my-app
docker update --cpus 4.0 -m 4g my-app
docker start my-app

# 4. 验证更新结果
docker inspect -f 'CPU: {{.HostConfig.NanoCpus}} Memory: {{.HostConfig.Memory}}' my-app

动态扩容实战:

#!/bin/bash
# 根据负载动态调整容器资源

CONTAINER="my-web-app"
MAX_CPU=8.0
MAX_MEM=8g
STEP_CPU=1.0
STEP_MEM=1g

# 获取当前资源使用
current_cpu=$(docker inspect -f '{{.HostConfig.NanoCpus}}' "$CONTAINER")
current_cpu=$((current_cpu / 1000000000))  # 纳秒转秒
current_mem=$(docker inspect -f '{{.HostConfig.Memory}}' "$CONTAINER")
current_mem=$((current_mem / 1024 / 1024 / 1024))  # 字节转GB

# 获取实时使用率
stats=$(docker stats --no-stream --format "{{.CPUPerc}}|{{.MemPerc}}" "$CONTAINER")
cpu_usage=$(echo "$stats" | cut -d'|' -f1 | tr -d '%')
mem_usage=$(echo "$stats" | cut -d'|' -f2 | tr -d '%')

echo "当前限制: CPU=${current_cpu}核, MEM=${current_mem}GB"
echo "当前使用: CPU=${cpu_usage}%, MEM=${mem_usage}%"

# CPU使用率>80%,扩容CPU
if (( $(echo "$cpu_usage > 80" | bc -l) )); then
    new_cpu=$(echo "$current_cpu + $STEP_CPU" | bc)
    if (( $(echo "$new_cpu <= $MAX_CPU" | bc -l) )); then
        echo "CPU负载过高,扩容CPU: ${current_cpu} -> ${new_cpu}"
        docker update --cpus "$new_cpu" "$CONTAINER"
    fi
fi

# 内存使用率>80%,扩容内存
if (( $(echo "$mem_usage > 80" | bc -l) )); then
    new_mem="${current_mem}g+${STEP_MEM}"
    new_mem_num=$(echo "${current_mem} + 1" | bc)
    if [ "$new_mem_num" -le "${MAX_MEM%g}" ]; then
        echo "内存使用过高,扩容内存: ${current_mem}g -> ${new_mem_num}g"
        docker update -m "${new_mem_num}g" --memory-swap "${new_mem_num}g" "$CONTAINER"
    fi
fi

3.10 容器信号处理(SIGTERM/SIGKILL/SIGINT)

信号处理是容器优雅停止的关键机制。理解不同信号的作用以及容器如何响应这些信号,对于编写可靠的容器化应用至关重要。

常见信号说明:

信号 编号 说明 Docker中的用途
SIGTERM 15 优雅终止信号 docker stop默认发送
SIGKILL 9 强制终止信号 stop超时后发送,docker kill默认
SIGINT 2 中断信号(Ctrl+C) 交互式终止
SIGHUP 1 挂断信号 重载配置
SIGQUIT 3 退出信号 优雅退出并生成core dump
SIGUSR1 10 用户自定义信号1 Nginx重新打开日志
SIGUSR2 12 用户自定义信号2 应用热重启

docker stop的信号处理流程:

docker stop
    |
    v
发送SIGTERM (信号15)
    |
    v
等待超时时间 (默认10秒)
    |
    +--- 进程退出 ---> 容器停止 (退出码0或信号相关)
    |
    +--- 超时未退出 ---> 发送SIGKILL (信号9)
                           |
                           v
                        强制终止进程 (退出码137)
# 演示docker stop的信号处理
docker run -d --name signal-demo alpine:latest \
  sh -c 'trap "echo 收到SIGTERM信号; exit 0" TERM; echo 容器启动; while true; do sleep 1; done'

# 优雅停止(进程会收到SIGTERM并自行处理)
docker stop signal-demo
docker logs signal-demo
# 输出: 收到SIGTERM信号

# 演示强制停止
docker run -d --name force-demo alpine:latest sleep 3600
docker kill force-demo
# 直接发送SIGKILL,进程无法捕获或处理

# 查看退出码
docker inspect -f '{{.State.ExitCode}}' force-demo
# 输出: 137 (128 + 9 = 137,表示被SIGKILL终止)

Dockerfile中的信号处理最佳实践:

# 错误示例: shell形式启动,信号无法传递
FROM alpine:latest
CMD sleep 3600
# 实际执行: /bin/sh -c "sleep 3600"
# sh进程作为PID 1,但sh不会将SIGTERM转发给sleep

# 正确示例1: exec形式启动
FROM alpine:latest
CMD ["sleep", "3600"]
# sleep直接作为PID 1,可以接收SIGTERM

# 正确示例2: 使用exec命令
FROM alpine:latest
CMD exec sleep 3600
# exec替换sh进程,sleep成为PID 1

# 正确示例3: 使用init进程
FROM alpine:latest
RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["sleep", "3600"]
# tini作为PID 1,负责信号转发和僵尸进程回收

# 正确示例4: 使用--init标志(docker run时)
# docker run --init my-image
# Docker会自动注入tini作为PID 1

应用中正确处理信号的代码示例:

# Python应用信号处理示例
import signal
import sys
import time

# 全局变量,标记是否需要退出
running = True

def handle_sigterm(signum, frame):
    global running
    print("收到SIGTERM信号,正在优雅关闭...")
    running = False

def handle_sigint(signum, frame):
    print("收到SIGINT信号(Ctrl+C)")
    handle_sigterm(signum, frame)

# 注册信号处理函数
signal.signal(signal.SIGTERM, handle_sigterm)
signal.signal(signal.SIGINT, handle_sigint)

print("应用启动,等待请求...")

# 主循环
while running:
    # 处理请求...
    time.sleep(1)

# 清理资源
print("正在释放资源...")
# 关闭数据库连接
# 刷新缓冲区
# 保存状态
print("优雅关闭完成")
sys.exit(0)
// Java应用信号处理示例
public class App {
    private static volatile boolean running = true;
    
    public static void main(String[] args) {
        // 注册SIGTERM处理
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("收到关闭信号,正在优雅关闭...");
            running = false;
            
            // 优雅关闭逻辑
            try {
                // 关闭数据库连接池
                // 关闭HTTP服务器
                // 保存状态
                Thread.sleep(2000); // 模拟清理
                System.out.println("优雅关闭完成");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }));
        
        System.out.println("应用启动");
        while (running) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                break;
            }
        }
    }
}
# 发送特定信号给容器
docker kill --signal=SIGTERM my-nginx     # 等同于docker stop但无超时
docker kill --signal=SIGUSR1 my-nginx     # Nginx重新打开日志
docker kill --signal=SIGHUP my-nginx      # Nginx重新加载配置
docker kill --signal=SIGWINCH my-nginx    # Nginx优雅停止worker进程

# 自定义stop信号(在Dockerfile中)
# Dockerfile:
# STOPSIGNAL SIGQUIT
# 这样docker stop会发送SIGQUIT而非默认的SIGTERM

信号处理不当导致的实际问题排查:

# 问题: docker stop需要10秒才能停止容器
# 原因: 容器PID 1进程未正确处理SIGTERM信号

# 排查步骤:
# 1. 检查容器PID 1是什么进程
docker exec my-app ps -p 1 -o pid,comm
# 如果是sh/bash,说明使用了shell形式启动

# 2. 检查Dockerfile中的CMD/ENTRYPOINT
docker inspect -f '{{.Config.Cmd}}' my-app
docker inspect -f '{{.Config.Entrypoint}}' my-app

# 3. 检查STOPSIGNAL设置
docker inspect -f '{{.Config.StopSignal}}' my-app

# 4. 解决方案:
# 方案A: 使用exec形式
# 方案B: 添加STOPSIGNAL指令
# 方案C: 使用--init标志
# 方案D: 使用tini/dumb-init作为PID 1

# 验证修复效果
time docker stop my-app
# 应该在几秒内完成

第四章 容器查看与信息

4.1 docker ps命令详解(格式化输出、过滤器)

docker ps是日常使用频率最高的Docker命令之一,用于列出容器信息。

基本用法:

# 列出运行中的容器
docker ps

# 列出所有容器(包括停止的)
docker ps -a
# 简写: docker ps --all

# 只显示容器ID
docker ps -q

# 显示所有容器的ID(包括停止的)
docker ps -aq

# 显示最近创建的容器
docker ps -l

# 显示最近创建的N个容器
docker ps -n 5

# 显示文件大小
docker ps -s

# 输出示例:
# CONTAINER ID   IMAGE          COMMAND                  CREATED        STATUS                    PORTS                  NAMES
# 3a7c9f2b8e1d   nginx:latest   "/docker-entrypoint.…"   2 minutes ago  Up 2 minutes              0.0.0.0:8080->80/tcp   my-nginx
# 5b8d1a3c9f2e   mysql:8.0      "docker-entrypoint.s…"   5 minutes ago  Up 5 minutes              3306/tcp               mysql-db
# 7c2e9b4d1a3f   redis:7        "docker-entrypoint.s…"   10 minutes ago Exited (0) 3 minutes ago                          redis-cache

使用过滤器:

# 按状态过滤
docker ps --filter "status=running"
docker ps --filter "status=exited"
docker ps --filter "status=paused"
docker ps -a --filter "status=created"

# 按名称过滤
docker ps --filter "name=web"
docker ps --filter "name=web-server-01"

# 按镜像过滤
docker ps --filter "ancestor=nginx:latest"
docker ps --filter "ancestor=nginx"

# 按标签过滤
docker ps --filter "label=com.docker.compose.project=myapp"
docker ps --filter "label=environment=production"

# 按端口过滤
docker ps --filter "publish=8080"
docker ps --filter "expose=3306"

# 按网络过滤
docker ps --filter "network=my-net"

# 按健康状态过滤
docker ps --filter "health=healthy"
docker ps --filter "health=unhealthy"

# 按退出码过滤(仅停止的容器)
docker ps -a --filter "exited=0"
docker ps -a --filter "exited=137"

# 按创建时间之前/之后过滤
docker ps --filter "before=my-nginx"
docker ps --filter "since=mysql-db"

# 组合多个过滤器
docker ps -a --filter "status=exited" --filter "exited=0" --filter "name=web"

常用过滤器汇总表:

过滤器 说明 示例
status 容器状态 running/exited/paused/created/dead
name 容器名称(支持模糊匹配) name=web
id 容器ID id=3a7c9f2b
ancestor 基于的镜像 ancestor=nginx:latest
label 标签 label=env=prod
publish 发布的端口 publish=8080
expose 暴露的端口 expose=3306
network 所在的网络 network=my-net
health 健康状态 healthy/unhealthy/starting
exited 退出码 exited=0
before/since 时间范围 before=container-name

4.2 docker ps自定义输出格式(Go模板)

docker ps支持使用Go模板语法自定义输出格式,这是高级运维中非常实用的功能。

# 使用--format参数
# 基本格式: --format "{{.字段名}}"

# 只显示名称和状态
docker ps --format "{{.Names}}: {{.Status}}"

# 表格形式显示名称、镜像、端口
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Ports}}"

# JSON格式输出
docker ps --format "{{json .}}"

# 自定义详细输出
docker ps --format "容器名: {{.Names}}\n镜像: {{.Image}}\n状态: {{.Status}}\n端口: {{.Ports}}\n创建时间: {{.CreatedAt}}\n"

可用的模板字段:

字段 说明
.ID 容器ID
.Names 容器名称
.Image 镜像名称
.Command 启动命令
.CreatedAt 创建时间
.RunningFor 运行时长(如"2 minutes ago")
.Status 状态(如"Up 2 minutes")
.Ports 端口映射
.Labels 所有标签
.Label 单个标签(需指定key)
.Size 容器可写层大小
.Mounts 挂载点
.Networks 网络
# 实用格式化示例

# 1. 紧凑输出(只显示ID和名称)
docker ps --format "{{.ID}}\t{{.Names}}"

# 2. 显示带标题的表格
docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Ports}}"

# 3. 只显示容器ID(用于脚本)
docker ps -q --filter "name=web"

# 4. 显示容器IP地址(需要结合inspect)
docker ps -q | xargs -I {} docker inspect -f '{{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' {}

# 5. 显示容器的标签
docker ps --format "{{.Names}}: {{.Labels}}"

# 6. 按特定标签过滤并格式化
docker ps --filter "label=app=web" --format "table {{.Names}}\t{{.Status}}"

# 7. 显示容器运行时长(秒)
docker ps --format "{{.Names}}: {{.RunningFor}}"

# 8. 显示所有停止容器及其退出码
docker ps -a --filter "status=exited" --format "{{.Names}}\t{{.Status}}"

# 9. 自定义分隔符输出(便于脚本处理)
docker ps --format "{{.Names}}|{{.Status}}|{{.Ports}}" | column -t -s'|'

# 10. 显示容器对应的镜像和创建时间
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.CreatedAt}}"

创建常用的ps别名:

# 在~/.bashrc中添加常用别名
# 查看所有容器(简洁模式)
alias dps='docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"'

# 查看所有容器包括停止的
alias dpsa='docker ps -a --format "table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"'

# 只显示容器ID
alias dpsq='docker ps -q'

# 查看容器IP
alias dip='docker ps -q | xargs -I {} docker inspect -f "{{.Name}}: {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}" {}'

4.3 docker inspect深入解析(容器详细信息JSON)

docker inspect返回容器或镜像的详细配置信息,以JSON格式输出,是排查问题的利器。

基本用法:

# 查看容器完整信息(JSON格式)
docker inspect my-nginx

# 格式化输出JSON
docker inspect my-nginx | python -m json.tool

# 查看特定字段(使用Go模板)
docker inspect -f '{{.State.Status}}' my-nginx
# 输出: running

# 查看多个字段
docker inspect -f '状态: {{.State.Status}}, PID: {{.State.Pid}}, IP: {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' my-nginx

常用inspect查询:

# === 容器状态信息 ===
# 容器状态
docker inspect -f '{{.State.Status}}' my-nginx

# 容器退出码
docker inspect -f '{{.State.ExitCode}}' my-nginx

# 容器PID
docker inspect -f '{{.State.Pid}}' my-nginx

# 容器启动时间
docker inspect -f '{{.State.StartedAt}}' my-nginx

# 容器停止时间
docker inspect -f '{{.State.FinishedAt}}' my-nginx

# 重启次数
docker inspect -f '{{.RestartCount}}' my-nginx

# === 网络信息 ===
# IP地址
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' my-nginx

# MAC地址
docker inspect -f '{{range .NetworkSettings.Networks}}{{.MacAddress}}{{end}}' my-nginx

# 网关
docker inspect -f '{{range .NetworkSettings.Networks}}{{.Gateway}}{{end}}' my-nginx

# 所有端口映射
docker inspect -f '{{json .NetworkSettings.Ports}}' my-nginx | python -m json.tool

# === 资源限制信息 ===
# CPU限制
docker inspect -f '{{.HostConfig.NanoCpus}}' my-nginx

# 内存限制
docker inspect -f '{{.HostConfig.Memory}}' my-nginx

# CPU核心绑定
docker inspect -f '{{.HostConfig.CpusetCpus}}' my-nginx

# 重启策略
docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' my-nginx

# === 镜像与配置信息 ===
# 镜像ID
docker inspect -f '{{.Image}}' my-nginx

# 启动命令
docker inspect -f '{{.Config.Cmd}}' my-nginx

# 入口点
docker inspect -f '{{.Config.Entrypoint}}' my-nginx

# 环境变量
docker inspect -f '{{json .Config.Env}}' my-nginx

# 挂载点
docker inspect -f '{{json .Mounts}}' my-nginx | python -m json.tool

# === 日志配置 ===
# 日志驱动
docker inspect -f '{{.HostConfig.LogConfig.Type}}' my-nginx

# 日志选项
docker inspect -f '{{json .HostConfig.LogConfig.Config}}' my-nginx

inspect在故障排查中的应用:

# 1. 检查容器为什么退出
docker inspect -f '
退出码: {{.State.ExitCode}}
错误信息: {{.State.Error}}
退出时间: {{.State.FinishedAt}}
OOM是否杀死: {{.State.OOMKilled}}
' my-nginx

# 2. 检查健康检查状态
docker inspect -f '{{json .State.Health}}' my-nginx | python -m json.tool
# 输出示例:
# {
#   "Status": "healthy",
#   "FailingStreak": 0,
#   "Log": [
#     {
#       "Start": "2024-01-01T10:00:00Z",
#       "End": "2024-01-01T10:00:01Z",
#       "ExitCode": 0,
#       "Output": "HTTP/1.1 200 OK\n"
#     }
#   ]
# }

# 3. 检查容器资源限制
docker inspect -f '
CPU限制: {{.HostConfig.NanoCpus}} ns
内存限制: {{.HostConfig.Memory}} bytes
内存Swap: {{.HostConfig.MemorySwap}} bytes
CPU Shares: {{.HostConfig.CpuShares}}
CPU绑定: {{.HostConfig.CpusetCpus}}
PIDS限制: {{.HostConfig.PidsLimit}}
' my-nginx

# 4. 检查容器网络配置
docker inspect -f '
IP: {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}
网关: {{range .NetworkSettings.Networks}}{{.Gateway}}{{end}}
DNS: {{.HostConfig.Dns}}
额外hosts: {{json .HostConfig.ExtraHosts}}
网络模式: {{.HostConfig.NetworkMode}}
' my-nginx

# 5. 检查容器挂载
docker inspect -f '{{range .Mounts}}
类型: {{.Type}}
源: {{.Source}}
目标: {{.Destination}}
只读: {{.RW}}{{end}}' my-nginx

4.4 docker logs日志查看(实时跟踪、时间戳、限制)

docker logs命令用于获取容器的日志输出,是日常运维中最常用的命令之一。

基本用法:

# 获取所有日志
docker logs my-nginx

# 实时跟踪日志输出(类似tail -f)
docker logs -f my-nginx

# 显示最近N行日志
docker logs --tail 100 my-nginx

# 显示带时间戳的日志
docker logs -t my-nginx

# 显示指定时间之后的日志
docker logs --since "2024-01-01T00:00:00" my-nginx

# 显示指定时间之前的日志
docker logs --until "2024-01-01T12:00:00" my-nginx

# 显示最近5分钟的日志
docker logs --since 5m my-nginx

# 相对时间
docker logs --since 1h my-nginx
docker logs --since 30m my-nginx
docker logs --since 2h30m my-nginx

# 组合使用:实时跟踪最近100行
docker logs -f --tail 100 my-nginx

# 组合使用:显示最近10分钟的最后50行
docker logs --since 10m --tail 50 my-nginx

logs命令参数详解:

参数 说明 示例
-f, --follow 实时跟踪日志 docker logs -f my-app
–tail 显示最后N行 docker logs --tail 100 my-app
-t, --timestamps 显示时间戳 docker logs -t my-app
–since 显示某时间后的日志 docker logs --since 2h my-app
–until 显示某时间前的日志 docker logs --until 1h my-app
–details 显示额外细节 docker logs --details my-app
–no-color 不使用颜色
# 高级用法示例

# 1. 只查看错误日志(结合grep)
docker logs my-app 2>&1 | grep -i "error"

# 2. 实时跟踪并过滤
docker logs -f my-app 2>&1 | grep -i "error\|warning"

# 3. 查看指定时间段的日志
docker logs --since "2024-01-01T10:00:00" --until "2024-01-01T11:00:00" my-app

# 4. 导出日志到文件
docker logs my-app > /tmp/app-logs-$(date +%Y%m%d).log 2>&1

# 5. 压缩导出日志
docker logs my-app 2>&1 | gzip > /tmp/app-logs-$(date +%Y%m%d).log.gz

# 6. 查看stderr和stdout分别
docker logs my-app 2>/tmp/stderr.log 1>/tmp/stdout.log

# 7. 统计各类型日志数量
docker logs my-app 2>&1 | grep -c "ERROR"
docker logs my-app 2>&1 | grep -c "WARNING"
docker logs my-app 2>&1 | grep -c "INFO"

# 8. 查看最近的异常退出日志
docker logs --tail 50 my-crashed-app

日志轮转配置对logs的影响:

# 如果配置了日志轮转,日志文件会被分割
# docker logs只能查看当前保留的日志文件

# 在daemon.json中配置全局日志限制
# /etc/docker/daemon.json:
cat > /etc/docker/daemon.json << 'EOF'
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
EOF

# 重启Docker使配置生效
sudo systemctl restart docker

# 或者在单个容器上配置
docker run -d \
  --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  --name my-app \
  my-image:latest

4.5 docker events事件监听

docker events命令用于实时监听Docker守护进程产生的事件,包括容器、镜像、网络、卷等对象的生命周期事件。

# 监听所有Docker事件
docker events

# 输出示例:
# 2024-01-01T10:00:01 container create 3a7c9f2b8e1d (image=nginx:latest, name=my-nginx)
# 2024-01-01T10:00:02 container start 3a7c9f2b8e1d (image=nginx:latest, name=my-nginx)
# 2024-01-01T10:05:00 container kill 3a7c9f2b8e1d (image=nginx:latest, name=my-nginx, signal=15)
# 2024-01-01T10:05:10 container die 3a7c9f2b8e1d (image=nginx:latest, name=my-nginx, exitCode=0)
# 2024-01-01T10:05:11 container stop 3a7c9f2b8e1d (image=nginx:latest, name=my-nginx)

# 按类型过滤
docker events --filter "type=container"
docker events --filter "type=image"
docker events --filter "type=network"
docker events --filter "type=volume"

# 按事件类型过滤
docker events --filter "event=start"
docker events --filter "event=stop"
docker events --filter "event=die"
docker events --filter "event=create"
docker events --filter "event=destroy"

# 按容器过滤
docker events --filter "container=my-nginx"

# 按时间范围过滤
docker events --since "2024-01-01T00:00:00" --until "2024-01-01T12:00:00"
docker events --since 1h

# 格式化输出
docker events --format '{{.Time}} {{.Type}} {{.Action}} {{.Actor.Attributes.name}}'

# JSON格式输出
docker events --format '{{json .}}'

容器生命周期事件列表:

事件 说明
create 容器被创建
start 容器被启动
restart 容器被重启
pause 容器被暂停
unpause 容器被恢复
attach 容器被附加
detach 容器被分离
resize 终端大小改变
exec_create exec命令被创建
exec_start exec命令开始执行
kill 容器收到kill信号
die 容器进程退出
stop 容器被停止
destroy 容器被删除
health_status 健康检查状态变化
oom 容器发生OOM
# 实际应用: 监控容器异常退出并告警
#!/bin/bash
# 监听容器die事件并检查退出码

docker events --filter "event=die" --format "{{.Actor.Attributes.name}}|{{.Actor.Attributes.exitCode}}" | while IFS='|' read name exit_code; do
    if [ "$exit_code" != "0" ]; then
        echo "[ALERT] 容器 $name 异常退出,退出码: $exit_code"
        echo "最后日志:"
        docker logs --tail 10 "$name"
        echo "---"
    fi
done

# 监控容器健康状态变化
docker events --filter "event=health_status" --format '{{.Actor.Attributes.name}} {{.Actor.Attributes.healthStatus}}' | while read name status; do
    if [ "$status" = "unhealthy" ]; then
        echo "[ALERT] 容器 $name 健康检查失败"
    fi
done

4.6 docker port端口映射查看

docker port命令用于查看容器的端口映射情况。

# 查看容器所有端口映射
docker port my-nginx
# 输出:
# 80/tcp -> 0.0.0.0:8080
# 443/tcp -> 0.0.0.0:8443

# 查看特定端口的映射
docker port my-nginx 80
# 输出: 0.0.0.0:8080

# 查看特定协议的端口映射
docker port my-nginx 80/tcp
docker port my-nginx 53/udp

4.7 docker diff查看文件变更

docker diff命令用于查看容器文件系统相对于镜像的变更,包括添加(A)、删除(D)和修改©的文件。

# 查看容器文件系统变更
docker diff my-nginx

# 输出示例:
# C /etc
# C /etc/nginx
# C /etc/nginx/nginx.conf
# A /etc/nginx/conf.d/custom.conf
# C /var
# C /var/cache
# C /var/cache/nginx
# A /var/cache/nginx/client_temp
# C /var/log
# A /var/log/nginx/access.log
# A /var/log/nginx/error.log
# A /tmp/debug.txt
# D /usr/share/nginx/html/index.html

变更标记说明:

标记 含义
A Added(新增的文件/目录)
D Deleted(删除的文件/目录)
C Changed(修改的文件/目录)
# 实际应用: 检查容器运行后产生了哪些文件变更

# 1. 在容器中做一些修改
docker exec my-nginx sh -c "echo 'custom config' > /etc/nginx/conf.d/custom.conf"
docker exec my-nginx sh -c "rm /usr/share/nginx/html/index.html"
docker exec my-nginx sh -c "echo 'test' > /tmp/test.txt"

# 2. 查看变更
docker diff my-nginx

# 3. 基于变更创建新镜像
docker commit -m "添加自定义配置" my-nginx my-nginx-custom:latest

# 4. 检查容器是否被意外修改(安全审计)
docker diff my-nginx | grep "^A" | grep -v "/tmp\|/var/log\|/var/cache"
# 如果出现意外的文件,可能容器被入侵或修改

4.8 docker history查看镜像层历史

docker history命令用于查看镜像的构建历史,包括每一层的指令和大小。

# 查看镜像构建历史
docker history nginx:latest

# 输出示例:
# IMAGE          CREATED       CREATED BY                                      SIZE
# 605c77e624dd   2 weeks ago   /bin/sh -c #(nop)  CMD ["nginx" "-g" "daemon…   0B
# 7959f5d8f5b1   2 weeks ago   /bin/sh -c #(nop)  STOPSIGNAL SIGQUIT           0B
# a0f81a3eb653   2 weeks ago   /bin/sh -c #(nop)  EXPOSE 80                    0B
# 2edcec3598d6   2 weeks ago   /bin/sh -c #(nop)  ENTRYPOINT ["/docker-ent…   0B
# 8b3c5d5e7f9a   2 weeks ago   /bin/sh -c #(nop) COPY file:... /docker-ent…   1.2kB
# 5a8d6e9f0b1c   2 weeks ago   /bin/sh -c set -x && groupadd ...              61MB
# ...

# 显示完整信息(不截断)
docker history --no-trunc nginx:latest

# 只显示镜像ID和大小
docker history --format "{{.ID}}: {{.Size}}" nginx:latest

# 显示带创建指令的表格
docker history --format "table {{.CreatedBy}}\t{{.Size}}" nginx:latest

# 以JSON格式输出
docker history --format "{{json .}}" nginx:latest

history参数说明:

参数 说明
–no-trunc 显示完整信息(不截断)
-q, --quiet 只显示镜像ID
–format 自定义输出格式
# 实际应用: 分析镜像大小

# 1. 找出最大的层
docker history nginx:latest --format "{{.Size}}\t{{.CreatedBy}}" | sort -rh | head -10

# 2. 查看自定义镜像的构建历史
docker history my-app:latest --no-trunc

# 3. 比较两个镜像的差异
docker history my-app:v1 --format "{{.CreatedBy}}" > /tmp/v1.txt
docker history my-app:v2 --format "{{.CreatedBy}}" > /tmp/v2.txt
diff /tmp/v1.txt /tmp/v2.txt

4.9 常用容器查看技巧汇总

#!/bin/bash
# 常用容器查看技巧集合

echo "=== 1. 查看所有运行中容器的IP地址 ==="
docker ps -q | xargs -I {} docker inspect -f '{{.Name}}: {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' {}

echo ""
echo "=== 2. 查看所有容器的资源使用情况 ==="
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"

echo ""
echo "=== 3. 查看所有容器的镜像和创建时间 ==="
docker ps -a --format "table {{.Names}}\t{{.Image}}\t{{.CreatedAt}}\t{{.Status}}"

echo ""
echo "=== 4. 查看退出码非0的容器 ==="
docker ps -a --filter "exited=1" --format "{{.Names}}: {{.Status}}"
docker ps -a --filter "exited=137" --format "{{.Names}}: {{.Status}} (OOM或被KILL)"

echo ""
echo "=== 5. 查看容器健康状态 ==="
docker ps --format "{{.Names}}" | while read name; do
    health=$(docker inspect -f '{{.State.Health.Status}}' "$name" 2>/dev/null)
    if [ -n "$health" ]; then
        echo "$name: $health"
    fi
done

echo ""
echo "=== 6. 查看容器的端口映射 ==="
docker ps --format "{{.Names}}" | while read name; do
    ports=$(docker port "$name" 2>/dev/null)
    if [ -n "$ports" ]; then
        echo "$name: $ports"
    fi
done

echo ""
echo "=== 7. 查看所有容器的挂载卷 ==="
docker ps -q | xargs -I {} docker inspect -f '{{.Name}}: {{range .Mounts}}{{.Type}}:{{.Source}}->{{.Destination}} {{end}}' {}

echo ""
echo "=== 8. 查看容器重启次数排行 ==="
docker ps -a --format "{{.Names}}" | while read name; do
    count=$(docker inspect -f '{{.RestartCount}}' "$name")
    echo "$count $name"
done | sort -rn | head -10

echo ""
echo "=== 9. 查看运行时间最长的容器 ==="
docker ps --format "{{.Names}}" | while read name; do
    started=$(docker inspect -f '{{.State.StartedAt}}' "$name")
    echo "$started $name"
done | sort | head -5

echo ""
echo "=== 10. 按网络分组显示容器 ==="
docker network ls --format "{{.Name}}" | while read net; do
    containers=$(docker network inspect "$net" -f '{{range .Containers}}{{.Name}} {{end}}')
    if [ -n "$containers" ]; then
        echo "网络 $net: $containers"
    fi
done


第五章 容器资源限制

5.1 CPU限制(–cpus, --cpu-shares, --cpuset-cpus)

CPU资源限制是容器化部署中最核心的资源管理能力之一。Docker提供了三种CPU限制机制,各有不同的使用场景。

三种CPU限制方式对比:

限制方式 参数 说明 特点
CPU配额 –cpus 限制可使用的CPU核数 硬限制,精确控制
CPU权重 –cpu-shares 设置CPU相对权重 软限制,按比例分配
CPU绑定 –cpuset-cpus 绑定到指定CPU核心 独占核心,避免争抢

–cpus(CPU配额):

# 限制使用1个CPU核心
docker run -d --cpus 1.0 --name my-app my-image:latest

# 限制使用1.5个CPU核心
docker run -d --cpus 1.5 --name my-app my-image:latest

# 限制使用0.5个CPU核心(半个核心)
docker run -d --cpus 0.5 --name my-app my-image:latest

# 限制使用4个CPU核心
docker run -d --cpus 4.0 --name my-app my-image:latest

--cpus的工作原理:它实际上是通过cgroup的CPU子系统设置cpu.cfs_period_uscpu.cfs_quota_us来实现的。例如--cpus 1.5相当于设置cpu.cfs_period_us=100000cpu.cfs_quota_us=150000,表示每100000微秒的周期内,容器最多可以使用150000微秒的CPU时间(即1.5个核心)。

# 验证cgroup设置
# 获取容器在宿主机上的PID
PID=$(docker inspect -f '{{.State.Pid}}' my-app)

# 查看CPU配额设置
cat /sys/fs/cgroup/cpu/docker/$(docker inspect -f '{{.Id}}' my-app)/cpu.cfs_period_us
# 输出: 100000

cat /sys/fs/cgroup/cpu/docker/$(docker inspect -f '{{.Id}}' my-app)/cpu.cfs_quota_us
# 输出: 150000 (对于--cpus 1.5)

–cpu-shares(CPU权重):

# 默认权重为1024
docker run -d --cpu-shares 1024 --name app-default my-image:latest

# 高优先级容器(权重2048,获得2倍CPU时间)
docker run -d --cpu-shares 2048 --name app-high my-image:latest

# 低优先级容器(权重512,获得一半CPU时间)
docker run -d --cpu-shares 512 --name app-low my-image:latest

--cpu-shares是一个相对值,只有在CPU资源紧张时才起作用。当CPU空闲时,即使设置了低权重的容器也能使用全部CPU资源。

# 演示CPU权重分配
# 假设有3个容器都在执行CPU密集型任务
docker run -d --cpu-shares 1024 --name app-1 my-stress-image
docker run -d --cpu-shares 512 --name app-2 my-stress-image
docker run -d --cpu-shares 256 --name app-3 my-stress-image

# 查看CPU分配情况
docker stats app-1 app-2 app-3
# 当CPU满载时:
# app-1 获得 1024/(1024+512+256) = 57.1% 的CPU
# app-2 获得 512/(1024+512+256) = 28.6% 的CPU
# app-3 获得 256/(1024+512+256) = 14.3% 的CPU

–cpuset-cpus(CPU核心绑定):

# 绑定到CPU 0
docker run -d --cpuset-cpus 0 --name my-app my-image:latest

# 绑定到CPU 0和1
docker run -d --cpuset-cpus 0,1 --name my-app my-image:latest

# 绑定到CPU 0到3
docker run -d --cpuset-cpus 0-3 --name my-app my-image:latest

# 绑定到不连续的CPU核心
docker run -d --cpuset-cpus 0,2,4,6 --name my-app my-image:latest

# 绑定CPU核心并限制CPU数量
docker run -d --cpuset-cpus 0-1 --cpus 1.5 --name my-app my-image:latest

CPU绑定对于性能敏感的应用非常重要,它避免了CPU缓存失效带来的性能损耗,适合数据库、消息队列等场景。

# 验证CPU绑定
docker exec my-app cat /sys/fs/cgroup/cpuset/cpuset.cpus
# 输出: 0-1

# 查看宿主机CPU拓扑
lscpu
# 查看NUMA节点
numactl --hardware

组合使用三种限制:

# 生产环境推荐: 同时使用--cpus和--cpuset-cpus
docker run -d \
  --cpus 2.0 \
  --cpuset-cpus 0-3 \
  --name db-server \
  mysql:8.0

# 这样容器最多使用2个CPU核心,且只能使用0-3号核心
# 结合了配额限制和核心绑定的优势

5.2 内存限制(–memory, --memory-swap, --memory-reservation)

内存限制防止容器消耗过多内存导致系统不稳定,是生产环境必不可少的配置。

内存限制参数详解:

# --memory(-m): 硬限制,容器最多使用的内存量
docker run -d -m 512m --name my-app my-image:latest

# --memory-swap: 内存+交换空间的总限制
# 设置为memory的2倍
docker run -d -m 512m --memory-swap 1g --name my-app my-image:latest
# 禁用swap(--memory-swap设置为与--memory相同)
docker run -d -m 512m --memory-swap 512m --name my-app my-image:latest
# 不限制swap(--memory-swap设置为-1)
docker run -d -m 512m --memory-swap -1 --name my-app my-image:latest

# --memory-reservation: 软限制,Linux内核内存回收的阈值
docker run -d -m 1g --memory-reservation 512m --name my-app my-image:latest

# --memory-swappiness: 调整swap使用倾向(0-100)
docker run -d -m 1g --memory-swappiness 0 --name my-app my-image:latest
# 0表示尽量不使用swap,100表示积极使用swap

# --kernel-memory: 内核内存限制(通常不需要单独设置)
docker run -d --kernel-memory 100m --name my-app my-image:latest

内存限制参数对比:

参数 类型 说明 超出时的行为
-m/–memory 硬限制 容器最多使用的物理内存 触发OOM Killer
–memory-swap 硬限制 内存+swap的总限制 触发OOM Killer
–memory-reservation 软限制 内核回收内存的阈值 回收内存,不杀进程
–memory-swappiness 调优参数 swap使用倾向 不直接导致OOM
–kernel-memory 硬限制 内核内存限制 触发OOM Killer
# 实际配置示例

# 1. Web服务器: 适中内存,禁用swap
docker run -d \
  -m 1g \
  --memory-swap 1g \
  --memory-reservation 512m \
  --memory-swappiness 0 \
  --name web-server \
  nginx:latest

# 2. 数据库: 大内存,允许少量swap
docker run -d \
  -m 4g \
  --memory-swap 6g \
  --memory-reservation 2g \
  --memory-swappiness 10 \
  --name mysql \
  mysql:8.0

# 3. 缓存服务: 固定内存,完全禁用swap
docker run -d \
  -m 2g \
  --memory-swap 2g \
  --memory-swappiness 0 \
  --name redis \
  redis:7-alpine

# 4. 批处理任务: 限制内存,允许swap
docker run -d \
  -m 2g \
  --memory-swap 4g \
  --memory-reservation 1g \
  --name batch-job \
  my-batch-image:latest

验证内存限制:

# 查看容器内存限制
docker inspect -f '{{.HostConfig.Memory}}' my-app
# 输出: 536870912 (即512MB = 512*1024*1024)

# 查看swap限制
docker inspect -f '{{.HostConfig.MemorySwap}}' my-app

# 查看cgroup中的内存限制
cat /sys/fs/cgroup/memory/docker/$(docker inspect -f '{{.Id}}' my-app)/memory.limit_in_bytes
# 输出: 536870912

# 在容器内验证
docker exec my-app cat /sys/fs/cgroup/memory/memory.limit_in_bytes

5.3 IO限制(–device-read-bps, --device-write-iops)

Docker支持对容器的磁盘IO进行限制,包括读写速率(BPS)和读写操作频率(IOPS)。

# --device-read-bps: 限制读取速率(字节/秒)
# 限制/dev/sda读取速率为10MB/s
docker run -d --device-read-bps /dev/sda:10mb --name my-app my-image:latest

# --device-write-bps: 限制写入速率(字节/秒)
# 限制/dev/sda写入速率为5MB/s
docker run -d --device-write-bps /dev/sda:5mb --name my-app my-image:latest

# --device-read-iops: 限制读取IOPS(每秒IO操作数)
docker run -d --device-read-iops /dev/sda:1000 --name my-app my-image:latest

# --device-write-iops: 限制写入IOPS
docker run -d --device-write-iops /dev/sda:500 --name my-app my-image:latest

# --blkio-weight: 块IO权重(10-1000,默认500)
docker run -d --blkio-weight 800 --name high-io-app my-image:latest
docker run -d --blkio-weight 200 --name low-io-app my-image:latest

# 组合使用
docker run -d \
  --device-read-bps /dev/sda:50mb \
  --device-write-bps /dev/sda:20mb \
  --device-read-iops /dev/sda:2000 \
  --device-write-iops /dev/sda:1000 \
  --blkio-weight 600 \
  --name my-app \
  my-image:latest

IO限制单位说明:

单位 说明
b 字节(bytes)
k / kb 千字节(KB)
m / mb 兆字节(MB)
g / gb 吉字节(GB)
# 测试IO限制效果
# 创建一个不限制IO的容器
docker run -d --name no-limit --rm alpine sh -c "dd if=/dev/zero of=/tmp/test bs=1M count=500 oflag=direct"

# 创建一个限制写入速率的容器
docker run -d --name with-limit --rm --device-write-bps /dev/sda:10mb alpine sh -c "dd if=/dev/zero of=/tmp/test bs=1M count=500 oflag=direct"

# 比较写入速度
docker logs no-limit   # 约500MB/s
docker logs with-limit # 约10MB/s

5.4 PID限制(–pids-limit)

--pids-limit限制容器内可以创建的最大进程(包括线程)数量,防止fork炸弹等异常情况。

# 限制容器内最多100个进程
docker run -d --pids-limit 100 --name my-app my-image:latest

# 不限制(默认行为,或设置为0)
docker run -d --pids-limit 0 --name my-app my-image:latest

# 限制容器内最多500个进程
docker run -d --pids-limit 500 --name my-app my-image:latest
# 演示PID限制
docker run -d --name pid-test --pids-limit 10 alpine sh -c "for i in $(seq 1 20); do sleep 3600 & done"

# 查看容器内进程数
docker exec pid-test ps aux | wc -l
# 不会超过10个进程

# 查看cgroup中的PID限制
cat /sys/fs/cgroup/pids/docker/$(docker inspect -f '{{.Id}}' pid-test)/pids.max
# 输出: 10

5.5 网络带宽限制

Docker本身不直接提供网络带宽限制参数,但可以通过以下方式实现:

方法1: 使用tc(traffic control)工具

# 启动容器
docker run -d --name my-app -p 8080:80 nginx:latest

# 获取容器的网络接口(veth)
PID=$(docker inspect -f '{{.State.Pid}}' my-app)
# 在容器的网络命名空间中查看接口

# 使用tc限制带宽(限制为1Mbps)
# 安装tc工具
apt-get install -y iproute2

# 限制容器的网络带宽
# 获取容器veth接口在宿主机上的名称
VETH=$(docker exec my-app cat /sys/class/net/eth0/ifindex)
# 限制入站带宽为1mbit
tc qdisc add dev veth${VETH} root tbf rate 1mbit burst 32kbit latency 400ms

# 查看限制
tc qdisc show dev veth${VETH}

# 删除限制
tc qdisc del dev veth${VETH} root

方法2: 使用Docker网络QoS(通过daemon.json)

{
  "default-shm-size": "64M",
  "max-concurrent-downloads": 10,
  "max-concurrent-uploads": 5
}

方法3: 使用第三方工具(如wondershaper)

# 在容器内安装并使用wondershaper
docker exec my-app apk add wondershaper
docker exec my-app wondershaper eth0 1024 512  # 限制为1Mbps下载,512Kbps上传

5.6 OOM Killer机制与OOM调整(–oom-kill-disable)

Linux的OOM(Out Of Memory)Killer是一个内核机制,当系统内存严重不足时,它会选择并杀死某些进程以释放内存。

OOM Killer的工作原理:

  1. 内核检测到可用内存低于阈值
  2. 为每个进程计算OOM分数(基于内存使用量、运行时间等因素)
  3. 选择OOM分数最高的进程杀死
  4. 分数范围: 0(最不可能被杀)到1000(最可能被杀)
# 查看进程的OOM分数
cat /proc/$(docker inspect -f '{{.State.Pid}}' my-app)/oom_score

# 查看OOM分数调整值
cat /proc/$(docker inspect -f '{{.State.Pid}}' my-app)/oom_score_adj

# --oom-kill-disable: 禁止OOM Killer杀死容器进程
docker run -d --oom-kill-disable -m 512m --name critical-app my-image:latest
# 注意: 必须同时设置-m限制,否则可能导致系统崩溃

# --oom-score-adj: 调整OOM分数(-1000到1000)
# -1000表示最不可能被OOM Killer选中
docker run -d --oom-score-adj -500 --name important-app my-image:latest
# 500表示更容易被OOM Killer选中
docker run -d --oom-score-adj 500 --name low-priority-app my-image:latest

OOM相关配置策略:

场景 推荐配置 说明
关键服务 –oom-kill-disable -m 1g 禁止OOM杀进程,但限制内存
重要服务 –oom-score-adj -500 降低被OOM选中的概率
普通服务 –oom-score-adj 0(默认) 正常OOM优先级
低优先级 –oom-score-adj 500 优先被OOM选中
# 检测容器是否被OOM杀死
docker inspect -f '{{.State.OOMKilled}}' my-app
# 输出: true 或 false

# OOM事件的系统日志
dmesg | grep -i "out of memory"
dmesg | grep -i "oom"
journalctl -k | grep -i oom

# 演示OOM触发
# 创建一个内存限制很小的容器,运行内存消耗程序
docker run -d --name oom-test -m 50m python:3.11-slim python -c "
import time
data = []
while True:
    data.append('x' * 1000000)  # 每次分配1MB
    print(f'内存使用: {len(data)}MB')
    time.sleep(0.5)
"

# 等待OOM触发后检查
docker inspect -f 'OOMKilled: {{.State.OOMKilled}} ExitCode: {{.State.ExitCode}}' oom-test
# 输出: OOMKilled: true ExitCode: 137

5.7 cgroups资源限制底层原理

控制组(cgroups, Control Groups)是Linux内核提供的资源限制和隔离机制,Docker通过cgroups实现容器的资源限制。

cgroups子系统:

子系统 说明 Docker参数
cpu CPU时间分配 –cpus, --cpu-shares
cpuset CPU核心绑定 –cpuset-cpus
memory 内存限制 -m, --memory-swap
blkio 块设备IO –blkio-weight, --device-read-bps
pids 进程数限制 –pids-limit
freezer 进程冻结 docker pause/unpause
net_cls 网络分类 (网络QoS)
devices 设备访问控制 –device
net_prio 网络优先级 (网络优先级)
# 查看cgroups挂载点
mount | grep cgroup

# cgroups v1的结构
ls /sys/fs/cgroup/
# blkio/  cpu/  cpuacct/  cpuset/  devices/  freezer/  memory/  net_cls/  ...

# cgroups v2的结构(统一层级)
ls /sys/fs/cgroup/
# cgroup.controllers  cgroup.stat  cpu.max  cpu.weight  memory.max  ...

# 查看Docker容器的cgroup路径
docker inspect -f '{{.HostConfig.CgroupParent}}' my-app

# 直接查看容器的cgroup配置
CONTAINER_ID=$(docker inspect -f '{{.Id}}' my-app)

# CPU限制(cgroups v1)
cat /sys/fs/cgroup/cpu/docker/$CONTAINER_ID/cpu.cfs_period_us
cat /sys/fs/cgroup/cpu/docker/$CONTAINER_ID/cpu.cfs_quota_us
cat /sys/fs/cgroup/cpu/docker/$CONTAINER_ID/cpu.shares

# 内存限制(cgroups v1)
cat /sys/fs/cgroup/memory/docker/$CONTAINER_ID/memory.limit_in_bytes
cat /sys/fs/cgroup/memory/docker/$CONTAINER_ID/memory.memsw.limit_in_bytes

# PID限制(cgroups v1)
cat /sys/fs/cgroup/pids/docker/$CONTAINER_ID/pids.max
cat /sys/fs/cgroup/pids/docker/$CONTAINER_ID/pids.current

# BlkIO限制(cgroups v1)
cat /sys/fs/cgroup/blkio/docker/$CONTAINER_ID/blkio.weight
cat /sys/fs/cgroup/blkio/docker/$CONTAINER_ID/blkio.throttle.read_bps_device

cgroups v1 vs v2:

# 检查系统使用的cgroups版本
stat -fc %T /sys/fs/cgroup/
# 输出: cgroup2fs 表示v2
# 输出: tmpfs 表示v1

# cgroups v2中的CPU限制
cat /sys/fs/cgroup/docker/$CONTAINER_ID/cpu.max
# 输出: 150000 100000 (quota period)

# cgroups v2中的内存限制
cat /sys/fs/cgroup/docker/$CONTAINER_ID/memory.max
# 输出: 536870912

# cgroups v2中的进程数限制
cat /sys/fs/cgroup/docker/$CONTAINER_ID/pids.max

5.8 资源限制实战: 限制Web服务器资源

#!/bin/bash
# 生产环境Web服务器资源限制配置脚本

echo "=== 创建自定义网络 ==="
docker network create --driver bridge web-network

echo ""
echo "=== 启动资源受限的Nginx Web服务器 ==="
docker run -d \
  --name web-server \
  --network web-network \
  -p 80:80 \
  \
  `# CPU限制: 最多使用2个核心` \
  --cpus 2.0 \
  --cpuset-cpus "0-3" \
  --cpu-shares 1024 \
  \
  `# 内存限制: 1GB内存,1GB swap,512MB软限制` \
  -m 1g \
  --memory-swap 2g \
  --memory-reservation 512m \
  --memory-swappiness 30 \
  \
  `# IO限制: 读取50MB/s,写入20MB/s` \
  --device-read-bps /dev/sda:50mb \
  --device-write-bps /dev/sda:20mb \
  --blkio-weight 600 \
  \
  `# PID限制: 最多200个进程` \
  --pids-limit 200 \
  \
  `# OOM调整: 降低被杀概率` \
  --oom-score-adj -500 \
  \
  `# 日志限制` \
  --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  \
  `# 重启策略` \
  --restart unless-stopped \
  \
  `# 健康检查` \
  --health-cmd "curl -f http://localhost/ || exit 1" \
  --health-interval 10s \
  --health-timeout 5s \
  --health-retries 3 \
  \
  nginx:latest

echo ""
echo "=== 验证资源限制配置 ==="
echo "CPU限制:"
docker inspect -f '  CPUs: {{.HostConfig.NanoCpus}} (纳秒)' web-server
docker inspect -f '  CPU Shares: {{.HostConfig.CpuShares}}' web-server
docker inspect -f '  CPU绑定: {{.HostConfig.CpusetCpus}}' web-server

echo "内存限制:"
docker inspect -f '  内存限制: {{.HostConfig.Memory}} bytes' web-server
docker inspect -f '  Swap限制: {{.HostConfig.MemorySwap}} bytes' web-server
docker inspect -f '  内存软限制: {{.HostConfig.MemoryReservation}} bytes' web-server

echo "其他限制:"
docker inspect -f '  PID限制: {{.HostConfig.PidsLimit}}' web-server
docker inspect -f '  OOM调整: {{.HostConfig.OomScoreAdj}}' web-server

echo ""
echo "=== 实时监控 ==="
docker stats web-server

5.9 动态调整运行中容器的资源限制

生产环境中经常需要根据负载动态调整容器资源,使用docker update可以在不重启容器的情况下修改资源限制。

#!/bin/bash
# 容器资源动态调整脚本

CONTAINER_NAME="web-server"
LOG_FILE="/var/log/container-resize.log"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# 获取当前资源限制
get_current_limits() {
    local cpu=$(docker inspect -f '{{.HostConfig.NanoCpus}}' "$CONTAINER_NAME")
    local mem=$(docker inspect -f '{{.HostConfig.Memory}}' "$CONTAINER_NAME")
    cpu=$((cpu / 1000000000))
    mem=$((mem / 1024 / 1024 / 1024))
    echo "CPU: ${cpu}核, 内存: ${mem}GB"
}

# 获取当前资源使用率
get_usage() {
    local stats=$(docker stats --no-stream --format "{{.CPUPerc}}|{{.MemPerc}}" "$CONTAINER_NAME")
    local cpu=$(echo "$stats" | cut -d'|' -f1 | tr -d '%')
    local mem=$(echo "$stats" | cut -d'|' -f2 | tr -d '%')
    echo "CPU使用率: ${cpu}%, 内存使用率: ${mem}%"
}

# 扩容函数
scale_up() {
    local new_cpu=$1
    local new_mem=$2
    
    log "扩容容器 $CONTAINER_NAME -> CPU: ${new_cpu}核, 内存: ${new_mem}GB"
    
    docker update \
        --cpus "$new_cpu" \
        -m "${new_mem}g" \
        --memory-swap "${new_mem}g" \
        "$CONTAINER_NAME"
    
    if [ $? -eq 0 ]; then
        log "扩容成功"
    else
        log "扩容失败!"
        return 1
    fi
}

# 缩容函数
scale_down() {
    local new_cpu=$1
    local new_mem=$2
    
    log "缩容容器 $CONTAINER_NAME -> CPU: ${new_cpu}核, 内存: ${new_mem}GB"
    
    docker update \
        --cpus "$new_cpu" \
        -m "${new_mem}g" \
        --memory-swap "${new_mem}g" \
        "$CONTAINER_NAME"
    
    if [ $? -eq 0 ]; then
        log "缩容成功"
    else
        log "缩容失败,可能当前内存使用量超过新限制"
        return 1
    fi
}

# 主逻辑: 根据负载自动调整
auto_scale() {
    log "当前限制: $(get_current_limits)"
    log "当前使用: $(get_usage)"
    
    stats=$(docker stats --no-stream --format "{{.CPUPerc}}|{{.MemPerc}}" "$CONTAINER_NAME")
    cpu_usage=$(echo "$stats" | cut -d'|' -f1 | tr -d '%')
    mem_usage=$(echo "$stats" | cut -d'|' -f2 | tr -d '%')
    
    # CPU使用率>80%,扩容CPU
    if (( $(echo "$cpu_usage > 80" | bc -l) )); then
        current_cpu=$(docker inspect -f '{{.HostConfig.NanoCpus}}' "$CONTAINER_NAME")
        current_cpu=$((current_cpu / 1000000000))
        new_cpu=$((current_cpu + 1))
        if [ "$new_cpu" -le 8 ]; then
            log "CPU负载过高(${cpu_usage}%),扩容CPU: ${current_cpu} -> ${new_cpu}"
            docker update --cpus "${new_cpu}.0" "$CONTAINER_NAME"
        else
            log "CPU已达上限(8核),无法继续扩容"
        fi
    fi
    
    # CPU使用率<20%,缩容CPU
    if (( $(echo "$cpu_usage < 20" | bc -l) )); then
        current_cpu=$(docker inspect -f '{{.HostConfig.NanoCpus}}' "$CONTAINER_NAME")
        current_cpu=$((current_cpu / 1000000000))
        if [ "$current_cpu" -gt 1 ]; then
            new_cpu=$((current_cpu - 1))
            log "CPU负载较低(${cpu_usage}%),缩容CPU: ${current_cpu} -> ${new_cpu}"
            docker update --cpus "${new_cpu}.0" "$CONTAINER_NAME"
        fi
    fi
    
    # 内存使用率>80%,扩容内存
    if (( $(echo "$mem_usage > 80" | bc -l) )); then
        current_mem=$(docker inspect -f '{{.HostConfig.Memory}}' "$CONTAINER_NAME")
        current_mem=$((current_mem / 1024 / 1024 / 1024))
        new_mem=$((current_mem + 1))
        if [ "$new_mem" -le 8 ]; then
            log "内存使用过高(${mem_usage}%),扩容内存: ${current_mem}g -> ${new_mem}g"
            docker update -m "${new_mem}g" --memory-swap "${new_mem}g" "$CONTAINER_NAME"
        fi
    fi
}

# 执行自动扩缩容
auto_scale

第六章 容器与宿主机交互

6.1 docker cp文件复制(容器与宿主机)

docker cp命令用于在容器和宿主机之间复制文件或目录。

# 从宿主机复制到容器
docker cp /path/to/local/file my-nginx:/path/in/container/
docker cp /path/to/local/dir my-nginx:/path/in/container/

# 从容器复制到宿主机
docker cp my-nginx:/etc/nginx/nginx.conf /tmp/nginx.conf
docker cp my-nginx:/var/log/nginx/ /tmp/nginx-logs/

# 复制到标准输出
docker cp my-nginx:/etc/nginx/nginx.conf -

# 使用容器ID而非名称
docker cp 3a7c9f2b:/etc/hosts /tmp/
# 实际应用场景

# 1. 向容器上传配置文件
docker cp ./nginx.conf my-nginx:/etc/nginx/nginx.conf
docker exec my-nginx nginx -s reload

# 2. 从容器下载日志
docker cp my-app:/var/log/app.log ./app-$(date +%Y%m%d).log

# 3. 备份容器数据
docker cp db-container:/var/lib/mysql/ /backup/mysql-$(date +%Y%m%d)/

# 4. 向容器部署代码(不推荐生产使用)
docker cp ./target/app.jar my-app:/app/app.jar
docker restart my-app

# 5. 从容器提取诊断信息
docker cp my-app:/tmp/heapdump.hprof /local/diagnostics/

docker cp的注意事项:

# 1. cp命令保留文件权限和属主信息
# 2. 复制目录时会递归复制
# 3. 目标路径不存在时会自动创建
# 4. 不能在容器停止状态下使用(某些旧版本不支持)
# 5. 符号链接会被复制为实际文件

# 归档模式(-a): 保留所有属性
docker cp -a my-nginx:/etc/nginx/ /backup/nginx-config/

# 使用tar进行更灵活的文件传输
# 将容器目录打包到宿主机
docker exec my-app tar cf - /app/data | tar xf - -C /backup/

# 将宿主机文件打包传入容器
tar cf - -C /local/data . | docker exec -i my-app tar xf - -C /app/data/

6.2 docker export容器导出为tar

docker export将容器的文件系统导出为一个tar归档文件。

# 导出容器文件系统为tar文件
docker export my-nginx > nginx-backup.tar

# 导出到指定文件
docker export -o /backup/my-app.tar my-app

# 导出并压缩
docker export my-app | gzip > /backup/my-app.tar.gz

# 查看导出文件内容
docker export my-nginx | tar tvf - | head -20
# 实际应用

# 1. 备份运行中容器的完整文件系统
docker export web-server | gzip > /backup/web-$(date +%Y%m%d).tar.gz

# 2. 将容器迁移到另一台机器
docker export my-app > my-app.tar
scp my-app.tar user@remote-server:/tmp/
# 在远程机器上导入
ssh user@remote-server "cat /tmp/my-app.tar | docker import - my-app:latest"

# 3. 分析容器的文件系统内容
docker export my-app | tar tf - | wc -l  # 统计文件数量
docker export my-app | tar tf - | grep "\.log$"  # 查找日志文件

6.3 docker import从tar创建镜像

docker import从一个tar文件创建新的镜像,通常与docker export配合使用。

# 从tar文件创建镜像
docker import nginx-backup.tar my-nginx-backup:latest

# 从标准输入创建镜像
cat my-app.tar | docker import - my-app:latest

# 导入并设置提交信息
docker import -m "导入备份的容器文件系统" my-app.tar my-app:latest

# 导入并设置变更指令(类似Dockerfile指令)
docker import \
  -c "ENV TZ=Asia/Shanghai" \
  -c "EXPOSE 80" \
  -c "CMD [\"nginx\", \"-g\", \"daemon off;\"]" \
  my-nginx.tar my-nginx-custom:latest

# 从压缩文件导入
gunzip -c /backup/my-app.tar.gz | docker import - my-app:latest

export/import vs save/load对比:

特性 docker export/import docker save/load
操作对象 容器 镜像
包含内容 容器文件系统(扁平) 镜像层(含历史)
保留层信息 否(合并为一层)
保留配置 否(需要重新指定CMD等)
文件大小 较小 较大
适用场景 容器迁移、备份 镜像分发、备份
# export + import: 导出容器再导入为镜像
docker export my-app | docker import - my-app-exported:latest

# save + load: 保存镜像再加载
docker save my-app:latest | gzip > my-app-image.tar.gz
docker load < my-app-image.tar.gz

# 验证区别
docker history my-app-exported:latest
# 只有一层(import创建的)

docker history my-app:latest
# 多层(原始构建历史)

6.4 docker commit从容器创建镜像

docker commit将容器的当前状态(包括文件系统变更)保存为一个新的镜像。

# 基本用法
docker commit my-nginx my-nginx-custom:latest

# 添加提交信息
docker commit -m "添加了自定义配置" -a "运维团队" my-nginx my-nginx-custom:v1

# 在提交时修改配置
docker commit \
  -c 'CMD ["nginx", "-g", "daemon off;"]' \
  -c 'EXPOSE 80 443' \
  -c 'ENV TZ=Asia/Shanghai' \
  my-nginx my-nginx-custom:latest

# 指定暂停容器(默认true,提交时暂停容器)
docker commit --pause=false my-nginx my-nginx-custom:latest
# 实际应用: 从运行中的容器创建自定义镜像

# 1. 启动基础容器
docker run -d --name build-container nginx:latest

# 2. 在容器中安装软件和修改配置
docker exec build-container apt-get update
docker exec build-container apt-get install -y vim curl
docker exec build-container sh -c "echo 'server_tokens off;' >> /etc/nginx/nginx.conf"
docker cp ./custom.conf build-container:/etc/nginx/conf.d/custom.conf

# 3. 提交为新镜像
docker commit \
  -m "Nginx with vim, curl and custom config" \
  -a "DevOps Team" \
  -c 'CMD ["nginx", "-g", "daemon off;"]' \
  -c 'EXPOSE 80' \
  build-container my-nginx-custom:v1.0

# 4. 验证新镜像
docker images my-nginx-custom
docker history my-nginx-custom:v1.0

# 5. 清理构建容器
docker stop build-container
docker rm build-container

# 6. 使用新镜像启动容器
docker run -d --name prod-web -p 80:80 my-nginx-custom:v1.0

commit的最佳实践和注意事项:

# 注意: docker commit不推荐用于生产镜像构建
# 原因:
# 1. 不可追溯: 无法知道镜像中做了什么修改
# 2. 镜像臃肿: 每次commit都会增加一层
# 3. 安全隐患: 可能包含敏感信息(密码、密钥)
# 4. 不可重复: 无法自动化重建

# 推荐使用Dockerfile构建镜像,而非commit
# commit适合快速调试和临时保存容器状态

6.5 端口映射详解(-p单端口、范围映射、UDP/TCP、随机端口)

端口映射是容器与外部通信的关键配置,Docker提供了灵活的端口映射机制。

端口映射的各种形式:

# 1. 基本端口映射: 宿主机端口 -> 容器端口
docker run -d -p 8080:80 --name my-web nginx:latest
# 访问: http://宿主机IP:8080

# 2. 指定协议(TCP/UDP)
docker run -d -p 53:53/tcp -p 53:53/udp --name dns-server my-dns:latest

# 3. 绑定到指定IP
docker run -d -p 127.0.0.1:8080:80 --name my-web nginx:latest
# 只有本机可以访问

# 4. 绑定到特定网卡IP
docker run -d -p 192.168.1.100:8080:80 --name my-web nginx:latest

# 5. 随机端口映射(-P大写)
docker run -d -P --name my-web nginx:latest
# Docker随机分配宿主机端口(通常从32768开始)

# 6. 随机映射特定端口
docker run -d -p 80 --name my-web nginx:latest
# 只指定容器端口,宿主机端口随机分配

# 7. 范围映射
docker run -d -p 8080-8090:8080-8090 --name my-app my-image:latest

# 8. 多端口映射
docker run -d \
  -p 80:80 \
  -p 443:443 \
  -p 8080:8080 \
  --name my-web \
  nginx:latest

# 9. 映射到不同端口
docker run -d -p 80:8080 --name my-app my-image:latest
# 宿主机80 -> 容器8080

端口映射参数格式:

-p [宿主机IP:]宿主机端口:容器端口[/协议]
格式 说明 示例
-p 宿主机:容器 映射到所有IP -p 8080:80
-p IP:宿主机:容器 绑定到指定IP -p 127.0.0.1:8080:80
-p 容器端口 随机宿主机端口 -p 80
-P 随机映射所有EXPOSE端口 -P
-p 宿主机:容器/udp UDP协议 -p 53:53/udp
# 查看端口映射
docker port my-web
# 80/tcp -> 0.0.0.0:8080
# 443/tcp -> 0.0.0.0:8443

# 查看特定端口
docker port my-web 80

# 在docker ps中查看端口映射
docker ps --format "table {{.Names}}\t{{.Ports}}"
# NAMES      PORTS
# my-web     0.0.0.0:8080->80/tcp, 0.0.0.0:8443->443/tcp

# 随机端口映射的范围配置
# 在daemon.json中配置
# {
#   "default-address-pools": [
#     {"base": "172.17.0.0/16", "size": 24}
#   ]
# }

6.6 端口映射底层原理(iptables DNAT)

Docker的端口映射底层通过Linux的iptables/netfilter实现,具体使用DNAT(Destination Network Address Translation)规则将宿主机端口的流量转发到容器内部。

# 查看Docker创建的iptables规则
sudo iptables -t nat -L -n -v

# 查看DNAT规则(端口映射)
sudo iptables -t nat -L DOCKER -n -v
# 输出示例:
# Chain DOCKER (2 references)
#  target  prot  opt  source    destination
#  RETURN  all   --   0.0.0.0/0 0.0.0.0/0
#  DNAT    tcp   --   0.0.0.0/0 0.0.0.0/0  tcp dpt:8080 to:172.17.0.2:80

# 查看完整的NAT规则链
sudo iptables -t nat -L -n -v --line-numbers

# Docker端口映射的iptables流程:
# 1. 外部请求到达宿主机的8080端口
# 2. PREROUTING链匹配Docker规则
# 3. DNAT将目标地址改为容器IP:容器端口(如172.17.0.2:80)
# 4. 请求通过docker0网桥转发到容器
# 5. 容器处理后,响应通过NAT返回给客户端
# 查看Docker网络配置
docker network inspect bridge

# 查看docker0网桥
brctl show docker0
# bridge name  bridge id       STP enabled  interfaces
# docker0      8000.0242...    no           veth12345
#                                            veth67890

# 查看容器的网络接口
docker exec my-web ip addr show eth0

# 查看宿主机到容器的路由
ip route show
# 172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1

6.7 容器间文件共享

容器间共享文件有多种方式,根据场景选择合适的方案。

方式1: 使用数据卷(Volume)

# 创建命名数据卷
docker volume create shared-data

# 多个容器共享同一个数据卷
docker run -d --name app-1 -v shared-data:/data my-image:latest
docker run -d --name app-2 -v shared-data:/data my-image:latest

# 两个容器都可以读写/data目录中的文件

方式2: 使用绑定挂载(Bind Mount)

# 多个容器挂载同一个宿主机目录
mkdir -p /opt/shared

docker run -d --name app-1 -v /opt/shared:/shared my-image:latest
docker run -d --name app-2 -v /opt/shared:/shared:ro my-image:latest
# app-2以只读方式挂载

方式3: 使用–volumes-from

# 创建数据容器
docker create -v /data --name data-container alpine:latest

# 其他容器从数据容器挂载卷
docker run -d --name app-1 --volumes-from data-container my-image:latest
docker run -d --name app-2 --volumes-from data-container my-image:latest

方式4: 使用NFS共享

# 挂载NFS共享到容器
docker run -d \
  --name nfs-client \
  --mount type=volume,dst=/mnt/nfs,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/nfs/share,"volume-opt=o=addr=nfs.server.com,rw" \
  my-image:latest

6.8 容器时区与本地化配置

容器默认使用UTC时区,需要根据应用需求配置正确的时区和语言环境。

# 方法1: 通过环境变量设置时区
docker run -d -e TZ=Asia/Shanghai --name my-app my-image:latest

# 方法2: 挂载宿主机的时区文件
docker run -d \
  -v /etc/localtime:/etc/localtime:ro \
  -v /etc/timezone:/etc/timezone:ro \
  --name my-app \
  my-image:latest

# 方法3: 在Dockerfile中设置
# Dockerfile:
# FROM alpine:latest
# RUN apk add --no-cache tzdata
# ENV TZ=Asia/Shanghai
# RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

# 方法4: 同时设置时区和语言环境
docker run -d \
  -e TZ=Asia/Shanghai \
  -e LANG=C.UTF-8 \
  -e LC_ALL=C.UTF-8 \
  --name my-app \
  my-image:latest
# 验证时区设置
docker exec my-app date
# 输出: Thu Jan  1 10:00:00 CST 2024 (CST表示中国标准时间)

docker exec my-app cat /etc/timezone
# 输出: Asia/Shanghai

# 查看容器的locale设置
docker exec my-app locale

# 配置中文环境(适用于Ubuntu/Debian)
docker run -it --rm \
  -e TZ=Asia/Shanghai \
  -e LANG=zh_CN.UTF-8 \
  -e LANGUAGE=zh_CN:zh \
  ubuntu:22.04 \
  bash -c "apt-get update && apt-get install -y locales && locale-gen zh_CN.UTF-8 && locale"

时区配置的最佳实践:

# 推荐的Dockerfile时区配置
FROM debian:bookworm-slim

# 安装时区数据
RUN apt-get update && \
    apt-get install -y --no-install-recommends tzdata && \
    ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
    echo "Asia/Shanghai" > /etc/timezone && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

# 设置环境变量
ENV TZ=Asia/Shanghai
ENV LANG=C.UTF-8

# 验证
RUN date

第七章 容器日志管理

7.1 Docker日志驱动(json-file/syslog/journald/fluentd/gelf/none)

Docker提供了多种日志驱动,决定了容器日志的收集、存储和传输方式。日志驱动在容器启动时通过--log-driver参数指定。

Docker支持的日志驱动:

日志驱动 说明 适用场景
json-file 默认驱动,日志以JSON格式存储在文件中 单机开发、测试
syslog 将日志写入syslog 系统日志集中管理
journald 将日志写入journald systemd环境
fluentd 将日志发送到Fluentd 日志聚合、EFK栈
gelf 发送Graylog扩展日志格式 Graylog日志系统
awslogs 发送到AWS CloudWatch AWS云环境
splunk 发送到Splunk Splunk日志分析
gcplogs 发送到Google Cloud Logging GCP云环境
local 存储在自定义格式文件中 本地高性能存储
none 禁用日志收集 不需要日志的场景
# 查看当前Docker默认日志驱动
docker info --format '{{.LoggingDriver}}'
# 输出: json-file

# 查看容器使用的日志驱动
docker inspect -f '{{.HostConfig.LogConfig.Type}}' my-nginx
# 输出: json-file

# 指定日志驱动启动容器
docker run -d --log-driver json-file --name my-app my-image:latest
docker run -d --log-driver syslog --name my-app my-image:latest
docker run -d --log-driver none --name my-app my-image:latest

# 禁用日志的容器
docker run -d --log-driver none --name silent-app my-image:latest
# docker logs silent-app 将不返回任何内容

配置全局默认日志驱动:

# 编辑 /etc/docker/daemon.json
cat > /etc/docker/daemon.json << 'EOF'
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
EOF

# 重启Docker
sudo systemctl restart docker

各日志驱动的配置示例:

# 1. json-file: 本地JSON文件
docker run -d \
  --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  --log-opt labels=production \
  --log-opt env=OS \
  --name my-app \
  my-image:latest

# 2. syslog: 系统日志
docker run -d \
  --log-driver syslog \
  --log-opt syslog-address=udp://192.168.1.100:514 \
  --log-opt syslog-facility=daemon \
  --log-opt tag="my-app" \
  --name my-app \
  my-image:latest

# 3. journald: systemd日志
docker run -d \
  --log-driver journald \
  --log-opt tag="my-app/{{.Name}}" \
  --name my-app \
  my-image:latest
# 使用journalctl查看
# journalctl CONTAINER_NAME=my-app

# 4. fluentd: Fluentd日志收集
docker run -d \
  --log-driver fluentd \
  --log-opt fluentd-address=localhost:24224 \
  --log-opt tag="docker.my-app" \
  --log-opt fluentd-async-connect=true \
  --name my-app \
  my-image:latest

# 5. gelf: Graylog日志格式
docker run -d \
  --log-driver gelf \
  --log-opt gelf-address=udp://graylog-server:12201 \
  --log-opt tag="my-app" \
  --name my-app \
  my-image:latest

# 6. awslogs: AWS CloudWatch
docker run -d \
  --log-driver awslogs \
  --log-opt awslogs-region=us-east-1 \
  --log-opt awslogs-group=my-log-group \
  --log-opt awslogs-stream-prefix=my-app \
  --name my-app \
  my-image:latest

7.2 json-file日志驱动配置(大小限制、轮转)

json-file是Docker默认的日志驱动,如果不配置日志轮转,容器日志文件会无限增长,最终耗尽磁盘空间。

日志轮转配置:

# 单容器配置
docker run -d \
  --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  --name my-app \
  my-image:latest

# max-size: 单个日志文件最大大小(达到后触发轮转)
# max-file: 保留的日志文件数量(超出后删除最旧的)

# 日志文件存储位置
ls -la /var/lib/docker/containers/<container-id>/
# -rw-r----- 1 root root  10M Jan 1 10:00 <container-id>-json.log
# -rw-r----- 1 root root  10M Jan 1 09:00 <container-id>-json.log.1
# -rw-r----- 1 root root  10M Jan 1 08:00 <container-id>-json.log.2

全局日志轮转配置(推荐):

// /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3",
    "labels": "env",
    "env": "OS"
  }
}
# 重启Docker使配置生效
sudo systemctl restart docker

# 验证配置
docker info | grep -A5 "Logging"

# 新启动的容器将自动应用这些日志配置
docker run -d --name test-app nginx:latest
docker inspect -f '{{json .HostConfig.LogConfig}}' test-app
# {"Type":"json-file","Config":{"max-file":"3","max-size":"10m"}}

日志大小单位:

单位 说明 示例
k KB max-size=100k
m MB max-size=10m
g GB max-size=1g

日志轮转的实际效果:

# 模拟大量日志输出
docker run -d \
  --log-driver json-file \
  --log-opt max-size=1m \
  --log-opt max-file=3 \
  --name log-generator \
  alpine:latest \
  sh -c "while true; do echo 'Generating log line...'; done"

# 观察日志文件
CONTAINER_ID=$(docker inspect -f '{{.Id}}' log-generator)
watch -n 1 "ls -lh /var/lib/docker/containers/${CONTAINER_ID}/*.log*"

# 日志文件会轮转:
# <id>-json.log     (当前写入,最大1MB)
# <id>-json.log.1   (上一次轮转的日志)
# <id>-json.log.2   (更早的日志)
# 当.log达到1MB时,.log2被删除,.log1变为.log2,.log变为.log1,新建.log

# 最大磁盘占用: 1MB * 3 = 3MB

7.3 容器标准输出与标准错误处理

Docker的日志驱动默认捕获容器的标准输出(stdout)和标准错误(stderr)。理解stdout和stderr的处理方式对于日志管理非常重要。

# stdout和stderr都会被Docker日志驱动捕获
docker run -d --name my-app my-image:latest

# stdout输出(正常日志)
docker exec my-app sh -c 'echo "This is stdout"'

# stderr输出(错误日志)
docker exec my-app sh -c 'echo "This is stderr" >&2'

# docker logs默认显示stdout和stderr
docker logs my-app
# This is stdout
# This is stderr

# 只查看stdout
docker logs my-app 2>/dev/null

# 只查看stderr
docker logs my-app 2>&1 1>/dev/null

应用日志输出最佳实践:

# Python: 正确输出到stdout和stderr
import sys
import logging

# 配置日志输出到stdout
logging.basicConfig(
    stream=sys.stdout,  # 正常日志输出到stdout
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

logger = logging.getLogger(__name__)

# 正常日志 -> stdout
logger.info("应用启动成功")
logger.info("处理请求: GET /api/users")

# 错误日志 -> stderr
logging.basicConfig(
    stream=sys.stderr,  # 错误日志输出到stderr
    level=logging.ERROR
)
logger.error("数据库连接失败")
# 应用日志应输出到stdout/stderr,而非写入文件
# 错误做法: 应用将日志写入容器内文件
# docker exec my-app cat /var/log/app.log  # 需要额外步骤获取日志

# 正确做法: 应用直接输出到stdout/stderr
# docker logs my-app  # 直接通过docker logs获取

# 如果应用必须写文件,可以创建符号链接
# 在Dockerfile中:
# RUN ln -sf /dev/stdout /var/log/nginx/access.log
# RUN ln -sf /dev/stderr /var/log/nginx/error.log
# 这样nginx的日志文件会输出到stdout/stderr,docker logs可以捕获

7.4 日志聚合方案(ELK/EFK)

在生产环境中,单机使用docker logs查看日志远远不够,需要集中化的日志管理方案。

ELK/EFK架构:

容器日志 -> Docker日志驱动 -> Fluentd/Fluent Bit -> Elasticsearch -> Kibana
                                                     ^
ELK = Elasticsearch + Logstash + Kibana
EFK = Elasticsearch + Fluentd + Kibana

使用Docker Compose部署EFK:

# docker-compose.yml - EFK日志收集系统
version: '3.8'

services:
  # Fluentd日志收集器
  fluentd:
    image: fluent/fluentd:v1.16-debian-1
    ports:
      - "24224:24224"
      - "24224:24224/udp"
    volumes:
      - ./fluentd/conf:/fluentd/etc
      - fluentd-data:/fluentd/log
    environment:
      - FLUENTD_CONF=fluent.conf

  # Elasticsearch搜索引擎
  elasticsearch:
    image: elasticsearch:8.11.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    volumes:
      - es-data:/usr/share/elasticsearch/data
    ports:
      - "9200:9200"

  # Kibana可视化界面
  kibana:
    image: kibana:8.11.0
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    depends_on:
      - elasticsearch

  # 示例应用(使用fluentd日志驱动)
  web-app:
    image: nginx:latest
    ports:
      - "8080:80"
    logging:
      driver: fluentd
      options:
        fluentd-address: localhost:24224
        tag: docker.web-app
    depends_on:
      - fluentd

volumes:
  es-data:
  fluentd-data:
# Fluentd配置文件 fluentd/conf/fluent.conf
cat > fluentd/conf/fluent.conf << 'EOF'
<source>
  @type forward
  port 24224
  bind 0.0.0.0
</source>

<match docker.**>
  @type elasticsearch
  host elasticsearch
  port 9200
  logstash_format true
  logstash_prefix docker-logs
  flush_interval 5s
</match>
EOF

# 启动EFK栈
docker-compose up -d

# 查看日志是否正确收集到Elasticsearch
curl 'http://localhost:9200/docker-logs-*/_search?pretty'

7.5 docker logs命令高级用法

# 实时跟踪并高亮关键字
docker logs -f my-app | grep --color=always -E "ERROR|WARNING|CRITICAL"

# 多行日志合并查看(适用于Java堆栈跟踪)
docker logs my-app | awk '/^[0-9]{4}/{print "---"} {print}'

# 按时间过滤并统计
docker logs --since 1h my-app | awk '{print $1}' | sort | uniq -c | sort -rn

# 提取JSON格式日志中的特定字段
docker logs my-app | jq '.level' | sort | uniq -c

# 日志搜索并显示上下文
docker logs my-app | grep -B2 -A5 "Exception"

# 导出指定时间段日志
docker logs --since "2024-01-01T10:00:00" --until "2024-01-01T12:00:00" my-app > /tmp/noon-logs.txt

# 同时监控多个容器日志
docker logs -f my-app &
docker logs -f my-db &
wait

# 使用docker compose查看所有服务日志
docker-compose logs -f
docker-compose logs -f web-app
docker-compose logs --tail 50 web-app

# 日志搜索脚本
#!/bin/bash
# 在所有运行中容器中搜索关键字
search_term="$1"
for container in $(docker ps -q); do
    name=$(docker inspect -f '{{.Name}}' "$container" | sed 's/\///')
    matches=$(docker logs "$container" 2>&1 | grep -c "$search_term")
    if [ "$matches" -gt 0 ]; then
        echo "=== $name: $matches matches ==="
        docker logs "$container" 2>&1 | grep "$search_term" | head -5
    fi
done

7.6 结构化日志输出

结构化日志(如JSON格式)便于日志聚合系统解析和查询,是生产环境的最佳实践。

# Python结构化日志输出(JSON格式)
import json
import logging
import sys
from datetime import datetime

class JSONFormatter(logging.Formatter):
    def format(self, record):
        log_entry = {
            "timestamp": datetime.utcnow().isoformat() + "Z",
            "level": record.levelname,
            "message": record.getMessage(),
            "logger": record.name,
            "module": record.module,
            "line": record.lineno,
        }
        if hasattr(record, 'request_id'):
            log_entry['request_id'] = record.request_id
        if hasattr(record, 'user_id'):
            log_entry['user_id'] = record.user_id
        if record.exc_info:
            log_entry['exception'] = self.formatException(record.exc_info)
        return json.dumps(log_entry)

# 配置日志
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(JSONFormatter())
logger.addHandler(handler)

# 使用示例
logger.info("用户登录", extra={"request_id": "abc123", "user_id": 1001})
logger.error("数据库查询失败", extra={"request_id": "abc123", "table": "users"})

# 输出(JSON格式,每行一条):
# {"timestamp": "2024-01-01T10:00:00Z", "level": "INFO", "message": "用户登录", "request_id": "abc123", "user_id": 1001}
# {"timestamp": "2024-01-01T10:00:01Z", "level": "ERROR", "message": "数据库查询失败", "request_id": "abc123", "table": "users"}
// Java结构化日志输出(使用Logback)
// pom.xml: 添加 net.logstash.logback:logstash-logback-encoder
// logback.xml:
/*
<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="net.logstash.logback.encoder.LogstashEncoder">
      <customFields>{"app":"my-app","env":"production"}</customFields>
    </encoder>
  </appender>
  <root level="INFO">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>
*/

7.7 日志管理最佳实践

日志管理最佳实践清单:

  1. 应用日志输出到stdout/stderr: 不要写入容器内文件,让Docker日志驱动统一管理
  2. 使用结构化日志格式: JSON格式便于解析和查询
  3. 配置日志轮转: 防止日志文件无限增长
  4. 包含足够的上下文信息: 时间戳、请求ID、用户ID等
  5. 区分日志级别: DEBUG/INFO/WARN/ERROR/FATAL
  6. 避免敏感信息: 不要在日志中输出密码、密钥等
  7. 使用集中化日志系统: EFK/ELK/Splunk等
  8. 监控日志量异常: 日志量突增可能是问题的信号
# 日志管理检查脚本
#!/bin/bash
echo "=== 容器日志大小检查 ==="
for container in $(docker ps -q); do
    name=$(docker inspect -f '{{.Name}}' "$container" | sed 's/\///')
    log_file="/var/lib/docker/containers/$(docker inspect -f '{{.Id}}' "$container")/${container}-json.log"
    if [ -f "$log_file" ]; then
        size=$(du -sh "$log_file" | cut -f1)
        echo "  $name: $size"
    fi
done

echo ""
echo "=== 日志驱动配置检查 ==="
for container in $(docker ps -q); do
    name=$(docker inspect -f '{{.Name}}' "$container" | sed 's/\///')
    driver=$(docker inspect -f '{{.HostConfig.LogConfig.Type}}' "$container")
    max_size=$(docker inspect -f '{{index .HostConfig.LogConfig.Config "max-size"}}' "$container" 2>/dev/null)
    max_file=$(docker inspect -f '{{index .HostConfig.LogConfig.Config "max-file"}}' "$container" 2>/dev/null)
    echo "  $name: driver=$driver, max-size=$max_size, max-file=$max_file"
done

第八章 容器健康检查

8.1 HEALTHCHECK指令详解

Docker的健康检查机制(HEALTHCHECK)允许Docker定期检查容器内应用是否正常运行,自动标记容器的健康状态。

在Dockerfile中使用HEALTHCHECK:

# 基本语法:
# HEALTHCHECK [OPTIONS] CMD command

# 示例: Nginx健康检查
FROM nginx:latest
HEALTHCHECK --interval=10s --timeout=5s --retries=3 --start-period=30s \
  CMD curl -f http://localhost/ || exit 1

# 示例: MySQL健康检查
FROM mysql:8.0
HEALTHCHECK --interval=10s --timeout=5s --retries=5 \
  CMD mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD || exit 1

# 示例: Redis健康检查
FROM redis:7-alpine
HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
  CMD redis-cli ping || exit 1

# 示例: 自定义Python应用健康检查
FROM python:3.11-slim
HEALTHCHECK --interval=15s --timeout=10s --start-period=40s --retries=3 \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/health')" || exit 1

HEALTHCHECK选项说明:

选项 说明 默认值
–interval 检查间隔 30s
–timeout 单次检查超时时间 30s
–start-period 启动宽限期(期间失败不计入重试次数) 0s
–retries 连续失败次数达到此值后标记为unhealthy 3

在docker run中指定健康检查:

# 覆盖镜像中的健康检查
docker run -d \
  --health-cmd "curl -f http://localhost:8080/health || exit 1" \
  --health-interval 10s \
  --health-timeout 5s \
  --health-retries 3 \
  --health-start-period 30s \
  --name my-app \
  my-image:latest

# 禁用健康检查
docker run -d --no-healthcheck --name my-app my-image:latest

8.2 健康检查状态(starting/healthy/unhealthy)

容器健康检查有三种状态:

状态 说明 颜色标识
starting 容器启动初始状态,处于启动宽限期 蓝色
healthy 健康检查通过 绿色
unhealthy 连续失败达到retries次数 红色
# 查看容器健康状态
docker inspect -f '{{.State.Health.Status}}' my-app
# 输出: healthy

# 在docker ps中显示健康状态
docker ps --format "table {{.Names}}\t{{.Status}}"
# NAMES     STATUS
# my-app    Up 5 minutes (healthy)
# my-db     Up 10 minutes (unhealthy)
# my-cache  Up 3 minutes (health: starting)

# 过滤健康状态
docker ps --filter "health=healthy"
docker ps --filter "health=unhealthy"
docker ps --filter "health=starting"

状态转换流程:

容器启动
   |
   v
starting (启动宽限期内)
   |
   +-- 检查成功 --> healthy
   |                 |
   |                 +-- 检查失败 --> 重试计数
   |                                   |
   |                                   +-- 达到retries --> unhealthy
   |                                   |
   |                                   +-- 未达到retries --> 继续检查
   |
   +-- 检查失败(宽限期内) --> 继续检查(不计数)

8.3 docker inspect查看健康状态

# 查看完整的健康检查信息
docker inspect -f '{{json .State.Health}}' my-app | python -m json.tool

# 输出示例:
# {
#   "Status": "healthy",
#   "FailingStreak": 0,
#   "Log": [
#     {
#       "Start": "2024-01-01T10:00:00.000000000Z",
#       "End": "2024-01-01T10:00:00.500000000Z",
#       "ExitCode": 0,
#       "Output": "HTTP/1.1 200 OK\n"
#     },
#     {
#       "Start": "2024-01-01T10:00:10.000000000Z",
#       "End": "2024-01-01T10:00:10.300000000Z",
#       "ExitCode": 0,
#       "Output": ""
#     }
#   ]
# }

# 查看最近一次检查结果
docker inspect -f '{{range .State.Health.Log}}{{.ExitCode}}: {{.Output}}{{end}}' my-app

# 查看连续失败次数
docker inspect -f '{{.State.Health.FailingStreak}}' my-app

# 查看健康检查命令
docker inspect -f '{{.Config.Healthcheck.Test}}' my-app

8.4 自定义健康检查脚本

对于复杂应用,可以编写专门的健康检查脚本:

#!/bin/bash
# healthcheck.sh - 复杂应用健康检查脚本

# 检查HTTP服务
check_http() {
    local url="http://localhost:8080/health"
    local response=$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 "$url")
    if [ "$response" = "200" ]; then
        return 0
    else
        echo "HTTP health check failed: $response"
        return 1
    fi
}

# 检查数据库连接
check_db() {
    if pg_isready -h localhost -p 5432 -q; then
        return 0
    else
        echo "Database connection failed"
        return 1
    fi
}

# 检查磁盘空间
check_disk() {
    local usage=$(df / | tail -1 | awk '{print $5}' | tr -d '%')
    if [ "$usage" -lt 90 ]; then
        return 0
    else
        echo "Disk usage too high: ${usage}%"
        return 1
    fi
}

# 检查内存
check_memory() {
    local available=$(free -m | awk '/Mem:/{print $7}')
    if [ "$available" -gt 100 ]; then
        return 0
    else
        echo "Memory too low: ${available}MB available"
        return 1
    fi
}

# 执行所有检查
main() {
    local failed=0
    
    check_http || failed=1
    check_db || failed=1
    check_disk || failed=1
    check_memory || failed=1
    
    if [ "$failed" -eq 0 ]; then
        echo "All health checks passed"
        exit 0
    else
        exit 1
    fi
}

main
# 在Dockerfile中使用自定义健康检查脚本
FROM python:3.11-slim
COPY healthcheck.sh /app/healthcheck.sh
RUN chmod +x /app/healthcheck.sh
HEALTHCHECK --interval=15s --timeout=10s --retries=3 \
  CMD /app/healthcheck.sh

8.5 健康检查与容器自动重启

Docker本身不会因为容器unhealthy而自动重启容器,但可以通过外部工具实现:

# 方法1: 使用脚本监控并重启unhealthy容器
#!/bin/bash
# monitor-health.sh - 监控容器健康状态并自动重启

while true; do
    # 查找unhealthy容器
    for container in $(docker ps --filter "health=unhealthy" -q); do
        name=$(docker inspect -f '{{.Name}}' "$container" | sed 's/\///')
        echo "[$(date)] 容器 $name 处于unhealthy状态,正在重启..."
        
        # 获取重启策略
        policy=$(docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' "$container")
        
        if [ "$policy" != "no" ]; then
            docker restart "$container"
            echo "[$(date)] 容器 $name 已重启"
        else
            echo "[$(date)] 容器 $name 重启策略为no,不自动重启"
        fi
    done
    
    sleep 30
done

# 方法2: 配合Swarm或Kubernetes自动处理unhealthy容器
# Docker Swarm: 自动重新调度unhealthy容器
# Kubernetes: liveness probe失败会重启Pod

# 方法3: 使用docker events监控
docker events --filter "event=health_status" --format "{{.Actor.Attributes.name}} {{.Actor.Attributes.healthStatus}}" | \
while read name status; do
    if [ "$status" = "unhealthy" ]; then
        echo "容器 $name 变为unhealthy,发送告警"
        # 发送告警通知(邮件/钉钉/Slack)
        # curl -X POST "https://api.webhook.com/alert" -d "container=$name&status=unhealthy"
    fi
done

8.6 健康检查实战: Web服务/数据库/缓存

# === Nginx Web服务器健康检查 ===
FROM nginx:1.25-alpine
# 安装curl用于健康检查
RUN apk add --no-cache curl
HEALTHCHECK --interval=10s --timeout=3s --retries=3 --start-period=5s \
  CMD curl -f http://localhost/ || exit 1

# === MySQL数据库健康检查 ===
FROM mysql:8.0
HEALTHCHECK --interval=10s --timeout=5s --retries=5 --start-period=30s \
  CMD mysqladmin ping -h 127.0.0.1 -u root -p"$$MYSQL_ROOT_PASSWORD" --silent || exit 1

# === Redis缓存健康检查 ===
FROM redis:7-alpine
HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
  CMD redis-cli ping | grep -q PONG || exit 1

# === PostgreSQL健康检查 ===
FROM postgres:16-alpine
HEALTHCHECK --interval=10s --timeout=5s --retries=5 --start-period=30s \
  CMD pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" || exit 1

# === Node.js应用健康检查 ===
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install
HEALTHCHECK --interval=15s --timeout=5s --retries=3 --start-period=10s \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["npm", "start"]

# === Spring Boot应用健康检查 ===
FROM openjdk:17-slim
COPY target/app.jar /app/app.jar
HEALTHCHECK --interval=15s --timeout=10s --retries=3 --start-period=40s \
  CMD curl -f http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

8.7 外部健康检查工具(HAProxy/Nginx)

除了Docker内置的健康检查,外部负载均衡器也可以进行健康检查:

# HAProxy健康检查配置
# haproxy.cfg:
"""
frontend web_front
    bind *:80
    default_backend web_servers

backend web_servers
    option httpchk GET /health
    http-check expect status 200
    server web1 172.17.0.2:80 check
    server web2 172.17.0.3:80 check
    server web3 172.17.0.4:80 check backup
"""

# Nginx健康检查(需要nginx-plus或第三方模块)
# nginx.conf:
"""
upstream web_servers {
    server 172.17.0.2:80 max_fails=3 fail_timeout=30s;
    server 172.17.0.3:80 max_fails=3 fail_timeout=30s;
}

server {
    listen 80;
    location / {
        proxy_pass http://web_servers;
    }
    location /health {
        access_log off;
        return 200 "healthy\n";
    }
}
"""
# 使用外部脚本进行综合健康检查
#!/bin/bash
# comprehensive-health-check.sh

SERVICES=(
    "web1:172.17.0.2:80"
    "web2:172.17.0.3:80"
    "db1:172.17.0.4:3306"
    "redis1:172.17.0.5:6379"
)

for service in "${SERVICES[@]}"; do
    name=$(echo "$service" | cut -d: -f1)
    host=$(echo "$service" | cut -d: -f2)
    port=$(echo "$service" | cut -d: -f3)
    
    # TCP连接检查
    if timeout 3 bash -c "echo > /dev/tcp/$host/$port" 2>/dev/null; then
        echo "[OK] $name ($host:$port) - TCP连接正常"
    else
        echo "[FAIL] $name ($host:$port) - TCP连接失败"
    fi
    
    # HTTP检查(如果是Web服务)
    if [ "$port" = "80" ] || [ "$port" = "8080" ] || [ "$port" = "443" ]; then
        http_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 "http://$host:$port/health" 2>/dev/null)
        if [ "$http_code" = "200" ]; then
            echo "[OK] $name ($host:$port) - HTTP健康检查通过"
        else
            echo "[FAIL] $name ($host:$port) - HTTP健康检查失败($http_code)"
        fi
    fi
done

第九章 容器批量管理与自动化

9.1 批量停止/删除容器

在生产环境中,经常需要对容器进行批量操作,以下是一些常用的批量管理命令和技巧。

批量停止容器:

# 停止所有运行中的容器
docker stop $(docker ps -q)

# 停止所有容器(包括暂停的)
docker stop $(docker ps -aq)

# 按名称模式停止容器
docker stop $(docker ps -q --filter "name=web")

# 按镜像停止容器
docker stop $(docker ps -q --filter "ancestor=nginx:latest")

# 按网络停止容器
docker stop $(docker ps -q --filter "network=my-net")

# 按标签停止容器
docker stop $(docker ps -q --filter "label=environment=staging")

# 停止所有处于unhealthy状态的容器
docker stop $(docker ps -q --filter "health=unhealthy")

# 优雅批量停止(给每个容器30秒超时)
docker stop -t 30 $(docker ps -q)

批量删除容器:

# 删除所有已停止的容器
docker rm $(docker ps -aq --filter "status=exited")

# 删除所有容器(包括运行中的,需要-f强制)
docker rm -f $(docker ps -aq)

# 按名称模式删除已停止的容器
docker rm $(docker ps -aq --filter "name=test-" --filter "status=exited")

# 删除指定退出码的容器
docker rm $(docker ps -aq --filter "exited=0")     # 正常退出的
docker rm $(docker ps -aq --filter "exited=137")    # 被OOM或SIGKILL的

# 删除超过一定时间的停止容器
docker container prune --filter "until=24h"    # 删除24小时前停止的
docker container prune --filter "until=7d"     # 删除7天前停止的

# 删除前先停止(安全做法)
docker stop $(docker ps -q --filter "name=temp-")
docker rm $(docker ps -aq --filter "name=temp-")

批量操作的安全实践:

#!/bin/bash
# 安全的批量删除脚本: 先显示再确认

# 收集要删除的容器
CONTAINERS=$(docker ps -aq --filter "status=exited" --filter "name=test-")

if [ -z "$CONTAINERS" ]; then
    echo "没有符合条件的容器"
    exit 0
fi

# 显示将要删除的容器
echo "以下容器将被删除:"
docker ps -a --filter "status=exited" --filter "name=test-" \
  --format "table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}"

# 确认
echo ""
read -p "确认删除以上容器? (y/N): " confirm
if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then
    docker rm $CONTAINERS
    echo "已删除 $(echo $CONTAINERS | wc -w) 个容器"
else
    echo "取消删除"
fi

9.2 docker container prune清理停止的容器

docker container prune命令专门用于清理已停止的容器,比手动docker rm更方便。

# 清理所有停止的容器
docker container prune

# 清理时不需要确认
docker container prune -f

# 带过滤器清理: 只清理24小时前停止的容器
docker container prune --filter "until=24h" -f

# 带过滤器清理: 只清理特定标签的容器
docker container prune --filter "label=environment=test" -f

# 查看会被清理的容器(不实际执行)
docker container prune --filter "until=24h" --dry-run 2>/dev/null || \
docker ps -a --filter "status=exited" --filter "until=24h"

prune命令家族:

# 清理停止的容器
docker container prune

# 清理悬空镜像(dangling images)
docker image prune

# 清理所有未使用的镜像
docker image prune -a

# 清理未使用的网络
docker network prune

# 清理未使用的卷
docker volume prune

# 清理未使用的构建缓存
docker builder prune

# 一键清理所有未使用的资源(容器、镜像、网络、卷)
docker system prune

# 一键清理所有未使用的资源(包括未使用的镜像)
docker system prune -a

# 带过滤器清理
docker system prune --filter "until=24h" -f

# 查看清理前会释放多少空间
docker system df
# TYPE      TOTAL  ACTIVE  SIZE     RECLAIMABLE
# Images    15     5       3.2GB    2.1GB (65%)
# Containers  12   5       150MB    80MB (53%)
# Local Volumes  8  4     2.5GB    1.2GB (48%)

9.3 docker system prune系统级清理

docker system prune是最全面的清理命令,可以一次性清理所有未使用的Docker资源。

# 基本清理(容器、网络、悬空镜像、构建缓存)
docker system prune
# 输出:
# WARNING! This will remove:
#   - all stopped containers
#   - all networks not used by at least one container
#   - all dangling images
#   - all dangling build cache
# Are you sure you want to continue? [y/N]

# 强制清理(不需要确认)
docker system prune -f

# 全面清理(包括所有未使用的镜像)
docker system prune -a
# WARNING! This will remove:
#   - all stopped containers
#   - all networks not used by at least one container
#   - all images without at least one container associated to them
#   - all build cache

# 全面清理(包括卷)
docker system prune -a --volumes
# WARNING! This will remove:
#   - all stopped containers
#   - all networks not used by at least one container
#   - all volumes not used by at least one container
#   - all images without at least one container associated to them
#   - all build cache

# 带时间过滤器
docker system prune -a --filter "until=48h" -f

定期清理脚本:

#!/bin/bash
# docker-cleanup.sh - Docker定期清理脚本
# 建议通过cron定期执行

LOG_FILE="/var/log/docker-cleanup.log"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

log "=== Docker清理开始 ==="

# 1. 显示当前磁盘使用情况
log "清理前磁盘使用:"
docker system df | tee -a "$LOG_FILE"

# 2. 删除7天前停止的容器
log "删除7天前停止的容器..."
docker container prune --filter "until=168h" -f 2>&1 | tee -a "$LOG_FILE"

# 3. 删除悬空镜像
log "删除悬空镜像..."
docker image prune -f 2>&1 | tee -a "$LOG_FILE"

# 4. 删除30天前未使用的镜像
log "删除30天前未使用的镜像..."
docker image prune -a --filter "until=720h" -f 2>&1 | tee -a "$LOG_FILE"

# 5. 删除未使用的网络
log "删除未使用的网络..."
docker network prune -f 2>&1 | tee -a "$LOG_FILE"

# 6. 删除未使用的构建缓存
log "删除未使用的构建缓存..."
docker builder prune -f 2>&1 | tee -a "$LOG_FILE"

# 注意: 不自动清理卷(可能包含重要数据)
# log "删除未使用的卷..."
# docker volume prune -f 2>&1 | tee -a "$LOG_FILE"

# 7. 显示清理后磁盘使用情况
log "清理后磁盘使用:"
docker system df | tee -a "$LOG_FILE"

log "=== Docker清理完成 ==="

# Cron配置示例(每天凌晨3点执行):
# 0 3 * * * /opt/scripts/docker-cleanup.sh

9.4 容器过滤与批量操作技巧

# === 按各种条件过滤容器 ===

# 1. 按状态过滤
docker ps -a --filter "status=running"
docker ps -a --filter "status=exited"
docker ps -a --filter "status=paused"

# 2. 按名称过滤(支持正则)
docker ps -a --filter "name=^web-"
docker ps -a --filter "name=web-.*-01$"

# 3. 按镜像过滤
docker ps -a --filter "ancestor=nginx:1.25"
docker ps -a --filter "ancestor=nginx"

# 4. 按标签过滤
docker ps -a --filter "label=environment=production"
docker ps -a --filter "label=app=web"
docker ps -a --filter "label=environment=production" --filter "label=app=web"

# 5. 按端口过滤
docker ps -a --filter "publish=80"
docker ps -a --filter "expose=3306"

# 6. 按健康状态过滤
docker ps -a --filter "health=healthy"
docker ps -a --filter "health=unhealthy"

# 7. 按退出码过滤
docker ps -a --filter "exited=0"
docker ps -a --filter "exited=1"
docker ps -a --filter "exited=137"  # OOM或SIGKILL

# 8. 按创建时间过滤
docker ps -a --filter "since=2024-01-01T00:00:00"
docker ps -a --filter "before=my-old-container"

# === 组合批量操作 ===

# 停止所有production环境的web容器
docker stop $(docker ps -q --filter "label=environment=production" --filter "name=web")

# 删除所有exited状态且名字以test-开头的容器
docker rm $(docker ps -aq --filter "status=exited" --filter "name=test-")

# 重启所有unhealthy容器
docker restart $(docker ps -q --filter "health=unhealthy")

# 获取所有运行中容器的IP地址
docker ps -q | xargs -I {} docker inspect -f '{{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' {}

# 对所有容器执行同一命令
docker ps -q | xargs -I {} docker exec {} sh -c "echo 'hello from $(hostname)'"

# 导出所有运行中容器的日志
for container in $(docker ps -q); do
    name=$(docker inspect -f '{{.Name}}' "$container" | sed 's/\///')
    docker logs "$container" > "/tmp/${name}-logs.txt" 2>&1
done

9.5 编写Shell脚本管理容器

#!/bin/bash
# docker-manager.sh - Docker容器综合管理脚本

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# 显示菜单
show_menu() {
    echo -e "${BLUE}==============================${NC}"
    echo -e "${BLUE}   Docker容器管理工具   ${NC}"
    echo -e "${BLUE}==============================${NC}"
    echo "1. 查看所有容器"
    echo "2. 查看运行中容器"
    echo "3. 启动容器"
    echo "4. 停止容器"
    echo "5. 重启容器"
    echo "6. 删除容器"
    echo "7. 查看容器日志"
    echo "8. 进入容器Shell"
    echo "9. 查看资源使用"
    echo "10. 停止所有容器"
    echo "11. 删除所有停止的容器"
    echo "12. 系统清理"
    echo "13. 查看磁盘使用"
    echo "0. 退出"
    echo -e "${BLUE}==============================${NC}"
}

# 查看所有容器
list_all() {
    docker ps -a --format "table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"
}

# 查看运行中容器
list_running() {
    docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"
}

# 启动容器
start_container() {
    list_all
    read -p "输入容器名称或ID: " container
    docker start "$container" && echo -e "${GREEN}容器已启动${NC}" || echo -e "${RED}启动失败${NC}"
}

# 停止容器
stop_container() {
    list_running
    read -p "输入容器名称或ID: " container
    docker stop "$container" && echo -e "${GREEN}容器已停止${NC}" || echo -e "${RED}停止失败${NC}"
}

# 重启容器
restart_container() {
    list_running
    read -p "输入容器名称或ID: " container
    docker restart "$container" && echo -e "${GREEN}容器已重启${NC}" || echo -e "${RED}重启失败${NC}"
}

# 删除容器
remove_container() {
    list_all
    read -p "输入容器名称或ID: " container
    read -p "确认删除容器 $container? (y/N): " confirm
    if [ "$confirm" = "y" ]; then
        docker rm -f "$container" && echo -e "${GREEN}容器已删除${NC}" || echo -e "${RED}删除失败${NC}"
    fi
}

# 查看日志
view_logs() {
    list_running
    read -p "输入容器名称或ID: " container
    read -p "显示行数(默认50): " lines
    lines=${lines:-50}
    docker logs --tail "$lines" -f "$container"
}

# 进入Shell
exec_shell() {
    list_running
    read -p "输入容器名称或ID: " container
    docker exec -it "$container" /bin/sh 2>/dev/null || docker exec -it "$container" /bin/bash
}

# 查看资源使用
view_stats() {
    docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}"
}

# 停止所有容器
stop_all() {
    read -p "确认停止所有运行中的容器? (y/N): " confirm
    if [ "$confirm" = "y" ]; then
        docker stop $(docker ps -q) && echo -e "${GREEN}所有容器已停止${NC}"
    fi
}

# 删除停止的容器
remove_stopped() {
    read -p "确认删除所有停止的容器? (y/N): " confirm
    if [ "$confirm" = "y" ]; then
        docker container prune -f
        echo -e "${GREEN}已清理停止的容器${NC}"
    fi
}

# 系统清理
system_prune() {
    read -p "确认执行系统清理? 这将删除未使用的资源! (y/N): " confirm
    if [ "$confirm" = "y" ]; then
        docker system prune -f
        echo -e "${GREEN}系统清理完成${NC}"
    fi
}

# 查看磁盘使用
view_disk() {
    docker system df -v
}

# 主循环
while true; do
    show_menu
    read -p "请选择操作: " choice
    case $choice in
        1) list_all ;;
        2) list_running ;;
        3) start_container ;;
        4) stop_container ;;
        5) restart_container ;;
        6) remove_container ;;
        7) view_logs ;;
        8) exec_shell ;;
        9) view_stats ;;
        10) stop_all ;;
        11) remove_stopped ;;
        12) system_prune ;;
        13) view_disk ;;
        0) echo "再见!"; exit 0 ;;
        *) echo -e "${RED}无效选择${NC}" ;;
    esac
    echo ""
    read -p "按回车键继续..." dummy
done

9.6 容器定时任务(cron)

在容器中运行定时任务是常见的需求,有以下几种方式:

方式1: 在容器内使用cron

# Dockerfile: 在容器内安装cron
FROM alpine:latest

# 安装cron
RUN apk add --no-cache dcron

# 复制cron任务文件
COPY crontab /etc/crontabs/root

# 复制执行脚本
COPY backup.sh /app/backup.sh
RUN chmod +x /app/backup.sh

# 启动cron(前台运行)
CMD ["crond", "-f", "-l", "8"]
# crontab文件内容
# 每天凌晨2点执行备份
0 2 * * * /app/backup.sh
# 每小时检查服务健康
0 * * * * /app/healthcheck.sh
# 每周日凌晨3点清理日志
0 3 * * 0 /app/cleanup.sh

方式2: 使用宿主机cron管理Docker命令

# 在宿主机的crontab中配置
# crontab -e

# 每天凌晨3点备份容器数据
0 3 * * * docker exec db-container /app/backup.sh

# 每小时检查容器健康状态
0 * * * * docker ps --filter "health=unhealthy" -q | xargs -r docker restart

# 每周清理停止的容器
0 4 * * 0 docker container prune -f

# 每5分钟检查容器状态并发送告警
*/5 * * * * /opt/scripts/check-containers.sh

方式3: 使用专门的定时任务容器

# 使用ofelia定时任务管理器
docker run -d \
  --name scheduler \
  -v /var/run/docker.sock:/var/run/docker.sock:ro \
  -v /opt/scheduler/config.ini:/etc/ofelia.ini \
  mcuadros/ofelia:latest

# config.ini:
"""
[job-exec "backup-db"]
schedule = 0 3 * * *
container = db-container
command = /app/backup.sh

[job-run "cleanup"]
schedule = 0 4 * * 0
image = alpine:latest
command = sh -c "docker container prune -f"
"""

9.7 容器优雅停止与超时处理

容器的优雅停止是生产环境中的重要课题,需要确保应用在停止前完成正在处理的请求。

#!/bin/bash
# graceful-stop.sh - 容器优雅停止脚本

CONTAINER_NAME="$1"
MAX_WAIT="${2:-60}"  # 默认最大等待60秒

if [ -z "$CONTAINER_NAME" ]; then
    echo "用法: $0 <容器名> [最大等待秒数]"
    exit 1
fi

# 检查容器是否存在
if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
    echo "容器 $CONTAINER_NAME 不存在"
    exit 1
fi

# 检查容器是否运行
STATUS=$(docker inspect -f '{{.State.Status}}' "$CONTAINER_NAME")
if [ "$STATUS" != "running" ]; then
    echo "容器 $CONTAINER_NAME 未在运行(状态: $STATUS)"
    exit 0
fi

echo "正在优雅停止容器 $CONTAINER_NAME (最大等待 ${MAX_WAIT}秒)..."

# 发送停止命令
docker stop -t "$MAX_WAIT" "$CONTAINER_NAME" &
STOP_PID=$!

# 等待并监控
WAITED=0
while [ $WAITED -lt $MAX_WAIT ]; do
    sleep 1
    WAITED=$((WAITED + 1))
    
    STATUS=$(docker inspect -f '{{.State.Status}}' "$CONTAINER_NAME" 2>/dev/null)
    if [ "$STATUS" = "exited" ]; then
        echo "容器已优雅停止(等待了 ${WAITED}秒)"
        exit 0
    fi
    
    # 显示进度
    if [ $((WAITED % 10)) -eq 0 ]; then
        echo "已等待 ${WAITED}秒,容器仍在停止中..."
    fi
done

# 超时后强制停止
echo "等待超时,强制停止..."
docker kill "$CONTAINER_NAME"

# 验证
STATUS=$(docker inspect -f '{{.State.Status}}' "$CONTAINER_NAME")
if [ "$STATUS" = "exited" ]; then
    EXIT_CODE=$(docker inspect -f '{{.State.ExitCode}}' "$CONTAINER_NAME")
    echo "容器已停止(退出码: $EXIT_CODE)"
    
    # 检查是否被OOM杀死
    OOM=$(docker inspect -f '{{.State.OOMKilled}}' "$CONTAINER_NAME")
    if [ "$OOM" = "true" ]; then
        echo "警告: 容器被OOM Killer杀死!"
    fi
fi

9.8 容器自动重启与服务可用性保障

#!/bin/bash
# container-watchdog.sh - 容器看门狗脚本
# 监控容器状态,异常时自动重启并告警

# 配置
WATCH_CONTAINERS=("web-server" "db-server" "redis-server" "api-server")
ALERT_WEBHOOK="https://hooks.slack.com/services/xxx"
CHECK_INTERVAL=30
MAX_RESTARTS=5
RESTART_WINDOW=300  # 5分钟窗口

# 告警函数
send_alert() {
    local message="$1"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] ALERT: $message"
    # 发送Slack告警
    # curl -X POST "$ALERT_WEBHOOK" -H 'Content-type: application/json' \
    #   --data "{\"text\":\"$message\"}"
}

# 检查并重启容器
check_container() {
    local name="$1"
    local status=$(docker inspect -f '{{.State.Status}}' "$name" 2>/dev/null)
    
    if [ -z "$status" ]; then
        send_alert "容器 $name 不存在!"
        return
    fi
    
    if [ "$status" != "running" ]; then
        send_alert "容器 $name 状态异常: $status"
        
        # 检查重启次数
        restart_count_file="/tmp/${name}_restart_count"
        restart_time_file="/tmp/${name}_restart_time"
        
        current_time=$(date +%s)
        last_restart_time=$(cat "$restart_time_file" 2>/dev/null || echo 0)
        restart_count=$(cat "$restart_count_file" 2>/dev/null || echo 0)
        
        # 重置计数器(如果超出窗口)
        if [ $((current_time - last_restart_time)) -gt $RESTART_WINDOW ]; then
            restart_count=0
        fi
        
        if [ "$restart_count" -lt "$MAX_RESTARTS" ]; then
            echo "正在重启容器 $name (第 $((restart_count + 1)) 次)..."
            docker start "$name"
            
            echo $((restart_count + 1)) > "$restart_count_file"
            echo "$current_time" > "$restart_time_file"
            
            # 等待启动
            sleep 5
            
            new_status=$(docker inspect -f '{{.State.Status}}' "$name")
            if [ "$new_status" = "running" ]; then
                echo "容器 $name 重启成功"
                send_alert "容器 $name 已自动重启成功"
            else
                send_alert "容器 $name 重启失败! 当前状态: $new_status"
            fi
        else
            send_alert "容器 $name${RESTART_WINDOW}秒内重启次数已达上限($MAX_RESTARTS),不再自动重启!"
        fi
    fi
    
    # 检查健康状态
    health=$(docker inspect -f '{{.State.Health.Status}}' "$name" 2>/dev/null)
    if [ "$health" = "unhealthy" ]; then
        send_alert "容器 $name 健康检查失败(unhealthy)"
    fi
}

# 主循环
echo "容器看门狗已启动,监控间隔: ${CHECK_INTERVAL}秒"
echo "监控容器: ${WATCH_CONTAINERS[*]}"

while true; do
    for container in "${WATCH_CONTAINERS[@]}"; do
        check_container "$container"
    done
    sleep "$CHECK_INTERVAL"
done

第十章 容器管理最佳实践与故障排查

10.1 容器生命周期管理最佳实践清单

容器创建最佳实践:

  1. 始终为容器指定有意义的名称(--name)
  2. 使用--restart unless-stopped作为默认重启策略
  3. 配置资源限制(CPU、内存)防止资源耗尽
  4. 配置日志轮转防止磁盘填满
  5. 使用--init确保信号正确处理和僵尸进程回收
  6. 设置时区环境变量(-e TZ=Asia/Shanghai)
  7. 使用数据卷持久化重要数据
  8. 配置健康检查(HEALTHCHECK)
  9. 遵循最小权限原则(--cap-drop ALL --cap-add <需要的>)
# 生产环境容器启动模板
docker run -d \
  --name "${APP_NAME}" \
  --hostname "${APP_NAME}" \
  --restart unless-stopped \
  --init \
  \
  --cpus 2.0 \
  -m 2g \
  --memory-swap 2g \
  --pids-limit 200 \
  \
  --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=5 \
  \
  --health-cmd "curl -f http://localhost:8080/health || exit 1" \
  --health-interval 10s \
  --health-timeout 5s \
  --health-retries 3 \
  --health-start-period 30s \
  \
  --cap-drop ALL \
  --cap-add CHOWN \
  --cap-add SETGID \
  --cap-add SETUID \
  --security-opt no-new-privileges \
  \
  -e TZ=Asia/Shanghai \
  -e APP_ENV=production \
  --env-file /opt/app/production.env \
  \
  -v app-data:/app/data \
  -v app-logs:/app/logs \
  \
  --network app-network \
  -p 8080:8080 \
  \
  my-app:latest

10.2 容器无法启动的排查方法

# === 容器无法启动排查流程 ===

# 1. 查看容器状态和错误信息
docker ps -a --filter "name=my-app"
docker inspect -f '{{.State.Error}}' my-app

# 2. 查看容器日志
docker logs my-app
docker logs --tail 50 my-app

# 3. 检查镜像是否存在
docker images | grep my-image

# 4. 检查端口是否被占用
docker port my-app
netstat -tlnp | grep 8080
lsof -i :8080

# 5. 检查容器配置
docker inspect my-app | python -m json.tool

# 6. 检查资源限制是否合理
docker inspect -f 'Memory: {{.HostConfig.Memory}} CPU: {{.HostConfig.NanoCpus}}' my-app

# 7. 检查挂载路径是否存在
docker inspect -f '{{json .Mounts}}' my-app | python -m json.tool
ls -la /host/path  # 检查宿主机路径

# 8. 检查网络配置
docker network ls
docker network inspect bridge

# 9. 尝试前台运行查看错误
docker run -it --rm my-image:latest

# 10. 检查Docker守护进程状态
systemctl status docker
docker info

# 常见启动失败原因:
# - 端口被占用: "bind: address already in use"
# - 镜像不存在: "Unable to find image"
# - 挂载路径不存在: "no such file or directory"
# - 命令不存在: "exec: \"xxx\": executable file not found"
# - 权限不足: "permission denied"
# - 内存不足: "Cannot start container: ... memory"
# 排查脚本
#!/bin/bash
# diagnose-container.sh - 容器诊断脚本

CONTAINER_NAME="$1"
if [ -z "$CONTAINER_NAME" ]; then
    echo "用法: $0 <容器名>"
    exit 1
fi

echo "=== 容器基本信息 ==="
docker inspect -f '
名称: {{.Name}}
镜像: {{.Config.Image}}
状态: {{.State.Status}}
退出码: {{.State.ExitCode}}
错误: {{.State.Error}}
启动时间: {{.State.StartedAt}}
停止时间: {{.State.FinishedAt}}
OOM: {{.State.OOMKilled}}
重启次数: {{.RestartCount}}
' "$CONTAINER_NAME"

echo ""
echo "=== 容器日志(最后20行) ==="
docker logs --tail 20 "$CONTAINER_NAME" 2>&1

echo ""
echo "=== 资源限制 ==="
docker inspect -f '
CPU: {{.HostConfig.NanoCpus}}
内存: {{.HostConfig.Memory}}
PID限制: {{.HostConfig.PidsLimit}}
' "$CONTAINER_NAME"

echo ""
echo "=== 网络配置 ==="
docker inspect -f '
IP: {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}
端口: {{json .NetworkSettings.Ports}}
网络模式: {{.HostConfig.NetworkMode}}
' "$CONTAINER_NAME"

echo ""
echo "=== 挂载配置 ==="
docker inspect -f '{{range .Mounts}}
{{.Type}}: {{.Source}} -> {{.Destination}} (RW: {{.RW}})
{{end}}' "$CONTAINER_NAME"

echo ""
echo "=== 系统资源检查 ==="
echo "Docker磁盘使用:"
docker system df
echo ""
echo "宿主机内存:"
free -h
echo ""
echo "宿主机磁盘:"
df -h /var/lib/docker

10.3 容器异常退出排查

# === 容器异常退出排查 ===

# 常见退出码含义:
# 0   - 正常退出
# 1   - 应用错误
# 125 - docker run失败
# 126 - 命令不可执行
# 127 - 命令未找到
# 137 - 被SIGKILL终止(OOM或docker kill)
# 139 - 段错误(SIGSEGV)
# 143 - 被SIGTERM终止(docker stop)

# 1. 查看退出码
docker inspect -f '{{.State.ExitCode}}' my-app

# 2. 检查是否OOM
docker inspect -f '{{.State.OOMKilled}}' my-app
# 如果为true,说明内存不足被系统杀死

# 3. 查看系统日志中的OOM记录
dmesg | grep -i "out of memory"
dmesg | grep -i "killed process"
journalctl -k --since "1 hour ago" | grep -i oom

# 4. 查看容器退出前的日志
docker logs --tail 100 my-app

# 5. 检查应用日志(如果挂载了日志卷)
docker inspect -f '{{json .Mounts}}' my-app

# OOM问题排查流程:
echo "=== OOM排查 ==="
echo "1. 检查容器内存限制:"
docker inspect -f '{{.HostConfig.Memory}}' my-app

echo "2. 检查容器内存使用历史:"
docker stats --no-stream my-app

echo "3. 检查系统内存:"
free -h

echo "4. 检查其他容器的内存使用:"
docker stats --no-stream

echo "5. 解决方案:"
echo "   - 增加容器内存限制: docker update -m 2g my-app"
echo "   - 优化应用内存使用"
echo "   - 添加--oom-kill-disable保护关键容器"

10.4 容器资源耗尽排查

#!/bin/bash
# resource-check.sh - 容器资源耗尽排查脚本

echo "=== 系统资源概览 ==="
echo "CPU使用率: $(top -bn1 | grep "Cpu(s)" | awk '{print $2}')%"
echo "内存使用: $(free -h | grep Mem | awk '{print $3 "/" $2}')"
echo "磁盘使用: $(df -h / | tail -1 | awk '{print $3 "/" $2 " (" $5 ")"}')"
echo ""

echo "=== Docker资源使用 ==="
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.PIDs}}"
echo ""

echo "=== 内存使用最高的容器 ==="
docker stats --no-stream --format "{{.Name}}\t{{.MemUsage}}" | sort -t$'\t' -k2 -rh | head -5
echo ""

echo "=== CPU使用最高的容器 ==="
docker stats --no-stream --format "{{.Name}}\t{{.CPUPerc}}" | sort -t$'\t' -k2 -rh | head -5
echo ""

echo "=== 容器可写层大小 ==="
docker ps -s --format "table {{.Names}}\t{{.Size}}"
echo ""

echo "=== Docker磁盘使用详情 ==="
docker system df -v
echo ""

echo "=== 宿主机磁盘IO ==="
iostat -x 1 3 2>/dev/null || echo "iostat未安装"
echo ""

echo "=== 网络连接数 ==="
echo "容器网络连接:"
for c in $(docker ps -q); do
    name=$(docker inspect -f '{{.Name}}' "$c" | sed 's/\///')
    pid=$(docker inspect -f '{{.State.Pid}}' "$c")
    if [ "$pid" -gt 0 ]; then
        conns=$(nsenter -t "$pid" -n ss -s 2>/dev/null | wc -l)
        echo "  $name: $conns 连接"
    fi
done

10.5 容器僵尸进程处理

容器中的僵尸进程(Zombie Process)是父进程未正确回收的子进程,积累过多会影响系统性能。

# 检查容器内僵尸进程
docker exec my-app ps aux | awk '{if($8 ~ /Z/) print}'
# 或
docker top my-app | awk '$8 ~ /Z/'

# 查看容器内进程状态统计
docker exec my-app ps aux | awk '{print $8}' | sort | uniq -c | sort -rn
# 输出示例:
#  15 S   (睡眠状态)
#   3 R   (运行状态)
#   2 Z   (僵尸状态) <-- 问题!

# 解决方案1: 使用--init标志(推荐)
docker run -d --init --name my-app my-image:latest
# --init会在容器内运行tini作为PID 1,自动回收僵尸进程

# 解决方案2: 在Dockerfile中使用tini/dumb-init
# Dockerfile:
# FROM alpine:latest
# RUN apk add --no-cache tini
# ENTRYPOINT ["/sbin/tini", "--"]
# CMD ["my-app"]

# 解决方案3: 修改应用代码,正确处理子进程退出
# 在应用中调用waitpid()或注册SIGCHLD信号处理函数

# 解决方案4: 在容器内手动清理(临时方案)
docker exec my-app sh -c "kill -9 $(docker exec my-app ps aux | awk '$8 ~ /Z/{print $2}')"
# 注意: 僵尸进程无法被直接杀死,需要杀死其父进程

10.6 容器逃逸风险防范

容器逃逸是指攻击者从容器内部获取宿主机权限,是Docker安全的重要威胁。

容器安全加固清单:

# 1. 不要使用--privileged
# 错误: docker run --privileged my-image
# 正确: 只授予必要的capabilities
docker run -d --cap-drop ALL --cap-add NET_BIND_SERVICE my-image

# 2. 使用只读文件系统
docker run -d --read-only --tmpfs /tmp my-image

# 3. 禁止容器获取新权限
docker run -d --security-opt no-new-privileges my-image

# 4. 限制capabilities
docker run -d \
  --cap-drop ALL \
  --cap-add CHOWN \
  --cap-add SETGID \
  --cap-add SETUID \
  --cap-add NET_BIND_SERVICE \
  my-image

# 5. 使用用户命名空间隔离
# 在daemon.json中配置:
# {
#   "userns-remap": "default"
# }
docker run -d --userns=host my-image  # 仅在需要时禁用

# 6. 限制资源防止DoS攻击
docker run -d \
  --cpus 1.0 \
  -m 512m \
  --pids-limit 100 \
  --ulimit nofile=1024:1024 \
  --ulimit nproc=100:100 \
  my-image

# 7. 使用seccomp配置文件限制系统调用
docker run -d --security-opt seccomp=/path/to/seccomp.json my-image

# 8. 不要挂载Docker socket(极其危险)
# 错误: docker run -v /var/run/docker.sock:/var/run/docker.sock my-image

# 9. 使用AppArmor或SELinux
docker run -d --security-opt apparmor=docker-default my-image

# 10. 定期扫描镜像漏洞
docker scan my-image:latest
trivy image my-image:latest

10.7 生产环境容器运维SOP

标准操作流程(SOP):

#!/bin/bash
# ==========================================
# 生产环境容器运维SOP
# ==========================================

# === 日常巡检(每日执行) ===
daily_inspection() {
    echo "========== 日常巡检 $(date) =========="
    
    # 1. 检查所有容器状态
    echo "--- 容器状态 ---"
    docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
    
    # 2. 检查unhealthy容器
    echo ""
    echo "--- 健康检查异常 ---"
    unhealthy=$(docker ps --filter "health=unhealthy" -q)
    if [ -n "$unhealthy" ]; then
        echo "发现unhealthy容器:"
        docker ps --filter "health=unhealthy" --format "{{.Names}}"
        # 发送告警
    else
        echo "所有容器健康状态正常"
    fi
    
    # 3. 检查资源使用
    echo ""
    echo "--- 资源使用TOP5 ---"
    docker stats --no-stream --format "{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}" | \
      sort -t$'\t' -k2 -rh | head -5
    
    # 4. 检查磁盘空间
    echo ""
    echo "--- 磁盘空间 ---"
    df -h /var/lib/docker
    docker system df
    
    # 5. 检查异常退出的容器
    echo ""
    echo "--- 最近异常退出 ---"
    docker ps -a --filter "status=exited" --filter "exited=137" \
      --format "{{.Names}}\t{{.Status}}" | head -5
    
    # 6. 检查容器重启次数
    echo ""
    echo "--- 重启次数排行 ---"
    for c in $(docker ps -q); do
        name=$(docker inspect -f '{{.Name}}' "$c" | sed 's/\///')
        count=$(docker inspect -f '{{.RestartCount}}' "$c")
        echo "$count $name"
    done | sort -rn | head -5
}

# === 容器发布流程 ===
deploy_container() {
    local app_name="$1"
    local image="$2"
    local new_container="${app_name}-new"
    local old_container="${app_name}-old"
    
    echo "========== 发布流程 =========="
    
    # 1. 拉取新镜像
    echo "1. 拉取新镜像..."
    docker pull "$image"
    
    # 2. 启动新容器(使用临时名称)
    echo "2. 启动新容器..."
    docker run -d --name "$new_container" \
      --network app-network \
      -p 8081:8080 \
      "$image"
    
    # 3. 等待健康检查通过
    echo "3. 等待健康检查..."
    for i in $(seq 1 30); do
        health=$(docker inspect -f '{{.State.Health.Status}}' "$new_container" 2>/dev/null)
        if [ "$health" = "healthy" ]; then
            echo "   新容器健康检查通过"
            break
        fi
        sleep 2
        if [ $i -eq 30 ]; then
            echo "   健康检查超时,回滚!"
            docker rm -f "$new_container"
            return 1
        fi
    done
    
    # 4. 重命名旧容器
    echo "4. 重命名旧容器..."
    docker rename "$app_name" "$old_container" 2>/dev/null
    
    # 5. 重命名新容器
    echo "5. 切换容器..."
    docker rename "$new_container" "$app_name"
    
    # 6. 停止旧容器
    echo "6. 停止旧容器..."
    docker stop "$old_container" 2>/dev/null
    
    # 7. 验证
    echo "7. 验证..."
    docker ps --filter "name=$app_name"
    
    echo "发布完成!"
    echo "如需回滚,执行: docker rename $old_container $app_name && docker start $app_name"
}

# === 容器回滚流程 ===
rollback_container() {
    local app_name="$1"
    local old_container="${app_name}-old"
    
    echo "========== 回滚流程 =========="
    
    # 1. 停止当前容器
    echo "1. 停止当前容器..."
    docker stop "$app_name"
    
    # 2. 重命名当前容器
    echo "2. 重命名当前容器..."
    docker rename "$app_name" "${app_name}-failed"
    
    # 3. 重命名旧容器
    echo "3. 恢复旧容器..."
    docker rename "$old_container" "$app_name"
    
    # 4. 启动旧容器
    echo "4. 启动旧容器..."
    docker start "$app_name"
    
    # 5. 验证
    echo "5. 验证..."
    docker ps --filter "name=$app_name"
    
    echo "回滚完成!"
}

# === 紧急故障处理 ===
emergency_response() {
    echo "========== 紧急故障处理 =========="
    
    # 1. 收集诊断信息
    echo "收集诊断信息..."
    mkdir -p /tmp/docker-diagnosis-$(date +%Y%m%d%H%M%S)
    DIAG_DIR="/tmp/docker-diagnosis-$(date +%Y%m%d%H%M%S)"
    
    docker ps -a > "$DIAG_DIR/containers.txt"
    docker system df > "$DIAG_DIR/disk-usage.txt"
    docker info > "$DIAG_DIR/docker-info.txt"
    docker stats --no-stream > "$DIAG_DIR/stats.txt"
    
    for c in $(docker ps -q); do
        name=$(docker inspect -f '{{.Name}}' "$c" | sed 's/\///')
        docker inspect "$c" > "$DIAG_DIR/inspect-${name}.json"
        docker logs --tail 100 "$c" > "$DIAG_DIR/logs-${name}.txt" 2>&1
    done
    
    # 2. 检查系统日志
    dmesg | tail -100 > "$DIAG_DIR/dmesg.txt"
    journalctl -u docker --since "1 hour ago" > "$DIAG_DIR/docker-journal.txt"
    
    echo "诊断信息已保存到 $DIAG_DIR"
    
    # 3. 如果是磁盘满导致的问题
    disk_usage=$(df /var/lib/docker | tail -1 | awk '{print $5}' | tr -d '%')
    if [ "$disk_usage" -gt 90 ]; then
        echo "磁盘使用率过高(${disk_usage}%),执行紧急清理..."
        docker container prune -f
        docker image prune -f
        docker builder prune -f
    fi
    
    # 4. 如果是内存不足
    mem_available=$(free -m | awk '/Mem:/{print $7}')
    if [ "$mem_available" -lt 100 ]; then
        echo "可用内存不足(${mem_available}MB),停止低优先级容器..."
        docker stop $(docker ps -q --filter "label=priority=low")
    fi
}

# 执行日常巡检
daily_inspection

10.8 本章总结与下一期预告

全文总结:

本文全面深入地讲解了Docker容器生命周期管理的所有知识,从理论到实践,从基础命令到高级运维,涵盖了以下核心内容:

  1. 容器生命周期概述: 详细介绍了容器的五种状态(created/running/paused/exited/dead)及其状态转换规则,深入讲解了容器与进程的关系、命名空间隔离机制以及生命周期中的资源变化。

  2. 创建与启动容器: 系统讲解了docker createdocker run命令的所有参数,包括50+个参数的详细说明,并提供了生产级容器启动的完整示例。

  3. 容器运行管理: 深入讲解了start/stop/restart/pause/unpause/exec/attach等核心命令,详细对比了exec与attach的区别,并深入剖析了信号处理机制(SIGTERM/SIGKILL)和优雅停止的实现。

  4. 容器查看与信息: 全面介绍了docker ps/inspect/logs/events/port/diff/history等查看命令,包括Go模板格式化输出、过滤器使用和实用查看技巧汇总。

  5. 容器资源限制: 深入讲解了CPU/内存/IO/PID限制的配置和原理,剖析了cgroups底层机制和OOM Killer工作原理,并提供了动态资源调整的自动化脚本。

  6. 容器与宿主机交互: 详解了文件复制(cp)、容器导出导入(export/import)、镜像提交(commit)、端口映射及底层iptables原理,以及容器间文件共享和时区配置。

  7. 容器日志管理: 全面介绍了Docker日志驱动体系,重点讲解了json-file日志轮转配置、结构化日志输出、ELK/EFK日志聚合方案以及日志管理最佳实践。

  8. 容器健康检查: 详解了HEALTHCHECK指令的配置和使用,包括健康检查状态转换、自定义检查脚本编写、与容器自动重启的配合以及外部健康检查工具。

  9. 容器批量管理与自动化: 提供了大量批量管理命令和技巧,包括prune清理、Shell管理脚本、定时任务配置、优雅停止处理和容器看门狗实现。

  10. 最佳实践与故障排查: 总结了容器管理的最佳实践清单,提供了容器无法启动、异常退出、资源耗尽、僵尸进程、容器逃逸等常见问题的排查方法,以及生产环境运维SOP。

更多推荐