六 Docker 数据卷管理及优化

Docker 数据卷是可供容器使用的特殊目录,它绕过容器 UnionFS 分层文件系统,通过 Linux mount 系统调用直接将数据存储在宿主机磁盘,核心实现三大核心能力:

  • 数据持久化:即使容器被删除 / 重建,数据卷中的数据仍然存在,不会丢失
  • 数据共享:多个容器可同时挂载同一个数据卷,实现数据的共享与交互
  • 生命周期独立:数据卷的生命周期完全独立于容器,不受容器启动、停止、删除的影响

6.1 为什么要用数据卷

docker 分层文件系统(UnionFS)核心缺陷

  • 性能差:多层镜像叠加的写时复制(CoW)机制,导致 IO 性能远低于宿主机原生磁盘
  • 生命周期与容器完全绑定:容器删除时,分层文件系统内的所有数据会被同步清空,无法持久化

docker 数据卷核心优势

  • 直接 mount 到宿主机文件系统,完全绕开分层文件系统
  • IO 性能与宿主机原生磁盘完全一致,容器删除后数据依然保留
  • 本地磁盘存储,默认无法随容器跨主机迁移

docker 提供两种核心卷类型:

  • bind mount(绑定挂载卷)
  • docker managed volume(Docker 托管卷)

板块完整解析

底层核心逻辑

通过 Linux mount() 系统调用,将宿主机目录映射到容器的 mnt 命名空间中,容器读写操作直接作用于宿主机磁盘,跳过 UnionFS 的转发与 CoW 机制,从根源解决性能与持久化问题。

坑(Gotchas)

  1. 新手易混淆「容器内路径」与「宿主机路径」,写反挂载顺序导致宿主机文件被覆盖
  2. 数据卷默认仅本地生效,跨主机迁移容器时,数据不会自动同步,极易导致数据丢失
  3. 未配置权限的卷,默认属主为 root,非 root 容器运行时会报Permission denied

企业级生产应用(千万级并发场景)

电商核心交易系统、支付流水、数据库持久化文件、日志采集等有状态业务,必须通过数据卷落地存储,避免 UnionFS 性能瓶颈;配合分布式存储(Ceph/Rook)实现数据卷跨节点共享,适配 K8s 集群动态扩缩容,保证千万级并发下数据不丢失、IO 无瓶颈。

进阶优化空间
  1. 高频读写的秒杀订单场景,用 bind mount 挂载宿主机tmpfs内存文件系统,将磁盘 IO 延迟降低到微秒级
  2. 核心数据卷结合存储阵列快照 + Docker 卷快照,实现秒级备份,满足金融级 RTO/RPO 要求

课后防宕机指南(Troubleshooting)

  1. 报错:mkdir /xxx: permission denied(容器写入卷失败)

排查思路:① 执行ls -ld 宿主机路径检查目录权限,确保容器运行用户有读写权限;② 临时关闭 SELinux(setenforce 0)验证是否被安全策略拦截;③ 检查是否开启--user非 root 运行,配置目录 ACL 权限适配

  1. 报错:no such file or directory(容器内找不到挂载路径)

排查思路:① 核对-v参数路径分隔符(Linux 用/,Windows 用\,跨平台极易写错);② 检查宿主机路径是否为软链接,Docker 默认禁止挂载软链接,需用绝对路径

生活类比

容器分层文件系统 = 酒店一次性洗漱用品,退房(删容器)就被全部清走;数据卷 = 你自带的行李箱,住哪个酒店(容器运行)都能用,退房后东西还在,多个同行人(容器)也能共用。


6.2 bind mount 数据卷

  • 核心定义:将宿主机上的指定目录或文件,直接 mount 到容器内的指定路径
  • 核心特点:使用直观高效、可控性极强、易于理解
  • 核心语法:使用 -v 选项指定路径,格式 <宿主机绝对路径>:<容器内绝对路径>[:权限标记]
  • 核心特性:-v指定的宿主机路径,如果不存在,挂载时会自动创建
  • 权限标记:ro表示只读权限;默认不写为rw读写权限

准备工作:

ro:表示只读;默认:读写

核心代码示例

bash

运行

[root@docker ~]# docker run  -it --rm \

-v /tmp/data1:/data1 \

-v /tmp/data1:/data2:ro \

-v /etc/passwd:/data/passwd:ro busybox

/ # tail -n 3 /data/passwd

lee:x:1000:1000:lee:/home/lee:/bin/bash

apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin

nginx:x:1001:1001::/home/nginx:/sbin/nologin

/ # touch  /data1/leefile1

/ # touch /data2/leefile1

touch: /data2/leefile1: Read-only file system

逐行解析(Line-by-Line Breakdown)

表格

代码行

底层系统触发动作

docker run -it --rm \

1. 调用 Docker daemon 创建容器;2. -it 分配伪终端并绑定标准输入,实现交互式操作;3. --rm 注册容器退出钩子,容器停止后自动删除所有容器资源(含分层文件系统、网络命名空间);4. \ 为 shell 换行符,无系统动作

-v /tmp/data1:/data1 \

1. 检查宿主机/tmp/data1,不存在则执行mkdir -p /tmp/data1创建,权限默认0755 root:root;2. 调用 Linux mount --bind 系统调用,将宿主机路径绑定到容器 mnt 命名空间的/data1路径;3. 内核标记挂载类型为 bind,绕开 UnionFS,容器读写直接落地宿主机磁盘

-v /tmp/data1:/data2:ro \

1. 同 bind 挂载逻辑,将同一宿主机路径挂载到容器/data2;2. 内核添加MS_RDONLY挂载标记,拦截容器内所有对该路径的写入、修改、删除系统调用,仅允许读操作

-v /etc/passwd:/data/passwd:ro busybox

1. 将宿主机系统账号文件/etc/passwd只读挂载到容器内;2. 指定容器镜像为 busybox,启动容器并进入交互式 shell

tail -n 3 /data/passwd

读取挂载的宿主机/etc/passwd文件的最后 3 行,验证只读挂载的读权限正常

touch /data1/leefile1

向读写挂载的/data1路径创建文件,系统调用直接落地宿主机/tmp/data1/leefile1,验证读写权限正常

touch /data2/leefile1

向只读挂载的/data2路径发起创建文件的系统调用,被内核 VFS 层直接拦截,返回Read-only file system错误


