Posted in

为什么你的Go程序在Linux能跑,在Alpine却Segmentation Fault?执行环境兼容性5大断点分析

第一章:Go程序执行模型与Linux/Alpine差异本质

Go 程序的执行模型天然具备“静态链接”倾向:默认编译时将运行时(runtime)、标准库及依赖的 C 函数(如 mallocclock_gettime)全部打包进二进制,形成自包含的可执行文件。这一特性使 Go 二进制在多数 GNU/Linux 发行版上“开箱即用”,但其底层行为仍深度依赖宿主系统的内核接口与 C 运行时环境。

关键差异源于 libc 实现的选择:

  • 主流 Linux 发行版(Ubuntu、CentOS 等)使用 glibc,功能完备但体积大、符号复杂;
  • Alpine Linux 使用 musl libc,轻量、简洁、严格遵循 POSIX,但缺少部分 glibc 特有扩展(如 getaddrinfo_a 异步解析、__cxa_thread_atexit_impl)。

当 Go 程序调用 net 包进行 DNS 解析时,行为显著分化:

  • 在 glibc 环境中,Go 默认启用 cgo,委托系统 resolver(如 /etc/resolv.conf + getaddrinfo);
  • 在 Alpine 中,若未显式禁用 cgo,链接会失败(musl 不提供 libresolv.so 的兼容符号);而启用 CGO_ENABLED=0 后,Go 切换至纯 Go DNS 解析器(net.DefaultResolver),绕过 libc,但失去 nsswitch.conf 和 SRV 记录等高级能力。

验证差异的典型操作:

# 构建 Alpine 兼容二进制(强制纯 Go 模式)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app-alpine .

# 构建标准 Linux 二进制(依赖 glibc)
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o app-glibc .

