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

资讯详情

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

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

Argo Workflows Workflow Executors 详解:Emissary 执行器的工作原理、镜像命令解析与故障排查 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 内执行三类关键动作监控 Pod 日志——将主容器 stdout/stderr 收集为工作流日志提供与收集工件——在启动前装载输入工件在结束后打包输出参数与输出工件管理容器生命周期——按终止宽限期terminationGracePeriod先发送 SIGTERM、超时后升级为 SIGKILL。从源码实现看执行器的核心接口定义在 workflow/executor/executor.go包含Init、GetFileContents、CopyFile、GetOutputStream、Wait、Kill等方法而emissary是当前仓库中唯一保留的实现见 workflow/executor/emissary/emissary.go。历史上曾存在多种执行器类型但自 3.4 起唯一可用的执行器是emissary。Emissary ExecutorEmissary 的设计目标可以从四个方面理解这也是官方文档给出的核心特性清单可靠性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 布局layoutLegacy 布局由 init 容器创建/var/run/argo/argoexec从argoexec镜像复制来的二进制复制逻辑见 workflow/executor/emissary/binary.go权限为0o555即r-xr-xr-x确保非 root 用户也可执行以及/var/run/argo/template模板的 JSON 编码写入逻辑见 emissary.goInit-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}/combinedstdoutstderr 的合并副本按需/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}/signalemissary 监听该文件的变化并以文件中写入的值作为信号如 SIGTERM15、SIGKILL9发送给子进程。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 的用户容器若容器未显式指定commandlen(c.Command) 0则调用pb.deps.lookupImage(ctx, c.Image, ...)查询镜像的 Entrypoint/Cmd将查询到的Entrypoint写入c.Command若Args为 nil 则将查询到的Cmd写入c.Args注意判空用的是c.Args nil而非len 0因为零长度也是合法参数最终把用户命令整体前置拼接为argoexec emissary ... -- 原始命令即主容器实际执行的是被 emissary 包裹后的命令。若查询失败控制器会返回明确错误failed to look-up entrypoint/cmd for image %q, you must either explicitly specify the command, or list the images command in the index——这正是官方文档中Image Index/Cache一节所讲的配置手段。Image Index/Cache镜像命令索引与缓存三级查找顺序Emissary 决定运行什么命令的顺序是工作流 spec 中显式指定的命令Command specified in the workflow spec镜像索引缓存中的命令Command from the image index cache容器镜像自身的命令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覆盖容器的 entrypointCmd[]string覆盖容器的命令配置字段的自动化文档可参考 workflow-controller-configmap.md 中的Image一节。底层查找链实现从源码看控制器将images配置与镜像仓库查询能力组合为一条查找链chain index构造逻辑在 workflow/controller/entrypoint/image.gofunc New(kubernetesClient kubernetes.Interface, config map[string]config.Image) Interface { return cacheIndex{ lru.New(1024), chainIndex{ configIndex(config), containerRegistryIndex{kubernetesClient}, }, } }其结构为三层cacheIndexLRU 缓存cache_index.go以image字符串为 key、*ImageEntrypointCmd为 value容量 1024 条命中即直接返回未命中则委托下一层并在成功后回填缓存chainIndex链式索引chain_index.go顺序遍历内部索引遇到第一个返回非 nil 结果或错误即返回configIndex配置索引config_index.go直接查 configmap 中images配置的 mapcontainerRegistryIndex镜像仓库索引container_registry_index.go当配置中找不到时使用go-containerregistry库借助工作负载的 ServiceAccount 与 ImagePullSecrets 构造 k8schain 认证拉取镜像 Config 文件返回其Entrypoint与Cmd查询平台为控制器自身架构。缓存行为的重要提醒官方文档特别强调控制器使用镜像版本作为 key、命令作为 value 创建缓存条目并对特定的image:version组合复用该缓存。因此如果你更新了镜像中的命令但没有变更版本 tag可能得到出乎意料的行为即依然命中旧的缓存命令。这一点在cacheIndex的实现中可以得到印证——它只以镜像字符串为 key不校验镜像内容摘要digest。Troubleshooting故障排查官方文档给出的核心排查线索是Emissary 失败时将以退出码 64 退出。这通常表明 emissary 自身存在 bug。从源码看退出码 64 是runEmissary中声明的默认退出码cmd/argoexec/commands/emissary.goexitCode : 64该函数通过defer在返回前将exitCode写入/var/run/argo/ctr/${containerName}/exitcode文件emissary.go供等待方读取。也就是说正常情况下该退出码会被成功执行的用户命令的退出码覆盖若你观察到容器以 64 退出说明 emissary 在准备阶段读取模板、等待依赖、挂载输入工件、初始化追踪等就失败了而不是你的业务命令失败。此外init-less 模式还有两个额外的哨兵退出码定义于workflow/common常量当 supervisor 在主容器启动前失败如模板写入失败、输入工件 stage 失败时主容器会以ExitCodeSupervisorPreMainFailure65退出控制器据此将失败原因归因于 supervisor 的主前pre-main设置而非用户命令。在 cmd/argoexec/commands/emissary.go 中可以看到waitForReady环境变量开启时emissary 会先等待 supervisor 的 ready 标记再读取模板、stage 输入工件。实用排查建议结合官方文档与源码遇到执行器相关问题时可按下述顺序排查查看 Pod 内各容器退出码kubectl get pod workflow-pod -o yaml区分主容器main与辅助容器wait/supervisor的退出码若退出码为 64检查 emissary 日志kubectl logs pod -c main定位是模板读取、依赖等待还是其他准备阶段的失败若退出码为 65init-less 模式检查 supervisor 容器日志排查模板写入或输入工件 staging 失败原因若报错 failed to look-up entrypoint/cmd for image ...说明镜像命令解析失败应在 workflow spec 中显式指定command或在 workflow-controller-configmap 的images索引中登记该镜像的cmd/entrypoint参考 workflow-controller-configmap.yaml若更新镜像命令后行为未变检查是否命中了 image index 缓存image:version未变考虑更换版本 tag。相关深入阅读官方文档原文docs/workflow-executors.mdEmissary 核心实现含文件协议完整注释workflow/executor/emissary/emissary.goEmissary CLI 命令入口cmd/argoexec/commands/emissary.go镜像命令解析执行阶段Prepare/Run/Collect 计划workflow/executor/plan.go镜像索引查找链workflow/controller/entrypoint/image.go及同目录下的cache_index.go、chain_index.go、config_index.go、container_registry_index.go控制器注入 emissary 命令的逻辑workflow/controller/workflowpod.go非 root 运行指南docs/workflow-pod-security-context.mdController ConfigMap 参考含images与executor配置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),仅供参考
返回列表