坑(Gotchas)

  1. 致命级坑:ro权限标记必须写在容器路径的末尾,写反为/data2:/tmp/data1:ro会导致宿主机/tmp/data1被设为只读,直接破坏宿主机系统写入能力
  2. 高频坑:挂载宿主机系统文件(如/etc/passwd、/etc/hosts)未加ro标记,容器内修改会直接覆盖宿主机系统文件,导致宿主机账号、网络配置崩溃
  3. 权限坑:宿主机/tmp目录默认有sticky粘滞位(1777 权限),多容器共享该目录下的卷时,非 root 用户创建的文件仅自身可删除,极易出现「文件无法删除」的权限冲突
  4. 隐式坑:-v自动创建的宿主机目录,属主为 root,非 root 容器运行时会直接报权限不足,必须提前创建目录并配置对应权限

企业级生产应用(千万级并发场景)

  1. 微服务配置中心统一挂载:Nginx、MySQL、Redis 等中间件的配置文件,通过 bind mount 只读挂载到容器,保证上千个容器配置统一,修改宿主机配置即可批量生效,无需重建镜像
  2. 日志采集:宿主机日志目录挂载到所有业务容器,容器日志直接落地宿主机,配合 Filebeat+ELK 实现千万级并发下的日志统一采集、不落容器分层文件系统,避免容器删除日志丢失
  3. 敏感文件注入:CA 证书、密钥文件只读挂载到容器,防止容器被入侵后密钥泄露,满足等保 2.0 要求

进阶优化空间

  1. 用--mount type=bind,source=宿主机路径,target=容器路径,readonly替代-v,语法更严谨,避免隐式创建目录,减少生产环境误操作
  2. 高频读写场景,绑定挂载宿主机 SSD 磁盘路径,IO 性能比机械盘提升 10 倍以上
  3. 多容器共享卷时,配置 ACL 权限控制,实现不同容器的读写权限精细化隔离

课后防宕机指南(Troubleshooting)

  1. 报错:touch: cannot touch 'xxx': Read-only file system

排查思路:① 执行docker inspect 容器ID | grep Mounts查看挂载权限,确认是否误加ro标记;② 核对-v参数顺序,确认宿主机路径在前、容器路径在后;③ 检查宿主机目录本身是否为只读挂载

  1. 报错:cannot mount directory over non-directory

排查思路:① 核对挂载的宿主机与容器内路径类型,必须同为目录或同为文件,不能目录挂载到文件、反之亦然;② 检查宿主机路径是否为软链接,Docker 默认禁止挂载软链接,需替换为绝对路径

生活类比

bind mount = 你自己指定的快递柜,你精准指定哪个柜子(宿主机路径)放东西,容器只能用这个柜子,可控性拉满,但换个小区(换宿主机 / 移植)就要重新指定柜子,移植性差。


补充 bind mount 完整示例代码

bash

运行

[root@docker-node1 ~]# ls -ld /data

ls: 无法访问 '/data': 没有那个文件或目录

[root@docker-node1 ~]# docker run  -it --rm --name test  -v /data:/data -v /data1:/data1:ro  -v /etc/passwd:/passwd:ro  busybox:latest

/ # ls -ld /data /data1 /etc/passwd

drwxr-xr-x    2 root     root             6 Mar 22 01:56 /data

drwxr-xr-x    2 root     root             6 Mar 22 01:58 /data1

-rw-r--r--    1 root     root           340 May 18  2023 /etc/passwd

/ # touch  /data/file

/ # ls /data

file

/ # touch  /data1/file

touch: /data1/file: Read-only file system

/ # > passwd

sh: can't create passwd: Read-only file system

逐行解析

  1. ls -ld /data:检查宿主机/data目录是否存在,确认无此目录,验证-v自动创建特性
  2. docker run -v /data:/data:宿主机无/data目录,Docker 自动执行mkdir -p /data创建并挂载
  3. touch /data/file:读写挂载卷正常写入,文件直接落地宿主机/data/file
  4. touch /data1/file:只读挂载卷的写入请求被内核 VFS 层拦截,返回只读错误
  5. > passwd:向只读挂载的系统文件发起截断操作,被内核拦截,验证只读权限生效

6.3 docker managed 数据卷

  • 核心解决痛点:bind mount 必须指定宿主机文件系统路径,严重限制了容器的移植性
  • 核心特性:无需指定宿主机挂载源(也不能手动指定),Docker 自动为容器创建数据卷目录
  • 默认存储路径:所有自动创建的数据卷目录,都在宿主机/var/lib/docker/volumes/卷名/_data下
  • 核心数据特性:如果挂载时指向容器内已有的目录,容器原有数据会被自动复制到 volume 中,容器原目录内容依然可见,不会被覆盖

示例截图:

核心代码示例

bash

运行

[root@docker volumes]# docker run  -d --name mysql -e  MYSQL_ROOT_PASSWORD='lee' mysql:5.7

[root@docker volumes]# ls -l /var/lib/docker/volumes

总用量 0

drwx-----x 3 root root 19  8月 20 16:34 ad74662b8d6bb6fdcc6e82925ae9942b94bac5f9da4bd52b0a14ac451ae9ef75

[root@docker volumes]# touch ad74662b8d6bb6fdcc6e82925ae9942b94bac5f9da4bd52b0a14ac451ae9ef75/_data/leefile

[root@docker volumes]# docker exec  -it mysql  bash

bash-4.2# cd /var/lib/mysql

bash-4.2# ls

auto.cnf    client-cert.pem  ib_logfile0  ibtmp1   mysql.sock          public_key.pem   sys

ca-key.pem  client-key.pem   ib_logfile1  leefile  performance_schema  server-cert.pem

ca.pem      ib_buffer_pool   ibdata1      mysql    private_key.pem     server-key.pem

bash-4.2# pwd

逐行解析(Line-by-Line Breakdown)

表格

代码行

底层系统触发动作

docker run -d --name mysql -e MYSQL_ROOT_PASSWORD='lee' mysql:5.7

1. 后台启动 MySQL 5.7 容器,设置 root 密码;2. 读取 Dockerfile 中VOLUME /var/lib/mysql声明,自动调用docker volume create创建匿名托管卷;3. 在宿主机/var/lib/docker/volumes/下生成随机 UUID 命名的卷目录;4. 启动容器时,将该卷 bind 挂载到容器内/var/lib/mysql路径

ls -l /var/lib/docker/volumes

查看 Docker 托管卷的根目录,验证自动创建的匿名卷

touch 卷UUID/_data/leefile

宿主机直接向托管卷的数据目录写入文件,操作直接落地磁盘

docker exec -it mysql bash

进入运行中的 MySQL 容器,验证数据同步

cd /var/lib/mysql && ls

进入容器内挂载目录,可看到宿主机写入的leefile文件,同时 MySQL 镜像原有数据文件完整保留,验证「容器原有数据自动复制到卷」的特性

pwd

验证当前路径为容器内/var/lib/mysql,即托管卷的挂载路径


