Docker与K8s学习(二)理解Docker的技术底座:Namespace、Cgroups、UnionFs

张开发
2026/5/5 8:19:26 15 分钟阅读
Docker与K8s学习(二)理解Docker的技术底座:Namespace、Cgroups、UnionFs
前言第一篇文章主要介绍了虚拟化技术的发展和主要分类当时简单提到了Docker并不是实现虚拟化的技术虚拟化实质上依赖于Linux系统本身提供的进程资源隔离等一系列技术这篇文章我们就来详细探究Linux这三大内核特性以及Docker中是如何应用的。理解三大核心技术Docker 主要基于三大 Linux 内核特性技术作用类比Namespaces进程隔离PID、网络、用户、IPC等每个容器有自己的房间Cgroups资源限制CPU、内存、磁盘IO等给每个房间分配固定的水电配额UnionFS分层文件系统镜像分层、写时复制像乐高积木一样堆叠文件层Namespacenamespace很容易让我们想到C中的命名空间事实上这两者没有什么关联。Linux内核的namespace让进程拥有独立的系统资源视图注意是视图也就是让进程以为自己是独立拥有了这些资源事实上系统资源仍然是共享的。Linux内核提供了多种类型的namespace目的是实现按需隔离模块化控制。使用如下命令我们可以看到当前系统支持的namespace类型。类型[rootdocker-test ~]# ls /proc/$$/ns/cgroup ipc mnt net pid pid_for_childrentimetime_for_children user utsnamespace类型隔离内容典型用例PID进程ID树容器内进程从1开始编号Network网络栈、接口、端口容器有自己的网络配置Mount文件系统挂载树容器有独立的根文件系统UTS主机名和域名容器有自己的主机名IPC进程间通信资源容器间IPC隔离User用户和组ID映射容器内root不是真正的rootCgroupcgroup根目录容器有自己的cgroup视图Time系统时间修改能力容器内的时间调整Linux的namespace可以自由组合Docker容器默认会使用所有的namespace类型当然也可以按需禁用一般不会去禁用容器就是要保证隔离性的。隔离性可以使用unshare命令来创建namespace下面我们可以创建一个简单的uts namespace来看看这个隔离性。# 原来的主机名[rootdocker-test ~]# hostnamedocker-test# 创建一个uts namespace并设置新的主机名[rootdocker-test ~]# unshare --uts bash[rootdocker-test ~]# hostname new-space# 查看新的主机名[rootdocker-test ~]# hostnamenew-space# 复制一个窗口打开查看hostname我们可以发现还是hostname仍然是docker-test# 我们设置的新的主机名只在当前的容器bash进程中生效# 使用exit退出当前容器需要指出的是namespace提供了一定的隔离性但是并不保证安全边界。如何理解呢我们可以创建一个pid命名空间在这个命名空间中我们是看不到宿主机进程的但是在宿主机中是可以看到命名空间中的进程的也就是宿主机可以和容器进程进行通信。# 创建pid命名空间[rootdocker-test ~]# unshare --pid --fork --mount-proc bash# 新建一个sleep进程并查看命名空间内的进程树可以看到不包含宿主机进程[rootdocker-test ~]# sleep 100 [1]23[rootdocker-test ~]# ps auxUSERPID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root10.00.0151603796pts/0 S16:230:00bashroot230.00.07388916pts/0 S16:260:00sleep100root240.00.0477803808pts/0 R16:270:00psaux## 打开另一个窗口查看宿主机中的进程宿主机可以查看到命名空间中创建的sleep进程[rootdocker-test ~]# ps aux | grep sleeproot11812740.00.07388916pts/0 S16:260:00sleep100root11812770.00.0122161176pts/1 S16:270:00grep--colorautosleepnamespace可以创建也可以加入已有的namespacenamespace存在引用计数当引用数为0时就会被销毁。CgroupsCgroups主要是用来限制资源的分配。想象一下系统中运行多个进程时假设一个进程的程序存在Bug例如无法退出的循环那是不是很快就会把CPU打满影响其它进程。一向以严谨安全著称的Linux系统绝不允许出现这种场景所以出现了Cgroups技术限制进程资源获取避免一颗老鼠屎坏了整锅汤的悲剧。限制资源的类型使用如下命令可以看到系统支持对哪些资源进行限制。ls/sys/fs/cgroup# 输出如下blkio cpu cpuacct cpu,cpuacct cpuset devices freezer hugetlb memory net_cls net_cls,net_prio net_prio perf_event pids rdma systemd除了常见的CPU内存外还有blkio磁盘io网络优先级进程数等等这些并不需要记住有个大概印象需要用的时候再查就行。手动限制资源创建一个资源组手动限制CPU使用率为50%。# 创建资源组实际上就是在/sys/fs/cgroup对应的目录下创建一个目录mkdir-p/sys/fs/cgroup/cpu/game_group# 看看这个目录下有什么文件[rootdocker-test game_group]# lltotal0-rw-r--r--1root root0Apr817:41 cgroup.clone_children -rw-r--r--1root root0Apr817:41 cgroup.procs -r--r--r--1root root0Apr817:41 cpuacct.stat -rw-r--r--1root root0Apr817:41 cpuacct.usage -r--r--r--1root root0Apr817:41 cpuacct.usage_all -r--r--r--1root root0Apr817:41 cpuacct.usage_percpu -r--r--r--1root root0Apr817:41 cpuacct.usage_percpu_sys -r--r--r--1root root0Apr817:41 cpuacct.usage_percpu_user -r--r--r--1root root0Apr817:41 cpuacct.usage_sys -r--r--r--1root root0Apr817:41 cpuacct.usage_user -rw-r--r--1root root0Apr817:41 cpu.cfs_period_us -rw-r--r--1root root0Apr817:41 cpu.cfs_quota_us -rw-r--r--1root root0Apr817:41 cpu.rt_period_us -rw-r--r--1root root0Apr817:41 cpu.rt_runtime_us -rw-r--r--1root root0Apr817:41 cpu.shares -r--r--r--1root root0Apr817:41 cpu.stat -rw-r--r--1root root0Apr817:41 notify_on_release -rw-r--r--1root root0Apr817:41 tasks# 100000微秒(100ms)是一个周期50000微秒(50ms)是配额# 配额/周期 50000/100000 0.5 50% CPUecho100000/sys/fs/cgroup/cpu/game_group/cpu.cfs_period_usecho50000/sys/fs/cgroup/cpu/game_group/cpu.cfs_quota_us把进程放入资源组。按照下面的命令创建两个无限循环的进程获取其进程ID加入到上面创建的资源组注意这两个进程加入的是同一资源组代表两个进程一共只能占用50%的CPU。[rootdocker-test game_group]# bash -c while true; do :; done [1]1184390[rootdocker-test game_group]# GAME1_PID$![rootdocker-test game_group]# echo 游戏进程1 PID: $GAME1_PID游戏进程1 PID:1184390[rootdocker-test game_group]# bash -c while true; do :; done [2]1184391[rootdocker-test game_group]# GAME2_PID$![rootdocker-test game_group]# echo 游戏进程2 PID: $GAME2_PID游戏进程2 PID:1184391[rootdocker-test game_group]# echo $GAME1_PID /sys/fs/cgroup/cpu/game_group/cgroup.procs[rootdocker-test game_group]# echo $GAME2_PID /sys/fs/cgroup/cpu/game_group/cgroup.procs[rootdocker-test game_group]# vi /sys/fs/cgroup/cpu/game_group/cgroup.procs某一时刻的监控如下查看后记得kill掉这两个进程以及删除game_group这个资源组目录。docker中cgroups是如何生效的呢例如对于CPU的限制我们可以看到在/sys/fs/cgroup/cpu/目录下有docker目录docker目录下有容器id目录而容器id目录下则是对CPU资源的限制文件配置。UnionFs终于到了最后一个特性了前面的Namespace主要实现了容器的隔离Cgroups实现了对容器资源的限制那么UnionFs做了些什么呢UnionFS​ 是一种联合文件系统它允许你将多个目录称为分支合并成一个虚拟的统一视图。它是 Docker 镜像分层存储的核心技术。核心在于分层用过PS的就知道多个图层叠加可以在不破坏任意单个图层的前提下组合出不同的效果UnionFs就是这种思想的文件系统的实现。关键概念只读分支基础层不可以被修改。可写分支最上层支持修改。联合视图由各层叠加最终呈现出的文件视图。修改写入总是发生在最上层并且只记录与基础层的差异删除不会真实删除只是标记删除同样的新建也是发生在可写层不会影响基础层总之要时刻记住基础层是只读的。手动创建UnionFs通过下面的命令我们手动创建并挂载一个UnionFs并对其中的文件进行修改删除等操作核心还是要明白上层的增删改不会影响到基础层# 1. 创建测试目录[rootdocker-test ~]# mkdir -p unionfs_demo/{base,overlay1,overlay2,work,merged}[rootdocker-test ~]# cd unionfs_demo[rootdocker-test unionfs_demo]# ls -latotal4drwxr-xr-x7root root76Apr915:55.dr-xr-x---.8root root4096Apr915:55..drwxr-xr-x2root root6Apr915:55 base drwxr-xr-x2root root6Apr915:55 merged drwxr-xr-x2root root6Apr915:55 overlay1 drwxr-xr-x2root root6Apr915:55 overlay2 drwxr-xr-x2root root6Apr915:55 work# 2. 向基础层中写入文件[rootdocker-test unionfs_demo]# echo it is base file base/base_file.txt[rootdocker-test unionfs_demo]# echo it is base config file base/config.txt[rootdocker-test unionfs_demo]# ls -la base/total8drwxr-xr-x2root root45Apr915:56.drwxr-xr-x7root root76Apr915:55..-rw-r--r--1root root16Apr915:56 base_file.txt -rw-r--r--1root root23Apr915:56 config.txt[rootdocker-test unionfs_demo]# ls -la overlay1/total0drwxr-xr-x2root root6Apr915:55.drwxr-xr-x7root root76Apr915:55..[rootdocker-test unionfs_demo]# ls -la merged/total0drwxr-xr-x2root root6Apr915:55.drwxr-xr-x7root root76Apr915:55..# 3. 创建一个overlay文件系统挂载UnionFs实际上有多种实现目前默认实现是overlay2第一个overlay指定文件系统类型第二个overlay是设备名# 对于overlay文件系统设备名也是overlay。然后通过-o指定overlay的各个层。# lowerdir指定只读层如果有多个可以使用:分隔如base1:base2# upperdir指定读写层记录文件的修改差异# workdir是overlay文件系统内部使用的目录用于文件重命名等原子操作它需要与upperdir在同一目录[rootdocker-test unionfs_demo]# mount -t overlay overlay -o lowerdirbase,upperdiroverlay1,workdirwork merged[rootdocker-test unionfs_demo]# ls -la merged/total8drwxr-xr-x1root root6Apr915:55.drwxr-xr-x7root root76Apr915:55..-rw-r--r--1root root16Apr915:56 base_file.txt -rw-r--r--1root root23Apr915:56 config.txt[rootdocker-test unionfs_demo]# cat merged/base_file.txtit is basefile# 4. 在合并视图中进行修改、新增、删除等操作# 4.1 文件修改[rootdocker-test unionfs_demo]# echo it is base file and it was updated merged/base_file.txt[rootdocker-test unionfs_demo]# cat merged/base_file.txtit is basefileand basefilewas updated[rootdocker-test unionfs_demo]# cat base/base_file.txtit is basefile# 4.2 文件新增rootdocker-test unionfs_demo]# echo this is new created file merged/new_file.txt[rootdocker-test unionfs_demo]# ls -la merged/total12drwxr-xr-x1root root47Apr916:23.drwxr-xr-x7root root76Apr915:55..-rw-r--r--1root root42Apr916:10 base_file.txt -rw-r--r--1root root23Apr915:56 config.txt -rw-r--r--1root root25Apr916:23 new_file.txt[rootdocker-test unionfs_demo]# ls -la base/total8drwxr-xr-x2root root45Apr915:56.drwxr-xr-x7root root76Apr915:55..-rw-r--r--1root root16Apr915:56 base_file.txt -rw-r--r--1root root23Apr915:56 config.txt# 4.3 文件删除[rootdocker-test unionfs_demo]# rm merged/base_file.txtrm: remove regularfilemerged/base_file.txt? y[rootdocker-test unionfs_demo]# ls -la merged/total8drwxr-xr-x1root root47Apr916:25.drwxr-xr-x7root root76Apr915:55..-rw-r--r--1root root23Apr915:56 config.txt -rw-r--r--1root root25Apr916:23 new_file.txt[rootdocker-test unionfs_demo]# ls -la base/total8drwxr-xr-x2root root45Apr915:56.drwxr-xr-x7root root76Apr915:55..-rw-r--r--1root root16Apr915:56 base_file.txt -rw-r--r--1root root23Apr915:56 config.txt# 4.4 覆盖层会记录所有变更可以看到删除的文件实际存在但有特殊标识# 可以使用overlay2再创建一个覆盖层我后面就不演示了[rootdocker-test unionfs_demo]# ls -la overlay1total4drwxr-xr-x2root root47Apr916:25.drwxr-xr-x7root root76Apr915:55..c---------1root root0,0Apr916:25 base_file.txt -rw-r--r--1root root25Apr916:23 new_file.txt# 4.5 清理注意文件夹有挂载点时需要先解除挂载然后删除文件夹[rootdocker-test ~]# rm -rf unionfs_demo/rm: cannot removeunionfs_demo/merged:Device or resource busy[rootdocker-test ~]# umount unionfs_demo/merged[rootdocker-test ~]# rm -rf unionfs_demo/Docker中的UnionFs通过上面的实验我们知道了UnionFs文件系统的显著特点复用带来的空间节省以及纯粹做加法带来的可回退感觉这种思想在设计软件中很常见到例如blender中的修改器也是基于这种思想不修改原模型仅做叠加。接下来我们来看看docker容器中是如何使用UnionFs文件系统的。# 使用如下命令可以得到类似输出可以看到与我们上面简单实验中类似存在lower upper merged work这几个目录并且lower目录可以指定多个文件组合# RootFS中的Layers记录了每一层的sha256编码dockerimage inspect mysql:8.0.32GraphDriver:{Data:{LowerDir:/home/docker/overlay2/a94401a329c2b0150ba7c52e73445107cff4d43e06febeea5a4280637e7a0d3b/diff:/home/docker/overlay2/8fb4c464552e2b41e997582548cbefd8c2d8beedd59a7639e3ea38eaf649b94a/diff:/home/docker/overlay2/9d1369f1a2d45d8f8ee109f567c40e7ab326563c9464c298df2e28265fbf848b/diff....,MergedDir:/home/docker/overlay2/51de5e81c24a5839fb7b272ec9983cf902dd25697f5d630c1e33c158e1ab072c/merged,UpperDir:/home/docker/overlay2/51de5e81c24a5839fb7b272ec9983cf902dd25697f5d630c1e33c158e1ab072c/diff,WorkDir:/home/docker/overlay2/51de5e81c24a5839fb7b272ec9983cf902dd25697f5d630c1e33c158e1ab072c/work},Name:overlay2},RootFS:{Type:layers,Layers:[sha256:caefa4e45110eab274ebbdbc781f9227229f947f8718cee62ebeff1aac8f1d5b,.......]},

更多文章