应使用 ansible-runner cli 而非 exec.command 直接调 ansible-playbook,因其封装了环境、路径、输出解析等细节;需用 –json + 逐行解析 json lines,传参走 -e,每个实例独占 project_dir 和 artifact-dir,并由 go 层控制超时。

用 ansible-runner 调用 Ansible 而不是自己拼 exec.Command
Go 原生不支持 Ansible 的 Python 运行时环境,硬起子进程调 ansible-playbook 容易挂:找不到 ansible、模块路径错、Python 版本冲突、甚至 stdout/stderr 混乱。官方推荐的 ansible-runner 是封装层,它把执行逻辑、env 注入、artifact 收集都包好了,Go 只需调它的 CLI 接口。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 安装
ansible-runner(pip3 install ansible-runner),确保在 Go 进程能访问到的 PATH 里 - 用
ansible-runner run启动,不要用ansible-runner invoke—— 后者是调试用的,不保证输出稳定 - 必须加
--json参数,否则解析 stdout 成本高且不可靠;配合--quiet减少干扰日志 - Playbook 路径要用绝对路径,
ansible-runner不自动 resolve 相对路径,尤其在容器或非 cwd 下执行时容易静默失败
stdout 解析失败:别直接 json.Unmarshal 整个输出
ansible-runner --json 输出不是纯 JSON,而是每行一个 JSON 对象(JSON Lines 格式),顶部还有状态摘要行,末尾可能带空行或错误提示。直接 json.Unmarshal 整个字节流必然 panic 或丢数据。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 逐行读取
stdout,用Strings.HasPrefix(line, "{")判断是否为事件行 - 跳过开头的 summary 行(如
{"Event": "runner_on_start", ...}前的{"stats": {...}})—— 它是最后才写入的,不在流式输出里 - 关注
"event": "runner_on_ok"和"event": "runner_on_failed",它们对应任务结果;"event": "playbook_on_stats"才是最终统计 - 别依赖
"status"字段判断成功:Ansible 本身 exit code 才是权威,runner_on_failed事件只表示 task 失败,不等于整个 playbook 退出非零
传参给 Playbook:用 -e 而不是改 vars_files
想让 Go 动态控制 Playbook 行为(比如目标主机、配置值、开关项),最直接的是通过 extra vars。但有人会去生成临时 vars.yml 再挂 vars_files,这引入文件 I/O、竞态和清理负担,也绕过了 Ansible 自身的变量优先级规则。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 所有动态参数走
-e '{"key1":"val1","key2":42}',Go 侧用json.Marshal构造字符串即可 - 避免嵌套过深的 JSON:Ansible 对 double-quoted key 支持好,但单引号或未转义的换行会直接报
Invalid json string - 敏感字段(如密码)不要塞进
-e,改用--vault-id+ vaulted vars 文件,否则会泄露到进程列表和 runner artifact 日志中 - 如果参数含 shell 特殊字符(如
$、`),Go 侧用shellescape.Quote包一层,否则 bash 解析会出错
并发执行多个 Playbook 时,ansible-runner 实例不能共享 project_dir
多个 Go goroutine 并发跑 ansible-runner run -p playbook.yml /path/to/project,若共用同一个 project_dir,会出现 artifact 覆盖、inventory 锁冲突、甚至 status 文件被截断。Ansible Runner 默认把运行状态写死在 project_dir/artifacts/ 下,没做隔离。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 每个执行实例分配独立临时目录:
os.MkdirTemp("", "ansible-run-"),然后复制 playbook 和 inventory 过去 - 用
--artifact-dir显式指定输出路径,别依赖默认位置;同时设--rotate-artifacts 5防磁盘涨满 - 别图省事用
in-memoryinventory:Go 侧构造inventory字符串传给--inventory参数虽可行,但复杂 host group 关系、变量继承会失效,不如写临时 INI/YAML 文件可靠 - 超时控制必须由 Go 层做:
cmd.Run()配context.WithTimeout,ansible-runner自身的--timeout只杀子进程,不回收资源
真正麻烦的从来不是调通一次,而是当 project_dir 被复用、artifact-dir 没隔离、或者 stdout 按整块 JSON 解析时,问题只在高并发或长时间运行后浮现,日志还看不出明显报错。