托管卷管理核心命令

1. 清理未使用的 Docker 数据卷

bash

运行

[root@docker ~]# docker volume prune

[!NOTE]

  1. 在执行 docker volume prune 命令之前,请确保你确实不再需要这些数据卷中的数据,因为该操作是不可逆的,一旦删除数据将无法恢复。
  2. 如果有重要的数据存储在数据卷中,建议先进行备份,或者确保数据已经被妥善保存到其他地方。

2. 手动创建命名托管卷

bash

运行

[root@docker ~]# docker volume create  leevol1

[root@docker ~]# ls -l  /var/lib/docker/volumes/leevol1/_data/

3. 查看所有数据卷

bash

运行

[root@docker ~]# docker volume ls

DRIVER    VOLUME NAME

local     leevol1

4. 使用自定义托管卷启动容器

bash

运行

[root@docker _data]# docker run  -d --name web1 -p 80:80  -v leevol1:/usr/share/nginx/html nginx

e76706848323d6c329c41c4140903f8cc441458daf1459d9016bd1ed0ab3360a

[root@docker _data]# cd /var/lib/docker/volumes/leevol1/_data

[root@docker _data]# ls

[root@docker _data]# echo leevol1 > index.html

[root@docker _data]# curl  172.25.254.100

leevol1

逐行解析(管理命令)

  1. docker volume prune:遍历所有托管卷,删除无任何容器关联的卷,释放磁盘空间,不可逆
  2. docker volume create leevol1:手动创建命名托管卷,Docker 在/var/lib/docker/volumes/下生成leevol1目录,包含数据目录_data
  3. docker volume ls:读取/var/lib/docker/volumes/目录,列出所有托管卷的驱动与名称
  4. -v leevol1:/usr/share/nginx/html:用卷名替代宿主机路径,Docker 自动关联命名托管卷,挂载到容器内 Nginx 静态资源目录
  5. echo leevol1 > index.html:宿主机向托管卷写入首页文件,容器内 Nginx 实时同步
  6. curl 172.25.254.100:访问容器 Nginx 服务,返回写入的首页内容,验证挂载生效

补充托管卷完整示例代码

bash

运行

[root@docker-node1 ~]# docker volume create timinglee

timinglee

[root@docker-node1 ~]# docker volume ls

DRIVER    VOLUME NAME

local     timinglee

[root@docker-node1 volumes]# touch  timinglee/_data/file

[root@docker-node1 ~]# docker run  -it --rm -v timinglee:/data:ro busybox:latest

/ # ls

bin    data   dev    etc    home   lib    lib64  proc   root   sys    tmp    usr    var

/ # touch  data/file

touch: data/file: Read-only file system

/ # ls data/

file

[root@docker-node1 ~]# docker volume rm timinglee

timinglee

[root@docker-node1 ~]# docker volume ls

DRIVER    VOLUME NAME

逐行解析

  1. docker volume create timinglee:创建命名托管卷timinglee
  2. touch timinglee/_data/file:宿主机向托管卷写入测试文件
  3. -v timinglee:/data:ro:将托管卷只读挂载到容器内/data路径
  4. touch data/file:只读挂载写入被内核拦截,返回错误
  5. ls data/:验证宿主机写入的文件可读,只读权限生效
  6. docker volume rm timinglee:删除未被容器使用的托管卷,释放空间

坑(Gotchas)

  1. 致命级坑:docker volume prune会删除所有无容器关联的卷,生产环境误执行会导致核心数据永久丢失,必须先执行docker volume ls确认,再加上-f强制参数
  2. 磁盘满坑:托管卷默认存储在/var/lib/docker/volumes,通常在系统盘,千万级并发下日志 / 数据写入会快速占满系统盘,导致宿主机宕机
  3. 数据复制坑:容器内已有目录挂载托管卷时,Docker 会自动复制容器内数据到卷,大镜像场景会占用双倍磁盘空间,导致磁盘溢出
  4. 匿名卷坑:未指定卷名的匿名卷,容器删除后会残留,长期运行会占用大量磁盘空间,生产环境必须用命名卷

企业级生产应用(千万级并发场景)

MySQL、Redis、MongoDB、Elasticsearch 等有状态中间件,默认使用托管卷实现数据持久化,移植性强,完美适配 K8s PV/PVC 调度体系;千万级并发的电商库存系统、用户中心数据库,通过托管卷 + 分布式存储驱动,实现容器跨节点漂移时数据不丢失,保证集群高可用。

进阶优化空间

  1. 修改 Docker daemon 配置data-root,将托管卷存储路径迁移到 SSD 数据盘,避免系统盘占满,同时提升 IO 性能
  2. 使用第三方卷驱动(如 Rex-Ray、Portworx),实现托管卷跨主机共享、快照、备份,适配大规模集群
  3. 生产环境禁用匿名卷,全部使用命名卷,便于管理、备份与迁移

课后防宕机指南(Troubleshooting)

  1. 报错:no space left on device(容器写入失败)

排查思路:① 执行df -h查看/var/lib/docker所在磁盘使用率,确认是否磁盘满;② 执行docker system df查看卷占用空间,清理无用匿名卷;③ 迁移托管卷到数据盘

  1. 报错:volume is already in use(删除卷失败)

排查思路:① 执行docker ps -a | grep 卷名查看是否有容器关联该卷;② 先停止并删除关联容器,再执行卷删除操作

生活类比

托管卷 = 商场公共储物柜,不用你自己指定柜子位置,商场(Docker)自动给你分配带编号的柜子,你只用记编号(卷名)就行,换个入口(换容器 / 移植)直接用编号开柜,不用重新找位置,移植性拉满。


6.4 数据卷容器(Data Volume Container)

数据卷容器(Data Volume Container)是 Docker 中一种特殊的容器,核心用途是专门管理数据卷,供其他容器批量继承挂载配置,实现多容器之间数据卷的统一管理、权限统一配置、批量共享。

核心解决场景:想让多个容器的挂载配置、数据、权限完全一致,无需每个容器重复写挂载参数。

1. 建立数据卷容器

bash

运行

[root@docker ~]# docker run  -d --name datavol \

-v /tmp/data1:/data1:rw \

-v /tmp/data2:/data2:ro \

-v /etc/resolv.conf:/etc/hosts busybox

2. 使用数据卷容器

bash

运行

[root@docker ~]# docker run  -it --name test --rm --volumes-from datavol busybox

/ # ls

bin    data1  data2  dev    etc    home   lib    lib64  proc   root   sys    tmp    usr    var

/ # cat /etc/resolv.conf

# Generated by Docker Engine.

# This file can be edited; Docker Engine will not make further changes once it

