Docker相关操作¶
一. Docker与操作系统的关系¶
Docker是系统内的一个进程,靠namespace隔离。
容器没有自己的单独的内核,使用的内核是宿主机的。
对操作系统来说,容器的运行就是一个进程。
将容器所对应的进程结束掉,容器也会结束。
容器的生命周期完全取决于1号进程,当1号进程结束时,容器退出。
二. Docker的隔离机制¶
Docker 的隔离基于 Linux 内核的四种机制,而非虚拟机。四者从不同维度协同工作:
Docker 隔离 = Namespace(视角隔离)+ Cgroups(资源限制)+ UnionFS(文件分层)+ Capabilities/Seccomp(安全裁剪)
1. Namespace(命名空间)— 「我能看到什么」¶
Namespace 管的是可见性——每个容器拿到自己独立的"视图",看不到宿主机和其他容器的东西。
# 先启动一个虚拟机
[root@4 ~]# docker run -d --rm --name 123 httpd:latest
3d04873fa53c85b3af0100a3df8ff48b129fc3323d843baf3159ae2ae153e84e
# 获取PID号
[root@4 ~]# docker inspect 123 --format '{{.State.Pid}}'
5836
# 查看命名空间
[root@4 ~]# ls -l /proc/5836/ns/
总用量 0
lrwxrwxrwx 1 root root 0 7月 15 10:42 cgroup -> 'cgroup:[4026532968]'
lrwxrwxrwx 1 root root 0 7月 15 10:42 ipc -> 'ipc:[4026532966]'
lrwxrwxrwx 1 root root 0 7月 15 10:37 mnt -> 'mnt:[4026532964]'
lrwxrwxrwx 1 root root 0 7月 15 10:37 net -> 'net:[4026532969]'
lrwxrwxrwx 1 root root 0 7月 15 10:42 pid -> 'pid:[4026532967]'
lrwxrwxrwx 1 root root 0 7月 15 10:42 pid_for_children -> 'pid:[4026532967]'
lrwxrwxrwx 1 root root 0 7月 15 10:42 time -> 'time:[4026533023]'
lrwxrwxrwx 1 root root 0 7月 15 10:42 time_for_children -> 'time:[4026533023]'
lrwxrwxrwx 1 root root 0 7月 15 10:42 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 7月 15 10:42 uts -> 'uts:[4026532965]'
| Namespace | 作用 | 效果 |
|---|---|---|
| pid | 进程ID隔离 | 容器内进程从PID=1开始,容器内看不到宿主机进程 |
| mnt | 挂载点隔离 | 容器有自己的根目录文件系统 |
| net | 网络栈隔离 | 容器有独立的网卡、IP和端口空间 |
| UTS | 主机名/域名隔离 | 容器可以使用自己的hostname,不影响宿主机 |
| IPC | 进程间通信隔离 | 信号量、消息队列互不干扰 |
| user | 用户/组ID隔离 | 容器内的root映射到宿主机的普通用户 |
| cgroup | cgroup视图隔离 | 容器只能看到自己的资源限制 |
| time | 时间视图隔离 | 容器内的 /proc/uptime 可能不同于宿主(墙上时钟 date 不受影响) |
2. Cgroups(控制组)— 「我能用多少资源」¶
Namespace 让你看不见别人,Cgroups 让你伤害不到别人。
没有 Cgroups 时,一个容器的内存泄漏就能吃光宿主机内存,导致其他容器被 OOM Killer 随机杀掉——这不叫隔离。Cgroups 在资源层面围了一个围栏:
- CPU:限制容器能用几个核(
--cpus=2) - 内存:硬限制 + OOM 只杀自己容器内的进程(
-m 300MB) - 磁盘 I/O:限制读写速率
- 进程数:限制容器内能 fork 的进程数上限
有了 Cgroups,一个容器资源超限只会影响自己,不会连累其他容器。详细操作见第三节。
3. UnionFS / OverlayFS(联合文件系统)— 「我的文件在哪」¶
Docker 镜像由只读层堆叠而成,容器启动时在最上面加一个可写层:
┌──────────────────────┐
│ 容器层(可写) │ ← 修改的文件都写在这里
├──────────────────────┤
│ 层3 COPY index.html │ ← 只读
│ 层2 apt install nginx│ ← 只读
│ 层1 ubuntu:22.04 │ ← 只读
└──────────────────────┘
写时复制(Copy-on-Write):修改只读层的文件时,系统先把文件拷贝到容器层再修改,原文件在只读层保持不变。
删除:在容器层创建一个"已删除"标记,只读层的原文件不受影响。
带来的好处:
- 镜像复用,省磁盘:10 个容器共享同一份只读层,各自只有薄薄的可写层
- 快速启动:新容器不需要复制整个文件系统
- 删容器不影响镜像:删除可写层,镜像毫发无伤
4. Capabilities + Seccomp — 「即使 root 也不是真正的 root」¶
容器里的 root 能做什么,不能做什么?
Linux 传统上 root(UID=0)拥有全部权限。Docker 把 root 的权力拆成了 40+ 个小开关(Capabilities),默认只保留最安全的 ~14 个,其余全部拿走:
| 能力 | 能做什么 | Docker 默认 |
|---|---|---|
| CAP_SYS_ADMIN | 挂载文件系统、修改内核参数 | ❌ 拿走 |
| CAP_SYS_MODULE | 加载/卸载内核模块 | ❌ 拿走 |
| CAP_SYS_TIME | 修改系统时间 | ❌ 拿走 |
| CAP_SYS_BOOT | 重启机器 | ❌ 拿走 |
| CAP_NET_RAW | 使用原始套接字(如 ping) | ✅ 保留 |
| CAP_NET_BIND_SERVICE | 绑定 1024 以下的端口 | ✅ 保留 |
| CAP_CHOWN | 修改文件所有者 | ✅ 保留 |
Seccomp 在系统调用层面再加一层拦截——就算某个 Capability 保留了,它底层对应的危险系统调用也会被 Seccomp 过滤掉。Docker 默认禁用了约 44 个系统调用(如 reboot、kexec_load)。
所以:默认情况下容器 root 就是宿主机 root(UID 都是 0),但权力被 Capabilities 和 Seccomp 大幅裁剪了。如果再加上 User Namespace 映射(
--userns-remap),则容器 UID=0 映射到宿主机普通用户 UID=65536,连 root 身份都不是了。
三. Docker的资源限制¶
1. 设置内存限制¶
a. 只设置内存
-m或--memory: 设置内存的使用限额,比如100MB,2GB
[root@4 ~]# docker run -itd --name test01 -m 300MB httpd:latest
b58697d745eaec4e06343e57f256d0502ad7d907e9ce1eff5b56008c48942bd6
[root@4 ~]# docker run -itd --name test02 -m 500MB httpd:latest
496e3b2375b76cdac050405ca3ce7e7134fe73d15b1a219564b517f3f7cfc1b7
[root@4 ~]# docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
496e3b2375b7 test02 0.00% 12.55MiB / 500MiB 2.51% 936B / 126B 0B / 4.1kB 82
b58697d745ea test01 0.00% 18.14MiB / 300MiB 6.05% 1.28kB / 126B 13.4MB / 4.1kB 82
b. 设置内存和swap
--memory-swap: 设置内存+swap的使用限额。
[root@4 ~]# docker run -itd --name test03 -m 300MB --memory-swap 400MB httpd:latest
688c0d219fe2855c139ec8492ce021fff70bed623594d31ba4d2a6e4644823b0
[root@4 ~]# docker run -itd --name test04 -m 200MB --memory-swap 500MB httpd:latest
0c12d904f1a7ebe4197152547c80b1e83bec6a64f5aff81b74e8bffe436bed64
[root@4 ~]# docker inspect test03 | grep -E "Memory|MemorySwap"
"Memory": 314572800,
"MemoryReservation": 0,
"MemorySwap": 419430400,
"MemorySwappiness": null,
[root@4 ~]# docker inspect test04 | grep -E "Memory|MemorySwap"
"Memory": 209715200,
"MemoryReservation": 0,
"MemorySwap": 524288000,
"MemorySwappiness": null,
注: 设置--memory-swap必须和--memory一起设置,不设置--memory而只设置--memory-swap会报错。
--memory设置的是内存限制,--memory-swap设置的是内存和swap的总量,后者一定要大于前者。
[root@4 ~]# docker stop $(docker ps -a -q)
0c12d904f1a7
688c0d219fe2
496e3b2375b7
b58697d745ea
[root@4 ~]# docker rm $(docker ps -a -q)
0c12d904f1a7
688c0d219fe2
496e3b2375b7
b58697d745ea
2. 设置CPU限制¶
a. 对CPU份额的控制
一般来说,我们设置CPU限制,设置的都是CPU份额控制,用-c或--cpu-shares控制
--cpu-shares 的默认值是 1024,它是一个相对权重而非绝对配额。当多个容器争抢 CPU 时,按各自权重比例分配;没有竞争时,单个容器可以用满所有 CPU。
例如a分配1024,b分配512。那么他们争抢CPU时的占用时间比例就是2比1
[root@4 ~]# docker run -itd --name "cont-A" --rm -c 1024 httpd:latest
d172bbc02a03b6c157beb42eb07d8025c36b22af4e85c6a36224b5abacb3a483
[root@4 ~]# docker run -itd --name "cont-B" --rm -c 512 httpd:latest
7f54eb2f04295efcb083b40694244cfa6f9eda989116bb1e7e3821241524c472
[root@4 ~]# docker inspect cont-A cont-B --format='容器: {{.Name}} , CPU份额: {{.HostConfig.CpuShares}}'
容器: /cont-A , CPU份额: 1024
容器: /cont-B , CPU份额: 512
# 我们也可以用docker stats来查看他们的相对比率
b. 对具体CPU核的控制
--cpuset-cpus=""
我们首先要查看CPU的核心数,CPU的核心从0开始计数,0,1代表有两核。0,1,2,3代表CPU有四核
[root@4 ~]# docker run -itd --name 'cont' --cpuset-cpus="1,0" busybox:latest
6faa140adcde55331c028ffa8b2af9733a84e736038112bdcba369b2127634d4
表示容器可以在cpu0和1这两核上运行
c. 对CPU核心数的限制
--cpus=
例如--cpus=2就表示容器最多使用两个CPU,也可以用小数,比如--cpus=1.5
[root@4 ~]# docker run -itd --name "cont-c" --rm --cpus=1.5 busybox:latest
[root@4 ~]# docker exec -d cont-c sh -c "while :; do :; done & while :; do :; done &"
[root@4 ~]# docker stats cont-c --no-stream
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
a98e546d67cd cont-c 151.85% 1.398MiB / 1.698GiB 0.08% 876B / 126B 131kB / 0B 3
d. 绝对的CPU周期控制
--cpu-period或--cpu-quota单位为微秒us。1000us=1ms 1000ms=1s
--cpu-period(时间周期/尺子):规定“多久算一个结算周期”。单位是微秒(μs)。默认值是 100000(即 0.1 秒)。--cpu-quota(时间配额/预算):规定“在每个周期里,最多能跑多长时间”。单位也是微秒。
[root@4 ~]# docker run -itd --name mongo1 --rm --cpuset-cpus 1 --cpu-quota=5000 --cpu-period=10000 busybox:latest
docker run -itd --name mongo2 --rm --cpuset-cpus 1 --cpu-quota=5000 --cpu-period=50000 busybox:latest
9ca74ceac630a32dc07e94687841e3072e0b0365c3c3891ca69d6f72b4b075b2
dfb2d0c2fbc19e095683f52b09b4a9bbf04d59c3153647d449b1846ccc556b98
[root@4 ~]# docker exec -d mongo1 sh -c "while :; do :; done & while :; do :; done &"
docker exec -d mongo2 sh -c "while :; do :; done & while :; do :; done &"
[root@4 ~]# docker stats --no-stream
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
dfb2d0c2fbc1 mongo2 10.17% 1.328MiB / 1.698GiB 0.08% 740B / 126B 0B / 0B 3
9ca74ceac630 mongo1 49.92% 1.328MiB / 1.698GiB 0.08% 1kB / 126B 0B / 0B 3
[root@4 ~]# docker stats --no-stream
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
dfb2d0c2fbc1 mongo2 9.99% 1.328MiB / 1.698GiB 0.08% 880B / 126B 0B / 0B 3
9ca74ceac630 mongo1 50.09% 1.328MiB / 1.698GiB 0.08% 1.14kB / 126B 0B / 0B 3
docker实际占用CPU资源时间为--cpu-quota除以--cpu-period,因此我的mongo2占用时间比率约为百分之10,而mongo1为百分之50
e. 小结
--cpu-shares → 相对权重,竞争时生效(软限制)
--cpus → 硬上限,绝对限制
--cpuset-cpus → 绑核,限定只能跑在哪些核上
--cpu-quota/period → 精确到微秒的CPU时间控制
四. 使用export和import命令制作镜像¶
1. 先启动一个镜像¶
[root@4 ~]# docker run -d --name test busybox:latest sleep 99999999
caa6c145f9137d126c3ade05f9c1f72406c0ad0ec79d32240c79c205dab47142
2. 进入镜像,然后创建两个目录¶
[root@4 ~]# docker exec -it test sh
/ # ls
bin dev etc home lib lib64 proc root sys tmp usr var
/ # mkdir /abc /test
/ # ls
abc bin dev etc home lib lib64 proc root sys test tmp usr var
/ # exit
3. 导出容器文件¶
4. 利用导出的文件制作成新镜像¶
[root@4 ~]# docker import test-busybox.tgz 123:import
sha256:4fd901188fc746f76f03053c6a96353c9f3670d6fac7621da67328a3d5a97278