恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Docker沙箱与容器池:在线代码安全执行的核心技术
首页
资讯中心
/
Docker沙箱与容器池:在线代码安全执行的核心技术
Docker沙箱与容器池:在线代码安全执行的核心技术
发布时间:2026/10/11 2:41:51
1. 为什么在线代码执行离不开沙箱——从一次真实事故说起前一段时间帮一个平台排查问题他们的在线代码评测服务偶尔会出现整个宿主机卡死的情况轻则响应变慢重则连SSH都进不去。翻看日志发现罪魁祸首是一段来自用户的代码——申请了两倍于机器内存的数组又用死循环把这个数组写了一遍直接把宿主机的内存和磁盘同时打满。更要命的是这段代码还能读到同机其他用户的临时文件。这件事之后我们彻底停掉了裸机跑用户代码的方案全面转向Docker代码沙箱加容器池的架构。很多人一听到“沙箱”两个字第一反应是某种高深的安全技术但其实Docker容器本身就是一种非常成熟的轻量级沙箱方案。它最大的价值在于利用Linux内核自带的命名空间和控制组机制把一段不可信代码的运行环境限制在一个“看得见摸不着”的隔离空间里它以为自己拥有整台机器实际上只是被关在一个资源受限的小格子中。所有系统调用、文件读写、网络请求、进程创建都要经过内核权限的层层过滤。这套方案适合谁来参考如果你在做在线教育平台的代码练习功能、技术博客的“运行代码”按钮、算法竞赛的评测系统、自动化测试的多租户执行环境或者任何需要安全执行不可信代码的场景Docker沙箱加容器池都是现阶段最成熟、性价比最高的解法。它能解决三个核心问题一是隔离用户代码对宿主机的破坏二是限制单段代码的资源消耗上限三是通过容器池复用机制让成百上千次代码执行请求不至于拖垮整个系统。2. 容器即沙箱隔离机制的底层原理拆解Docker容器之所以能当沙箱用归根到底靠的是Linux内核的两大能力命名空间Namespace和控制组cgroups。这两个词看起来唬人拆开看并不复杂。2.1 内核命名空间让容器看不见外面的世界命名空间的作用通俗讲就是“障眼法”。它把全局的系统资源包装成独立的实例容器内的进程只能看到属于自己的那一份。Docker默认创建容器时会启用六类命名空间PID、NET、MNT、UTS、IPC和USER。PID命名空间容器里第一个进程的PID是1容器内看不到宿主机的进程列表反过来宿主机能看到容器内的所有进程因为本质上它们就是宿主机上的普通进程只是“视野”被限制在了自己的PID空间里。NET命名空间每个容器有自己独立的网络栈包括网卡、路由表、防火墙规则。容器以为自己在独立组网实际上通过虚拟网桥转发流量。MNT命名空间这是文件系统层面的隔离。容器内看到的/etc、/usr、/home都是镜像叠加出来的视图与宿主机的目录并不相同。UTS命名空间隔离主机名和域名容器内执行hostname看到的自然不会是宿主机的主机名。IPC命名空间隔离进程间通信资源比如消息队列、信号量、共享内存避免容器内的进程访问宿主机的进程间通信通道。USER命名空间实现用户权限的映射容器内的root用户可以被映射为宿主机上的普通用户从而实现“看似root、实则受限”。这里最核心的一个认知是容器不是虚拟机容器内进程在宿主机上就是普通进程只不过内核给这些进程带上了“特制眼镜”。所以隔离的强度取决于内核本身是否足够安全以及我们在创建容器时是否正确配置了安全选项。2.2 控制组把硬件资源关进笼子命名空间解决的是“看得见”的问题控制组解决的是“用得了多少”的问题。cgroups按类型管理进程组的资源使用上限Docker主要使用以下几个子系统cpu子系统限制CPU使用率上限也可以设置CPU权重做相对分配。memory子系统限制内存使用上限包括物理内存和交换分区。blkio子系统限制磁盘读写带宽。pids子系统限制进程总数防止进程泛滥式的fork炸弹。net_cls子系统配合流量控制做网络带宽限制实际场景中较少直接使用。以内存为例Docker通过--memory参数设置内存上限后会在cgroups的memory子系统中写入对应的字节数。当容器内所有进程的内存使用量超过这个值时内核会触发OOMOut Of Memory策略按配置杀掉容器内最胖的进程或者直接杀死整个cgroup内的所有进程。所以“限制资源”这件事不是Docker守护进程每秒盯着容器看而是内核在每次内存分配、每次进程创建时自动执行审查。这也是Docker沙箱性能开销极低的原因——它不需要像虚拟机那样翻译每一条指令只是在资源分配的关键路径上多做了几道检查。2.3 叠加文件系统容器文件层的隔离与限制文件系统的隔离除了依赖MNT命名空间还依赖联合文件系统OverlayFS。Docker镜像由一层一层的只读层构成容器启动时在最上面加一层可写层所以多个容器可以共享同一个基础镜像的只读层写入操作只发生在各自的薄薄的可写层中。这个机制对沙箱场景意义重大。第一容器内对文件系统的任何修改包括恶意代码创建的大量垃圾文件都只停留在可写层容器销毁时全部随之消失第二容器重新创建时又是干净状态第三容器内无法修改镜像本身的内容因为底层是只读挂载需要写权限的操作会被拒绝。配合只读根文件系统选项--read-only还可以把可写层进一步收紧只允许写入指定的tmpfs目录这样容器连“写坏自己”的机会都没有这是后话第5部分会详细展开。3. 容器池技术从单容器沙箱到批量执行系统理解了单容器怎么当沙箱下一个问题自然浮出水面如果每秒有几十个代码执行请求进来总不能每次都现场创建容器吧。Docker容器冷启动虽然比虚拟机快但也要经过镜像拉取、分层解压、网络设置、存储卷挂载等流程实测本地冷启动一个小镜像的容器也要300到800毫秒即使镜像已存在并预热完毕创建流程依然有相当开销。面对高并发请求这个速度显然不够。容器池就是解决这类问题的标准化手段。3.1 为什么不能每次请求都新起容器除了慢还有一个容易被忽略的隐患如果你用Docker SDK或CLI逐个创建容器在并发高峰期Docker守护进程会变成瓶颈。每一次容器创建、销毁操作都要经过守护进程的API接口加入大量网络请求排队后所有操作的延迟都会放大。另一个层面的问题是资源碎片化——每个容器创建时都需要一段连续的内存和磁盘空间频繁创建销毁会导致宿主机可用资源分布不均匀最终容器在随机位置创建时可能因资源不足而失败。容器池的核心思路借鉴对象池模式提前创建一批容器泡在那里“待命”请求到来时从中取一个空闲容器执行代码执行完毕后清理容器内部状态再放回池中复用。这样把“创建容器的耗时”从请求的关键路径上剥离出来请求处理变成了“取容器、执行、干净回收”三步整体延迟可控制在几十毫秒级别。3.2 容器池的三种模式预热、复用与自动伸缩我实际落地时总结出容器池的三种工作模式不同场景可以组合使用或者针对其中一种做深度优化。预热模式Pre-warm。系统启动早期或者流量低谷期容器池管理器按预估容量预先创建固定数量的容器让它们处于暂停或空闲状态。这里的“预热”不仅是容器创建还包括把编译环境、常用依赖库、运行时都加载进容器缓存。最典型的预热策略是让容器保持运行但挂起进程组或者干脆让一个轻量级的占位进程常驻确保内核页缓存里已有可执行文件。这样真正执行用户代码时缺页中断少运行速度快。复用模式Reuse。容器执行完一段用户代码后不直接销毁而是清理现场再放回池子。清理动作包括杀掉所有残留进程、清空临时文件目录、重置网络连接、清理由用户代码留下的环境变量变更。这个模式的难点在于“清理干净”到什么程度后面第6节会给出一个可操作的状态恢复方案。自动伸缩模式Auto-scale。根据队列长度、容器空闲率、宿主机资源水位三个指标动态增加或收缩池子容量。伸缩规则要有上限和冷却时间否则流量抖动时容器池会频繁振荡反而制造新的不稳定因素。实现上最稳妥的是提前半小时做容量预测结合实时水位做二次修正而不是瞬时响应。3.3 容器池的调度与状态管理容器池不仅仅是一个“容器列表”它背后需要一套调度逻辑来处理以下问题容器状态机设计上一般分为空闲、占用、清理中、死亡四个状态。空闲容器有一个空闲时间戳超过一定阈值后由后台协程回收。占用容器绑定一个请求ID防止并发取用同一容器。清理中的容器对外不可见清理超时则直接销毁重建。死亡容器由心跳检测发现移出池子并补充新容器。还有一个细节是容器与宿主机CPU核心的绑定关系。对CPU密集型的代码执行场景建议给容器池中的每个容器绑定固定的CPU核心--cpuset-cpus避免多个容器在宿主机上相互抢CPU时间片导致频繁上下文切换。实测同一个容器池绑定核心后平均执行时间方差能缩小50%以上。4. 资源配额与控制组参数实战CPU、内存、磁盘与进程数容器池建好之后最危险的阶段来了怎么给每个容器设资源上限既不让单段恶意代码打垮宿主又不过度限制导致正常代码运行缓慢。这一节的参数直接在创建容器时通过docker run命令或Docker SDK传入统一整理成表格方便参考。资源维度Docker参数说明建议默认值物理内存上限--memory超出后触发OOM策略512m起步视语言而定内存软限制--memory-reservation内核尽量控制在上限内但不绝对等于--memory的80%交换分区--memory-swap等于--memory时表示禁用swap禁用直接设为与内存相同CPU时间片上限--cpu-quota/--cpu-periodquota/period即CPU核心数的百分比1核设为100000/100000绑核--cpuset-cpus指定允许使用的CPU核心编号按容器池规划分配进程数上限--pids-limit限制容器内总进程数防fork炸弹64或128磁盘写带宽--device-write-bps限制块设备写入速率10MB/s起步临时目录大小--tmpfs可写临时目录限制大小/tmp64m文件句柄--ulimit nofile限制打开文件数128或2564.1 内存配额与OOM策略的坑我给内存配额时踩过一个比较深的坑。--memory限制的是进程的常驻内存RSS加上内核页缓存中属于该容器的部分但它不会限制虚拟内存的申请。一个进程可以用mmap申请1TB虚拟地址空间而不触发OOM因为在真正写入内存页之前内核不会为这段空间分配物理页。所以针对“大数组申请”这类攻击光靠--memory是不够的还得配合--memory-swap和合理的语言运行时参数。以Python为例memoryview和bytearray的扩容是惰性分配你很难从代码层面预判它到底会吃多少内存。更靠谱的方式是在容器内配置语言级的内存上限比如JVM的-Xmx、Python的resource.setrlimit(RLIMIT_AS)双管齐下才能防住“申请不触达、写时才爆炸”的套路。另一个OOM策略的细节默认Docker在OOM时优先杀死容器内消耗内存最高的进程但整个容器不一定会退出。如果你的执行器无法感知“OOM杀了一个内部进程”就可能出现容器还在运行但用户代码已经死掉的残留态。建议在启动容器时显式设置--oom-kill-disablefalse并且由外层执行器负责判断代码是否正常退出而不是依赖容器内进程的存活状态来判断。4.2 CPU配额与实时性能的平衡CPU配额在容器池里需要特别小心配置。--cpu-quota与--cpu-period的比例关系就是容器能使用的CPU核心数。比如cpu-period默认100000微秒设置cpu-quota200000表示容器在每100毫秒周期内最多使用200毫秒CPU时间即两个核心。这里有一个容易被忽视的问题Docker的CPU配额是基于CFS调度器的即使容器内只有一个线程在跑它也只能在配额允许的时间片内运行。如果配额恰好等于一个核心quota100000理论上足以跑满单核但实际上因为调度器切换、配额周期边界等因素只有多留10%到20%的余量才能保证计算密集型任务不拖尾。容器池场景建议直接分配1.2到1.5核的配额给计算密集型语言如C、Rust而脚本语言分配1核就够。4.3 磁盘、进程数与文件句柄的细节磁盘限制是很多人会漏掉的一环。容器内的磁盘写入如果不加限制恶意代码可以瞬间写满宿主机的空闲空间触发整机磁盘爆满。--device-write-bps针对块设备生效但要注意叠加文件系统本身的写入路径最好同时给容器的/tmp、/var/tmp挂tmpfs并限制大小因为这两个目录是恶意代码最爱用的倾倒场。进程数限制非常实用。一个简单的fork炸弹就能让容器内进程数指数级膨胀如果没有--pids-limit宿主机的进程表会被占满触发系统级异常。实测在64位Linux上一个无限fork的C程序如果不限制pids能在几秒内创建数万个进程直接把Docker守护进程拖崩。设了--pids-limit128之后坏代码顶多搞乱自己容器内部影响不到外面。文件句柄限制--ulimit nofile128:256主要防两类问题一是恶意代码打开大量文件导致容器内存浪费二是利用/proc/self/fd泄露的内容做信息收集。语言运行时也要注意比如Java的JIT编译器会打开库文件Python的import机制同样需要读文件限制太死会导致正常代码运行失败。5. 沙箱安全加固的完整检查清单资源配额保证用户代码“跑不死”安全加固保证用户代码“打不穿”。这一节列出的每一项都不是可选项只要跑的是不可信代码这些配置必须全部到位。5.1 运行权限收缩从用户名到内核能力首先要禁止容器内进程以root身份运行任意指令。哪怕USER命名空间映射了root也建议在镜像里创建普通用户并在运行容器时指定--user参数。Docker容器内的root和宿主机的root如果同属一个USER命名空间权限是完全等价的一旦找到逃逸漏洞就是直接提权到宿主机root。其次要裁剪内核能力Capabilities。Docker默认会给容器授予一堆capabilities其中像SYS_ADMIN、NET_ADMIN、SYS_PTRACE这类能力在沙箱场景下风险极高。一键式做法是--cap-dropALL然后按需加回业务必需的能力。在线编译执行场景几乎不需要任何特殊能力所以全部舍弃是安全的。如果你要支持某些调试功能也要非常克制地审查每一项能力的作用范围。--security-opt no-new-privileges这个选项也建议加上。它防止容器内进程通过setuid程序等方式获取额外权限。就算镜像里某个二进制文件带有setuid位这个参数也会让提权尝试直接失败。5.2 seccomp 与 syscall 过滤最有效的一层防线seccomp安全计算模式是内核提供的一种系统调用过滤机制。Docker内置了一个默认的seccomp配置禁用了大约44个危险系统调用包括mount、reboot、kexec_load、ptrace等。对沙箱来说默认配置已经能挡住大部分逃逸尝试但我建议在默认基础上进一步收紧。怎么判断哪些系统调用要放行还是禁用一个实用原则代码执行沙箱的本质是“编译运行”编译器的正常工作需要访问文件、创建子进程、读环境变量运行则可能需要网络取决于你的业务是否需要。除此以外的系统调用都值得怀疑。你可以先记录容器内所有系统调用观察跑真实测试时用到了哪几个再把白名单之外的全部封死。这个动作在公网暴露的沙箱平台上尤其必要。对seccomp配置有一个常见的误解配置了seccomp不代表系统调用就能直接访问内核资源它只是过滤了“是否允许发起某个系统调用”。比如即使放行了open如果文件路径不存在或者权限不够调用同样会失败。所以seccomp只是一个过滤器真正的访问控制依然由文件权限、capabilities、挂载选项共同决定。5.3 网络隔离与文件系统只读化沙箱服务如果不是专门做网络代理或爬虫类业务的建议直接让容器跑在隔离网络模式。最严格的做法是不分配网络接口--networknone容器内进程连DNS解析都用不了从网络层彻底断掉外连可能。如果业务上必须允许容器访问外网做接口测试用--networkbridge加白名单规则的复杂度非常高实际项目中很多人最后都选了none加宿主机侧HTTP代理的组合方案反而更可控。文件系统方面基础操作是--read-only让根文件系统只读然后用tmpfs挂载可读写目录。注意挂载点要合理设计编译器的中间文件目录、语言运行时的缓存目录、用户代码的工作目录分别挂载独立的tmpfs既能互相隔离容量又能分别限制大小。tmpfs本身是内存文件系统写入内容会占用容器内存配额所以这同时意味着容器内存上限也在约束可写空间两个限制叠在一起很有效。5.4 镜像与基础系统的选择策略镜像的质量决定了沙箱的起点安全性。建议遵循以下原则基础镜像尽量选择distroless仅包含运行时和依赖库没有shell或精简的Alpine变体镜像内必须包含一个非root用户去掉所有不需要的编译工具链——除非业务明确要求用户在容器内编译否则容器里只保留一个编译好的二进制和最小动态依赖即可。这里有一个取舍在线代码评测通常需要多种语言运行时于是很多方案会把Python、Node、Java、GCC全塞进同一个镜像。这样做镜像体积大、攻击面广任何一个组件的漏洞都会波及全部业务。更合理的做法是每种语言一个独立镜像容器池按请求的语言类型从不同镜像池中取容器。改动不算大但隔离效果和故障隔离能力完全不同。6. 从零搭建一套最小可用沙箱服务完整实操理论部分说了不少现在进入动手环节。这一节我会给出一个可以直接落地的沙箱服务最小实现目标是接收一段代码和语言类型在容器池的容器里编译并运行返回标准输出、退出码、耗时和内存消耗。6.1 设计目标与整体架构架构上分三个组件执行入口服务、容器池管理器、容器执行器。执行入口服务对外提供HTTP接口接收代码和语言类型构造执行任务后发送到任务队列容器池管理器维护空闲容器列表从队列中获取任务时挑选一个容器容器执行器负责把代码写入容器、执行命令、收集结果。实际部署时执行入口服务和容器池管理器可以各跑一台机器。容器池管理器和Docker守护进程同机部署因为要用Docker SDK管理容器生命周期。宿主机上另外跑一个监控采集器持续采样每台宿主机的CPU、内存、磁盘水位供池子伸缩决策使用。6.2 镜像制作以Python为例的Dockerfile# 基于精简版Python运行时 FROM python:3.11-slim # 创建沙箱用户禁用登录shell RUN groupadd --gid 10001 sandbox \ useradd --uid 10001 --gid 10001 --shell /usr/sbin/nologin --create-home sandbox \ mkdir /box \ chown sandbox:sandbox /box # 设置工作目录 WORKDIR /box # 切到非root用户 USER sandbox这个镜像构建完后运行时用以下参数创建容器docker run \ --name sandbox-py-001 \ --network none \ --read-only \ --cap-drop ALL \ --security-opt no-new-privileges \ --security-opt seccomp/etc/docker/seccomp-box.json \ --memory 512m \ --memory-swap 512m \ --pids-limit 64 \ --tmpfs /box/tmp:size64m,uid10001,gid10001 \ sandbox-py:latest \ sleep infinitysleep infinity的作用是让容器创建后保持运行而不退出这样它才能进入空闲池待命。真正执行用户代码时执行器向这个容器发出新命令而不是重新创建一个容器。6.3 沙箱执行器的核心逻辑执行器的职责是将代码写入容器、启动编译/执行进程、等待结果、清理暂存。我写过一版Python实现核心流程拆成几步第一步向容器传入代码。我建议用Docker SDK的put_archive接口把代码打包传入容器内指定目录。直接在宿主机宿目录挂载卷的做法在沙箱场景下不可取因为宿主机路径暴露给了容器内进程降低了隔离强度。put_archive是把数据流写入容器自身的文件系统没有共享路径的问题。第二步启动命令。用exec_create和exec_start在已有容器内启动进程。这里要设置环境变量关闭语言运行时的缓存和调试特性比如Python的PYTHONDONTWRITEBYTECODE1避免.pyc文件到处写。第三步流式读取输出。exec_start返回的是一个可读流需要同时捕获stdout和stderr。这里有个容易被忽略的点容器内的进程如果fork出子进程并且子进程继承了父进程的输出句柄那么在父进程结束后输出流不会自动关闭读取端会一直等待。解决办法是给容器的进程设置detach_keys或者在外层用超时加结果缓冲宁可丢尾部输出也不能死等。第四步超时处理。执行器侧设一个硬超时比如10秒超过之后主动杀掉容器内的进程组并且这次的执行结果直接按失败处理。杀进程组要小心常规的kill只杀主进程不会杀子进程恶意代码就喜欢fork一堆子进程做干扰。正确做法是启动时把新进程放入专用的进程组超时后对整个进程组发SIGKILL。6.4 容器池管理器的状态恢复方案容器执行完一段代码后直接复用之前必须保证以下几点恢复杀掉容器内所有残留的用户进程执行pkill时要针对沙箱用户而非root防止把容器自身的占位命令也杀了。清空/box/tmp目录直接销毁并重建tmpfs挂载比逐文件删除更高效。删除容器内可能产生的用户文件恢复到/box目录初始状态。环境变量恢复容器内壳配置恢复。最快的恢复操作是直接销毁当前容器从池子状态机里把另一个预备容器补进来。新建一个容器的成本在这里比精确清理低得多也更不容易出错。所以我的容器池设计是“池子里保持N个空闲容器被清理的容器不等恢复完成就把名额释放给创建协程”这样别名复用看起来像是对象复用实际上每次执行都拿的是干净容器。容器池管理器还需要一个心跳协程定期去Docker守护进程查询池内容器状态发现容器退出立即从池中移除并触发自动补偿创建。容器在业务低谷被系统回收的情况不少见心跳检测的周期设置在5到10秒比较合理。7. 实测对比与踩坑记录为什么容器沙箱偶尔会被穿透理论和方法都讲完了最后分享我实测过程中踩过的坑和总结出的经验。这部分内容教科书里很少写但对真正上线跑业务的团队价值很大。7.1 容器池方案与前期的裸机方案对比我搭的演示环境中同一段冒泡排序的Python代码裸机直接执行耗时约1.2秒Docker沙箱执行耗时约1.35秒额外开销约15%。但如果算上容器创建时间差距立刻拉开每请求新容器的方案平均耗时约2.8秒容器池方案容器已预热平均耗时约1.4秒基本等于纯执行时间加十几毫秒的传输损耗。流量从每秒1个请求增加到每秒20个请求时每请求新容器方案延迟指数级上涨容器池方案基本稳定。原因前面分析过高频销毁创建让Docker守护进程成为瓶颈而池化把高频操作变成了低频的“状态机缓存”操作。7.2 四个“你以为安全但实际有洞”的坑坑一挂载了宿主机目录还自认为隔离。我最早把代码目录直接挂载进容器图的是“改代码方便”。结果发现容器内进程可以通过挂载的目录对宿主机做硬链接操作读取其他目录下的文件。排查之后彻底放弃宿主机目录挂载改用put_archive方式传文件。坑二shell注入。执行器里有一步是把用户提交的代码用命令行拼接的方式传给容器执行例如docker exec box timeout 5 python -c {code}。这么做的后果是代码里的双引号、反引号、$(...)都会被shell解析直接变成任意命令执行。后来改成了把代码先写文件、再执行python /box/main.py杜绝了二次解析。坑三环境变量泄露。容器池方案里所有容器共享同一套环境变量配置。有一次在环境变量里放了内部服务的访问令牌结果用户代码里写个print(os.environ)就能把令牌打出来。这个教训提醒我任何放入容器的信息默认都视为已经公开敏感信息一概不能进容器。坑四镜像里藏后门。从第三方仓库拉取的镜像里面可能预装了挖矿程序或代理程序。这类镜像运行时会向外连接固定地址流量也不大初期很难发现。解决办法是镜像统一走内部托管仓库拉取后做离线扫描并且锁定镜像摘要digest而不是标签。7.3 验证沙箱抗攻击的三条测试思路如果你搭建了自己的沙箱服务不妨按以下三条思路做攻击测试第一资源耗尽类。提交一段无限申请内存的代码、一段无限fork的代码、一段死循环写磁盘的代码看宿主机是否稳定。稳定标准是这几个容器各自归西但宿主机上其他容器和Docker守护进程不受影响。第二逃逸探测类。在用户代码里尝试读取/etc/shadow、/proc/1/environ、宿主机IP、其他容器的IP。正常配置下这些尝试应该全部失败失败不代表成功但系统日志里要能看到权限拒绝的记录证明我们配置的防线真实生效。第三隐蔽破坏类。提交一个程序它试图把自己替换为启动时的常驻进程、修改容器内的cgroup参数、向宿主机的Unix套接字发送请求。这些都不太可能成功但能帮我们确认seccomp配置是否真的拦截了对应的系统调用。实测结束后记录攻击代码的行为能发现不少配置上的盲区比如某些基镜像自带/proc挂载的权限过宽、某些sysctl参数覆盖了Docker默认配置等等。修复这些盲区的过程就是沙箱系统渐进完善的过程。我个人的体会是Docker沙箱加容器池这套组合在现阶段依然是最好的平衡点——它不像虚拟机沙箱那样有额外的Hypervisor开销但隔离强度在正确配置的前提下足以应对绝大多数在线代码执行场景。真正要花心思的不是“容器怎么创建”而是“容器池怎么管理、安全基线怎么收敛、异常行为怎么观测”。把这几个问题想透了你的沙箱服务就能稳定跑很久。