# has been modified.

nameserver 114.114.114.114

search timinglee.org

# Based on host file: '/etc/resolv.conf' (legacy)

# Overrides: []

/ # touch  data1/leefile1

/ # touch /data2/leefile1

touch: /data2/leefile1: Read-only file system

逐行解析(Line-by-Line Breakdown)

表格

代码行

底层系统触发动作

docker run -d --name datavol -v 多个挂载 busybox

1. 后台创建名为datavol的专用容器,不运行任何业务进程,仅作为数据卷的载体;2. 批量配置 3 个挂载卷,分别定义读写 / 只读权限;3. 容器启动后,将所有挂载配置注册到 Docker daemon 中

docker run --volumes-from datavol busybox

1. 创建新容器时,从datavol容器完整继承所有挂载配置、路径、权限;2. Docker daemon 直接复用datavol的 mount 配置,无需重复解析;3. 新容器与datavol共享完全一致的卷数据与权限

cat /etc/resolv.conf

验证继承的挂载卷可读,内容与宿主机一致

touch data1/leefile1

读写挂载卷正常写入,数据同步到所有共享该卷的容器

touch /data2/leefile1

只读挂载卷的写入请求被内核拦截,权限完全继承自数据卷容器


坑(Gotchas)

  1. 生命周期坑:数据卷容器删除后,其他正在使用卷的容器不受影响,卷的生命周期独立于数据卷容器,不会被同步删除
  2. 并发坑:多容器通过--volumes-from共享同一读写卷,并发写入同一文件会导致文件锁冲突、数据损坏,必须配置分布式文件锁
  3. 权限坑:数据卷容器的挂载权限会被完整继承,修改数据卷容器的挂载配置,必须重启所有继承的容器才能生效
  4. 循环引用坑:两个容器互相--volumes-from,会导致 Docker daemon 死锁,容器无法启动

企业级生产应用(千万级并发场景)

  1. 微服务集群统一配置管理:上百个微服务容器通过--volumes-from继承统一的配置卷容器,实现配置文件批量更新、权限统一管控,无需修改每个容器的启动参数
  2. 静态资源共享:CDN 节点、Nginx 静态资源集群,通过数据卷容器共享静态资源卷,实现千万级并发下静态资源的统一更新、多容器实时同步
  3. 日志统一采集:所有业务容器继承日志卷容器,日志统一落地到同一宿主机目录,配合采集工具实现批量采集,降低运维复杂度

进阶优化空间

  1. 数据卷容器使用--pause暂停状态,不占用系统资源,仅保留挂载配置,提升宿主机资源利用率
  2. 结合配置中心,数据卷容器定时同步配置文件,实现所有继承容器的配置热更新,无需重启容器
  3. 生产环境给数据卷容器添加--restart=always,保证宿主机重启后挂载配置不丢失

课后防宕机指南(Troubleshooting)

  1. 报错:cannot access 'xxx': No such file or directory(继承容器找不到路径)

排查思路:① 检查数据卷容器是否正常启动,执行docker ps | grep datavol确认;② 检查数据卷容器的挂载路径是否正确,执行docker inspect datavol | grep Mounts验证;③ 重启继承容器,重新加载挂载配置

  1. 报错:read-only file system(继承容器写入失败)

排查思路:① 检查数据卷容器的挂载配置是否误加ro标记;② 执行docker inspect 容器ID | grep Mounts确认继承的权限是否正确

生活类比

数据卷容器 = 钥匙串,你把所有柜子的钥匙(挂载卷、权限配置)都串在这个钥匙串上,其他容器直接拿这个钥匙串就能开所有柜子,不用自己一把一把配钥匙,批量管理效率拉满。


6.5 bind mount 数据卷和 docker managed 数据卷的对比

相同点:

  • 两者本质都是宿主机文件系统中的真实路径,数据均落地宿主机磁盘,均绕开容器 UnionFS 分层文件系统

不同点

  • bind mount:可控性极强、移植性差、挂载后覆盖容器原有目录内容
  • docker managed volume:可控性弱、移植性强、挂载后自动复制容器原有数据到卷中,不覆盖

挂载覆盖特性验证测试

我们现在观察index.html默认发布文件,会不会把原来的覆盖掉,如果是,那就是新挂载的卷会把原来的隐藏

我们现在 curl 查看,如果看到 timing lee, 那就是说明覆盖了

结论:bind mount 挂载后,容器原有目录内容被隐藏覆盖

结论:docker managed volume 不会覆盖容器原有内容,会自动复制到卷中


6.6 备份与迁移数据卷

备份数据卷

bash

运行

#建立容器并指定使用卷到要备份的容器

[root@docker ~]# docker run --volumes-from datavol  \

-v `pwd` :/backup busybox \

tar zcf /backup/data1.tar.gz  /data1

补充说明:pwd 会先执行获取当前目录绝对路径,挂载到容器内 /backup 目录,用于存储备份包

数据恢复

bash

运行

docker run  -it --name test -v leevol1:/data1 -v `pwd`:/backup busybox /bin/sh -c "tar zxf /backup/data1.tar.gz;/bin/sh"

/ # ls

backup  data1   etc     lib     proc    sys     usr

bin     dev     home    lib64   root    tmp     var

/ # cd data1/

/data1 # ls

index.html  leefile1

逐行解析(备份 + 恢复)

表格

代码行

底层系统触发动作

docker run --volumes-from datavol

启动临时容器,完整继承待备份容器的所有挂载卷,确保能访问到完整的卷数据

-v $(pwd):/backup busybox

将宿主机当前目录挂载到容器内/backup路径,作为备份包的输出目录,实现容器内打包的文件直接落地宿主机

tar zcf /backup/data1.tar.gz /data1

容器内执行 tar 打包命令,将/data1卷的数据压缩打包,输出到/backup目录,即宿主机当前目录

docker run -v leevol1:/data1 -v $(pwd):/backup

启动恢复容器,挂载目标恢复卷、备份包所在的宿主机目录

tar zxf /backup/data1.tar.gz

解压备份包到目标卷,完成数据恢复

cd /data1 && ls

验证恢复的数据完整,与备份前一致


补充完整备份与迁移示例代码

bash

运行

[root@docker-node1 ~]# docker run  -d --name webserver -p 80:80 -v /data:/usr/share/nginx/html nginx:1.23

[root@docker-node1 ~]# docker exec  -it webserver bash

root@23951ce13871:/# cd /usr/share/nginx/html/

root@23951ce13871:/usr/share/nginx/html# ls

index.html  timinglee

root@23951ce13871:/usr/share/nginx/html# touch timinglee{1..10}

root@23951ce13871:/usr/share/nginx/html# ls

