Argo Workflows Workflow Executors 详解:Emissary 执行器的工作原理、镜像命令解析与故障排查
2026/9/23 1:39:48 网站建设 项目流程

Argo Workflows Workflow Executors 详解:Emissary 执行器的工作原理、镜像命令解析与故障排查

【免费下载链接】argo-workflowsWorkflow Engine for Kubernetes项目地址: https://gitcode.com/gh_mirrors/ar/argo-workflows

导读

Workflow Executor(工作流执行器)是 Argo Workflows 中运行在每个工作负载 Pod 内的核心组件,它以 init 容器和 sidecar 容器两种身份协同工作,负责监控 Pod 日志、提供与收集工件(Artifacts)、管理容器生命周期等关键动作。自 3.4 版本起,Argo Workflows 仅保留一种执行器类型——emissary。本文以官方文档 workflow-executors.md 为主线,结合仓库源码深入讲解 Emissary 的可靠性/安全性/可扩展性设计、/var/run/argo共享卷上的文件协议、容器命令(Command)的三级解析链路与镜像索引(Image Index/Cache)配置,以及退出码 64 的故障排查方法,帮助你在实际集群中正确配置与诊断执行器相关问题。

什么是 Workflow Executor

工作流执行器运行在承载你工作负载的 Pod 内部。它同时扮演两个角色:

  • init 容器:在工作负载容器启动前完成环境准备(如写入模板、安装 argoexec 二进制);
  • sidecar 容器(历史上称为wait容器,init-less 模式下称为supervisor):与主容器并发运行,负责收集退出码、日志与输出,并向主容器发送终止信号。

它使得 Argo 能够在 Pod 内执行三类关键动作:

  1. 监控 Pod 日志——将主容器 stdout/stderr 收集为工作流日志;
  2. 提供与收集工件——在启动前装载输入工件,在结束后打包输出参数与输出工件;
  3. 管理容器生命周期——按终止宽限期(terminationGracePeriod)先发送 SIGTERM、超时后升级为 SIGKILL。

从源码实现看,执行器的核心接口定义在 workflow/executor/executor.go,包含InitGetFileContentsCopyFileGetOutputStreamWaitKill等方法;而emissary是当前仓库中唯一保留的实现(见 workflow/executor/emissary/emissary.go)。历史上曾存在多种执行器类型,但自 3.4 起唯一可用的执行器是emissary

Emissary Executor

Emissary 的设计目标可以从四个方面理解,这也是官方文档给出的核心特性清单:

可靠性(Reliability)

  • 可在 GKE Autopilot 上运行:GKE Autopilot 对 Pod 运行方式有严格限制,Emissary 不需要任何特权能力即可工作;
  • 不依赖init进程来杀死子进程:它通过共享卷上的信号文件机制来终止子进程,而非依赖 PID 1 的 init 语义。

安全性(Security)

  • 无需privileged(特权)访问
  • 无法越权逃逸 Pod 服务账号(Service Account)的权限
  • 支持以非 root 用户运行,详见 workflow-pod-security-context.md。该文档同时指出,Argo 提供了默认以用户 8737 运行的非 root 执行器镜像,可通过 workflow-controller-configmap 的executor配置项启用(workflow-controller-configmap.yaml)。

可扩展性(Scalability)

  • 读写均通过容器磁盘完成:Emissary 的数据交换几乎全部经由/var/run/argo共享卷(emptyDir),不经过网络 API;
  • 仅在资源型模板(Resource template)时使用网络 API:例如调用 Kubernetes API 创建/等待资源时才会走网络。

工件(Artifacts)

  • 输出工件可以位于基础镜像层:例如/tmp下的文件也可以被收集为输出工件,这正是 Emissary 相比旧执行器的关键优势之一。

底层文件协议:/var/run/argo共享卷

Emissary 的实现与旧执行器完全不同。其核心思路是:在所有容器上挂载一个 emptyDir 卷到/var/run/argo,并将主容器的命令替换为一个新的argoexec二进制,由该二进制以子进程方式启动原始命令,在结束之后捕获输出(该设计说明完整记录在 workflow/executor/emissary/emissary.go)。

argoexec二进制与模板的投递方式取决于 Pod 布局(layout):

  • Legacy 布局:由 init 容器创建/var/run/argo/argoexec(从argoexec镜像复制来的二进制,复制逻辑见 workflow/executor/emissary/binary.go,权限为0o555r-xr-xr-x,确保非 root 用户也可执行)以及/var/run/argo/template(模板的 JSON 编码,写入逻辑见 emissary.go);
  • Init-less 布局initlessPod启用时):不存在 init 容器。二进制通过 Kubernetes image volume 从argoexec镜像挂载到/argo-bin/bin/argoexecsupervisor容器与main并发运行,负责写/var/run/argo/template,并通过/var/run/argo/status标记文件上报进度(首行为状态令牌:RUNNING心跳、成功时READY、主容器启动前失败时FAILED及原因)。主容器的 emissary 会阻塞在该标记上,并把过期心跳视为 supervisor 已死。

