星点匠心建站 星点匠心建站XD JIANXIN STUDIO
400-883-9527 免费定制方案
ARTICLE DETAIL

资讯详情

深耕新锐建站与运营推广的一线实战洞察。

操作系统级虚拟化原理解析:命名空间、cgroup与手工构建容器

操作系统级虚拟化原理解析:命名空间、cgroup与手工构建容器 操作系统级虚拟化这个词很多人第一次见是在某个百科词条里读完一圈名词解释还是不知道它跟自己有什么关系。我第一次接触它是因为一台 8 核 16G 的测试机要同时跑五个服务每个服务还都有自己的依赖版本装在一台机器上互相打架重装了三遍系统之后我才认真去啃这个方向。说白了操作系统级虚拟化就是让同一个内核上同时跑多个彼此看不见对方的用户空间你跟别人共享内核但文件系统、进程号、网络、主机名、用户 ID 全是各自独立的。它解决的是轻量隔离这件事启动快、开销小、密度高。适合谁看手里有闲置服务器想提高利用率的运维、被在我本地是好的折磨过的后端开发、想搞清楚容器到底是什么东西而不是只会敲几行命令的同学以及需要给不可信任务做沙箱的人。下面我按自己的理解路径从原理讲到手工复现再讲到选型和踩坑能抄的部分我尽量给到可执行的命令。1. 操作系统级虚拟化到底解决了什么问题1.1 从一台机器跑十个服务说起假设你手里有一台机器要跑 A、B、C 三个服务。A 依赖某个运行时的 1.x 版本B 依赖 3.xC 是个老系统只认 1.x。最朴素的做法是装三台虚拟机每个里面一套完整系统。问题是你付出了三份内核、三份系统初始化进程、三份内存常驻开销换来的是三个服务其实九成时间都在睡觉。这在资源紧张的年代完全无法接受。操作系统级虚拟化的思路是反过来的内核只留一份把内核之上的用户空间切成若干份。每份有自己的根目录、自己的进程树、自己的网络栈、自己的主机名。进程发出系统调用进入的是同一个内核但内核会根据调用者属于哪一份返回不同的视图。这个设计的好处是几乎没有额外损耗——新建一个容器本质上是新建一组进程加几个挂载点耗时在几十毫秒到几百毫秒量级而虚拟机冷启动是秒级到分钟级。代价也很明确隔离强度弱于硬件虚拟化。因为共用内核内核里的任何一个漏洞都可能被打穿成从容器跳到宿主机。这也是为什么这个技术早期被质疑根本不叫虚拟化后来大家才慢慢接受它是隔离不是虚拟化这个说法。理解这一点后面所有的加固手段你都能明白是在补哪块短板。1.2 三条隔离路线的成本账业内的隔离方案大致三条路我把它们的账算给你看维度操作系统级隔离硬件虚拟化语言级沙箱进程内隔离隔离边界内核 命名空间 权限硬件虚拟化指令运行时/解释器单实例启动毫秒到百毫秒级秒级到分钟级微秒到毫秒级内存额外开销极低共享内核页每个实例一份内核内存极低单机密度数百到数千数十数千以上隔离强度中高低跨平台能力必须同内核可跑异构系统依赖运行时这张表不是为了证明谁更好而是帮你判断该不该用。我见过太多团队在本该用硬件虚拟化的场景硬上容器最后因为一个内核共享的问题排查了两周也见过有人给一个内部小脚本上了完整虚拟机机器利用率不到 5%。判断标准其实就一句话被隔离的东西你信不信得过。自己团队写的服务信得过用操作系统级隔离足够跑第三方上传的代码、跑不可信任务那就别省这点资源。1.3 那个词条背后其实是一张技术地图回到那个词条本身。它表面上讲的是操作系统级虚拟化这一个概念实际上它是一张入口地图往下能牵出四条线第一条是内核提供的原语也就是命名空间和控制组第二条是用户态的实现各种容器运行时和管理工具第三条是文件系统层面的支撑联合挂载第四条是不同系统上的等价实现有的系统叫 jail有的叫 zone思路大同小异都是把一个内核切多份用户空间这件事做了不同取舍。我自己读这类词条的习惯是先看它列的几个实现然后挑一个最简单的去复现。因为概念看十遍不如动手做一遍做完之后你对隔离这两个字的理解会从抽象变成具体的——你会清楚地知道容器不是什么魔法它就是几个系统调用按特定顺序调了一遍。后面的章节我就按这个思路走先讲清楚三大支柱再手工拼一个出来。2. 三大支柱命名空间、控制组与根文件系统2.1 命名空间把全局资源折叠成私有视图命名空间是这套体系的地基。它的作用机制可以用一个类比说明内核里维护着一堆全局资源表比如进程号表、网络设备表、挂载点树、主机名。命名空间相当于给这些表加了分组标签一个进程只能看到自己所属分组的那些条目。你在自己组里看到 PID 是 1在别人组里它可能是 3847互不干扰。主流的命名空间有这么几类我在下面表里列全顺便标注实际使用中的注意事项命名空间隔离的内容常见误区挂载mnt挂载点树默认不隔离传播属性父命名空间挂载会被子看到必须先设为私有进程pid进程号空间只影响新建进程当前进程不会被移入必须 fork 之后才生效网络net网卡、路由、端口、防火墙规则新命名空间里只有一张 down 状态的 loopback必须手动拉起IPC消息队列、信号量、共享内存老程序用了 System V IPC 却忘了隔离会出现跨容器互相干扰UTS主机名、域名最容易被忽略但日志里主机名全一样时排查会疯用户user用户 ID、组 ID 映射映射关系要在父进程侧写顺序错了直接失败控制组cgroup控制组视图看 /proc/self/cgroup 才能确认自己在哪一层时间time单调时钟、启动时间偏移较新内核才有老内核上跑会直接报错这里面最容易翻车的是挂载命名空间。内核在创建 mnt 命名空间时会复制一份当前进程的挂载点树而挂载点默认是共享传播的意思是你在子命名空间里挂载一个目录父命名空间也会看到。做隔离的时候第一件事必须是把这个传播属性改成私有mount --make-rprivate /不写这一行后面所有隔离都可能出现我明明在容器里挂的宿主机怎么也有了的诡异现象。2.2 控制组账本与刹车片命名空间解决看不见控制组解决用不超。它做的是资源记账和限额一组进程用了多少 CPU 时间、多少内存、多少个进程号、多少 IO 带宽超过阈值怎么办。这两件事必须配套只隔离不限流的下场是——某个服务内存泄漏把整台机器的内存吃光内核的 OOM 机制会挑一个看起来最该杀的进程干掉很可能干掉的是你另一个正常运行的服务。目前的实现有两代。老一代是分控制器的树形结构每个资源一个独立层级管理起来很麻烦要把一个进程同时挂到 CPU 层级和内存层级的不同节点上。新一代改成了统一层级一棵树管所有控制器进程只能在一个节点上。新一点的发行版基本都默认用新一代了你可以用一条命令确认stat -fc %T /sys/fs/cgroup # 返回 cgroup2fs 就是新一代返回 tmpfs 是老一代新一代有个必须知道的规则叫内部进程约束一棵子树中间节点上不能直接放进程只有叶子节点才能挂进程否则你给中间节点配的限额对下面的子节点无效。我第一次遇到这个坑是给某个中间层配了内存上限怎么测都测不出来最后发现是进程根本不在那个节点上配置形同虚设。常用控制器的关键参数# 内存硬限制 512MiB echo 536870912 /sys/fs/cgroup/demo/memory.max # 内存软限制超过会被节流但不杀进程 echo 402653184 /sys/fs/cgroup/demo/memory.high # CPU 配额每 100ms 周期内最多用 50ms即半核 echo 50000 100000 /sys/fs/cgroup/demo/cpu.max # 最多允许 64 个进程/线程 echo 64 /sys/fs/cgroup/demo/pids.maxcpu.max这两个数字的单位是微秒第一个是配额第二个是周期。想给它 2.5 核就写250000 100000。这个换算一定要自己算一遍见过不少配置里写了100000 100000以为是100% 使用率实际是最多一核机器有 16 核却只让用一个性能问题查了半天。2.3 根文件系统与联合挂载文件系统的套娃隔离做好了视图还得给每个实例一份看起来完整的文件系统。这里的关键技术叫联合挂载它能把多个目录叠加成一个视图底下的层是只读的上面的层可写读的时候从上往下找写的时候只写到最上层。这个设计的精妙之处在于——十个实例可以共享同一份只读底层每个实例只存自己改动的那几个文件。这就是为什么一个基础镜像几百兆你起十个实例并不会多占几个 G。手工挂一个联合文件系统大概是这样mount -t overlay overlay \ -o lowerdir/layers/base,lowerdir/layers/mid,upperdir/tmp/ct/upper,workdir/tmp/ct/work \ /tmp/ct/rootfsworkdir是必须的而且必须和upperdir在同一个文件系统上它是用来做原子性操作的临时区忘了给会直接报错。多个只读层用逗号分隔的多个lowerdir表达顺序是从右往左优先级升高。用联合挂载有两个必须提前知道的限制。第一下层的改动不会通知上层。你在宿主机上直接改了底层目录里的文件正在运行的实例通过 inotify 是收不到事件的很多配置改了不生效的玄学问题都出在这里。第二它不适合放数据库这类需要频繁随机写和特殊文件操作的数据因为每次写都要先触发一次写时复制把整个文件从下层拷到上层文件大的时候这一下就很痛。所以数据卷要单独挂一个普通目录进去别图省事放在可写层里。2.4 权限收缩能力位、系统调用过滤与访问控制前面三样都是隔离这一层是收权。传统上进程分普通用户和超级用户两档粒度太粗。能力位机制把它拆成了几十个细项比如绑定 1024 以下端口是一个能力、修改网络配置是一个能力、创建特殊设备节点是一个能力。默认应该全部丢掉只按需加回# 丢掉所有能力只保留绑定低端口这一个 # 在容器引擎的配置里等价于 --cap-dropALL --cap-addNET_BIND_SERVICE比能力位更细的是系统调用过滤。它允许你给进程挂一个白名单/黑名单某些系统调用一调用就被内核直接拒绝进程收到的是不允许而不是不存在。默认策略一般是黑名单模式把那些危险但正常业务用不到的系统调用砍掉比如重启系统、加载内核模块、挂载文件系统、调试其他进程这些。数量大概几十个具体清单会随版本变化不用死记你只需要知道这条防线主要防的是内核接口被滥用而不是防应用逻辑漏洞。第三层是访问控制模块做的是文件级和操作级的强制策略。这一层配置起来最麻烦因为要写规则描述谁能对什么做什么写宽了没意义写严了程序跑不起来。我的建议是先用默认策略跑通业务把拒绝日志收集起来看再逐条放开别一上来就自己写全套规则。3. 不用任何容器引擎手工拼一个容器出来这一节是最有实操价值的部分。我建议你在虚拟机或测试机上做别在生产机上试因为下面有些操作会修改宿主机的挂载点和网络配置。3.1 先准备一份干净的根文件系统你需要一份最小的目录树至少包含 shell、几个基本命令、必要的动态库。两个稳妥的做法第一种用静态编译的 busybox最省事mkdir -p /tmp/ct/rootfs/{bin,proc,sys,dev,tmp,etc} cp /usr/bin/busybox /tmp/ct/rootfs/bin/ cd /tmp/ct/rootfs/bin for c in sh ls cat echo mount ps; do ln -sf busybox $c; done第二种用发行版自带的引导脚本在空目录里装一套最小系统好处是包管理能用坏处是慢、占空间大。我用第一种因为这一节的目标是理解机制不是造一个能用的生产镜像。准备完之后检查一下动态链接情况如果你的 busybox 是动态链接的还得把对应的库拷进去ldd /tmp/ct/rootfs/bin/busybox # 如果输出里有 /lib/xxx.so就把这些库按原路径拷进 rootfs这一步漏了会得到一个经典报错No such file or directory但你明明ls看得到那个文件。这个报错说的是找不到解释器/加载器不是找不到文件本身新手几乎都会被它骗一次。3.2 第一层UTS 与 PID 隔离先做最简单的一层只隔离主机名和进程号unshare --uts --pid --fork --mount-proc/tmp/ct/rootfs/proc \ chroot /tmp/ct/rootfs /bin/sh进到里面之后执行hostname demo-ct hostname # 输出 demo-ct echo $$ # 输出 1 或者很小的数字说明 PID 隔离生效 ps -ef # 只能看到自己命名空间里的进程这里有个细节值得说清楚--fork是必须的。原因在于 PID 命名空间只对在命名空间创建之后新产生的进程生效调用 unshare 的那个进程本身不会被移进去。所以必须先 fork 一次让父进程留在外面等待子进程成为新命名空间的 1 号进程。少了--fork你会发现自己的 PID 还是原来的大数字。另一个细节是--mount-proc。新 PID 命名空间里的/proc必须重新挂载否则你看到的还是宿主机的进程列表等于隔离白做了。这个参数就是帮你自动挂一次等价于在命名空间里执行mount -t proc proc /proc。还有那个1 号进程的身份问题。在普通系统里 1 号进程是初始化进程它有个特殊职责回收孤儿进程。容器里的 1 号进程如果是个普通应用它不会做这件事孤儿进程就会变成僵尸堆积起来。而且 1 号进程对某些信号的默认处理也和普通进程不同——比如它默认不响应终止信号。这就是为什么很多容器里要额外加一个小巧的初始化进程做转发。你自己手工玩的时候体会不到一旦上生产应用停不掉、僵尸进程越堆越多八成就是这个原因。3.3 第二层挂载命名空间与切换根目录把 chroot 换成更彻底的挂载命名空间方案unshare --uts --pid --mount --fork \ sh -c mount --make-rprivate / mount --bind /tmp/ct/rootfs /tmp/ct/rootfs cd /tmp/ct/rootfs mount -t proc proc proc mount -t sysfs sys sys exec chroot . /bin/sh 为什么不只用 chroot因为 chroot 只改路径解析的起点一个持有足够权限的进程可以用chdir加..的技巧跳出这个根或者重新执行一次 chroot 把自己解出来。它从来不是安全边界只是路径重定向。更严格的方案是切换根挂载点先把新根挂载好再把旧根挪到新根下的某个目录然后卸载旧根。命令行的做法需要系统里有对应的工具写代码的话就是调用pivot_root系统调用。顺序也很讲究先--make-rprivate再挂载新根最后切根。反过来的话你在子命名空间里做的挂载会往宿主机泄漏。我吃过这个亏测试机上跑完脚本宿主机df -h里多出一堆莫名其妙的挂载点清理了半天。挂/proc和/sys的时候还有个坑/sys里有大量可写接口容器里如果有权限理论上能通过它改宿主机的设备状态。稳妥做法是把/sys以只读方式挂载或者干脆不挂完整的/sys只挂需要的子目录。3.4 第三层控制组限额隔离好了现在加刹车。新一代统一层级下CG/sys/fs/cgroup/demo mkdir -p $CG # 在父节点上启用需要的控制器 echo cpu memory pids io /sys/fs/cgroup/cgroup.subtree_control echo 268435456 $CG/memory.max # 256 MiB echo 100000 100000 $CG/cpu.max # 一核 echo 32 $CG/pids.max参数怎么算我给你两个实际例子。内存一个 Java 服务堆设了 1G加上元空间、线程栈、直接内存、JVM 自身开销实际常驻大概 1.6G 到 1.8G那容器内存上限至少给 2G2 × 1024 × 1024 × 1024 2147483648。给 1.5G 的结果是进程活着但被 OOM 杀了日志里只有一行退出码 137你会以为是代码问题查一下午。CPU一个服务压测时峰值用到 3.2 核那配额给320000 100000留一点余量给突发可以给 3.5 核。配完之后把进程放进去echo $$ $CG/cgroup.procs cat /proc/self/cgroup # 确认自己在 demo 这层验证限额是否真的生效看统计文件cat $CG/memory.current $CG/memory.events cat $CG/cpu.stat # 关注 nr_throttled 和 throttled_useccpu.stat里的节流次数如果持续增长说明你的配额给低了进程一直在被掐脖子。这个指标比看 CPU 使用率有用得多因为被节流的进程使用率看起来可能只有 100%实际它想用 300%。3.5 第四层网络命名空间与地址转换网络是最麻烦的一块。新网络命名空间里只有一张 down 状态的回环网卡要通外网得自己接。标准做法是虚拟网卡对加网桥# 宿主机侧 ip link add veth-host type veth peer name veth-ct ip link set veth-ct netns 容器进程PID ip addr add 10.88.0.1/24 dev veth-host ip link set veth-host up # 在容器命名空间里执行 nsenter -t PID -n ip link set lo up nsenter -t PID -n ip link set veth-ct up nsenter -t PID -n ip addr add 10.88.0.2/24 dev veth-ct nsenter -t PID -n ip route add default via 10.88.0.1再开地址转换让容器能出网sysctl -w net.ipv4.ip_forward1 iptables -t nat -A POSTROUTING -s 10.88.0.0/24 ! -o veth-host -j MASQUERADE这两个步骤漏一个都通不了。ip_forward忘了开表现是容器能 ping 通网关ping 不通外网很多人第一反应是 DNS 问题其实是转发没开。DNS 是另一个高发点容器里的/etc/resolv.conf是根文件系统里那份如果里面写的是127.0.0.1那就是在容器自己内部找 DNS 服务器必然失败。解决办法是把宿主机的配置文件绑定挂载进去mount --bind /etc/resolv.conf /tmp/ct/rootfs/etc/resolv.conf3.6 验收怎么证明隔离真的生效了做完上面这些别急着高兴按这个清单逐条验检查项命令期望结果主机名隔离hostname与宿主机不同进程隔离ps -ef只看到容器内进程文件系统隔离ls /只有最小目录树挂载隔离cat /proc/mounts看不到宿主机业务挂载网络隔离ip addr只有 lo 和 veth内存限额写一个大文件或跑内存分配触发 OOM 而非拖垮宿主进程数限额循环 fork达到上限后报错而非拖死系统权限收缩cat /proc/self/status | grep Cap能力位远小于全量一条条过完你对容器这个词的理解就和之前完全不一样了。你会清楚地知道所谓容器引擎无非是把上面这堆事封装成一套配置和一堆文件系统层再用一个守护进程或者一个轻量运行时串起来加上镜像分发和生命周期管理。本质上没有魔法。4. 方案选型什么场景用哪种实现4.1 几类实现的定位对照市面上能直接用的实现分几类我不给具体品牌只讲类型你对号入座类型特点适合的场景不适合的场景系统级容器管理工具把容器当轻量虚拟机管有 init 系统、能常驻、能多进程需要长期运行的类虚拟机环境、测试环境需要镜像分层分发、弹性伸缩的场景应用容器引擎单进程模型、镜像分层、生态完善微服务、CI 流水线、标准化交付需要在容器内跑完整系统服务栈无守护进程的运行时与系统安全模块集成好、默认非特权个人开发机、安全要求高的环境需要复杂编排的传统运维体系系统自带隔离机制与操作系统深度集成、开销极低同系统版本的批量实例、托管服务跨发行版、需要镜像生态我个人的判断逻辑是看你需不需要镜像这个东西。如果你的场景是我要一个干净环境跑测试那系统级工具足够配置简单可以直接进去装东西。如果你的场景是这套环境要一模一样地交付到十台机器上那必须有镜像分层走应用容器引擎那一类。如果你在开发机上只是想让依赖不打架优先选无守护进程的运行时少一个常驻服务就少一份资源占用和一份攻击面。4.2 选型要看的四个维度第一个维度是隔离需求。这个实例里跑的是你自己的代码还是外部代码后者要额外考虑用户 ID 映射隔离、只读根文件系统、能力位全丢这些。有的方案支持无根模式用普通用户就能起容器这对多租户场景意义很大。第二个维度是进程模型。单进程模型简单但你要跑一个带定时任务、带后台工作进程的老系统就得自己想办法。多进程模型方便但信号转发、日志收集、退出码传递这些都得遵循 init 系统的规则。我见过有人把四五个服务塞进一个容器里用 shell 脚本串起来启动最后挂了一个全挂排查时连哪个先死的都看不出来。第三个维度是网络与存储的接入方式。有些方案默认用桥接加地址转换有些默认用宿主机网络。存储上联合挂载的性能特性你要心里有数写密集的应用必须单独挂卷。第四个维度是运维成本。多一个组件就多一套监控、多一套升级路径、多一份故障可能。团队小的话能用系统自带的能力就别引入新组件。4.3 什么时候必须回到硬件虚拟化有几个信号一出现就别犹豫上硬件虚拟化需要跑不同内核版本的场景需要强隔离保证的场景比如执行完全不可信的外部提交代码需要特定内核模块或自定义内核参数的场景以及合规上明确要求物理或虚拟级隔离的场景。还有一个容易被忽略的情况你需要做内核调优。操作系统级隔离共享内核容器里改sysctl参数很多时候改不了或者改了影响全局。我遇到过一次某个服务要求调大连接队列长度容器里改不动最后只能放弃容器方案。这种限制不是 bug是设计使然提前想清楚能省很多返工。5. 排查实录那些文档里不写的坑5.1 隔离类问题速查现象大概率原因处理方式容器里看到宿主机进程proc 没重新挂载在挂载命名空间内重新挂 proc容器里挂载泄漏到宿主挂载传播属性没设私有先执行mount --make-rprivate /主机名改了宿主也变了UTS 命名空间没隔离加 UTS 标志用户映射失败uid_map 写入顺序或权限不对先写setgroups deny再写 gid_map容器内权限不足但 ID 是 0能力位被丢干净了按需加回具体能力老程序跨容器互相干扰IPC 命名空间没隔离加 IPC 隔离隔离类问题的排查有一个通用思路从外往里一层层确认。先在宿主机上确认命名空间确实创建了看/proc/pid/ns/下的链接和 PID 1 的链接是不是不一样再进到里面确认视图对不对最后确认权限配置。跳过前两步直接怀疑权限是最浪费时间的做法。5.2 资源类问题的三个高发点第一个高发点是内存限额给少了日志里只有退出码。进程被 OOM 杀掉的时候应用自己来不及打日志你只能从系统侧看证据。容器场景下要看控制组的memory.events里面的oom_kill计数会增加。同时宿主机内核日志里会有对应记录。判断方法很简单看被杀时刻的memory.current是不是刚好贴着memory.max。第二个高发点是CPU 节流导致延迟抖动。表现是平均响应时间正常但 P99 突然飙高。原因是你给的配额是每周期最多用这么多一旦某次请求需要更多算力就会被强制等到下个周期。排查看cpu.stat里的节流时间和次数如果节流比例超过 5%说明配额偏紧要么加配额要么减少并发。第三个高发点是进程数限额被线程吃光。很多运行时一个请求就起一个线程pids.max设小了表现是偶尔报错说资源不可用其实就是 fork 失败。这个参数要按进程数 线程数来估不是按进程数。注意内存的软限制和硬限制要配对使用。软限制负责提前节流让应用自己降速硬限制负责兜底。只设硬限制等于让应用毫无预警地撞墙。5.3 网络与存储的典型故障网络这块除了前面说的转发没开和 DNS 配置还有两个隐蔽的一是连接跟踪表打满表现是新连接建不上旧连接正常看内核日志会有对应提示需要调大表容量或缩短空闲超时二是端口区间冲突做地址转换的时候源端口池不够用高并发下大量连接超时。存储这块联合挂载最常见的坑有两个。一是写时复制的放大效应你改一个几百兆的数据库文件里的一个字节整个文件都要从只读层拷到可写层这一下 IO 就爆了。解决办法是把数据目录单独挂载成普通目录绕过联合挂载。二是inode 数量容器里跑大量小文件的应用底层文件系统的 inode 可能不够表现出来是磁盘还有空间但就是写不进去。还有一个很隐蔽的问题容器里的应用把文件删了磁盘空间却没释放。原因是文件还被某个进程持有句柄。查法是找出已删除但仍被打开的文件从持有它的进程入手重启对应服务。5.4 安全加固的避坑清单绝不使用完全特权模式。特权模式等于把宿主机的设备访问、内核能力全交出去隔离基本失效。确实需要某项能力就单项加回。不要挂载宿主机的管理接口。某些进程间通信的套接字文件一旦挂进容器容器内就等于拿到了宿主机的控制权。根文件系统尽量只读需要写的目录单独挂载可写卷。这样即使应用被攻破也没法往系统目录里塞东西。屏蔽敏感的系统路径。/proc下有几个文件能读到内核内存布局或者密钥信息/sys下有固件和调试接口这些应该在容器启动时用空文件覆盖掉或者挂一个空目录上去。用户 ID 映射别偷懒。让容器内的 root 映射到宿主机的一个普通用户这样即使容器内提权成功落到宿主机上也只是一个普通身份。这个配置只多几行收益很大。定期更新基础层。共享内核意味着宿主内核的安全更新会影响所有实例这一块的补丁节奏要纳入日常运维。提示加固的优先级顺序是——先收能力位再上系统调用过滤再挂只读根最后做访问控制策略。前面三步成本低、收益高最后一步成本高按需做。6. 我个人在实际操作中的几点体会这套东西我用了几年最大的体会是隔离是配置出来的不是用了某个工具就自动有的。默认配置为了兼容性往往放得很宽你以为隔离了实际上只是换了个目录。真正有效的隔离得你自己一条条确认——能力位是不是丢了、根文件系统是不是只读、/proc和/sys是不是挂了该挂的、用户映射是不是真的生效了。这些确认一次写成检查脚本放进流水线后面就一劳永逸。第二个体会是资源限额一定要压测。拍脑袋给个内存和 CPU 数值上线后必然出问题而且出问题的形式往往是随机某个实例被杀极难定位。正确做法是先给一个宽松值跑一周收集峰值再按峰值的一点二到一点五倍定限额然后再观察节流指标调整。第三个体会是别把容器当虚拟机用。它的设计前提是一个实例一个关注点。你在里面塞定时任务、塞多个服务、塞完整的系统初始化流程短期能跑长期维护成本会越来越高。真的有这类需求不如老老实实上轻量虚拟机省下的那点资源不值得你后面花的时间。最后一个偏门的技巧如果你只是想在本地复现一个线上问题不一定要搭完整容器。用命名空间工具手工起一个最小环境几秒钟的事比拉镜像快得多而且你能精确控制每一层的状态。我排查权限相关问题基本都这么干比在容器里加调试工具高效。
返回列表