index.html  timinglee1   timinglee2  timinglee4  timinglee6  timinglee8

timinglee   timinglee10  timinglee3  timinglee5  timinglee7  timinglee9

#数据备份

[root@docker-node1 ~]# docker run  -it --rm  --volumes-from webserver  -v $(pwd):/backup  busybox:latest

/ # ls /usr/share/nginx/html/

index.html   timinglee1   timinglee2   timinglee4   timinglee6   timinglee8

timinglee    timinglee10  timinglee3   timinglee5   timinglee7   timinglee9

/ # tar zcf /backup/html.tar.gz /usr/share/nginx/

tar: removing leading '/' from member names

/ # exit

[root@docker-node1 ~]# ls

busybox-latest.tar.gz  docker                                mysql-8.0.tar

busyboxplus.tar        harbor-offline-installer-v2.14.0.tgz  nginx-1.23.tar.gz

debian11.tar.gz        html.tar.gz                           phpmyadmin-latest.tar.gz

#数据恢复

[root@docker-node1 ~]# rm -fr /data/*

[root@docker-node1 ~]# docker exec  -it webserver bash

root@23951ce13871:/# cd /usr/share/nginx/html/

root@23951ce13871:/usr/share/nginx/html# ls

root@23951ce13871:/usr/share/nginx/html# exit

[root@docker-node1 ~]# docker run  -d --name webserver -p 80:80 -v /data:/usr/share/nginx/html  -v $(pwd):/backup nginx:1.23

83a26edb472ecda951e241dc207847111cd4a2712cb349c52205c3d0e2727238

[root@docker-node1 ~]# docker exec  -it webserver bash

root@83a26edb472e:/# tar zxf /backup/html.tar.gz -C /

root@83a26edb472e:/# ls /usr/share/nginx/html/

index.html  timinglee1   timinglee2  timinglee4  timinglee6  timinglee8

timinglee   timinglee10  timinglee3  timinglee5  timinglee7  timinglee9

坑(Gotchas)

  1. 备份空包坑:打包时路径写错,导致 tar 打包空目录,备份无效,生产环境备份后必须解压验证包内容
  2. 恢复路径坑:解压时未加-C /,导致数据解压到容器分层文件系统,容器删除后数据丢失,必须解压到挂载的卷路径
  3. 权限坑:备份时未保留文件权限,恢复后容器非 root 用户无法读取文件,tar 命令必须加-p参数保留权限
  4. 业务中断坑:业务运行中执行备份,会导致数据不一致,必须先暂停容器再备份,或使用快照备份

企业级生产应用(千万级并发场景)

金融级交易系统、电商核心数据库,通过定时任务执行数据卷热备份,备份包同步到异地对象存储,满足等保三级容灾要求;千万级并发的大促场景,通过备份包快速恢复测试环境、预发环境,实现多环境数据一致;容器跨主机迁移时,通过备份包实现数据卷的跨节点迁移,保证业务不中断。

进阶优化空间

  1. 使用docker cp替代 tar 打包,直接从容器复制数据到宿主机,减少中间环节,提升备份效率
  2. 结合增量备份工具(如 rsync),实现千万级数据量的秒级增量备份,降低磁盘 IO 占用
  3. 生产环境使用存储阵列快照 + 卷快照,实现热备份,无需暂停业务,保证数据一致性
  4. 备份包自动同步到 OSS/S3 对象存储,实现异地容灾,满足 RPO=5 分钟的金融级要求

课后防宕机指南(Troubleshooting)

  1. 报错:tar: /backup/html.tar.gz: No such file or directory(备份失败)

排查思路:① 检查-v $(pwd):/backup的路径是否正确,当前目录是否有写入权限;② 核对容器内/backup路径是否存在,挂载是否生效;③ 替换$(pwd)为绝对路径,避免 shell 变量解析错误

  1. 报错:tar: This does not look like a tar archive(恢复失败)

排查思路:① 检查备份包是否完整,执行file html.tar.gz验证包格式;② 检查备份包是否为空,执行tar -tf html.tar.gz查看包内容;③ 重新执行备份,确保打包路径正确

生活类比

数据卷备份 = 给行李箱里的东西拍打包视频 + 压缩包,换酒店(迁移容器)时,直接用压缩包还原所有东西,不用重新收拾;恢复 = 用压缩包把东西还原到新的行李箱(新卷)里,和原来一模一样。


七 Docker 的安全优化

Docker 容器的安全性,核心依赖于 Linux 系统自身的安全机制,评估 Docker 的安全性时,主要考虑以下几个核心维度:

  • Linux 内核的命名空间机制提供的容器隔离安全(与安全正相关)
  • Linux 控制组机制对容器资源的控制能力安全
  • Linux 内核的能力机制所带来的操作权限安全
  • Docker 程序(特别是服务端)本身的抗攻击性
  • 其他安全增强机制(SELinux/AppArmor)对容器安全性的影响

bash

运行

#在rhel9中默认使用cgroup-v2 但是cgroup-v2中不利于观察docker的资源限制情况,所以推荐使用cgroup-v1

[root@docker ~]#  grubby --update-kernel=/boot/vmlinuz-$(uname -r) \

--args="systemd.unified_cgroup_hierarchy=0 systemd.legacy_systemd_cgroup_controller"

一.更改系统 cgroup 版本(企业中非必须)

bash

运行

[root@docker-node1 ~]# mount -t cgroup

[root@docker-node1 ~]# mount -t cgroup2

cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)

[root@docker-node1 ~]# grubby --update-kernel=/boot/vmlinuz-$(uname -r) \

--args="systemd.unified_cgroup_hierarchy=0 systemd.legacy_systemd_cgroup_controller"

[root@docker-node1 ~]# reboot

[root@docker-node1 ~]# mount -t cgroup

cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)

cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)

cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)

cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)

cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)

cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)

cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)

cgroup on /sys/fs/cgroup/misc type cgroup (rw,nosuid,nodev,noexec,relatime,misc)

cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)

cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)

cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)

cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)

cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)

补充说明:切换为 cgroup-v1 后,资源限制的观测更透明,每个子系统独立目录,便于排查问题

逐行解析

  1. mount -t cgroup2:查看当前系统 cgroup 版本,RHEL9 默认启用 cgroup-v2,统一层级结构
  2. grubby --update-kernel:修改内核启动参数,关闭统一 cgroup 层级,启用传统 cgroup-v1
  3. reboot:重启系统,内核参数生效
  4. mount -t cgroup:验证 cgroup-v1 的所有子系统(cpu、memory、blkio 等)已独立挂载,切换成功

