使用 kubectl 自动归档 argo workflow 日志
项目上使用到 argo-workflow 作为工作流引擎来编排运行一些 超融合 集群部署相关的任务,整套环境运行在一个单节点的 K3s 上。之所以选择 argo-workflow + K3s 的搭配主要是想尽可能少地占用系统资源,因为这套环境将来会运行在各种硬件配置不同的笔记本电脑上 😂。综合调研了一些常见的 K8s 部署工具最终就选择了系统资源占用较少的 K3s。
现在项目的一个需求就是在集群部署完成或失败之后需要将 workflow 的日志归档保存下来。虽然可以在 workflow 的 spec 字段中使用 archiveLogs: true
来让 argo 帮我们自动归档日志,但这个特性依赖于一个 S3 对象存储 Artifact Repository 。这就意味着还要再部署一个支持 S3 对象存储的组件比如 Minio ,直接把我给整不会了 🌚
其实嘛这个需求很简单的,我就想保存一个日志文件而已,你还再让我安装一个 Minio,实在是太过分了!本来系统的资源十分有限,需要尽可能减少安装一些不必要依赖,为的就是将资源利用率将到最低。但现在为了归档存储一个日志文件储而大动干戈装一个 minio 实在是不划算。这就好比你费了好大功夫部署一套 3 节点的 kubernetes 集群,然而就为了运行一个静态博客那样滑稽 😂
Deployed my blog on Kubernetes pic.twitter.com/XHXWLrmYO4
— For DevOps Eyes Only (@dexhorthy) April 24, 2017
对于咱这种 用不起
S3 对象存储的穷人家孩子,还是想一些其他办法吧,毕竟自己动手丰衣足食。
kubectl
实现起来也比较简单,对于咱这种 YAML 工程师来说,kubectl 自然再熟悉不过了。想要获取 workflow 的日志,只需要通过 kubectl logs 命令获取出 workflow 所创的 pod 日志就行了呀,要什么 S3 对象存储 😖
筛选 pod
对于同一个 workflow 来将,每个 stage 所创建出来的 pod name 有一定的规律。在定义 workflow 的时候,generateName 参数通常使用 ${name}-
格式。以 -
作为分隔符,最后一个字段是随机生成的一个数字 ID,倒数第二个字段则是 argo 随机生成的 workflow ID,剩余前面的字符则是我们定义的 generateName。
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: archive-log-test-
archive-log-test-jzt8n-3498199655 0/2 Completed 0 4m18s
archive-log-test-jzt8n-3618624526 0/2 Completed 0 4m8s
archive-log-test-jzt8n-2123203324 0/2 Completed 0 3m58s
在 pod 的 labels 中同样也包含着该 workflow 所对应的 ID,因此我们可以根据此 labels 过滤出该 workflow 所创建出来的 pod。
apiVersion: v1
kind: Pod
metadata:
annotations:
workflows.argoproj.io/node-id: archive-log-test-jzt8n-3498199655
workflows.argoproj.io/node-name: archive-log-test-jzt8n[0].list-default-running-pods
creationTimestamp: "2022-02-28T12:53:32Z"
labels:
workflows.argoproj.io/completed: "true"
workflows.argoproj.io/workflow: archive-log-test-jzt8n
name: archive-log-test-jzt8n-3498199655
namespace: default
ownerReferences:
- apiVersion: argoproj.io/v1alpha1
blockOwnerDeletion: true
controller: true
kind: Workflow
name: archive-log-test-jzt8n
uid: e91df2cb-b567-4cf0-9be5-3dd6c72854cd
resourceVersion: "1251330"
uid: ce37a709-8236-445b-8d00-a7926fa18ed0
通过 -l lables
过滤出一个 workflow 所创建的 pod;通过 --sort-by
以创建时间进行排序;通过 -o name
只输出 pod 的 name:
$ kubectl get pods -l workflows.argoproj.io/workflow=archive-log-test-jzt8n --sort-by='.metadata.creationTimestamp' -o name
pod/archive-log-test-jzt8n-3498199655
pod/archive-log-test-jzt8n-3618624526
pod/archive-log-test-jzt8n-2123203324
获取日志
通过上面的步骤我们就可以获取到一个 workflow 所创建的 pod 列表。然后再通过 kubectl logs 命令获取 pod 中 main 容器的日志,为方便区分日志的所对应的 workflow ,我们就以 workflow 的 ID 为前缀名。
$ kubectl logs archive-log-test-jzt8n-3618624526 -c main
LOG_PATH=/var/log
NAME=archive-log-test-jzt8n
kubectl get pods -l workflows.argoproj.io/workflow=${NAME} \
--sort-by='.metadata.creationTimestamp' -o name \
| xargs -I {} -t kubectl logs {} -c main >> ${LOG_PATH}/${NAME}.log
workflow
根据 argo-workflow 官方提供的 exit-handlers.yaml example,我们就照葫芦画瓢搓一个 workflow 退出后自动调用使用 kubectl 获取 workflow 日志的一个 step,定义的 exit-handler 内容如下:
- name: exit-handler
container:
name: "kubectl"
image: lachlanevenson/k8s-kubectl:v1.23.2
command:
- sh
- -c
- |
kubectl get pods -l workflows.argoproj.io/workflow=${POD_NAME%-*} \
--sort-by=".metadata.creationTimestamp" -o name | grep -v ${POD_NAME} \
| xargs -I {} -t kubectl logs {} -c main >> ${LOG_PATH}/${POD_NAME%-*}.log
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: LOG_PATH
value: /var/log/workflow
resources: {}
volumeMounts:
- name: nfs-datastore
mountPath: /var/log/workflow
retryStrategy:
limit: "5"
retryPolicy: OnFailure
entrypoint: archive-log-test
serviceAccountName: default
volumes:
- name: nfs-datastore
nfs:
server: NFS_SERVER
path: /data/workflow/log
onExit: exit-handler
将上述定义的 exit-handler
内容复制粘贴到你的 workflow spec 配置中就可以。由于日志需要持久化存储,我这里使用的是 NFS 存储,也可以根据自己的需要换成其他存储,只需要修改一下 volumes
配置即可。
完整的 workflow example 如下:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: archive-log-test-
namespace: default
spec:
templates:
- name: archive-log-test
steps:
- - name: list-default-running-pods
template: kubectl
arguments:
parameters:
- name: namespace
value: default
- - name: list-kube-system-running-pods
template: kubectl
arguments:
parameters:
- name: namespace
value: kube-system
- name: kubectl
inputs:
parameters:
- name: namespace
container:
name: "kubectl"
image: lachlanevenson/k8s-kubectl:v1.23.2
command:
- sh
- -c
- |
kubectl get pods --field-selector=status.phase=Running -n {{inputs.parameters.namespace}}
- name: exit-handler
container:
name: "kubectl"
image: lachlanevenson/k8s-kubectl:v1.23.2
command:
- sh
- -c
- |
kubectl get pods -l workflows.argoproj.io/workflow=${POD_NAME%-*} \
--sort-by=".metadata.creationTimestamp" -o name | grep -v ${POD_NAME} \
| xargs -I {} -t kubectl logs {} -c main >> ${LOG_PATH}/${POD_NAME%-*}.log
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: LOG_PATH
value: /var/log/workflow
resources: {}
volumeMounts:
- name: nfs-datastore
mountPath: /var/log/workflow
retryStrategy:
limit: "5"
retryPolicy: OnFailure
entrypoint: archive-log-test
serviceAccountName: default
volumes:
- name: nfs-datastore
nfs:
server: NFS_SERVER
path: /data/workflow/log
onExit: exit-handler