尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

youki 容器运行时调试实战:用 bpftrace 追踪 pipe 与双 fork 架构下的系统调用

youki 容器运行时调试实战:用 bpftrace 追踪 pipe 与双 fork 架构下的系统调用 容器运行时云原生【免费下载链接】youkiA container runtime written in Rust项目地址https://gitcode.com/gh_mirrors/yo/youki点击查看免费下载youki 是一个用 Rust 编写的 OCI 兼容底层容器运行时低层运行时通常被 containerd、Podman 等高层运行时调用其容器创建阶段采用pipe 管道通信 双重 forkdouble-fork的进程模型这给调试带来了独特的困难你经常会遇到信息量极少的 Broken pipe ... 错误——它只告诉你某个子进程以出错状态退出了却不说具体原因。本文围绕仓库自带的调试文档 docs/src/developer/debugging.md结合 hack/debug.bt 与 libcontainer 源码完整讲解如何用基于 eBPF 的 bpftrace 工具捕获 youki 在创建容器过程中发出的全部关键系统调用从而做到在子进程内部看见执行轨迹快速定位失败点。为什么 youki 难调试pipe 与双重 fork 的进程模型Broken pipe 错误从何而来youki 的创建流程不是单进程顺序执行而是由三个进程接力完成主进程comm 显示为youki接收 containerd/Docker 等上层运行时下发的 OCI 配置负责解析 spec、准备 cgroup 等前置工作中间进程youki:[1:INTER]由主进程 fork 出来作为跳板负责进入用户命名空间、设置 rlimits、进入 PID/Time 命名空间等中间态配置init 进程youki:[2:INIT]由中间进程再次 fork 出来执行pivot_root、装载 rootfs、执行容器负载等最终步骤。三个进程之间通过pipe管道/socket 通道交换消息init pid、命名空间映射确认、exec 失败通知等相关实现见 crates/libcontainer/src/process/channel.rs。当某个子进程在中间某一步出错并退出时写端或读端会随之关闭父进程就会收到 Broken pipeEPIPE——这个错误本身无法告诉你子进程究竟在哪一步、因为什么系统调用失败。这正是需要额外调试手段的原因。源码中的双重 fork 与进程命名从 crates/libcontainer/src/process/fork.rs 可以看到 youki 的 fork 设计container_clone_sibling第 44-51 行使用CLONE_PARENT标志克隆出兄弟进程init 进程与中间进程共享同一个父进程这样即使 youki 主进程退出init 进程也不会被孤儿化回收而是保持在正确进程树中clone_internal第 59-75 行优先调用clone3在遇到ENOSYS时回退到传统clone回退到clone时需要手动通过mmap分配子进程栈并设置 guard page默认栈上限 8MB细节见 crates/libcontainer/src/process/fork.rs。两个子进程在启动回调中都会立即改名便于在ps、trace 输出中区分角色中间进程在 crates/libcontainer/src/process/container_main_process.rs 中调用prctl::set_name(youki:[1:INTER])init 进程在 crates/libcontainer/src/process/container_intermediate_process.rs 中调用prctl::set_name(youki:[2:INIT])。中间进程的关键职责还包括创建 cgroup 管理器并把自身加入 cgroupapply_cgroups、必要时发起 user namespace 的 UID/GID 映射请求、把 init 进程的 PID 通过通道回传主进程日志sending init pid (Pid(...))对应实现见 crates/libcontainer/src/process/container_intermediate_process.rs。init 进程则负责unshare or setns、pivot_root、找到容器可执行文件并执行负载。由于这三个进程都在创建阶段短暂存在、随后 exec 成容器负载常规的在代码里打日志看标准输出方式难以覆盖它们——这就是 bpftrace 介入的价值所在。bpftrace基于 eBPF 的系统调用追踪器bpftrace 是一个基于 eBPF 的动态追踪工具可以在内核 tracepoint 上挂载探针实时记录进程发起的系统调用及参数。对 youki 而言它能做到捕获 youki 三个进程发出的write系统调用从而看到子进程内部的日志输出youki 子进程把 JSON 格式的 tracing 日志通过管道/文件描述符写出bpftrace 可以截获其内容实现类似print 调试的效果捕获open/openat、clone3、setns、capset、pivot_root、mount、setresuid等关键系统调用及其参数完整还原容器创建的执行轨迹。安装要求bpftrace 需要 root 权限运行且要求内核开启 eBPF 支持。安装方法请参考 bpftrace 官方仓库的 INSTALL.md 文档不同发行版的包管理器一般都能直接安装如apt install bpftrace/dnf install bpftrace等具体以官方文档为准。仓库自带的 hack/debug.bt13 个探针逐行解析youki 仓库在 hack/debug.bt 中预置了一份完整的 bpftrace 脚本专门针对 youki 的进程命名与创建流程设计。脚本开头先打印表头Tracing Youki syscalls... Hit Ctrl-C to end. TIME COMMAND PID EVENT CONTENT脚本共挂了13 个探针含 2 个入口探针共享同一处理逻辑全部通过comm过滤只追踪四个进程名youki、youki:[1:INTER]、youki:[2:INIT]以及 containerd 场景下显示为4的运行时主进程示例输出中主进程 PID 13743 的 comm 后来显示为4故过滤器将其一并纳入。探针tracepoint输出事件捕获内容sys_enter_writewritefd 与写入内容可截获子进程的 JSON 日志sys_enter_open/sys_enter_openatsys_exit_open/sys_exit_openatopenerrno、fd、文件名入口记录文件名出口报告结果sys_enter_clone3clone3仅记录事件双 fork 的分叉点sys_enter_setnssetnsfd 与 flags进入指定命名空间sys_enter_capsetcapset仅记录事件设置 capabilitiessys_enter_pivot_rootpivt_rootnew_root 与 put_old 路径sys_enter_mountmountdev_name 与 dir_namesys_enter_setresuidsetresuidruid、euid、suid几个实现要点write 探针hack/debug.bt用str(args-buf, args-count)读取写入缓冲区并打印内容跳过单独的换行符。youki 子进程通过tracing框架输出的 JSON 日志正是经write写出因此能被完整截获open 探针hack/debug.bt入口与出口分离用filename[tid]按线程暂存文件名出口时计算errno与fd成功时 fd 为返回值失败时 fd-1这样既能看打开的文件也能看到errno2ENOENT之类的失败原因BPFTRACE_STRLEN脚本通过环境变量BPFTRACE_STRLEN120控制字符串捕获长度保证 JSON 日志的头部时间戳、级别、消息、target 字段能够完整进入输出。实操三终端复现调试流程步骤 1安装 bpftrace按 bpftrace 官方安装文档完成安装。后续所有追踪操作都需要root 权限例如用sudo运行。步骤 2先启动追踪脚本在一个终端中进入 youki 仓库目录并运行预置命令$ cd ${youki_repo} $ just hack-bpftrace该命令来自 justfile 中的hack-bpftrace目标实际执行的是BPFTRACE_STRLEN120 ./hack/debug.bt运行后脚本开始挂载探针并等待Attaching 13 probes... Tracing Youki syscalls... Hit Ctrl-C to end. TIME COMMAND PID EVENT CONTENT此时不要关闭该终端脚本会持续捕获直到按 Ctrl-C 结束。步骤 3在另一终端运行要调试的命令保持追踪脚本运行然后在另一个终端执行任何会触发 youki 创建容器的操作——例如直接运行youki create/youki start或者像文档示例那样用 kind 启动一个以 youki 作为 runtime 的 Kubernetes 集群$ cd ${youki_repo} $ just test-kind docker buildx build --outputbin/ -f tests/k8s/Dockerfile --target kind-bin . ... Creating cluster youki ... ... kubectl --contextkind-youki apply -f tests/k8s/deploy.yaml runtimeclass.node.k8s.io/youki created deployment.apps/nginx-deployment created ... kubectl --contextkind-youki delete -f tests/k8s/deploy.yaml runtimeclass.node.k8s.io youki deleted deployment.apps nginx-deployment deletedtest-kind目标justfile会构建 kind 节点镜像、创建名为youki的集群然后通过 tests/k8s/runtimeclass.yaml 与 tests/k8s/deploy.yaml 部署一个 nginx 示例负载验证通过后再清理。kind 的完整测试背景可参考 docs/src/developer/e2e/kubernetes_test.md。步骤 4回到追踪终端查看输出容器创建完成后回到第一个终端即可看到 youki 三个进程发出的系统调用被逐条记录下来。由于是观察式调试你可以在不改动代码、不重启集群的情况下反复执行创建命令观察不同配置或不同 OCI spec 下系统调用序列的差异从而定位问题发生在哪一阶段。输出解读一份真实 trace 的完整走读以下是文档示例中捕获到的真实输出部分 JSON 日志行因长度被省略号截断$ just hack-bpftrace BPFTRACE_STRLEN120 ./hack/debug.bt Attaching 13 probes... Tracing Youki syscalls... Hit Ctrl-C to end. TIME COMMAND PID EVENT CONTENT 207033348942 youki 13743 open errno2, fd-1, file/opt/containerd/lib/glibc-hwcaps/x86-64-v3/libc.so.6 ... 207035462044 youki 13743 open errno0, fd3, file/proc/self/exe 207035478523 youki 13743 write fd4, ELF 207066996623 4 13743 open errno2, fd-1, file/opt/containerd/lib/glibc-hwcaps/x86-64-v3/libc.so.6 ... 207070130175 4 13743 clone3 207070418829 youki:[1:INTER] 13747 write fd4, {timestamp:2023-09-24T10:47:07.427846Z,level:INFO,message:cgroup manager V2 will be used,target:libcgrou ... 207084948440 youki:[1:INTER] 13747 clone3 207085058811 youki:[1:INTER] 13747 write fd4, {timestamp:2023-09-24T10:47:07.442502Z,level:DEBUG,message:sending init pid (Pid(1305)),target:libcontai 207085343170 youki:[2:INIT] 13750 write fd4, {timestamp:2023-09-24T10:47:07.442746Z,level:DEBUG,message:unshare or setns: LinuxNamespace { typ: Uts, path ... 207088256843 youki:[2:INIT] 13750 pivt_root new_root/run/containerd/io.containerd.runtime.v2.task/k8s.io/0fea8cf5f8d1619a35ca67fd6fa73d8d7c8fc70ac2ed43ee2ac2f8610bb938f6/r, put_old/run/containerd/io.containerd.runtime.v2.task/k8s.io/0fea8cf5f8d1619a35ca67fd6fa73d8d7c8fc70ac2ed43ee2ac2f8610bb938f6/r ... 207097207551 youki:[2:INIT] 13750 write fd4, {timestamp:2023-09-24T10:47:07.454645Z,level:DEBUG,message:found executable in executor,executable:\/pa ... 207139391811 youki:[2:INIT] 13750 write fd4, {timestamp:2023-09-24T10:47:07.496815Z,level:DEBUG,message:received: start container,target:libcontainer 207139423243 youki:[2:INIT] 13750 write fd4, {timestamp:2023-09-24T10:47:07.496868Z,level:DEBUG,message:executing workload with default handler,target字段含义TIME事件发生时的相对时钟elapsed单位纳秒可用来估算各阶段耗时COMMAND进程名对应youki主进程、youki:[1:INTER]中间进程、youki:[2:INIT]init 进程示例中主进程 PID 13743 的 comm 在后续事件中显示为4这是 containerd 场景下运行时进程的命名因此脚本过滤器也匹配comm 4PID进程号。示例中 13743主→ 13747中间→ 13750init三段进程逐级 fork 的关系一目了然EVENT / CONTENT系统调用事件名与参数详情。关键节点时间线解读主进程准备阶段youki 13743尝试openglibc 库errno2, fd-1表示失败这属于正常探测路径随后成功open /proc/self/exeerrno0, fd3并write fd4, ELF——主进程读取自身可执行文件并通过通道的写端fd4传递这是 youki 创建流程中通过管道传递数据、支撑后续子进程执行的直观体现双 fork 分叉点主进程clone3→ 出现youki:[1:INTER]PID 13747中间进程继续clone3→ 出现youki:[2:INIT]PID 13750对应 crates/libcontainer/src/process/fork.rs 中的兄弟进程克隆逻辑中间进程日志cgroup manager V2 will be used创建 cgroup v2 管理器、sending init pid (Pid(1305))把 init 进程 PID 回传主进程对应 channel.rs 的intermediate_ready消息——如果容器创建失败且日志停在发送 init pid之前说明问题出在中间进程的命名空间/cgroup 配置阶段init 进程配置与启动unshare or setns: LinuxNamespace { typ: Uts, ...}进入 UTS 等命名空间、pivt_root new_root... put_old...切换到容器 rootfs路径指向 containerd 的 bundle 目录、found executable in executor定位容器负载可执行文件、received: start container、executing workload with default handler最终执行负载。整个序列几乎完整覆盖了 OCI 容器从 spec 解析、cgroup 挂接、命名空间切换、rootfs pivot 到负载执行的每个阶段。当某一步失败时追踪输出会清楚地显示是哪条系统调用返回了错误 errno、发生在哪个进程、哪个文件路径上——这远比一句 Broken pipe 更能说明问题。扩展把 bpftrace 调试接入日常开发复用脚本hack/debug.bt是纯 bpftrace 脚本可以在其基础上增加探针例如sys_enter_execve观察最终 exec 的负载、sys_enter_prctl观察命名设置保持comm过滤器即可与其他测试配合先跑just test-kind这类集成场景复现问题再用 bpftrace 缩小范围最后回到源码单步定位是最省力的组合。仓库的测试入口可参考 docs/src/developer/basics.md注意性能开销bpftrace 探针对每个匹配的系统调用都会触发一次内核事件回调在高频场景下会有一定开销建议仅在调试容器创建阶段使用创建完成后按 Ctrl-C 结束追踪脚本的 END 块会清理暂存数据并打印Tracing ended.。综上所述youki 的 pipe 双重 fork 架构让创建阶段在子进程中发生了什么成为黑盒而 hack/debug.bt 提供的 13 个 eBPF 探针则把这个黑盒透明化——安装 bpftrace 后一条just hack-bpftrace加上任意触发容器创建的命令即可获得涵盖 open/write/clone3/setns/pivot_root/mount 等关键系统调用的完整执行轨迹从只知道 Broken pipe升级到精确定位到具体进程、具体系统调用、具体文件路径。赞分享容器运行时云原生【免费下载链接】youkiA container runtime written in Rust项目地址https://gitcode.com/gh_mirrors/yo/youki点击查看免费下载相关推荐Happy Island Designer核心技术解析Paper.js与Canvas的完美结合Happy Island Designer核心技术解析Paper.js与Canvas的完美结合 Happy Island Designer是一款基于Paper前端游戏开发ESP32 Arduino安装终极指南5步解决新手常见问题ESP32 Arduino安装终极指南5步解决新手常见问题 ESP32 Arduino开发板支持是物联网项目开发的重要基础但不少开发者在实际安装过程中会遇到嵌入式物联网驱动开发IronOS调试技巧使用GDB追踪运行时问题IronOS调试技巧使用GDB追踪运行时问题 在嵌入式开发中调试固件运行时问题是确保烙铁设备稳定性的关键步骤。IronOS作为开源烙铁固件提供了多种调试工嵌入式固件硬件开发智能硬件上一篇B站字幕下载一条命令搞定免费开源的BiliBiliCCSubtitle实战指南下一篇Salt eauth 之 sharedsecret 认证模块共享密钥外部认证External Auth配置实战与源码解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表