坑(Gotchas)

  1. 内核参数写错,导致系统无法启动,必须先备份内核启动配置,再修改
  2. 未重启系统直接使用,cgroup 版本不生效,资源限制配置不生效
  3. 部分 K8s 版本不兼容 cgroup-v1,生产环境切换前必须验证兼容性

企业级生产应用

千万级并发的容器集群,切换为 cgroup-v1 后,资源限制的可观测性更强,便于监控告警、故障排查,适配传统监控系统,保证大促峰值时资源限制精准生效,避免单容器耗尽宿主机资源。


1 命名空间隔离的安全

  • 当 docker run 启动一个容器时,Docker 将在后台为容器创建一个独立的命名空间。命名空间提供了最基础也最直接的隔离。
  • 与虚拟机方式相比,通过 Linux namespace 来实现的隔离不是那么彻底。
  • 容器只是运行在宿主机上的一种特殊的进程,多个容器之间共享同一个宿主机的操作系统内核。
  • 在 Linux 内核中,有很多资源和对象是不能被 Namespace 化的,比如:磁盘、内存、时间等

bash

运行

[root@docker ~]# docker run -d --name web nginx

3c6b649a200fc56afafe9f47494903fe56e71cabcd534d6c9e6f8b5854f29cac

[root@docker ~]# docker inspect web  | grep Pid

            "Pid": 4328,

            "PidMode": "",

            "PidsLimit": null,

[root@docker ~]# cd /proc/4328/ns/

[root@docker ns]# ls

cgroup  ipc  mnt  net  pid  pid_for_children  time  time_for_children  user  uts

[root@docker ns]# ls -d /sys/fs/cgroup/memory/docker/3c6b649a200fs省略部分854f29cac/

/sys/fs/cgroup/system.slice/docker-ecb8abbbfc85bf3d62fc82afb3950ab6b6a2e80092738274a233bbb8db0c5ce2.scope

/sys/fs/cgroup/system.slice/docker.service

/sys/fs/cgroup/system.slice/docker.socket

逐行解析

  1. docker run -d --name web nginx:后台启动 Nginx 容器,Docker 自动为其创建 6 大核心命名空间
  2. docker inspect web | grep Pid:获取容器在宿主机上的进程 PID,容器本质是宿主机上的一个进程
  3. cd /proc/4328/ns/:进入容器进程的命名空间目录,查看 Docker 创建的所有隔离命名空间
  4. ls -d /sys/fs/cgroup/memory/docker/容器ID/:查看容器对应的 cgroup 资源控制目录,验证资源限制的底层配置

坑(Gotchas)

  1. 容器共享宿主机内核,内核漏洞会导致容器逃逸,直接控制宿主机
  2. 时间命名空间隔离不完善,容器内修改时间会直接影响宿主机,生产环境必须禁用容器的时间修改权限
  3. 开启--privileged特权容器,会突破所有命名空间隔离,直接获得宿主机 root 权限,生产环境严禁使用

企业级生产应用

千万级并发的多租户容器平台,通过命名空间实现租户之间的严格隔离,避免租户之间的进程、网络、文件系统互相访问,满足等保要求;核心业务容器禁用所有不必要的命名空间共享,防止容器逃逸攻击。


2 控制组资源控制的安全

  • 当 docker run 启动一个容器时,Docker 将在后台为容器创建一个独立的控制组策略集合。
  • Linux Cgroups 提供了很多有用的特性,确保各容器可以公平地分享主机的内存、CPU、磁盘 IO 等资源。
  • 确保当发生在容器内的资源压力不会影响到本地主机系统和其他容器,它在防止拒绝服务攻击(DDoS)、容器病毒传播方面必不可少。

bash

运行

[root@docker ~]# docker run  -it --name test busybox

/ # free -m

              total        used        free      shared  buff/cache   available

Mem:           3627         648         516          16        2463        2678

Swap:          2063           1        2062

/ # exit

[root@docker ~]# free  -m

               total        used        free      shared  buff/cache   available

Mem:            3627         907         557          15        2463        2719

Swap:           2062           1        2061

逐行解析

  1. docker run -it --name test busybox:启动无资源限制的容器
  2. 容器内free -m:可以看到宿主机的全部内存,默认无任何内存限制
  3. 宿主机free -m:验证容器内看到的内存与宿主机完全一致,默认无资源隔离

坑(Gotchas)

  1. 容器默认无任何资源限制,单容器可以耗尽宿主机所有 CPU、内存、磁盘 IO,导致宿主机宕机,生产环境必须给所有容器配置资源限制
  2. 内存限制未配置--memory-swap,容器会使用宿主机 swap 分区,导致内存溢出时宿主机卡顿
  3. 未配置 blkio 限制,容器大量磁盘读写会导致宿主机 IO 耗尽,其他容器无法正常运行

企业级生产应用

千万级并发的电商容器集群,所有容器必须配置 CPU、内存、磁盘 IO、进程数的上限限制,防止大促峰值时单容器异常耗尽宿主机资源,导致集群雪崩;通过 Cgroups 限制容器的资源使用,抵御 DDoS 攻击、容器病毒传播,保证集群稳定性。


3 内核能力机制

  • 能力机制(Capability)是 Linux 内核一个强大的特性,可以提供细粒度的权限访问控制。
  • 大部分情况下,容器并不需要 “真正的” root 权限,容器只需要少数的能力即可。
  • 默认情况下,Docker 采用 “白名单” 机制,禁用 “必需功能” 之外的其他权限。

在容器里面,并不是什么都可以做:

补充说明:容器内执行高危系统操作,会被内核 Capability 机制拦截,报权限不够错误


4 Docker 服务端防护

  • 使用 Docker 容器的核心是 Docker 服务端,确保只有可信的用户才能访问到 Docker 服务。
  • 将容器的 root 用户映射到本地主机上的非 root 用户,减轻容器和主机之间因权限提升而引起的安全问题。
  • 允许 Docker 服务端在非 root 权限下运行,利用安全可靠的子进程来代理执行需要特权权限的操作。这些子进程只允许在特定范围内进行操作。

bash

运行

[root@docker ~]# ls -ld /var/lib/docker/

drwx--x--- 12 root root 171  8月 20 13:21 /var/lib/docker/

补充说明:默认 Docker 数据目录属主为 root,Docker daemon 以 root 权限运行,必须严格控制访问权限

7.1 Docker 的资源限制

Linux Cgroups 的全称是 Linux Control Group。

  • 核心作用:限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘 IO、网络带宽等等。
  • 附加能力:对进程进行优先级设置、审计,以及将进程挂起和恢复等操作。

Linux Cgroups 给用户暴露出来的操作接口是文件系统

  • 它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 路径下。
  • 执行此命令查看:mount -t cgroup

