DeepSeek DSec 安全沙箱:Agentic Training 执行环境设计与实现 1. 从标题说起DSec到底是个什么东西第一次看到“DeepSeek Elastic Compute”这个名字我下意识以为是某种云主机或者容器编排服务。毕竟“Elastic Compute”这个词在行业里太常见了第一反应就是弹性计算资源调度那一套。但仔细读完DSec的设计文档之后才发现它跟传统的弹性计算完全不是一回事——它是一套专门为Agentic Training智能体训练场景设计的安全沙箱执行环境。说白了DSec要解决的核心问题是当你训练一个能自主调用工具、执行代码、操作文件系统的AI智能体时你怎么保证它不会把宿主机搞崩怎么在成千上万个并发训练任务里给每个Agent一个干净、隔离、可快速创建和销毁的执行环境这就是DSec存在的意义。我之所以对这个项目感兴趣是因为过去大半年我一直在折腾各种Agent框架的本地部署和训练环境搭建。从最开始的裸机跑脚本到后来用Docker做隔离再到尝试各种轻量级沙箱方案踩过的坑可以说能写一本书。DSec的出现算是给这个领域提供了一个系统性的解法。这篇文章适合几类人看一是正在做Agent训练相关工作的工程师二是对沙箱隔离技术感兴趣的后端开发者三是想了解DeepSeek在训练基础设施层面做了哪些工作的技术爱好者。不管你之前有没有接触过类似的东西我都会尽量用大白话把里面的设计思路和实操细节讲清楚。2. 核心设计思路拆解为什么不用Docker而要自己造轮子2.1 Agentic Training对执行环境的特殊要求要理解DSec的设计首先得搞清楚Agentic Training和传统模型训练有什么区别。传统训练就是喂数据、算梯度、更新参数整个过程是确定性的不需要跟外部环境交互。但Agentic Training不一样它需要让模型在训练过程中实际执行动作——比如调用一个API、运行一段代码、读写一个文件——然后根据执行结果来调整策略。这就带来几个硬性要求第一是隔离性。Agent在执行任务时可能会运行任意代码这些代码可能有意或无意地破坏系统。你不能让一个训练任务里的Agent把另一个任务的环境搞乱更不能让它影响到宿主机。第二是启动速度。训练过程中需要频繁创建和销毁执行环境。如果每次创建一个环境要等好几秒那训练效率会低到无法接受。理想情况下环境创建应该在毫秒级别完成。第三是资源控制。每个Agent执行环境需要限制CPU、内存、磁盘IO等资源防止某个Agent跑飞了把整台机器的资源吃光。第四是可观测性。训练过程中需要记录Agent的每一步操作包括执行了什么命令、产生了什么输出、消耗了多少资源。这些数据对训练和调试都至关重要。2.2 为什么Docker方案不够用很多人第一反应是用Docker来做隔离毕竟这是最成熟的方案。我一开始也是这么想的但实际用下来发现几个致命问题。Docker容器的启动时间虽然在秒级但对于Agentic Training来说还是太慢了。你想想一个训练任务可能需要执行几万次动作每次动作都创建一个新容器光是启动开销就受不了。而且Docker的隔离是基于namespace和cgroup的本质上还是共享内核对于恶意代码的防护能力有限。更关键的是Docker的镜像管理机制对于这种高频创建销毁的场景来说太重了。每个容器都需要一个完整的文件系统层磁盘占用和IO开销都很大。虽然可以用overlayfs做优化但在大规模并发场景下还是会成为瓶颈。还有一个容易被忽略的问题是Docker容器里的进程管理跟宿主机是分离的。当Agent启动一个后台进程然后退出时这个进程可能会变成孤儿进程继续占用资源。要正确处理这种情况需要额外的机制增加了复杂度。2.3 DSec的架构选择轻量级虚拟化进程级隔离DSec最终选择的方案是轻量级虚拟化加上进程级隔离的组合。具体来说它利用了Linux内核提供的一些底层能力包括用户命名空间User Namespace让每个执行环境有独立的用户ID映射即使Agent以root身份运行在宿主机上也只是普通用户。挂载命名空间Mount Namespace每个环境有独立的文件系统视图可以快速挂载和卸载目录。PID命名空间环境内的进程与宿主机进程隔离避免相互干扰。seccomp过滤器限制Agent可以调用的系统调用防止危险操作。cgroup v2精确控制CPU、内存、IO等资源配额。这套组合的好处是启动速度极快——实测下来创建一个新执行环境只需要几毫秒。而且因为不需要完整的文件系统镜像磁盘占用也很小。每个环境只需要一个基础的文件系统模板然后通过写时复制Copy-on-Write机制来隔离修改。这里有个设计上的取舍值得注意DSec没有选择完全虚拟化比如KVM因为完全虚拟化的开销太大启动一个虚拟机至少需要几百毫秒。对于需要高频创建环境的训练场景来说这个延迟是不可接受的。轻量级虚拟化在隔离性和性能之间找到了一个平衡点。3. 核心细节解析DSec的关键组件与工作原理3.1 执行环境的生命周期管理DSec把一个执行环境的生命周期分为四个阶段创建、初始化、运行、销毁。每个阶段都有对应的优化策略。创建阶段是最关键的。DSec维护了一个预热池Warm Pool里面有一批已经创建好但尚未初始化的环境。当训练任务需要一个新环境时直接从池子里取一个省去了创建命名空间和cgroup的时间。池子的大小可以根据负载动态调整保证高峰期也有足够的供应。初始化阶段包括挂载文件系统、设置环境变量、安装必要的工具链等。DSec的做法是把这些操作做成可配置的初始化脚本不同的训练任务可以指定不同的初始化流程。为了加速常用的初始化操作会被缓存避免重复执行。运行阶段是Agent实际执行动作的阶段。DSec会监控每个环境的资源使用情况如果某个环境超出了配额会被自动限制或终止。同时所有执行的操作都会被记录到日志中包括命令内容、执行时间、退出码、标准输出和标准错误。销毁阶段需要确保环境被彻底清理。DSec会杀死所有残留进程卸载文件系统回收cgroup资源。如果环境在运行过程中产生了大量临时文件销毁时也会一并清理避免磁盘泄漏。3.2 文件系统隔离的实现细节文件系统隔离是DSec里比较精妙的部分。它没有用传统的镜像分层方案而是采用了一种叫“基础模板增量层”的结构。基础模板是一个只读的根文件系统包含了常用的工具和库。所有执行环境共享同一个基础模板所以磁盘上只需要存一份。当Agent需要修改文件时修改会被写入一个增量层这个增量层是每个环境独有的。读取文件时系统会先查增量层如果没有再查基础模板。这种结构的好处是创建环境时几乎不需要拷贝数据只需要创建一个空的增量层就行。实测下来创建一个新环境的磁盘开销只有几十KB创建时间在5毫秒以内。需要注意的是增量层的清理很重要。如果训练任务频繁创建环境但忘记清理增量层会越积越多最终把磁盘撑爆。DSec提供了自动清理策略可以设置增量层的最大存活时间或最大数量。3.3 网络隔离与通信机制Agent在执行任务时可能需要访问网络比如调用外部API或者下载依赖。DSec对网络访问做了精细控制。默认情况下每个执行环境有独立的网络命名空间只能访问内部的服务。如果需要访问外部网络需要通过一个代理服务这个代理会记录所有请求并执行访问控制策略。这样既能满足Agent的正常需求又能防止数据泄露或恶意访问。环境之间的通信通过一个消息队列实现。训练主进程可以通过消息队列向执行环境发送指令执行环境也可以通过消息队列返回结果。这种异步通信方式避免了直接的系统调用开销也方便做流量控制和错误处理。3.4 资源限制与监控资源限制方面DSec用了cgroup v2的各个控制器资源类型限制方式典型配置CPUcpu.max限制每个环境最多使用2个核心内存memory.max限制每个环境最多使用512MB磁盘IOio.max限制读写带宽为50MB/s进程数pids.max限制最多创建64个进程文件描述符通过rlimit限制最多打开1024个文件监控数据会被实时采集并发送到训练主进程。如果某个环境的内存使用接近上限系统会提前发出警告让训练逻辑有机会做出调整。如果超过了硬限制环境会被强制终止避免影响其他任务。4. 实操过程从零搭建一个DSec训练环境4.1 环境准备与依赖安装虽然DSec是DeepSeek内部使用的系统但它的设计思路和核心组件是通用的。我根据公开的技术资料和自己的实践整理了一套可以在本地复现的方案。需要说明的是这不是官方文档而是基于常见实践的个人总结。首先确认系统环境。DSec依赖较新的Linux内核特性建议使用5.10以上的内核版本。我用的是Ubuntu 22.04内核版本5.15实测可以正常运行。# 检查内核版本 uname -r # 检查cgroup v2是否启用 mount | grep cgroup2 # 检查用户命名空间是否启用 cat /proc/sys/kernel/unprivileged_userns_clone如果cgroup v2没有启用需要在启动参数里加上systemd.unified_cgroup_hierarchy1。用户命名空间如果被禁用需要设置为1。接下来安装必要的工具sudo apt update sudo apt install -y build-essential libseccomp-dev libcap-dev \ pkg-config git cmake golang-go4.2 基础文件系统模板的制作基础模板是整个环境的基础。我建议用一个精简的Linux发行版作为起点比如Alpine Linux或者Ubuntu Base。这里以Ubuntu Base为例# 创建模板目录 mkdir -p /opt/dsec/templates/base cd /opt/dsec/templates/base # 下载Ubuntu Base根文件系统 wget https://cdimage.ubuntu.com/ubuntu-base/releases/22.04/release/ubuntu-base-22.04-base-amd64.tar.gz # 解压 tar -xzf ubuntu-base-22.04-base-amd64.tar.gz # 安装常用工具 chroot . /bin/bash -c apt update apt install -y python3 python3-pip curl wget git制作好的模板需要设置为只读防止被意外修改chmod -R a-w /opt/dsec/templates/base4.3 创建执行环境的核心代码下面是一个简化版的执行环境创建逻辑用Go语言实现。这段代码展示了DSec核心思路的落地方式package dsec import ( os/exec syscall time ) type Sandbox struct { ID string Rootfs string UpperDir string WorkDir string MergedDir string CgroupPath string CreatedAt time.Time } func CreateSandbox(id string) (*Sandbox, error) { sb : Sandbox{ ID: id, Rootfs: /opt/dsec/templates/base, UpperDir: /opt/dsec/overlay/ id /upper, WorkDir: /opt/dsec/overlay/ id /work, MergedDir: /opt/dsec/overlay/ id /merged, } // 创建overlay目录 if err : exec.Command(mkdir, -p, sb.UpperDir, sb.WorkDir, sb.MergedDir).Run(); err ! nil { return nil, err } // 挂载overlay文件系统 mountCmd : exec.Command(mount, -t, overlay, overlay, -o, lowerdirsb.Rootfs,upperdirsb.UpperDir,workdirsb.WorkDir, sb.MergedDir) if err : mountCmd.Run(); err ! nil { return nil, err } // 创建cgroup sb.CgroupPath /sys/fs/cgroup/dsec/ id if err : exec.Command(mkdir, -p, sb.CgroupPath).Run(); err ! nil { return nil, err } // 设置资源限制 writeFile(sb.CgroupPath/memory.max, 536870912) // 512MB writeFile(sb.CgroupPath/cpu.max, 200000 100000) // 2 cores writeFile(sb.CgroupPath/pids.max, 64) sb.CreatedAt time.Now() return sb, nil }这段代码的核心逻辑是用overlayfs把只读的基础模板和可写的增量层合并成一个完整的文件系统视图然后用cgroup限制资源使用。创建过程只需要挂载和写几个配置文件所以速度非常快。4.4 在命名空间中执行命令创建好沙箱之后下一步是在沙箱里执行命令。这需要用到Linux的命名空间隔离func (sb *Sandbox) Execute(command string, timeout time.Duration) (string, error) { cmd : exec.Command(/bin/bash, -c, command) cmd.SysProcAttr syscall.SysProcAttr{ Cloneflags: syscall.CLONE_NEWNS | // 挂载命名空间 syscall.CLONE_NEWPID | // PID命名空间 syscall.CLONE_NEWNET | // 网络命名空间 syscall.CLONE_NEWUSER, // 用户命名空间 UidMappings: []syscall.SysProcIDMap{ {ContainerID: 0, HostID: os.Getuid(), Size: 1}, }, GidMappings: []syscall.SysProcIDMap{ {ContainerID: 0, HostID: os.Getgid(), Size: 1}, }, Chroot: sb.MergedDir, } // 设置超时 timer : time.AfterFunc(timeout, func() { cmd.Process.Kill() }) defer timer.Stop() output, err : cmd.CombinedOutput() return string(output), err }这里的关键是Cloneflags参数它指定了要创建哪些命名空间。Chroot参数把进程的根目录切换到合并后的文件系统视图这样Agent就只能看到沙箱内的文件。实操心得用户命名空间的UID映射一定要配置正确。如果映射错了Agent在沙箱内以root身份运行时在宿主机上可能变成真正的root那就完全失去了隔离的意义。我建议先用一个简单的测试脚本验证映射是否正确再跑正式的训练任务。4.5 资源监控与日志采集监控是训练过程中不可或缺的一环。DSec会采集每个沙箱的资源使用数据并记录所有执行过的命令。下面是一个简单的监控实现func (sb *Sandbox) Monitor(interval time.Duration) { ticker : time.NewTicker(interval) defer ticker.Stop() for range ticker.C { // 读取内存使用 memUsage : readFile(sb.CgroupPath /memory.current) // 读取CPU使用 cpuUsage : readFile(sb.CgroupPath /cpu.stat) // 读取进程数 pids : readFile(sb.CgroupPath /pids.current) log.Printf(sandbox%s mem%s cpu%s pids%s, sb.ID, memUsage, cpuUsage, pids) } }日志采集方面我建议把每个沙箱的标准输出和标准错误重定向到独立的文件同时记录命令的执行时间、退出码和资源消耗。这些数据在训练后期分析Agent行为时非常有用。5. 常见问题与排查技巧实录5.1 沙箱创建失败权限问题排查这是最常见的问题。DSec依赖用户命名空间如果系统配置不允许非特权用户创建命名空间沙箱创建就会失败。症状创建沙箱时报错operation not permitted。排查步骤检查/proc/sys/kernel/unprivileged_userns_clone的值如果是0需要设置为1。检查AppArmor配置某些发行版的AppArmor会限制用户命名空间的创建。检查是否在容器内运行。如果在Docker容器里跑DSec需要给容器加上--privileged参数或者特定的capability。# 临时启用用户命名空间 sudo sysctl -w kernel.unprivileged_userns_clone1 # 永久启用 echo kernel.unprivileged_userns_clone1 | sudo tee -a /etc/sysctl.conf5.2 文件系统挂载失败overlayfs的常见坑overlayfs虽然好用但有几个容易踩的坑。第一个坑是workdir和upperdir必须在同一个文件系统上。如果upperdir在一个分区workdir在另一个分区挂载会失败。我建议把这两个目录放在同一个父目录下确保它们在同一个文件系统。第二个坑是内核版本。老版本内核的overlayfs不支持某些特性比如在overlay之上再挂载overlay。如果遇到奇怪的问题先检查内核版本。第三个坑是SELinux。如果系统启用了SELinuxoverlayfs的挂载可能会被阻止。需要给相关目录打上正确的标签或者临时把SELinux设为permissive模式来排查问题。5.3 资源限制不生效cgroup v2的配置要点cgroup v2的配置跟v1有很大区别很多人会在这里踩坑。要点一cgroup v2的控制器需要显式启用。在根cgroup的cgroup.subtree_control文件里写入memory cpu pids子cgroup才能使用这些控制器。要点二cgroup v2的层级结构是单一的不能像v1那样把不同控制器挂载到不同层级。所有控制器都在同一个树里。要点三内存限制的生效有延迟。设置了memory.max之后不会立即生效需要等一段时间。如果Agent在设置限制后立刻分配大量内存可能会短暂超出限制。# 启用控制器 echo memory cpu pids | sudo tee /sys/fs/cgroup/cgroup.subtree_control # 验证 cat /sys/fs/cgroup/cgroup.controllers5.4 进程泄漏孤儿进程的处理Agent在执行任务时可能会启动后台进程然后退出。如果不处理这些进程会变成孤儿进程继续占用资源。解决方案在沙箱的PID命名空间里把第一个进程作为init进程PID 1由它来负责回收孤儿进程。具体做法是在沙箱启动时先运行一个简单的init程序这个程序会持续等待子进程退出并回收它们。// 简化的init进程 func initProcess() { for { var status syscall.WaitStatus pid, err : syscall.Wait4(-1, status, 0, nil) if err ! nil { break } log.Printf(reaped process %d, pid) } }5.5 常见问题速查表问题现象可能原因解决方法沙箱创建超时预热池耗尽增大预热池大小或降低创建频率命令执行无输出标准输出被重定向检查命令是否写入了文件而非终端内存限制不生效cgroup控制器未启用检查subtree_control配置文件系统只读overlay挂载参数错误确认upperdir可写且在同一文件系统网络访问失败网络命名空间隔离配置代理或共享网络命名空间进程数超限pids.max设置过小根据任务需求调整限制值磁盘空间不足增量层未清理配置自动清理策略或手动清理最后分享一个我踩过的坑在批量创建沙箱时如果并发度太高overlayfs的挂载操作可能会因为内核锁竞争而变慢。我的做法是把并发度控制在CPU核心数的2倍以内实测下来创建速度最稳定。另外预热池的大小建议设置为预期峰值并发的1.5倍留一些余量应对突发流量。这套方案我在自己的训练环境里跑了几个月整体稳定性还不错。当然跟DeepSeek官方的DSec实现肯定有差距但核心思路是一致的。如果你也在做Agent相关的训练工作希望这些经验能帮你少走一些弯路。