# 检查动态依赖(Alpine 二进制应无 .so 依赖)
ldd app-alpine     # → "not a dynamic executable"
ldd app-glibc      # → shows "libpthread.so.0", "libc.so.6", etc.
特性 glibc 环境(Ubuntu/CentOS) musl 环境(Alpine)
默认 CGO 状态 启用 启用但链接易失败
推荐构建方式 CGO_ENABLED=1 CGO_ENABLED=0musl-gcc
DNS 解析实现 系统 resolver(cgo) 纯 Go resolver(net)
二进制大小 较大(含符号表、调试信息) 极小(典型
os/user.Lookup* 依赖 NSS 模块 仅支持 /etc/passwd 文件

理解这一差异是构建可靠容器镜像、调试跨平台网络异常及优化启动性能的前提。

第二章:动态链接与C运行时兼容性断点

2.1 glibc vs musl libc:ABI级不兼容的底层原理与objdump实证分析

ABI不兼容根源在于符号版本控制、调用约定及全局偏移表(GOT)初始化方式的根本差异。

符号版本化对比

glibc广泛使用GLIBC_2.2.5等版本符号,而musl完全省略符号版本:

# 查看动态符号表(glibc编译)
$ objdump -T /bin/ls | grep 'read@'
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 read
# musl编译二进制中无版本后缀
$ objdump -T ./ls-musl | grep 'read@'
0000000000000000      DF *UND*  0000000000000000  read

-T参数输出动态符号表;GLIBC_2.2.5表明glibc强制绑定运行时版本,musl则依赖单一ABI快照,导致dlopen时符号解析失败。

GOT/PLT初始化差异

特性 glibc musl
PLT stub长度 16字节(含间接跳转) 6字节(直接jmp *%rax)
GOT首项用途 _dl_runtime_resolve地址 __libc_start_main地址
graph TD
    A[call read@plt] --> B{glibc PLT}
    A --> C{musl PLT}
    B --> D[跳转至_dl_runtime_resolve]
    C --> E[直接加载GOT[1]并jmp]

2.2 Go静态链接策略失效场景:cgo启用时的隐式动态依赖链追踪

CGO_ENABLED=1 时,Go 编译器自动放弃纯静态链接,转而引入系统级动态依赖。

隐式依赖触发点

  • 调用 netos/userdatabase/sql(含 sqlite3 驱动)等标准库子包
  • 使用任意含 #include 的 C 代码或 .h 头文件
  • 链接外部 C 库(如 -lcrypto)时未显式指定 --static

动态链接行为验证

# 编译后检查动态依赖
ldd ./myapp | grep -E "(libc|libpthread|libm)"
# 输出示例:
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6

该命令揭示运行时真实加载的 glibc 路径——证明静态链接已失效。ldd 解析的是 ELF 的 DT_NEEDED 条目,反映 cgo 激活后 linker 插入的隐式依赖。

典型依赖链传播路径

graph TD
    A[main.go] -->|import net| B[net/cgo_resnew.go]
    B -->|calls getaddrinfo| C[libc.so.6]
    C --> D[libresolv.so.2]
    D --> E[libnss_files.so.2]
场景 是否触发动态链接 关键依据
CGO_ENABLED=0 强制使用纯 Go DNS 解析器
import "net" + CGO cgo_resnew.go 中调用 libc
// #include <math.h> 编译器注入 libm.so.6 依赖

2.3 LD_LIBRARY_PATH与rpath在Alpine中被忽略的实操验证与strace日志解读

Alpine Linux 使用 musl libc 而非 glibc,其动态链接器 /lib/ld-musl-x86_64.so.1 默认忽略 LD_LIBRARY_PATH 和嵌入式 rpath(除非显式启用)。

验证环境准备

# 构建含 rpath 的二进制(使用 musl-gcc)
musl-gcc -Wl,-rpath,/tmp/lib test.c -o test_rpath
readelf -d test_rpath | grep PATH  # 确认 rpath 存在

readelf -d 显示 DT_RUNPATH 条目,但 musl 链接器不解析它——这是设计行为,非 bug。

strace 日志关键片段

openat(AT_FDCWD, "/tmp/lib/libm.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT
openat(AT_FDCWD, "/usr/lib/libm.so.6", O_RDONLY|O_CLOEXEC) = 3

strace 显示链接器跳过 /tmp/lib(rpath 指向路径),直接搜索系统路径 /usr/lib,印证 musl 的“安全优先”策略。

musl 链接器行为对比表

行为项 glibc (ld-linux) musl (ld-musl)
支持 LD_LIBRARY_PATH ✅ 默认启用 ❌ 完全忽略
解析 DT_RUNPATH ❌(需 --enable-rpath 编译选项)

根本解决路径

  • ✅ 使用 apk add --repository http://dl-cdn.alpinelinux.org/alpine/edge/community 安装依赖
  • ✅ 构建时用 -Wl,--dynamic-list-data + install_name_tool(不适用 musl)→ 改用 patchelf --set-rpath(需 apk add patchelf
  • ⚠️ 强制启用:export MUSL_DYNAMIC_LINKER=1 无效 —— musl 不提供运行时开关。

2.4 Alpine容器内ldd输出为空却仍Segmentation Fault的根源复现与readelf解析

Alpine Linux 使用 musl libc 替代 glibc,导致 ldd(本质是 bash 脚本调用 /lib/ld-musl-x86_64.so.1 --list)在静态链接或缺失解释器路径时静默退出,输出为空。

复现步骤

# 在 Alpine 容器中编译一个动态链接但缺失依赖的二进制
apk add build-base
echo 'int main(){return 0;}' | gcc -x c - -o /tmp/test
# 此时 ldd /tmp/test 输出为空 —— 并非无依赖,而是 musl ldd 不识别非标准链接方式

ldd 在 musl 下仅对 PT_INTERP 指向 /lib/ld-musl-*.so.1 的 ELF 生效;若编译时误加 -static-libgcc 或链接了混用 glibc 的 .so,PT_INTERP 可能为空或非法,ldd 直接跳过打印。

关键诊断命令

工具 作用
readelf -l /tmp/test \| grep INTERP 查看程序解释器路径是否存在
file /tmp/test 确认是否为 dynamically linked (no interpreter)
graph TD
    A[执行二进制] --> B{readelf -l 显示 PT_INTERP?}
    B -->|否| C[Segmentation Fault:内核无法加载解释器]
    B -->|是| D[检查 /lib/ld-musl-*.so.1 是否存在且可读]

2.5 替换基础镜像时未重编译CGO_ENABLED=0导致的符号解析崩溃现场还原

当从 golang:1.21-alpine 切换至 gcr.io/distroless/static:nonroot 后,若未重新设置构建环境,静态链接的二进制仍隐式依赖 glibc 符号(如 __vdso_clock_gettime),而 distroless 镜像无 libc 动态库,触发 SIGSEGV

崩溃复现命令

# ❌ 错误:沿用原 Alpine 构建产物(CGO_ENABLED=1 编译)
docker run --rm -v $(pwd)/app:/app gcr.io/distroless/static:nonroot /app/app
# panic: symbol __vdso_clock_gettime not found

此命令在无 libc 环境中加载了动态链接版二进制,运行时动态链接器尝试解析 VDSO 符号失败。关键参数:CGO_ENABLED=1 生成依赖系统 libc 的可执行文件,与 distroless 零依赖设计冲突。

正确构建流程

  • 必须显式启用纯静态编译:
    CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o app .
  • 构建后验证:file app 应输出 statically linkedldd app 应提示 not a dynamic executable
环境变量 作用
CGO_ENABLED=0 禁用 C 调用,强制 Go 标准库纯实现
GOOS=linux 保证跨平台 ABI 兼容性
graph TD
  A[原始构建 CGO_ENABLED=1] --> B[链接 libc.so]
  B --> C[运行于 distroless]
  C --> D[符号解析失败 SIGSEGV]
  E[重建 CGO_ENABLED=0] --> F[静态链接 syscall]
  F --> G[无外部依赖 ✅]

第三章:内核接口与系统调用语义断点

3.1 Linux内核版本差异引发的syscall ABI漂移(如clone3、membarrier)实测对比

数据同步机制

membarrier() 在 5.1+ 内核引入 MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE,而 4.18 仅支持基础命令。调用非法 cmd 将返回 EINVAL

// 检测 membarrier 扩展能力(需 >=5.1)
if (syscall(__NR_membarrier, MEMBARRIER_CMD_QUERY, 0) & 
    MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE) {
    syscall(__NR_membarrier, MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE, 0);
}

MEMBARRIER_CMD_QUERY 返回位掩码;未置位即内核不支持该语义,强行调用将破坏 ABI 兼容性。

clone3 行为差异

内核版本 clone3 支持 默认 flags 处理 CLONE_ARGS_SIZE_VER0 检查
5.3 严格校验结构体大小
5.10 宽松填充默认值 是(向后兼容)

ABI 漂移路径

graph TD
    A[用户态调用 clone3] --> B{内核版本 < 5.3?}
    B -->|是| C[拒绝非法 args.size]
    B -->|否| D[自动补全缺失字段]

3.2 Alpine默认内核标头(linux-headers)过旧导致syscall封装函数行为异常调试

Alpine Linux 默认使用 linux-headers 软件包提供系统调用接口定义,但其版本常滞后于主流内核(如 v5.15+),导致 glibc 或 musl 中的 syscall 封装函数(如 copy_file_range, statx)在运行时解析错误或返回 -ENOSYS

现象复现

// test_statx.c
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
int main() {
    struct statx stx;
    int r = statx(AT_FDCWD, "/etc/passwd", AT_STATX_SYNC_AS_STAT, STATX_BASIC_STATS, &stx);
    printf("statx ret=%d, errno=%d\n", r, errno); // Alpine edge 可能返回 -1, errno=38 (ENOSYS)
}

该调用在较新内核上应成功,但因 /usr/include/asm-generic/unistd_64.h 中缺失 __NR_statx 定义,编译期无法生成正确 syscall number,运行时触发 fallback 失败。

根本原因对比

组件 Alpine v3.19 (musl) Ubuntu 22.04 (glibc)
linux-headers 版本 5.10.124-r0 5.15.0-107-generic
__NR_statx 定义 ❌ 缺失 ✅ 存在(nr=332)

修复路径

  • 升级 linux-headersapk add linux-headers --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community
  • 或显式指定 syscall number(不推荐生产环境)
graph TD
    A[调用 statx()] --> B{头文件含 __NR_statx?}
    B -->|否| C[编译器设为 0 → 运行时 ENOSYS]
    B -->|是| D[生成正确 syscall number → 内核处理]

3.3 seccomp默认配置拦截非标准syscalls的容器级故障注入与auditctl日志取证

seccomp BPF 默认策略(如 runtime/default.json)显式拒绝非常规系统调用(如 pivot_root, cloneCLONE_NEWUSER 标志),形成容器级syscall熔断点。

故障注入实践

# 注入非法 clone 调用触发 seccomp kill
docker run --rm --security-opt seccomp=default alpine sh -c \
  'unshare --user /bin/true 2>/dev/null || echo "seccomp blocked"'

此命令触发 clone 系统调用,因默认 seccomp profile 中 clone 未显式允许且无 SCMP_ACT_ALLOW 规则,内核以 SIGSYS 终止进程。--security-opt seccomp=default 显式启用默认策略,确保可复现拦截。

auditctl 日志捕获

字段 示例值 说明
arch x86_64 系统架构
syscall 120 (clone) syscall 编号,查 /usr/include/asm/unistd_64.h
auid 4294967295 未登录用户的审计UID
graph TD
    A[容器进程发起 clone] --> B{seccomp BPF 过滤器匹配}
    B -->|规则缺失/拒绝动作| C[内核发送 SIGSYS]
    B -->|audit=1 启用| D[auditd 记录 SYSCALL 行]
    D --> E[ausearch -m avc -i \| aureport -f]

第四章:内存管理与信号处理机制断点

4.1 musl malloc实现与glibc malloc在并发arena分配上的SIGSEGV触发路径对比

核心差异根源

musl 采用全局单 arena + 无锁 freelist,而 glibc 使用多 arena + per-arena mutex + 首次分配时动态创建

SIGSEGV 触发关键路径

  • glibc:线程首次调用 malloc 时若 mp_.morecore 未就绪,且 arena_get2new_arena() 分配失败 → heap->ar_ptr = NULL → 后续 arena_memalign 解引用空指针。
  • musl:无 arena 概念,直接通过 __syscall(SYS_brk, ...) 调整 brk;若 sbrk 返回 -ENOMEM 且 fallback mmap 失败,则 malloc 返回 NULL不触发 SIGSEGV

并发分配行为对比

维度 glibc musl
arena 创建时机 首次 malloc 时延迟创建 无 arena,无创建开销
空间分配失败处理 解引用未初始化的 ar_ptr 安全返回 NULL
同步机制 arena_lock 互斥锁(可阻塞) 原子 CAS freelist(无锁)
// glibc arena_get2 中危险片段(简化)
if (!a) {
    a = new_arena(); // 可能返回 NULL
    if (!a) return NULL; // 但此处遗漏检查!
}
return a->memalign(...); // a->memalign → segfault if a==NULL

逻辑分析:new_arena() 在内存枯竭或 mmap 失败时返回 NULL,但调用方未校验即解引用 a->memalign,直接触发 SIGSEGV。参数 a 为未初始化指针,其 vtable 偏移访问非法地址。

graph TD
    A[线程调用 malloc] --> B{glibc: arena_get2}
    B --> C[new_arena()]
    C --> D{成功?}
    D -- 否 --> E[a = NULL]
    E --> F[a->memalign → SIGSEGV]
    D -- 是 --> G[正常分配]

4.2 Go runtime对SIGURG/SIGPIPE等信号的默认处理在musl环境下的未定义行为复现

Go runtime 在 glibc 环境中会显式屏蔽 SIGURGSIGPIPE 等非同步信号,但 musl libc 不保证信号掩码继承语义一致,导致 runtime 启动时的 sigprocmask 行为未定义。

关键差异点

  • musl 的 clone() 实现不严格遵循 POSIX 对子线程信号掩码的继承要求;
  • Go 的 runtime.sighandler 初始化早于 libmusl 的信号框架就绪,造成竞态。

复现场景代码

package main
import "os/exec"
func main() {
    // 触发 SIGPIPE(如管道写入已关闭读端)
    cmd := exec.Command("sh", "-c", "echo hello | head -c1 >/dev/null")
    cmd.Run() // musl 下可能 panic: signal received on thread not created by Go
}

此调用在 musl + Go 1.21+ 中触发 runtime 信号处理异常:因 SIGPIPE 未被正确屏蔽,且 musl 的 sigaltstack 初始化延迟,导致信号投递到非 Go-managed 线程。

信号 glibc 行为 musl 行为
SIGPIPE runtime 显式屏蔽 屏蔽时机不可靠,可能漏设
SIGURG 由 netpoll 专用处理 被误交由默认 handler 处理
graph TD
    A[Go runtime init] --> B[调用 sigprocmask]
    B --> C{musl libc 版本 < 1.2.4?}
    C -->|Yes| D[忽略部分信号掩码更新]
    C -->|No| E[按预期屏蔽]
    D --> F[后续 SIGPIPE 直接触发 abort]

4.3 ASLR与mmap区域对齐差异引发的stack guard page越界访问现场捕获(gdb+core dump)

当内核启用CONFIG_ARM64_VA_BITS=48且用户态启用ASLR时,mmap()分配的匿名映射默认按PAGE_SIZE(4KB)对齐,而栈guard page由arch_setup_new_exec()mm->def_flags中隐式预留,其起始地址受STACK_RND_MASK(如0x3ff << PAGE_SHIFT)扰动——导致guard page可能未严格对齐至mmap_min_addr边界。

核心触发条件

  • 进程通过mmap(NULL, 2*PAGE_SIZE, ..., MAP_ANONYMOUS|MAP_STACK)申请栈式内存
  • ASLR使mmap基址落在0x7f...ffe000(末尾非整页),guard page被置于0x7f...ffd000,但实际栈指针已越过该页

复现代码片段

#include <sys/mman.h>
#include <string.h>
char *p = mmap(NULL, 4096*2, PROT_READ|PROT_WRITE,
               MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0);
memset(p + 4096 + 1, 0, 1); // 越界写入guard page → SIGSEGV

此处p + 4096位于guard page起始,+1即触发页错误;MAP_STACK仅提示内核预留保护页,不保证对齐一致性。

gdb定位关键步骤

命令 作用
info proc mappings 查看guard page是否为---p权限且紧邻栈区
x/4xg $rsp 验证$rsp是否落入[guard_start, guard_end)区间
bt full 结合core dump确认faulting IP在越界写指令
graph TD
    A[进程调用mmap MAP_STACK] --> B{ASLR随机化基址}
    B --> C[基址 % PAGE_SIZE ≠ 0]
    C --> D[guard page起始 ≠ mmap基址 - PAGE_SIZE]
    D --> E[栈指针越过guard page边界]
    E --> F[SIGSEGV with si_code=SEGV_ACCERR]

4.4 CGO调用中C代码使用alloca或变长数组(VLA)在musl栈帧布局下的静默溢出分析

musl libc 的栈帧布局紧凑且无红区(red zone),alloca() 和 VLA 分配直接扩展栈顶,不触发内核栈保护页检查。

栈帧关键差异对比

特性 glibc (x86_64) musl (x86_64)
红区大小 128 字节 0 字节
alloca() 溢出检测 可能延迟触发 静默覆盖调用者帧

典型危险模式

// CGO 导出函数:隐式栈溢出
#include <alloca.h>
void unsafe_vla(int n) {
    char buf[n];           // VLA → 实际调用 alloca(n)
    for (int i = 0; i < n; i++) buf[i] = i % 256;
}

逻辑分析n 若由 Go 侧传入且未校验(如 n=8192),musl 下直接覆盖返回地址或前栈帧的 g 指针;因无红区与栈溢出信号机制,Go runtime 无法感知,导致后续 ret 跳转到非法地址,崩溃无迹可循。

防御路径

  • ✅ 在 CGO 函数入口对 n 做硬上限检查(如 n <= 4096
  • ✅ 用 malloc() 替代 VLA/alloca(),配合 free()
  • ❌ 禁用 #pragma GCC optimize("omit-frame-pointer")(加剧调试难度)

第五章:构建可移植Go二进制的终极实践范式

理解CGO与静态链接的本质冲突

Go默认启用CGO_ENABLED=1,导致二进制依赖系统glibc(如Ubuntu 22.04的libc-2.35.so),在Alpine或旧版CentOS上直接报错no such file or directory。实测某API服务在golang:1.22-alpine中编译后运行失败,ldd ./api显示not a dynamic executable——表面成功,实则因-ldflags '-extldflags "-static"'未生效而隐性失效。

强制纯静态编译的黄金组合

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -ldflags '-s -w -buildmode=exe' -o dist/api-linux-amd64 .

关键参数解析:-a强制重新编译所有依赖包(含标准库),-s -w剥离调试符号使体积减少40%,-buildmode=exe杜绝生成共享对象风险。某CI流水线应用此命令后,二进制从28MB降至9.3MB,且在RHEL 7.9、Debian 10、Alpine 3.19三平台零修改运行通过。

跨平台交叉编译矩阵验证

目标平台 GOOS/GOARCH 内核兼容性 libc依赖 验证结果
Amazon Linux 2 linux/amd64 4.14+ musl (Alpine) ✅ 无报错
Windows Server 2016 windows/amd64 NT 10.0+ N/A ✅ 生成.exe
ARM64嵌入设备 linux/arm64 5.4+ glibc 2.28 ⚠️ 需--sysroot指定交叉工具链

处理DNS解析的隐蔽陷阱

CGO_ENABLED=0下Go使用纯Go DNS解析器,但/etc/resolv.confoptions timeout:1 attempts:2等指令被忽略。某微服务在Kubernetes中因net.DefaultResolver.PreferGo = true导致DNS超时达5秒。解决方案:在容器启动脚本中注入环境变量GODEBUG=netdns=go,并预加载/etc/resolv.conf到内存映射区。

构建最小化Docker镜像

FROM scratch
COPY --from=builder /workspace/dist/api-linux-amd64 /api
EXPOSE 8080
ENTRYPOINT ["/api"]

对比测试:基于gcr.io/distroless/static:nonroot的镜像大小为12.4MB,而scratch基础镜像仅0 bytes,最终镜像体积压缩至9.3MB,且无CVE-2023-XXXX类基础镜像漏洞。

处理时区与SSL证书的运行时依赖

纯静态二进制仍需/usr/share/zoneinfo/etc/ssl/certs/ca-certificates.crt。采用embed.FS将证书打包:

import _ "embed"
//go:embed certs/ca-bundle.crt
var caBundle []byte

启动时通过os.WriteFile("/tmp/ca-bundle.crt", caBundle, 0644)写入临时路径,并设置SSL_CERT_FILE=/tmp/ca-bundle.crt

持续集成中的可重现性保障

GitLab CI配置强制校验构建指纹:

stages:
  - build
build-linux:
  stage: build
  image: golang:1.22
  script:
    - export BUILD_FINGERPRINT=$(go version -m ./main.go | sha256sum | cut -d' ' -f1)
    - echo "FINGERPRINT=$BUILD_FINGERPRINT" >> build.env
  artifacts:
    reports:
      dotenv: build.env

下游部署作业通过比对BUILD_FINGERPRINT确保二进制与源码完全对应。

生产环境热更新的原子切换方案

使用mv原子重命名实现零停机更新:

# 假设当前运行 api-v1.2.3
./api-v1.2.4 -health-check && \
mv api-v1.2.4 api-current && \
kill -USR2 $(cat /var/run/api.pid) # 触发graceful restart

进程管理器通过/proc/$PID/exe符号链接实时追踪实际运行版本,避免kill -9误杀新进程。

验证可移植性的自动化检查清单

  • file dist/api-linux-amd64 输出必须含statically linked
  • readelf -d dist/api-linux-amd64 | grep NEEDED 应为空
  • 在目标系统执行strace -e trace=openat,open ./api-linux-amd64 2>&1 | grep -E "(resolv|ca-bundle|timezone)" 确认无外部文件依赖

容器化部署的SELinux上下文适配

OpenShift集群中需添加安全上下文:

securityContext:
  seLinuxOptions:
    level: "s0:c123,c456"
  capabilities:
    drop: ["ALL"]

否则CGO_ENABLED=0二进制因openat(AT_FDCWD, "/proc/self/exe", O_RDONLY)被SELinux拒绝,日志显示avc: denied { read } for comm="api" path="/proc/12345/exe"

扎根云原生,用代码构建可伸缩的云上系统。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注