bash

运行

[root@docker ~]# mount -t cgroup

cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)

cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)

cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)

cgroup on /sys/fs/cgroup/misc type cgroup (rw,nosuid,nodev,noexec,relatime,misc)

cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)

cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)

cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)

cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)

cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)

cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)

cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)

cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)

cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)

补充说明:每个子目录对应一个 Cgroups 子系统,用于限制对应类型的资源

  • 在 /sys/fs/cgroup 下面有很多诸如 cpuset、cpu、 memory 这样的子目录,也叫子系统。
  • 在每个子系统下面,为每个容器创建一个控制组(即创建一个新目录)。
  • 控制组下面的资源文件里填上什么值,就靠用户执行 docker run 时的参数指定。

7.1.1. 限制 cpu 使用

1. 限制 cpu 的使用量(绝对限制)

bash

运行

[root@docker ~]# docker run  -it --rm --name test \

--cpu-period 100000 \

--cpu-quota 20000 ubuntu

root@5797d76b20f5:/# dd if=/dev/zero of=/dev/null &

[1] 8

root@5797d76b20f5:/# top

top - 11:53:22 up 1 day,  1:58,  0 user,  load average: 0.00, 0.00, 0.00

Tasks:   3 total,   2 running,   1 sleeping,   0 stopped,   0 zombie

%Cpu(s):  4.4 us,  6.0 sy,  0.0 ni, 89.5 id,  0.0 wa,  0.2 hi,  0.0 si,  0.0 st

MiB Mem :   3627.1 total,    558.1 free,    899.4 used,   2471.0 buff/cache

MiB Swap:   2063.0 total,   2062.0 free,      1.0 used.   2727.7 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND

      8 root      20   0    2736   1536   1536 R  20.0   0.0   0:00.92 dd

      1 root      20   0    4588   3968   3456 S   0.0   0.1   0:00.03 bash

      9 root      20   0    8856   5248   3200 R   0.0   0.1   0:00.00 top



#在cgroup中查看docker的资源限制

[root@docker ~]# cat /sys/fs/cgroup/cpu/docker/“docker id(所要查看容器的id)”/cpu.cfs_period_us

[root@docker ~]# cat /sys/fs/cgroup/cpu/docker/“docker id(所要查看容器的id)”/cpu.cfs_quota_us

补充说明:

--cpu-period 100000:设置 CPU 周期的长度,单位为微秒(通常为 100000,即 100 毫秒)

--cpu-quota 20000:设置容器在一个周期内可以使用的 CPU 时间,单位也是微秒,此配置限制容器最多使用 20% 的单核心 CPU

相关截图:

导入镜像截图:


逐行解析(CPU 绝对限制)

表格

代码行

底层系统触发动作

--cpu-period 100000

内核设置 CPU 调度周期为 100 毫秒(100000 微秒),这是 Linux CFS 调度器的标准周期,所有 CPU 时间分配都基于这个周期

--cpu-quota 20000

内核设置容器在每个 100 毫秒周期内,最多只能使用 20 毫秒的 CPU 时间,换算下来就是 20% 的单核心 CPU 上限,超过后容器进程会被内核限制,等待下一个周期

dd if=/dev/zero of=/dev/null &

后台启动 CPU 压测进程,持续占用 CPU,验证资源限制

top

查看容器内进程 CPU 使用率,dd 进程被限制在 20% 左右,验证限制生效

cat /sys/fs/cgroup/cpu/docker/容器ID/cpu.cfs_period_us

读取 Cgroup 子系统中,容器对应的 CPU 周期配置文件,验证底层参数生效

cat /sys/fs/cgroup/cpu/docker/容器ID/cpu.cfs_quota_us

读取 CPU 配额配置文件,验证限制值正确


2. 限制 cpu 的优先级(相对权重)

bash

运行

#确保在系统中只有一个cpu核心在下

[root@docker-node1 ~]# cd /sys/devices/system/cpu/

[root@docker-node1 cpu]# ls

cpu0  cpufreq  crash_hotplug  isolated    modalias   offline  possible  present  uevent

cpu1  cpuidle  hotplug        kernel_max  nohz_full  online   power     smt      vulnerabilities

[root@docker-node1 cpu]# echo 0 > cpu1/online

[root@docker-node1 cpu1]# cat /proc/cpuinfo | grep cores

cpu cores       : 1  #cpu1已被成功禁用,仅cpu0在线 可用

[root@docker-node1 ~]# docker run  -it --rm --name test  ubuntu

root@69183e546633:/# dd if=/dev/zero of=/dev/null &

root@69183e546633:/#  top

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND

      9 root      20   0    2736   1408   1408 R  49.5   0.1   0:40.64 dd

      1 root      20   0    4588   3712   3200 S   0.0   0.2   0:00.02 bash

     10 root      20   0    8848   5248   3200 R   0.0   0.3   0:00.00 top

    

[root@docker-node1 ~]# docker run  -it --rm --name test1  ubuntu

root@871f9f2bf1ba:/# dd if=/dev/zero of=/dev/null &

root@871f9f2bf1ba:/#  top

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND

      9 root      20   0    2736   1408   1408 R  9.5   0.1   0:40.64 dd

      1 root      20   0    4588   3712   3200 S   0.0   0.2   0:00.02 bash

     10 root      20   0    8848   5248   3200 R   0.0   0.3   0:00.00 top

#体现了:没人用的话我独占,有人用的话一半一半

#资源限制

[root@docker-node1 ~]#  docker run -it  --rm --cpu-shares 100 ubuntu

root@0dd481be0925:/# dd if=/dev/zero of=/dev/null &

root@0dd481be0925:/# top

[root@docker-node1 ~]# docker run  -it --rm  ubuntu:latest

root@f61925d3c218:/# dd if=/dev/zero of=/dev/null &

root@f61925d3c218:/# top

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND

      8 root      20   0    2736   1408   1408 R  89.7   0.1   0:24.24 dd

      1 root      20   0    4588   3968   3456 S   0.0   0.2   0:00.01 bash

      9 root      20   0    8848   5120   3072 R   0.0   0.3   0:00.00 top

补充关闭 CPU 核心完整代码

bash

运行

#关闭cpu的核心,当cpu都不空闲下才会出现争抢的情况,为了实验效果我们可以关闭一个cpu核心

[root@docker ~]# echo 0 > /sys/devices/system/cpu/cpu1/online

[root@docker ~]# cat /proc/cpuinfo

processor       : 0

vendor_id       : GenuineIntel

cpu family      : 6

model           : 58

model name      : Intel(R) Core(TM) i7-3770K CPU @ 3.50GHz

stepping        : 9