在主容器内,emissary 会创建以下文件(文件协议同样记录于 emissary.go):

文件路径含义
/var/run/argo/ctr/${containerName}/exitcode容器退出码
/var/run/argo/ctr/${containerName}/combinedstdout+stderr 的合并副本(按需)
/var/run/argo/ctr/${containerName}/stdoutstdout 的副本(按需)

如果容器名为main,还会将基础层工件复制到共享卷:

  • /var/run/argo/outputs/parameters/${path}:所有输出参数复制到这里,例如/tmp/message会被移动为/var/run/argo/outputs/parameters/tmp/message
  • /var/run/argo/outputs/artifacts/${path}.tgz:所有输出工件打包复制到这里,例如/tmp/message会被移动为/var/run/argo/outputs/artifacts/tmp/message.tgz

辅助容器(legacy 布局中的wait、init-less 布局中的supervisor)可以自行创建一个文件用于终止子进程:

  • /var/run/argo/ctr/${containerName}/signal:emissary 监听该文件的变化,并以文件中写入的值作为信号(如 SIGTERM=15、SIGKILL=9)发送给子进程。Kill方法的完整实现(先 SIGTERM、宽限期后 SIGKILL)见 emissary.go。

Wait方法则通过轮询等待exitcode文件被创建来判断容器结束(见 emissary.go),并使用osspecific.AllowGrantingAccessToEveryone()将 umask 归零,确保目录以0o777权限创建——因为不同容器可能以不同用户运行,需要互相写入退出码与日志文件。

Container Command:容器命令的确定

Emissary 需要以子进程方式启动原始命令,因此必须知道容器镜像的默认命令(Cmd/Entrypoint)。官方文档给出的方法是拉取镜像后用docker image inspect查看:

docker pull alpine:3.23 docker image inspect -f '{{.Config.Entrypoint}} {{.Config.Cmd}}' alpine:3.23

在 Kubernetes 中,镜像的 Entrypoint 与 Cmd 的合并规则与 Docker 的规则一致(官方文档中给出了指向 Kubernetes 官方手册的"Learn more about command and args"链接):Entrypoint 作为可执行程序、Cmd 作为其参数,二者拼接后构成容器的完整启动命令。

从源码看,这一查询动作发生在 workflow-controller 构建 Pod 阶段。在 workflow/controller/workflowpod.go 中,控制器遍历 Pod 内所有非 Argo sidecar 的用户容器:

  1. 若容器未显式指定commandlen(c.Command) == 0),则调用pb.deps.lookupImage(ctx, c.Image, ...)查询镜像的 Entrypoint/Cmd;
  2. 将查询到的Entrypoint写入c.Command,若Args为 nil 则将查询到的Cmd写入c.Args(注意判空用的是c.Args == nil而非len == 0,因为零长度也是合法参数);
  3. 最终把用户命令整体前置拼接为argoexec emissary ... -- <原始命令>,即主容器实际执行的是被 emissary 包裹后的命令。

若查询失败,控制器会返回明确错误:"failed to look-up entrypoint/cmd for image %q, you must either explicitly specify the command, or list the image's command in the index"——这正是官方文档中"Image Index/Cache"一节所讲的配置手段。

Image Index/Cache:镜像命令索引与缓存

三级查找顺序

Emissary 决定运行什么命令的顺序是:

  1. 工作流 spec 中显式指定的命令(Command specified in the workflow spec);
  2. 镜像索引缓存中的命令(Command from the image index cache);
  3. 容器镜像自身的命令(Command from the container image)。

其中第 2、3 步的"镜像索引"是 workflow-controller-configmap 中的一个配置项images(见 workflow-controller-configmap.yaml),其示例配置为:

# The command/args for each image, needed when the command is not specified and the emissary executor is used. images: | argoproj/argosay:v2: cmd: [/argosay] docker/whalesay:latest: cmd: [/bin/bash]

配置项的数据结构

images配置项对应config.Config中的Images map[string]Image字段(见 config/config.go)。每个Image包含两个字段(见 config/image.go):

字段类型含义
Entrypoint[]string覆盖容器的 entrypoint
Cmd[]string覆盖容器的命令

配置字段的自动化文档可参考 workflow-controller-configmap.md 中的Image一节。

底层查找链实现

从源码看,控制器将images配置与镜像仓库查询能力组合为一条查找链(chain index),构造逻辑在 workflow/controller/entrypoint/image.go:

