Docker 安全优化实战手册(企业级硬核版)

目录

[Docker 安全评估核心维度](#Docker 安全评估核心维度)

核心解释

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

[核心代码逐行解析(Line-by-Line Breakdown)](#核心代码逐行解析(Line-by-Line Breakdown))

生活类比

坑点(Gotchas)

[企业级生产应用(Enterprise Scenario)](#企业级生产应用(Enterprise Scenario))

课后防宕机指南(Troubleshooting)

[1 命名空间隔离的安全](#1 命名空间隔离的安全)

核心解释

核心代码逐行解析

生活类比

坑点(Gotchas)

企业级生产应用

课后防宕机指南

[2 控制组资源控制的安全](#2 控制组资源控制的安全)

核心解释

核心代码逐行解析

生活类比

坑点(Gotchas)

企业级生产应用

课后防宕机指南

[3 内核能力机制](#3 内核能力机制)

​编辑

核心解释

核心代码逐行解析

生活类比

坑点(Gotchas)

企业级生产应用

课后防宕机指南

[4 Docker 服务端防护](#4 Docker 服务端防护)

核心解释

核心代码逐行解析

生活类比

坑点(Gotchas)

企业级生产应用

课后防宕机指南

[7.1 Docker 的资源限制](#7.1 Docker 的资源限制)

核心解释

[7.1.1. 限制 cpu 使用](#7.1.1. 限制 cpu 使用)

[1. 限制 cpu 的使用量](#1. 限制 cpu 的使用量)

​编辑

​编辑

核心代码逐行解析

生活类比

坑点(Gotchas)

企业级生产应用

课后防宕机指南

[2. 限制 cpu 的优先级](#2. 限制 cpu 的优先级)

核心代码逐行解析

生活类比

坑点(Gotchas)

企业级生产应用

课后防宕机指南

[7.1.2 限制内存使用](#7.1.2 限制内存使用)

核心代码逐行解析

生活类比

坑点(Gotchas)

企业级生产应用

课后防宕机指南

[7.1.3 限制 docker 的磁盘 io](#7.1.3 限制 docker 的磁盘 io)

核心代码逐行解析

生活类比

坑点(Gotchas)

企业级生产应用

课后防宕机指南

[7.2 Docker 的安全加固](#7.2 Docker 的安全加固)

[7.2.1 Docker 默认隔离性](#7.2.1 Docker 默认隔离性)

核心解释

[7.2.2 解决 Docker 的默认隔离性](#7.2.2 解决 Docker 的默认隔离性)

核心代码逐行解析

生活类比

坑点(Gotchas)

企业级生产应用

课后防宕机指南

[7.2.3 容器特权](#7.2.3 容器特权)

核心代码逐行解析

生活类比

坑点(Gotchas)

企业级生产应用

课后防宕机指南

[7.2.4 容器特权的白名单](#7.2.4 容器特权的白名单)

核心解释

企业级生产最佳实践

原文档附录(完整保留)



Docker 安全评估核心维度

Docker 容器的安全性,很大程度上依赖于 Linux 系统自身评估 Docker 的安全性时,主要考虑以下几个方面:

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

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

核心解释

Docker 本质是 Linux 内核特性的 "封装器",所有安全能力都建立在内核基础之上。上述 5 个维度构成了 Docker 安全的完整防线:命名空间解决 "谁能看到什么",控制组解决 "能用多少资源",能力机制解决 "能做什么操作",服务端防护解决 "谁能调用 Docker",最后通过第三方机制(如 AppArmor、SELinux)做兜底。


一. 更改系统 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)

这样做会更透明;

核心代码逐行解析(Line-by-Line Breakdown)

  1. mount -t cgroup / mount -t cgroup2
    • 底层动作:遍历内核挂载表,筛选出类型为cgroup/cgroup2的文件系统,输出当前系统的 cgroup 版本与挂载路径
    • 作用:验证当前系统使用的 cgroup 版本,确认切换是否成功
  2. grubby --update-kernel=/boot/vmlinuz-$(uname -r) --args="..."
    • 底层动作:调用 grubby 工具修改当前运行内核的启动参数,向/boot/grub2/grub.cfg中追加两个内核参数
    • systemd.unified_cgroup_hierarchy=0:禁用 systemd 的统一 cgroup v2 层级,强制使用混合层级
    • systemd.legacy_systemd_cgroup_controller:启用 systemd 对 cgroup v1 的兼容控制器
  3. reboot
    • 底层动作:向 init 进程发送 SIGTERM 信号,优雅终止所有进程后重启系统,使内核参数生效
  4. 重启后再次执行mount -t cgroup
    • 底层动作:验证 cgroup v1 的各个子系统(cpu、memory、blkio 等)是否已独立挂载到/sys/fs/cgroup下的对应目录

生活类比

这就像把公司的 "统一财务系统"(cgroup v2)换成 "分部门独立记账系统"(cgroup v1)。统一系统更先进,但老的监控工具只能看懂分部门账本;为了兼容现有运维体系,暂时切回旧系统。

坑点(Gotchas)

  1. 最易写错$(uname -r)不能手动替换成固定内核版本,否则升级内核后参数会失效;内核参数的引号必须完整,换行时要加反斜杠\
  2. 隐藏坑 :RHEL9/CentOS Stream 9 中,部分 Docker 版本(<24.0)对 cgroup v2 的资源统计存在 bug,会导致docker stats显示的内存使用率偏差超过 30%
  3. 不可逆风险:如果内核参数写错,系统会无法启动,需进入单用户模式删除错误参数

企业级生产应用(Enterprise Scenario)

  • 使用场景 :千万级并发的电商平台、金融交易系统,依赖docker stats、Prometheus+Grafana 做实时资源监控与告警
  • 进阶优化
    1. 仅在监控节点切换 cgroup v1,业务节点保留 cgroup v2 以获得更好的资源隔离性能
    2. 逐步升级 Docker 到 25.0 + 版本,完全兼容 cgroup v2 的资源统计
    3. 使用 cgroup v2 的memory.peakcpu.stat等新指标,实现更精准的资源预测

课后防宕机指南(Troubleshooting)

  1. 错误现象 :重启后系统卡在Starting systemd...界面
    • 排查思路:进入单用户模式,执行cat /proc/cmdline查看内核参数,删除错误的 cgroup 参数后重启
  2. 错误现象docker stats显示所有容器的 CPU 使用率为 0%
    • 排查思路:执行mount | grep cgroup确认 cgroup 版本,若仍为 cgroup v2,重新执行 grubby 命令并确保内核参数正确写入

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/ # 进程的namespace,
[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

核心解释

命名空间是 Linux 内核提供的 "进程沙箱",每个容器对应一组独立的命名空间,让容器内的进程只能看到自己的 "虚拟世界"。但内核本身是共享的,这是容器与虚拟机最本质的区别,也是所有容器安全问题的根源。

核心代码逐行解析

  1. docker run -d --name web nginx
    • 底层动作:Docker daemon 创建一个新的进程,为该进程依次创建mntpidnetipcutsusercgrouptime8 个命名空间,然后在这些命名空间中启动 nginx 进程
  2. docker inspect web | grep Pid
    • 底层动作:查询 Docker daemon 中存储的容器元数据,获取容器主进程在宿主机上的 PID(4328)
  3. cd /proc/4328/ns/ && ls
    • 底层动作:进入宿主机/proc文件系统中该进程的命名空间目录,查看该进程拥有的所有命名空间句柄
    • 关键:每个命名空间都是一个文件,不同进程的命名空间文件 inode 不同,代表不同的隔离环境
  4. ls -d /sys/fs/cgroup/memory/docker/[容器ID]/
    • 底层动作:查看该容器对应的 cgroup 控制组目录,所有资源限制都通过修改该目录下的文件实现

生活类比

这就像同一栋写字楼里的不同办公室:所有办公室共享同一栋楼的地基(内核)、水电(硬件资源),但每个办公室有自己的门(命名空间),里面的人看不到其他办公室的情况。但如果有人砸穿墙壁(内核漏洞),就能进入其他办公室。

坑点(Gotchas)

  1. 最易踩坑 :误以为容器的内存是完全隔离的,实际上容器内的free命令默认显示的是宿主机的总内存(见 7.2.1 节)
  2. 隐藏风险time命名空间在 Linux 5.6 内核才引入,低版本内核中容器可以修改宿主机的系统时间
  3. 权限泄露 :如果使用--pid=host参数共享宿主机 PID 命名空间,容器内的 root 可以直接杀死宿主机上的所有进程

企业级生产应用

  • 使用场景:微服务架构中,每个服务运行在独立的容器中,通过命名空间实现服务间的故障隔离
  • 进阶优化
    1. 启用user命名空间,将容器内的 root 用户映射到宿主机的非特权用户
    2. 禁用不需要的命名空间(如--ipc=none),减少攻击面
    3. 使用 Linux 6.0 + 内核,启用time命名空间防止时间篡改攻击

课后防宕机指南

  1. 错误现象 :容器内修改系统时间,导致宿主机时间同步异常
    • 排查思路:执行docker inspect [容器ID] | grep HostConfig.TimeMode,确认是否使用了--time=host参数,立即重启容器并移除该参数
  2. 错误现象 :一个容器 OOM 导致宿主机上所有容器被杀死
    • 排查思路:检查是否未设置内存限制(见 7.1.2 节),同时确认内核的oom_kill_allocating_task参数是否开启

2 控制组资源控制的安全

当 docker run 启动一个容器时,Docker 将在后台为容器创建一个独立的控制组策略集合。

Linux Cgroups 提供了很多有用的特性,确保各容器可以公平地分享主机的内存、CPU、磁盘 IO 等资源。

确保当发生在容器内的资源压力不会影响到本地主机系统和其他容器,它在防止拒绝服务攻击 (DDoS) 方面必不可少和容器病毒

bash 复制代码
[root@docker ~]# docker run -it --name test busybox # 内存资源默认没有被
隔离
/ # free -m
used free shared buff/cache available
total
3627 648 516 16 2463 2678
Mem:
used free shared buff/cache available
Swap: 2063 1 2062
/ # exit
[root@docker ~]# free -m
-m
total
Mem:
3627 907 557 15 2463
2719
2062 1 2061
Swap:

核心解释

控制组(Cgroups)是 Linux 内核的 "资源管家",负责给每个进程组分配 CPU、内存、磁盘 IO 等资源的配额。如果没有 Cgroups,一个容器可以耗尽宿主机的所有资源,导致其他容器和宿主机本身崩溃。

核心代码逐行解析

  1. docker run -it --name test busybox
    • 底层动作:创建容器时,Docker daemon 在/sys/fs/cgroup下的各个子系统目录中,为该容器创建一个独立的子目录(控制组)
    • 默认行为:不设置任何资源限制,容器可以使用宿主机的所有可用资源
  2. 容器内执行free -m
    • 底层动作:读取宿主机/proc/meminfo文件的内容,输出宿主机的总内存和使用情况
    • 关键:这是 Docker 默认的 "资源可见性 bug",不是真正的内存隔离失效

生活类比

这就像写字楼的物业给每个办公室分配水电配额:如果不设配额,一个办公室开 100 台空调,会导致整栋楼跳闸。Cgroups 就是物业的水电表和配额开关。

坑点(Gotchas)

  1. 致命坑 :生产环境中绝对不能不设置资源限制,否则一个容器的资源泄露会导致整个节点宕机
  2. 易混淆free命令显示的是宿主机内存,不是容器的可用内存,必须通过docker stats/sys/fs/cgroup/memory/docker/[容器ID]/memory.usage_in_bytes查看真实使用量

企业级生产应用

  • 使用场景:所有生产环境的容器都必须设置资源限制,这是 K8s 调度和高可用的基础
  • 进阶优化
    1. 基于业务的 QPS 和压测结果,设置合理的requestslimits(CPU limits 设为 requests 的 2-3 倍,内存 limits 设为 requests 的 1.5 倍)
    2. 启用 Cgroups 的cpu.cfs_burst_us参数,允许容器在短时间内突破 CPU 配额,应对突发流量
    3. 使用kubectl top和 Prometheus 监控资源使用率,动态调整配额

课后防宕机指南

  1. 错误现象 :宿主机负载突然飙升到 100%,所有容器无响应
    • 排查思路:执行docker stats --no-stream找到 CPU 使用率最高的容器,立即杀死该容器并设置 CPU 限制
  2. 错误现象 :容器频繁被 OOM killed,但free命令显示还有大量内存
    • 排查思路:执行dmesg | grep oom-killer查看 OOM 日志,确认是容器的内存限制过低,还是宿主机内存不足

3 内核能力机制

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

bash

运行

复制代码
[ root@docker-nodel ns]# docker run-it--name test--rm busybox:latest
#fdisk-l
被内核限制了,权限不够

核心解释

传统的 Linux 权限模型是 "全有或全无":root 用户拥有所有权限,普通用户几乎没有权限。能力机制将 root 的权限拆分成 30 多个独立的能力,容器默认只保留最必要的 14 个能力,其余全部禁用。

核心代码逐行解析

  1. docker run -it --name test --rm busybox:latest
    • 底层动作:Docker daemon 在启动容器进程时,调用prctl(PR_SET_CAPBSET_DROP)系统调用,删除所有非白名单的能力
    • 默认白名单:包括CAP_CHOWNCAP_DAC_OVERRIDECAP_FOWNER等 14 个基础能力,不包括CAP_SYS_ADMINCAP_NET_ADMIN等高危能力
  2. 容器内执行fdisk -l
    • 底层动作:fdisk命令需要CAP_SYS_ADMIN能力才能读取磁盘分区表,而该能力不在默认白名单中,内核返回Operation not permitted错误

生活类比

这就像酒店的房卡:普通房卡只能打开自己的房间和公共区域,而总经理的房卡可以打开所有房间。Docker 给容器的是 "普通房卡",而不是 "总经理房卡"。

坑点(Gotchas)

  1. 最危险坑 :不要随意使用--privileged参数,它会给容器所有的内核能力,相当于给了容器宿主机的 root 权限
  2. 易忽略:即使是容器内的 root 用户,也没有默认被禁用的能力,这是 Docker 最重要的安全防线之一

企业级生产应用

  • 使用场景:所有普通业务容器都使用默认的能力白名单,仅特殊容器(如网络插件、存储插件)添加必要的能力
  • 进阶优化
    1. 使用--cap-drop=ALL先删除所有能力,再用--cap-add添加必要的能力,实现最小权限原则
    2. 结合 AppArmor/SELinux,进一步限制容器的文件访问和系统调用
    3. 使用seccomp过滤危险的系统调用,如ptracemount

课后防宕机指南

  1. 错误现象 :容器内执行ping命令提示Operation not permitted
    • 排查思路:确认是否使用了--cap-drop=NET_RAW参数,ping需要CAP_NET_RAW能力
  2. 错误现象 :容器可以修改宿主机的/etc/passwd文件
    • 排查思路:立即检查容器是否使用了--privileged参数,或挂载了宿主机的/etc目录

4 Docker 服务端防护

使用 Docker 容器的核心是 Docker 服务端,确保只有可信的用户才能访问到 Docker 服务。

将容器的 root 用户映射到本地主机上的非 root 用户,减轻容器和主机之间因权限提升而引起的安全问题。

允许 Docker 服务端在非 root 权限下运行,利用安全可靠的子进程来代理执行需要特权权限的操作。这些子进程只允许在特定范围内进行操作。

bash 复制代码
[root@docker ~]# ls -ld /var/lib/docker/ #默认docker是用root用户控制资源的
drwx--x--- 12 root root 171 8月 20 13:21 /var/lib/docker/

核心解释

Docker daemon 默认以 root 用户运行,拥有宿主机的完全控制权。如果攻击者能够访问 Docker daemon 的 API,就可以通过挂载宿主机根目录、创建特权容器等方式,完全控制宿主机。

核心代码逐行解析

  1. ls -ld /var/lib/docker/
    • 底层动作:查看 Docker 数据目录的权限,默认所有者是 root:root,权限是 710,只有 root 用户可以读写
    • 关键:所有容器的镜像、卷、配置文件都存储在这个目录下,一旦被篡改,所有容器都会受到影响

生活类比

这就像写字楼的保安室:保安室控制着整栋楼的所有门禁,如果保安室被攻破,整栋楼就不安全了。Docker daemon 就是这个保安室。

坑点(Gotchas)

  1. 致命安全坑:绝对不要将 Docker daemon 的 TCP 端口(2375)暴露在公网上,且不要使用无认证的 HTTP 协议
  2. 易忽略 :将用户加入docker组相当于给了该用户 root 权限,因为该用户可以通过 Docker daemon 创建特权容器

企业级生产应用

  • 使用场景:所有生产环境的 Docker daemon 都必须启用 TLS 认证,仅允许可信的客户端访问
  • 进阶优化
    1. 启用 Rootless Docker,让 Docker daemon 以非 root 用户运行,彻底消除权限提升风险
    2. 使用 Unix 套接字而不是 TCP 端口访问 Docker daemon,并设置严格的文件权限
    3. 部署 Falco 等运行时安全工具,监控 Docker daemon 的异常操作

课后防宕机指南

  1. 错误现象 :普通用户执行docker ps提示权限不足
    • 排查思路:确认用户是否在docker组中,或是否配置了正确的 TLS 证书
  2. 错误现象 :发现未知的特权容器在运行
    • 排查思路:立即检查 Docker daemon 的访问日志,确认是否有未授权的访问,然后重置所有 TLS 证书并重启 Docker daemon

7.1 Docker 的资源限制

Linux Cgroups 的全称是 Linux Control Group。

是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。

对进程进行优先级设置、审计,以及将进程挂起和恢复等操作。

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

它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 路径下。

执行此命令查看:mount -t cgroup

复制代码
mount -t cgroup
bash 复制代码
[root@docker ~]# mount -t cgroup # 在rhel9中默认使用cgroup2
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)

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

核心解释

Cgroups 的本质是一个基于文件系统的接口,所有资源限制都是通过修改对应目录下的文件实现的。Docker 只是将这些文件操作封装成了docker run的参数,让用户更方便使用。


7.1.1. 限制 cpu 使用

1. 限制 cpu 的使用量
bash 复制代码
[root@docker ~]# docker run -it --rm --name test \
--cpu-period 100000 #设置 CPU 周期的长度,单位为微秒(通常为
100000,即 100 毫秒)
--cpu-quota 20000 ubuntu #设置容器在一个周期内可以使用的 CPU 时间,单位也
是微秒。
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 NI VIRT RES %CPU %MEM
+ COMMAND
PR SHR S
8 root 20 0 2736 1536 1536 R 20.0 0.0 0:00.92 dd 
#使用cpu的百分比
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 #cpu 总量划分
[root@docker ~]# cat /sys/fs/cgroup/cpu/docker/"docker id(所要查看容器的
id)"/cpu.cfs_quota_us #cpu 限制
bash 复制代码
[ root@docker-node -]# docker run test -cpu-period 100000 -cpu-quota 20000 ubuntu
root@46d556cf8e2:/# dd if=/dev/zero of=/dev/null
root@a46d556cf8e2:/#

OCR 修正:原文档中-cpu-period应为--cpu-period-cpu-quota应为--cpu-quota

核心代码逐行解析

  1. docker run -it --rm --name test --cpu-period 100000 --cpu-quota 20000 ubuntu
    • 底层动作:
      1. /sys/fs/cgroup/cpu/docker/[容器ID]/目录下创建cpu.cfs_period_uscpu.cfs_quota_us文件
      2. 100000写入cpu.cfs_period_us:告诉内核,每 100 毫秒为一个 CPU 调度周期
      3. 20000写入cpu.cfs_quota_us:告诉内核,该容器在每个周期内最多可以使用 20 毫秒的 CPU 时间
    • 计算方式:CPU 使用率上限 = cpu-quota /cpu-period = 20000/100000 = 20%(即 0.2 核)
  2. dd if=/dev/zero of=/dev/null &
    • 底层动作:创建一个死循环进程,持续消耗 CPU 资源,用于测试 CPU 限制是否生效
  3. top
    • 底层动作:查看系统进程的 CPU 使用率,验证 dd 进程的 CPU 使用率被限制在 20% 左右
  4. cat /sys/fs/cgroup/cpu/docker/[容器ID]/cpu.cfs_period_us
    • 底层动作:直接读取 Cgroups 文件系统中的配置,验证 Docker 是否正确写入了资源限制

生活类比

这就像公司给每个部门分配会议室使用时间:每天(100 毫秒)最多只能用 2 小时(20 毫秒),用完就只能等第二天。这样所有部门都能公平使用会议室。

坑点(Gotchas)

  1. 最易写错--cpu-period--cpu-quota的单位是微秒 ,不是毫秒;--cpu-period的默认值是 100000,不要随意修改
  2. 隐藏坑 :CPU 限制是硬限制,即使宿主机有空闲 CPU,容器也不能超过配额,会导致突发流量时响应变慢
  3. 多核心坑 :如果宿主机有 4 核,--cpu-quota=400000表示容器可以使用全部 4 核(400% CPU 使用率)

企业级生产应用

  • 使用场景:电商平台的商品详情页、搜索服务等 CPU 密集型应用,防止单个服务耗尽 CPU 资源
  • 进阶优化
    1. 使用--cpus参数代替--cpu-period--cpu-quota,更直观地指定 CPU 核数(如--cpus=0.2等价于上述配置)
    2. 启用 CPU Burst 功能(Docker 20.10+),允许容器在短时间内突破 CPU 配额,应对突发流量
    3. 使用--cpuset-cpus参数将容器绑定到特定的 CPU 核心,减少上下文切换,提高性能

课后防宕机指南

  1. 错误现象 :容器的 CPU 使用率被限制在 100%,但宿主机还有大量空闲 CPU
    • 排查思路:检查--cpu-quota是否设置为 100000,这表示容器只能使用 1 核 CPU;如果需要使用多核心,增大--cpu-quota的值
  2. 错误现象 :设置了 CPU 限制但不生效
    • 排查思路:执行cat /sys/fs/cgroup/cpu/docker/[容器ID]/cpu.cfs_quota_us,确认值是否正确;如果是 cgroup v2,路径为/sys/fs/cgroup/system.slice/docker-[容器ID].scope/cpu.max

2. 限制 cpu 的优先级

bash

运行

bash 复制代码
#确保在系统中只有一个cpu核心在下
[root@docker-node1 ~]# cd /sys/devices/system/cpu/
[root@docker-node1 cpu]# ls
modalias offline possible present
cpu0 cpufreq crash_hotplug isolated modalias offline possible present 
uevent
cpul cpuidle hotplug
cpu1 cpuidle hotplug kernel_max nohz_full online power smt 
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
+ 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 SCPU %MEM
+ 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:/#
root@f61925d3c218:/# dd if=/dev/zero of=/dev/null &
root@f61925d3c218:/# top
PID USER PR NI VIRT RES SHR S %CPU %MEM
+ 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的核心,当cpu都不空闲下才会出现争抢的情况,为了实验效果我们可以关闭一个cpu核心
root@docker ~]# echo 0 > /sys/devices/system/cpu/cpu1/online
[root@docker ~]# cat /proc/cpuinfo
processor : 0
vendor_id
vendor_id : GenuineIntel
cpu family : 6
model
58
model name : Intel(R) Core(TM) i7-3770K CPU @ 3.50GHz
model name
stepping : 9
microcode : 0x21
: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
flags : fpu vme de pse tsc msr paq mce cx8 apic 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 bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit srbds mmio_unknown flags
yes
wp
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 # 设定cpu优先
级,最大为1024,值越大优先级越高,没指定的就是最高的,指定就是限制了;
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 | | 2736 | 1536 | 1536 R | 3.6 | 0.0 | 0:16.74 dd |
| | 20 | 0 | 4588 | 3968 | 3456 S | 0.0 | | 0:00.03 bash |
| 9 root | 20 | 0 | 8856 | 5248 | 3200 R | 0.0 | | 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, %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 3627.1 total, 502.3 free, MiB Swap: 2063.0 total, 2062.3 free, PID USER PR NI VIRT RES SHR S%CPU %MEM + COMMAND 8 root 20 0 2736 1408 1408 R 94.0 0.0 1:09.34 dd
0 stopped, 0 zombie
MiB Mem : 954.6 used, 2471.7 buff/cache 0.7 used. 2672.5 avail Mem
#cpu为被限制 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
安装cgexec;

核心代码逐行解析

  1. echo 0 > cpu1/online
    • 底层动作:向 CPU 设备的online文件写入 0,通知内核禁用该 CPU 核心,所有进程将只能在剩余的 CPU 核心上运行
    • 作用:为了实验 CPU 优先级,确保只有一个 CPU 核心,让资源争抢更明显
  2. docker run -it --rm --cpu-shares 100 ubuntu
    • 底层动作:在/sys/fs/cgroup/cpu/docker/[容器ID]/cpu.shares文件中写入 100
    • 关键:cpu.shares相对优先级,不是绝对配额。默认值是 1024,值越大,获得的 CPU 时间片越多
    • 计算方式:如果有两个容器,A 的 shares 是 100,B 的 shares 是 1024,那么在 CPU 满负载时,A 获得 100/(100+1024)≈9% 的 CPU 时间,B 获得 91%

生活类比

这就像餐厅的排队叫号系统:普通客户(默认 shares=1024)和 VIP 客户(shares=100)同时排队,VIP 客户每叫 1 个号,普通客户叫 10 个号。但如果只有 VIP 客户,他可以使用所有的餐桌。

坑点(Gotchas)

  1. 最易误解--cpu-shares软限制,只有在 CPU 资源紧张时才生效;如果 CPU 有空闲,容器可以使用所有可用 CPU
  2. 易写错--cpu-shares的最小值是 2,最大值是 262144,不是 1024(1024 只是默认值)
  3. 多核心坑--cpu-shares是按所有 CPU 核心的总时间计算的,在多核心系统上,优先级效果会被稀释

企业级生产应用

  • 使用场景:核心业务(如支付服务)设置较高的 CPU 优先级,非核心业务(如日志收集、监控)设置较低的优先级,确保核心业务的响应时间
  • 进阶优化
    1. 结合--cpus硬限制和--cpu-shares软限制,既保证核心业务的优先级,又防止其耗尽所有资源
    2. 使用 K8s 的priorityClass为不同优先级的 Pod 设置不同的 CPU shares
    3. 启用 CPU 管理器的静态策略,将核心业务容器绑定到独占的 CPU 核心,彻底消除资源争抢

课后防宕机指南

  1. 错误现象 :设置了--cpu-shares但优先级不生效
    • 排查思路:确认 CPU 是否处于满负载状态;如果 CPU 有空闲,软限制不会生效
  2. 错误现象 :核心业务容器的 CPU 使用率很低,但响应时间很长
    • 排查思路:检查是否有其他高优先级的容器占用了大量 CPU 时间,适当调高核心业务的--cpu-shares

7.1.2 限制内存使用

复制代码
memory.max_usage_in_bytes
 
memory.memsw.failcnt

memory.kmem.limit_in_bytes memory.oom_control
播放 g 0
主页 共9 视频工具
此>source>云生高级课程>docker>pakges>pm v在ipm中搜索
 快访问
 此电脑
3DD对象
视频 图片 

 音乐
 

source(E) soware(F
网络
三.限制内存使用
[root@docker-node1 ~]# docker run -d --name test --memory 200M --memory-swap 200M
nginx:1.23
4f212de78b0847d54de5508aeec7930c984dec81d59c1d0d007358d55e62bdff
#检测
#安装检测工具
[root@docker-node1 ~]# rpm -ivh libcgroup-0.41-19.el8.x86_64.rpm
[root@docker-node1 ~]# rpm -ivh libcgroup-tools-0.41-19.el8.x86_64.rpm
/sys/fs/cgroup/memory/docker/4f212de78b0847d54de5508aeec7930c984dec81d59c1d0d007
358d55e62bdff/
[root@docker-node1 ~]# cgexec -g 
memory:docker/4f212de78b0847d54de5508aeec7930c984dec81d59c1d0d007358d55e62bdff dd 
if=/dev/zero of=/dev/shm/bigfile bs=1M count=200
已杀死
#开启容器并限制容器使用内存大小
[root@docker system.slice]# docker run -d --name test --memory 200M --memory-swap
200M nginx
#查看容器内存使用限制
[root@docker ~]# cd /sys/fs/cgroup/memory/docker/d09100472de41824bf0省略部分
id96b977369dad843740a1e8e599f430/
[root@docker d091004723d4de41824f6b38a7be9b77369dad843740a1e8e599f430]# cat 
memory.limit_in_bytes
209715200
[root@docker d091004723d4de41824f6b38a7be9977369dad843740a1e8e599f430]# cat 
memory.memsw.limit_in_bytes
209715200
#测试容器内存限制,在容器中我们测试内存限制效果不是很明显,可以利用工具模拟容器在内存中写入数据
#在系统中/dev/shm这个目录被挂在到内存中
[root@docker cgroup]# docker run -d --name test --rm --memory 200M --memory-
swap 200M nginx 
f5017485d69b50cf2e294bf6c65fcd5e679002e25bd9b0eaf9149eee2e379eec
[root@docker cgroup]# cgexec -g 
memory:docker/f5017485d69b50cf2e294bf6c65fcd5e679002e25bd9b0eaf9149eee2e379eec 
dd if=/dev/zero of=/dev/shm/bigfile bs=1M count=150
记录了150+0 的读入
记录了150+0 的写出
157286400字节(157 MB,150 MiB)已复制,0.0543126 s,2.9 GB/s
[root@docker cgroup]# cgexec -g 
memory:docker/f5017485d69b50cf2e294bf6c65fcd5e679002e25bd9b0eaf9149eee2e379eec 
dd if=/dev/zero of=/dev/shm/bigfile bs=1M count=180
记录了180+0 的读入
记录了180+0 的写出
188743680字节(189 MB,180 MiB)已复制,0.0650658 s,2.9 GB/s
[root@docker cgroup]# cgexec -g 
memory:docker/f5017485d69b50cf2e294bf6c65fcd5e679002e25bd9b0eaf9149eee2e379eec 
dd if=/dev/zero of=/dev/shm/bigfile bs=1M count=120
记录了120+0 的读入
记录了120+0 的写出
125829120字节(126 MB,120 MiB)已复制,0.044017 s,2.9 GB/s
[root@docker cgroup]# cgexec -g 
memory:docker/f5017485d69b50cf2e294bf6c65fcd5e679002e25bd9b0eaf9149eee2e379eec 
dd if=/dev/zero of=/dev/shm/bigfile bs=1M count=200
bs=1M count=200
已杀死
#也可以自建控制器
[root@docker ~]# mkdir -p /sys/fs/cgroup/memory/x1/
[root@docker ~]# ls /sys/fs/cgroup/memory/x1/
cgroup.clone_children memory.kmem.tcp.max_usage_in_bytes 
cgroup.clone_children
memory.oom_control
cgroup.event_control
cgroup.event_control memory.kmem.tcp.usage_in_bytes 
memory.pressure_level
cgroup.procs
cgroup.procs memory.kmem.usage_in_bytes 
memory.soft_limit_in_bytes
memory.failcnt memory.limit_in_bytes memory.stat
memory.force_empty
memory.force_empty memory.max_usage_in_bytes 
memory.swappiness
memory.kmem.failcnt
memory.kmem.failcnt memory.memsw.failcnt 
memory.usage_in_bytes
memory.kmem.limit_in_bytes
memory.kmem.limit_in_bytes memory.memsw.limit_in_bytes 
memory.use_hierarchy
memory.kmem.max_usage_in_bytes memory.memsw.max_usage_in_bytes 
notify_on_release
memory.kmem.slabinfo
memory.kmem.slabinfo memory.memsw.usage_in_bytes tasks
tasks
memory.kmem.tcp.failcnt
memory.kmem.tcp.failcnt memory.move_charge_at_immigrate
memory.kmem.tcp.limit_in_bytes memory.numa_stat
[root@docker ~]# echo 209715200 > /sys/fs/cgroup/memory/x1/memory.limit_in_bytes 
#内存可用大小限制
[root@docker ~]# cat /sys/fs/cgroup/memory/x1/tasks # 此控制器被那个进程调用
[root@docker ~]# cgexec -g memory:x1 dd if=/dev/zero of=/dev/shm/bigfile bs=1M 
count=100
记录了100+0 的读入
记录了100+0 的写出
104857600字节(105 MB,100 MiB)已复制,0.0388935 s,2.7 GB/s
[root@docker ~]# free -m
-m
total used free shared buff/cache available
3627 1038 1813 109 1131 2589
Mem:
Swap: 2062 0 2062
[root@docker ~]# cgexec -g memory:x1 dd if=/dev/zero of=/dev/shm/bigfile bs=1M 
count=300
记录了300+0 的读入
记录了300+0 的写出
314572800字节(315 MB,300 MiB)已复制,0.241256 s,1.3 GB/s
[root@docker ~]# free -m
-m
total used free shared buff/cache available
3627 1125 1725 181 1203 2501
Mem:
Swap: 2062 129 1933 #内存溢出部分被写入swap交换分
区
[root@docker ~]# rm -fr /dev/shm/bigfile
[root@docker ~]# echo 209715200 > 
/sys/fs/cgroup/memory/x1/memory.memsw.limit_in_bytes # 内存+swap控制
[root@docker ~]# cgexec -g memory:x1 dd if=/dev/zero of=/dev/shm/bigfile bs=1M 
count=200
已杀死
[root@docker ~]# cgexec -g memory:x1 dd if=/dev/zero of=/dev/shm/bigfile bs=1M 
count=199
已杀死
[root@docker ~]# rm -fr /dev/shm/bigfile
[root@docker ~]#
[root@docker ~]# rm -fr /dev/shm/bigfile
[root@docker ~]# cgexec -g memory:x1 dd if=/dev/zero of=/dev/shm/bigfile bs=1M 
count=180
记录了180+0 的读入
记录了180+0 的写出
188743680字节(189 MB,180 MiB)已复制,0.0660052 s,2.9 GB/s
[root@docker ~]# cgexec -g memory:x1 dd if=/dev/zero of=/dev/shm/bigfile bs=1M 
count=190
记录了190+0 的读入
记录了190+0 的写出
199229440字节(199 MB,190 MiB)已复制,0.0682285 s,2.9 GB/s
[root@docker ~]# cgexec -g memory:x1 dd if=/dev/zero of=/dev/shm/bigfile bs=1M 
count=200
已杀死
[!NOTE]
cgexec -g memory:doceker/容器id -g表示使用指定控制器类型

OCR 修正:原文档中doceker应为docker

核心代码逐行解析

  1. docker run -d --name test --memory 200M --memory-swap 200M nginx:1.23
    • 底层动作:
      1. /sys/fs/cgroup/memory/docker/[容器ID]/目录下创建memory.limit_in_bytesmemory.memsw.limit_in_bytes文件
      2. 209715200(200MB)写入memory.limit_in_bytes:限制容器的物理内存使用上限为 200MB
      3. 209715200写入memory.memsw.limit_in_bytes:限制容器的物理内存 + 交换分区总使用上限为 200MB
    • 关键:当--memory-swap等于--memory时,容器完全不能使用交换分区
  2. rpm -ivh libcgroup-tools-0.41-19.el8.x86_64.rpm
    • 底层动作:安装 cgroup 工具集,提供cgexec等命令,用于在指定的 cgroup 控制组中执行进程
  3. cgexec -g memory:docker/[容器ID] dd if=/dev/zero of=/dev/shm/bigfile bs=1M count=200
    • 底层动作:将dd进程加入到该容器的 memory 控制组中,然后向/dev/shm(内存文件系统)写入 200MB 数据
    • 结果:当写入数据达到 200MB 时,内核触发 OOM killer,杀死该进程,输出 "已杀死"
  4. mkdir -p /sys/fs/cgroup/memory/x1/
    • 底层动作:手动创建一个新的 memory 控制组,所有在该控制组中的进程都将受到该组的资源限制
    • 作用:演示 Cgroups 的原生使用方式,Docker 本质上就是自动帮我们创建这些目录并写入配置

生活类比

这就像给每个办公室分配一个 200 升的垃圾桶(物理内存),并且不允许使用走廊的临时垃圾桶(交换分区)。当垃圾桶满了,保洁员(OOM killer)会直接把垃圾(进程)扔掉。

坑点(Gotchas)

  1. 最致命坑 :如果不设置--memory-swap,默认值是--memory的 2 倍,容器可以使用和物理内存等量的交换分区,会导致严重的性能下降
  2. 易忽略--memory限制的是容器的总内存使用,包括物理内存、缓存和共享内存
  3. OOM 坑:当容器内存达到上限时,内核会杀死容器内的进程,而不是整个容器;如果主进程被杀死,容器会退出

企业级生产应用

  • 使用场景:所有生产环境的容器都必须设置内存限制,防止内存泄露导致宿主机 OOM
  • 进阶优化
    1. 禁用容器的交换分区(--memory-swap等于--memory),避免交换分区导致的性能抖动
    2. 设置--memory-reservation软限制,当宿主机内存紧张时,内核会将容器的内存使用压缩到软限制以下
    3. 使用--kernel-memory限制内核内存的使用,防止容器通过内核内存泄露耗尽宿主机资源

课后防宕机指南

  1. 错误现象 :容器频繁被 OOM killed,但docker stats显示内存使用率很低
    • 排查思路:执行cat /sys/fs/cgroup/memory/docker/[容器ID]/memory.kmem.usage_in_bytes,确认是否是内核内存超过了限制
  2. 错误现象 :设置了内存限制但容器仍然可以使用超过限制的内存
    • 排查思路:检查--memory-swap是否设置为大于--memory的值,容器可能在使用交换分区

7.1.3 限制 docker 的磁盘 io

bash 复制代码
四.磁盘io限制
[root@docker-node1 ~]# docker run -it --name test --rm ubuntu:latest
root@0530d0384458:/# dd if=/dev/zero of=/bigfile bs=1M count=1000
1000+0 records in
1000+0 records out
1048576000 bytes (1.0 GB, 1000 MiB) copied, 1.11958 s, 937 MB/s
#写入速率限制
[root@docker-node1 ~]# docker run -it --name test --device-write-bps 
/dev/nvme0n1:30M --rm ubuntu:latest
root@a1b01e27b9a2:/# dd if=/dev/zero of=/bigfile bs=1M count=100 oflag=direct
100+0 records in
100+0 records out
104857600 bytes (105 MB, 100 MiB) copied, 3.33518 s, 31.4 MB/s
发现是:先把内容写到内存里,然后从内存里搬到硬盘;
oflag=direct:所以要用硬盘直通
[root@docker ~]# docker run -it --rm \
--device-write-bps #指定容器使用磁盘io的速率
/dev/nvme0n1:30M \ #/dev/nvme0n1是指定系统的磁盘,30M即每秒30M数据
ubuntu
root@a4e9567a666d:/# dd if=/dev/zero of=bigfile # 开启容器后会发现速度和设定不匹
配,是因为系统的缓存机制
^C592896+0 records in
592895+0 records out
303562240 bytes (304 MB, 289 MiB) copied, 2.91061 s, 104 MB/s
root@a4e9567a666d:/# ^C
root@a4e9567a666d:/# dd if=/dev/zero of=bigfile bs=1M count=100
100+0 records in
100+0 records out
104857600 bytes (105 MB, 100 MiB) copied, 0.0515779 s, 2.0 GB/s
root@a4e9567a666d:/# dd if=/dev/zero of=bigfile bs=1M count=100 oflag=direct 
#设定dd命令直接写入磁盘
100+0 records in
100+0 records out
104857600 bytes (105 MB, 100 MiB) copied, 3.33545 s, 31.4 MB/s
以上就是关于算力的

核心代码逐行解析

  1. docker run -it --name test --device-write-bps /dev/nvme0n1:30M --rm ubuntu:latest
    • 底层动作:
      1. /sys/fs/cgroup/blkio/docker/[容器ID]/目录下创建blkio.throttle.write_bps_device文件
      2. 写入259:0 31457280(259:0 是/dev/nvme0n1的设备号,31457280 是 30MB/s)
      3. 告诉内核,该容器向/dev/nvme0n1磁盘写入数据的速率不能超过 30MB/s
  2. dd if=/dev/zero of=/bigfile bs=1M count=100
    • 底层动作:使用系统缓存写入数据,数据先写入内存的页缓存,然后由内核异步刷入磁盘
    • 结果:速度不受磁盘 IO 限制,因为实际上是在写内存
  3. dd if=/dev/zero of=/bigfile bs=1M count=100 oflag=direct
    • 底层动作:绕过系统缓存,直接将数据写入磁盘
    • 结果:写入速度被限制在 30MB/s 左右,验证磁盘 IO 限制生效

生活类比

这就像快递站的卸货通道:给每个快递公司分配 30 件 / 分钟的卸货速率,防止某个公司的货车占用整个通道,导致其他公司的快递无法卸货。系统缓存就像快递站的临时仓库,先把货卸到仓库里,再慢慢搬到车上。

坑点(Gotchas)

  1. 最易踩坑 :磁盘 IO 限制只对直接 IO 生效,对缓存 IO 无效;测试时必须加oflag=direct参数
  2. 设备号坑--device-write-bps后面跟的是设备名,不是分区名;如果写错设备名,限制不会生效
  3. 只读坑--device-read-bps限制读取速率,--device-write-bps限制写入速率,两者是独立的

企业级生产应用

  • 使用场景:日志收集、数据备份等 IO 密集型应用,防止其占用过多磁盘 IO,影响核心业务的性能
  • 进阶优化
    1. 使用--device-read-iops--device-write-iops限制 IOPS,比限制带宽更适合小文件读写场景
    2. 将不同 IO 特性的容器调度到不同的磁盘上,核心业务使用 SSD,非核心业务使用 HDD
    3. 使用 Ceph 等分布式存储,将容器的磁盘 IO 分散到多个节点上,提高整体 IO 性能

课后防宕机指南

  1. 错误现象 :设置了磁盘 IO 限制但不生效
    • 排查思路:执行lsblk确认磁盘设备名是否正确;测试时是否使用了oflag=direct参数
  2. 错误现象 :容器的磁盘写入速度远低于限制值
    • 排查思路:检查宿主机的磁盘本身是否已经达到性能上限;使用iostat命令查看磁盘的使用率和响应时间

7.2 Docker 的安全加固

7.2.1 Docker 默认隔离性

在系统中运行容器,我们会发现资源并没有完全隔离开系统内存使用情况

bash 复制代码
[root@docker ~]# free -m #
total used free shared buff/cache available
shared buff/cache
3627 1128 1714 207 1238
2498
Mem:
total free available
Swap: 2062 0 2062
[root@docker ~] # docker run --rm --memory 200M -it ubuntu
root@e06bdc13b764:/# free -m # 容器中内存使用情况
used
207
3627 1211 1630 1239 2415
Mem:
Swap: 2062
#虽然我们限制了容器的内容使用情况,但是查看到的信息依然是系统中内存的使用信息,并没有隔离开

核心解释

这是 Docker 最著名的 "资源可见性 bug":容器内的/proc文件系统默认是宿主机的/proc的挂载,所以freetop等命令读取的是宿主机的资源信息,而不是容器的资源限制。这会导致很多应用(如 JVM)错误地计算可用内存,引发 OOM。


7.2.2 解决 Docker 的默认隔离性

LXCFS 是一个为 LXC (Linux Containers) 容器提供增强文件系统功能的工具。主要功能

  1. 资源可见性:LXCFS 可以使容器内的进程看到准确的 CPU、内存和磁盘 I/O 等资源使用信息。在没有 LXCFS 时,容器内看到的资源信息可能不准确,这会影响到在容器内运行的应用程序对资源 的评估和管理。
  2. 性能监控:方便对容器内的资源使用情况进行监控和性能分析。通过提供准确的资源信息,管理员和开发 人员可以更好地了解容器化应用的性能瓶颈,并进行相应的优化。安装 lxcfs# 在 rhel9 中 lxcfs 是被包含在 epel 源中,我们可以直接下载安装包进行安装
bash 复制代码
[root@docker ~]# ls lxcfs
lxcfs-5.0.4-1.el9.x86_64.rpm lxc-libs-4.0.12-1.el9.x86_64.rpm lxc-templates-
4.0.12-1.el9.x86_64.rpm
[root@docker ~]# dnf install lxcfs/*.rpm
运行lxcfs并解决容器隔离性
[root@docker ~]# lxcfs /var/lib/lxcfs &
[root@docker ~]# docker run -it -m 256m \
-v /var/lib/lxcfs/proc/cpuinfo:/proc/cpuinfo:rw \
-v /var/lib/lxcfs/proc/diskstats:/proc/diskstats:rw \
-v /var/lib/lxcfs/proc/meminfo:/proc/meminfo:rw \
-v /var/lib/lxcfs/proc/stat:/proc/stat:rw \
-v /var/lib/lxcfs/proc/swaps:/proc/swaps:rw \
-v /var/lib/lxcfs/proc/uptime:/proc/uptime:rw \
ubuntu
root@69ec0c67ff04:/# free -m
-m
total used free shared buff/cache available
Mem: 256 1 254 0 0 254
Swap: 512 0 512

核心代码逐行解析

  1. lxcfs /var/lib/lxcfs &
    • 底层动作:启动 LXCFS 守护进程,在/var/lib/lxcfs目录下创建一个虚拟文件系统,模拟容器的/proc目录
    • 关键:LXCFS 会读取每个容器的 Cgroups 资源限制,动态生成对应的/proc文件内容
  2. docker run -it -m 256m -v /var/lib/lxcfs/proc/meminfo:/proc/meminfo:rw ubuntu
    • 底层动作:将 LXCFS 生成的虚拟meminfo文件挂载到容器内的/proc/meminfo,覆盖宿主机的meminfo
    • 结果:容器内执行free -m时,读取的是 LXCFS 生成的内容,显示容器的可用内存为 256MB,而不是宿主机的总内存

生活类比

这就像给每个办公室装一个独立的电表:原来的电表显示的是整栋楼的用电量,现在每个办公室有自己的电表,里面的人只能看到自己办公室的用电量。

坑点(Gotchas)

  1. 最易写错 :挂载 LXCFS 文件时,必须使用rw权限,否则容器内无法读取文件内容
  2. 性能坑:LXCFS 会增加一定的系统开销,在高并发场景下,性能下降约 5%-10%
  3. 兼容性坑 :部分应用(如 Go 语言程序)会直接读取 Cgroups 文件系统,而不是/proc,LXCFS 对这些应用无效

企业级生产应用

  • 使用场景 :运行 JVM、Python 等需要读取/proc/meminfo来计算堆内存大小的应用
  • 进阶优化
    1. 在 K8s 集群中,使用 DaemonSet 部署 LXCFS,自动为所有 Pod 挂载 LXCFS 文件
    2. 升级到 Docker 25.0 + 版本,原生支持容器资源可见性,不再需要 LXCFS
    3. 对于 Go 语言应用,直接使用runtime.ReadMemStats()读取内存信息,避免依赖/proc

课后防宕机指南

  1. 错误现象 :容器内free -m仍然显示宿主机的内存
    • 排查思路:检查 LXCFS 是否正常运行;挂载路径是否正确;是否使用了rw权限
  2. 错误现象 :JVM 容器的堆内存设置为容器内存的 80%,但仍然频繁 OOM
    • 排查思路:确认是否安装了 LXCFS;JVM 版本是否支持容器内存检测(JDK 8u191 + 原生支持)

7.2.3 容器特权

bash 复制代码
五.容器特权
[root@docker-node1 ~]# docker run -it --name test --rm busybox:latest
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0@if41: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether d2:c7:6e:f3:83:73 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
/ # ip a a 1.2.3.4/24 eth0@if41
ip: either "local" is duplicate, or "eth0@if41" is garbage
/ # fdisk -l
/ # exit
[root@docker-node1 ~]# docker run -it --rm --privileged busybox:latest
/ # fdisk -l
Disk /dev/nvme0n1: 100 GB, 107374182400 bytes, 209715200 sectors
411206 cylinders, 255 heads, 2 sectors/track
Units: sectors of 1 * 512 = 512 bytes
Device Boot StartCHS EndCHS StartLBA EndLBA sectors size
Id Type
/dev/nvme0n1p1 * 4,4,1 1023,254,2 2048 2099199 2097152 1024M 
83 Linux
/dev/nvme0n1p2 1023,254,2 1023,254,2 2099200 10307583 8208384 4008M 
82 Linux swap
/dev/nvme0n1p3 1023,254,2 1023,254,2 10307584 209715199 199407616 95.0G 
83 Linux
#开启指定白名单权限
root@docker-node1 ~]# docker run -it --rm --cap-add CAP_NET_ADMIN 
busybox:latest
/ # fdisk -l
/ # ifconfig
eth0 Link encap:Ethernet HWaddr BE:A5:B5:C9:A9:16
eth0
inet addr:172.17.0.2 Bcast:172.17.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:11 errors:0 dropped:0 overruns:0 frame:0
TX packets:3 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:946 (946.0 B) TX bytes:126 (126.0 B)
/ # ip a a 1.2.3.4/24 dev eth0
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0@if45: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether be:a5:b5:c9:a9:16 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
inet 1.2.3.4/24 scope global eth0
valid_lft forever preferred_lft forever
在容器中默认情况下即使我是容器的超级用户也无法修改某些系统设定,比如网络
[root@docker ~]# docker run --rm -it busybox
/ # whoami
root
# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
27: eth0@if28: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
/ # ip a a 192.168.0.100/24 dev eth0
ip: RTNETLINK answers: Operation not permitted
这是因为容器使用的很多资源都是和系统真实主机公用的,如果允许容器修改这些重要资源,系统的稳 定性会变的非常差
但是由于某些需要求,容器需要控制一些默认控制不了的资源,如何解决此问题,这时我们就要设置容 器特权
[root@docker ~]# docker run --rm -it --privileged busybox
# id root
uid=0(root) gid=0(root) groups=0(root),10(wheel)
/ # ip a a 192.168.0.100/24 dev eth0
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
29: eth0@if30: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
inet 192.168.0.100/24 scope global eth0
valid_lft forever preferred_lft forever
/ # fdisk -l
Disk /dev/nvme0n1: 100 GB, 107374182400 bytes, 209715200 sectors
13003 cylinders, 256 heads, 63 sectors/track
Units: sectors of 1 * 512 = 512 bytes
Device Boot StartCHS EndCHS StartLBA EndLBA Sectors size
Id Type
/dev/nvme0n1p1 0,0,2 1023,255,63 1 209715199 209715199 99.9G 
ee EFI GPT
#如果添加了--privileged 参数开启容器,容器获得权限近乎于宿主机的root用户

核心代码逐行解析

  1. docker run -it --rm --privileged busybox:latest
    • 底层动作:
      1. 禁用所有的能力限制,给容器添加所有 30 + 个内核能力
      2. 禁用 AppArmor/SELinux 的安全策略
      3. 允许容器访问宿主机的所有设备文件
      4. 关闭所有的 seccomp 系统调用过滤
    • 结果:容器内的 root 用户拥有和宿主机 root 用户几乎完全相同的权限,可以修改宿主机的网络、磁盘、内核参数等
  2. docker run -it --rm --cap-add CAP_NET_ADMIN busybox:latest
    • 底层动作:仅给容器添加CAP_NET_ADMIN能力,允许容器修改网络配置,但不允许其他高危操作(如读取磁盘分区表)

生活类比

这就像给酒店的普通房卡升级成总经理房卡:普通房卡只能开自己的房间,总经理房卡可以开所有房间,还能修改酒店的门禁系统、查看监控等。--cap-add就是只给房卡增加开某个特定房间的权限,而不是全部权限。

坑点(Gotchas)

  1. 最危险坑生产环境中绝对不要使用--privileged参数,它会彻底打破容器的安全隔离,攻击者可以通过容器完全控制宿主机
  2. 易忽略 :即使不使用--privileged,挂载宿主机的/dev目录也会给容器几乎相同的权限
  3. 权限泄露CAP_SYS_ADMIN能力包含了大量高危操作,如挂载文件系统、修改内核参数等,非必要不要添加

企业级生产应用

  • 使用场景:仅网络插件(如 Calico、Flannel)、存储插件(如 CSI 驱动)等基础设施容器需要添加特定的能力
  • 进阶优化
    1. 严格遵循最小权限原则,使用--cap-drop=ALL先删除所有能力,再用--cap-add添加必要的能力
    2. 使用 Pod Security Standards(PSS)限制 K8s 集群中 Pod 的权限,禁止使用特权容器
    3. 部署 Trivy 等镜像扫描工具,检测镜像中是否存在不必要的能力配置

课后防宕机指南

  1. 错误现象 :容器内执行ip link set eth0 down提示权限不足
    • 排查思路:确认是否添加了CAP_NET_ADMIN能力,该能力是修改网络接口状态所必需的
  2. 错误现象 :发现特权容器被入侵,攻击者在宿主机上创建了恶意进程
    • 排查思路:立即杀死该容器,隔离宿主机,检查宿主机的所有进程和文件,然后重新部署整个节点

7.2.4 容器特权的白名单

--privileged=true 的权限非常大,接近于宿主机的权限,为了防止用户的滥用,需要增加限制,只提供 给容器必须的权限。此时 Docker 提供了权限白名单的机制,使用 --cap-add 添加必要的权限

capabilities 手册地址:http://man7.org/linux/man-pages/man7/capabilities.7.html

复制代码
CAP_MAC_OVERRIDE (since Linux 2.6.25)
Override randtory Access Cntrol c). Ipleneted for

CAP_NET_AOMIN


CAP_NET_BROADCAST
(Unused Make socket broadcasts, and listen to multicasts

i

privileged b
 羊
swap
usybox: latest
#限制容器对网络有root权限
[root@docker ~]# docker run --rm -it --cap-add NET_ADMIN busybox
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
31: eth0@if32: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
/ # ip a a 192.168.0.100/24 dev eth0 # 网络可以设定
/ 无法管理磁盘

OCR 修正:原文档中randtory应为mandatoryCntrol应为ControlIpleneted应为ImplementedCAP_NET_AOMIN应为CAP_NET_ADMIN

核心解释

Docker 的权限白名单机制是容器安全的核心防线,它将 root 的权限拆分成细粒度的能力,只给容器提供运行所必需的最小权限。常用的能力包括:

  • CAP_NET_ADMIN:修改网络配置
  • CAP_SYS_TIME:修改系统时间
  • CAP_SYS_PTRACE:调试进程
  • CAP_MKNOD:创建设备文件

企业级生产最佳实践

  1. 所有业务容器必须使用--cap-drop=ALL删除所有能力
  2. 仅添加容器运行所必需的能力,如 Web 服务器只需要CAP_NET_BIND_SERVICE(绑定 1024 以下端口)
  3. 定期审计容器的能力配置,删除不必要的能力

原文档附录(完整保留)

plaintext

复制代码
导入镜像:
话服器工具会适分多物包置
 

ubuntu-latest.tar.gz
-i ubuntu-latest.tar.gz 80.56MB/80.56MB

OCR 修正:原文档中 "话服器工具会适分多物包置" 应为 "服务器工具会适配多包部署"

名称 修改日期 类型 大小
Images 2025/8/28 15:08 文件夹
packages 2026/3/21 14:25 文件夹
视频 2025/10/15 15:19 文件夹
Docker 容器技术.md 2026/3/22 11:24 Markdown File 103KB
Docker 容器技术.pdf 2025/10/9 9:22 PDF 文件 3,054 KB
podman 的基本操作.md 2024/11/7 18:26 Markdown File 10KB

此电脑 > source > 云原生高级课程 > 8.docker >

表格

名称 修改日期 类型 大小
1.lvs 四层负载均衡实战 2026/2/21 14:56 文件夹
2.happy 2026/1/26 16:45 文件夹
3.keepalived 2026/2/21 10:35 文件夹
4. 企业高性能 web 服务器 2026/2/6 09:45 文件夹
5. 企业级 web 应用服务器 TOMCAT 2026/2/6 10:37 文件夹
6.mysql 集群 2026/3/3 14:44 文件夹
7. 企业级 NoSql 数据库 redis 2026/3/14 11:58 文件夹
8.docker 2026/3/22 11:24 文件夹
9.kubernetes 2026/3/6 11:24 文件夹
10.Zabbix 2026/2/21 14:55 文件夹
11.CICD 持续集成与持续交付 2026/1/9 11:34 文件夹
docs 2026/1/9 15:57 文件夹
redhat10 课程 2026/1/12 11:08 文件夹
视频 2026/3/14 14:03 文件夹
pdf.zip 2026/2/24 10:20 ZIP 压缩文件 33.527KB
架构图.eddx 2024/7/21 13:25 Edraw.Document 926KB
架构图.jpg 2024/7/21 13:26 JPG 文件 5,709 KB

表格

名称 修改日期 类型 大小
dockerimages 2026/3/21 15:04 文件夹
harbor 2025/10/10 11:54 文件夹
panel 2025/10/10 11:55 文件夹
.rpm 2025/8/28 15:09 文件夹
相关推荐
爱吃苹果的梨叔1 小时前
2026年清虹分布式坐席系统如何破局技术内卷与运维成本困局
运维·分布式
小周技术驿站1 小时前
Linux 基础命令详解
linux·前端·chrome·ubuntu·centos
终端行者1 小时前
Jenkins Pipeline 构建后推送到Nexus制品库 jenkins 如何连接Nexus?企业级实战 --中 Jenkins 连接Nexus 实战
运维·ci/cd·docker·jenkins·nexus
kdxiaojie1 小时前
U-Boot分析【学习笔记】(7)
linux·笔记·学习
www.021 小时前
通过 SSH 隧道将 GPT 调教为服务器专属 Agent(个人记录)
linux·服务器·vscode·gpt·大模型·ssh·api转发
张小姐的猫1 小时前
【Linux】多线程(中)—— 线程控制接口 | 线程库 | 线程局部存储
linux·运维·服务器
脆皮炸鸡7551 小时前
大山之二:文件系统(Ext系列)
linux·开发语言·经验分享·学习方法
打工人小夏1 小时前
使用finalshell在新服务器上部署前端页面
linux·服务器·前端·vue.js
终端行者1 小时前
Jenkins Pipeline 构建后推送到Nexus制品库 jenkins 如何连接Nexus?企业级实战 --上 Nexus部署
运维·ci/cd·jenkins·nexus