microcode       : 0x21

cpu MHz         : 3901.000

cache size      : 8192 KB

physical id     : 0

siblings        : 1

core id         : 0

cpu cores       : 1          ##cpu核心数为1

apicid          : 0

initial apicid  : 0

fpu             : yes

fpu_exception   : yes

cpuid level     : 13

wp              : yes

flags           : fpu vme de pse tsc msr pae mce cx8 apc sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx rdtscp lm constant_tsc arch_perfmon nopl xtopology tsc_reliable nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 cx16 pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm pti ssbd ibrs ibpb stibp fsgsbase tsc_adjust smep arat md_clear flush_l1d arch_capabilities

bugs            : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit srbds mmio_unknown

bogomips        : 7802.00

clflush size    : 64

cache_alignment : 64

address sizes   : 45 bits physical, 48 bits virtual

power management:

#开启容器时如果指定了cpu使用优先级,那么设定文件为

[root@docker ~]# cat /sys/fs/cgroup/cpu/docker/“docker id(所要查看容器的id)”/cpu.shares

#开启容器并限制资源

[root@docker ~]# docker run -it  --rm --cpu-shares 100 ubuntu

root@dc066aa1a1f0:/# dd if=/dev/zero of=/dev/null &

[1] 8

root@dc066aa1a1f0:/# top

top - 12:16:56 up 1 day,  2:22,  0 user,  load average: 1.20, 0.37, 0.20

Tasks:   3 total,   2 running,   1 sleeping,   0 stopped,   0 zombie

%Cpu(s): 37.3 us, 61.4 sy,  0.0 ni,  0.0 id,  0.0 wa,  1.0 hi,  0.3 si,  0.0 st

MiB Mem :   3627.1 total,    502.5 free,    954.5 used,   2471.7 buff/cache

MiB Swap:   2063.0 total,   2062.3 free,      0.7 used.   2672.6 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND

      8 root      20   0    2736   1536   1536 R   3.6   0.0   0:16.74 dd

      1 root      20   0    4588   3968   3456 S   0.0   0.1   0:00.03 bash

      9 root      20   0    8856   5248   3200 R   0.0   0.1   0:00.00 top

#开启另外一个容器不限制cpu的优先级

root@17f8c9d66fde:/# dd if=/dev/zero of=/dev/null &

[1] 8

root@17f8c9d66fde:/# top

top - 12:17:55 up 1 day,  2:23,  0 user,  load average: 1.84, 0.70, 0.32

Tasks:   3 total,   2 running,   1 sleeping,   0 stopped,   0 zombie

%Cpu(s): 36.2 us, 62.1 sy,  0.0 ni,  0.0 id,  0.0 wa,  1.3 hi,  0.3 si,  0.0 st

MiB Mem :   3627.1 total,    502.3 free,    954.6 used,   2471.7 buff/cache

MiB Swap:   2063.0 total,   2062.3 free,      0.7 used.   2672.5 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND

      8 root      20   0    2736   1408   1408 R  94.0   0.0   1:09.34 dd

      1 root      20   0    4588   3968   3456 S   0.0   0.1   0:00.02 bash

      9 root      20   0    8848   5248   3200 R   0.0   0.1   0:00.01 top

逐行解析(CPU 优先级)

  1. echo 0 > /sys/devices/system/cpu/cpu1/online:向内核 sysfs 写入 0,禁用 CPU1 核心,仅保留 CPU0 在线,制造 CPU 资源争抢的实验环境
  2. docker run --cpu-shares 100 ubuntu:设置容器的 CPU 权重为 100,默认权重为 1024,权重值越大,CPU 资源争抢时获得的时间片越多
  3. dd if=/dev/zero of=/dev/null &:启动 CPU 压测进程,持续占用 CPU,触发资源争抢
  4. top:查看 CPU 使用率,权重 100 的容器仅获得约 10% 的 CPU,默认权重 1024 的容器获得约 90% 的 CPU,验证优先级生效
  5. cat /sys/fs/cgroup/cpu/docker/容器ID/cpu.shares:读取 Cgroup 中 CPU 权重配置文件,验证底层参数生效

坑(Gotchas)

  1. 绝对限制坑:--cpu-quota单位是微秒,新手容易写错数值(如写 200000=2 核),导致限制过严或过松,生产环境必须根据业务峰值精准配置
  2. 优先级坑:--cpu-shares仅在 CPU 资源满负载时生效,CPU 空闲时无任何限制,新手容易误以为是绝对限制,导致资源争抢时业务异常
  3. 多核坑:多核环境下,--cpu-quota可以超过 100000(如 200000=2 核),新手容易忽略,导致单容器占用过多 CPU 核心
  4. 重启失效坑:修改 CPU 核心在线状态,重启后会恢复默认,实验环境需注意,生产环境禁用关闭 CPU 核心

企业级生产应用(千万级并发场景)

千万级并发的电商核心交易系统,核心支付容器 CPU 权重设为 1024,非核心的日志、监控容器权重设为 128/256,大促峰值时保证核心业务优先获得 CPU 资源;绝对 CPU 限制防止压测、异常进程耗尽宿主机 CPU,避免集群雪崩;多租户容器平台,通过 CPU 绝对限制实现租户资源配额管理,保证租户之间资源隔离。

进阶优化空间

  1. 使用--cpus参数替代--cpu-period/--cpu-quota,语法更简洁,直接指定容器可用的 CPU 核心数(如--cpus 0.2=20% 单核心),减少生产环境误操作
  2. 核心业务容器绑定指定 CPU 核心(--cpuset-cpus),避免 CPU 上下文切换,提升性能,千万级并发下延迟降低 30% 以上
  3. 结合 HPA 自动扩缩容,根据容器 CPU 使用率自动调整副本数,保证峰值时资源充足,低峰时释放资源

课后防宕机指南(Troubleshooting)

  1. 报错:container killed: OOM killed(容器被内核杀死)

排查思路:① 检查容器内存限制是否设置过低,业务峰值超过限制;② 执行dmesg | grep oom查看内核 OOM 日志,确认杀死原因;③ 调整内存限制,优化业务内存占用

  1. 报错:cpu.shares: invalid value(容器启动失败)

排查思路:① 检查--cpu-shares的值是否在 2-262144 范围内,超出范围会报错;② 修正权重值为合法范围,重新启动容器

生活类比

CPU 绝对限制 = 手机固定流量套餐,每个月最多用 20G,用完就断网;CPU 优先级 = 机场 VIP 通道,航班高峰期(CPU 满负载)时,VIP 旅客(高权重容器)优先登机,普通旅客(低权重容器)排队等待,空闲时所有人都能随便走。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