func New(kubernetesClient kubernetes.Interface, config map[string]config.Image) Interface { return &cacheIndex{ lru.New(1024), chainIndex{ configIndex(config), &containerRegistryIndex{kubernetesClient}, }, } }

其结构为三层:

  1. cacheIndex(LRU 缓存)(cache_index.go):以image字符串为 key、*Image(Entrypoint+Cmd)为 value,容量 1024 条,命中即直接返回,未命中则委托下一层并在成功后回填缓存;
  2. chainIndex(链式索引)(chain_index.go):顺序遍历内部索引,遇到第一个返回非 nil 结果或错误即返回;
  3. configIndex(配置索引)(config_index.go):直接查 configmap 中images配置的 map;
  4. containerRegistryIndex(镜像仓库索引)(container_registry_index.go):当配置中找不到时,使用go-containerregistry库,借助工作负载的 ServiceAccount 与 ImagePullSecrets 构造 k8schain 认证,拉取镜像 Config 文件,返回其EntrypointCmd(查询平台为控制器自身架构)。

缓存行为的重要提醒

官方文档特别强调:控制器使用"镜像+版本"作为 key、命令作为 value 创建缓存条目,并对特定的image:version组合复用该缓存。因此,如果你更新了镜像中的命令但没有变更版本 tag,可能得到出乎意料的行为(即依然命中旧的缓存命令)。这一点在cacheIndex的实现中可以得到印证——它只以镜像字符串为 key,不校验镜像内容摘要(digest)。

Troubleshooting:故障排查

官方文档给出的核心排查线索是:

Emissary 失败时将以退出码 64 退出。这通常表明 emissary 自身存在 bug。

从源码看,退出码 64 是runEmissary中声明的默认退出码(cmd/argoexec/commands/emissary.go):

exitCode := 64

该函数通过defer在返回前将exitCode写入/var/run/argo/ctr/${containerName}/exitcode文件(emissary.go),供等待方读取。也就是说:

  • 正常情况下,该退出码会被成功执行的用户命令的退出码覆盖;
  • 若你观察到容器以 64 退出,说明 emissary 在准备阶段(读取模板、等待依赖、挂载输入工件、初始化追踪等)就失败了,而不是你的业务命令失败。

此外,init-less 模式还有两个额外的哨兵退出码(定义于workflow/common常量):当 supervisor 在主容器启动前失败(如模板写入失败、输入工件 stage 失败)时,主容器会以ExitCodeSupervisorPreMainFailure(65)退出,控制器据此将失败原因归因于 supervisor 的主前(pre-main)设置,而非用户命令。在 cmd/argoexec/commands/emissary.go 中可以看到,waitForReady环境变量开启时,emissary 会先等待 supervisor 的 ready 标记,再读取模板、stage 输入工件。

实用排查建议

结合官方文档与源码,遇到执行器相关问题时可按下述顺序排查:

  1. 查看 Pod 内各容器退出码kubectl get pod <workflow-pod> -o yaml,区分主容器(main)与辅助容器(wait/supervisor)的退出码;
  2. 若退出码为 64:检查 emissary 日志(kubectl logs <pod> -c main),定位是模板读取、依赖等待还是其他准备阶段的失败;
  3. 若退出码为 65(init-less 模式):检查 supervisor 容器日志,排查模板写入或输入工件 staging 失败原因;
  4. 若报错 "failed to look-up entrypoint/cmd for image ...":说明镜像命令解析失败,应在 workflow spec 中显式指定command,或在 workflow-controller-configmap 的images索引中登记该镜像的cmd/entrypoint(参考 workflow-controller-configmap.yaml);
  5. 若更新镜像命令后行为未变:检查是否命中了 image index 缓存(image:version未变),考虑更换版本 tag。

相关深入阅读

  • 官方文档原文:docs/workflow-executors.md
  • Emissary 核心实现(含文件协议完整注释):workflow/executor/emissary/emissary.go
  • Emissary CLI 命令入口:cmd/argoexec/commands/emissary.go
  • 镜像命令解析执行阶段(Prepare/Run/Collect 计划):workflow/executor/plan.go
  • 镜像索引查找链:workflow/controller/entrypoint/image.go(及同目录下的cache_index.gochain_index.goconfig_index.gocontainer_registry_index.go
  • 控制器注入 emissary 命令的逻辑:workflow/controller/workflowpod.go
  • 非 root 运行指南:docs/workflow-pod-security-context.md
  • Controller ConfigMap 参考(含imagesexecutor配置):docs/workflow-controller-configmap.yaml、docs/workflow-controller-configmap.md

【免费下载链接】argo-workflowsWorkflow Engine for Kubernetes项目地址: https://gitcode.com/gh_mirrors/ar/argo-workflows

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询