ArgoWorkflow教程(四)---Workflow & 日誌歸檔

探索云原生發表於2024-09-13

argoworkflow-4-artifacts-archive.png

上一篇我們分析了argo-workflow 中的 artifact,包括 artifact-repository 配置以及 Workflow 中如何使用 artifact。本篇主要分析流水線 GC 以及歸檔,防止無限佔用叢集中 etcd 的空間。

1. 概述

因為 ArgoWorkflow 是用 CRD 方式實現的,不需要外部儲存服務也可以正常執行:

  • 執行記錄使用 Workflow CR 物件儲存
  • 執行日誌則存放在 Pod 中,透過 kubectl logs 方式檢視
    • 因此需要保證 Pod 不被刪除,否則就無法檢視了

但是也正因為所有資料都存放在叢集中,當資料量大之後 etcd 儲存壓力會很大,最終影響到叢集穩定性

為了解決該問題 ArgoWorkflow 提供了歸檔功能,將歷史資料歸檔到外部儲存,以降低 etcd 的儲存壓力。

具體實現為:

  • 1)將 Workflow 物件會儲存到 Postgres(或 MySQL)
  • 2)將 Pod 對應的日誌會儲存到 S3,因為日誌資料量可能會比較大,因此沒有直接存 PostgresQL。

為了提供歸檔功能,需要依賴兩個儲存服務:

  • Postgres:外部資料庫,用於儲存歸檔後的工作流記錄
  • minio:提供 S3 儲存,用於儲存 Workflow 中生成的 artifact 以及已歸檔工作流的 Pod 日誌

因此,如果不需要儲存太多 Workflow 記錄及日誌檢視需求的話,就不需要使用歸檔功能,定時清理叢集中的資料即可。

2.Workflow GC

Argo Workflows 有個工作流執行記錄(Workflow)的清理機制,也就是 Garbage Collect(GC)。GC 機制可以避免有太多的執行記錄, 防止 Kubernetes 的後端儲存 Etcd 過載。

開啟

我們可以在 ConfigMap 中配置期望保留的工作執行記錄數量,這裡支援為不同狀態的執行記錄設定不同的保留數量。

首先檢視 argo-server 啟動命令中指定的是哪個 Configmap

# kubectl -n argo get deploy argo-workflows-server -oyaml|grep args -A 5
      - args:
        - server
        - --configmap=argo-workflows-workflow-controller-configmap
        - --auth-mode=server
        - --secure=false
        - --loglevel

可以看到,這裡是用的argo-workflows-workflow-controller-configmap,那麼修改這個即可。

配置如下:

apiVersion: v1
data:
  retentionPolicy: |
    completed: 3
    failed: 3
    errored: 3
kind: ConfigMap
metadata:
  name: argo-workflows-workflow-controller-configmap
  namespace: argo

需要注意的是,這裡的清理機制會將多餘的 Workflow 資源從 Kubernetes 中刪除。如果希望能更多歷史記錄的話,建議啟用並配置好歸檔功能。

然後重啟 argo-workflow-controller 和 argo-server

kubectl -n argo rollout restart deploy argo-workflows-server
kubectl -n argo rollout restart deploy argo-workflows-workflow-controller

測試

執行多個流水線,看下是否會自動清理

for ((i=1; i<=10; i++)); do
cat <<EOF | kubectl create -f -
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: hello-world-
spec:
  entrypoint: whalesay
  templates:
  - name: whalesay
    container:
      image: docker/whalesay
      command: [cowsay]
      args: ["hello world $i"]
EOF
done

建立了 10 個 Workflow,看一下執行完成後會不會自動清理掉

[root@lixd-argo archive]# k get wf
NAME                STATUS      AGE   MESSAGE
hello-world-6hgb2   Succeeded   74s
hello-world-6pl5w   Succeeded   37m
hello-world-9fdmv   Running     21s
hello-world-f464p   Running     18s
hello-world-kqwk4   Running     16s
hello-world-kxbtk   Running     18s
hello-world-p88vd   Running     19s
hello-world-q7xbk   Running     22s
hello-world-qvv7d   Succeeded   10m
hello-world-t94pb   Running     23s
hello-world-w79q6   Running     15s
hello-world-wl4vl   Running     23s
hello-world-znw7w   Running     23s

過一會再看

[root@lixd-argo archive]# k get wf
NAME                STATUS      AGE    MESSAGE
hello-world-f464p   Succeeded   102s
hello-world-kqwk4   Succeeded   100s
hello-world-w79q6   Succeeded   99s

可以看到,只保留了 3 條記錄,其他的都被清理了,說明 GC 功能 ok。

3. 流水線歸檔

https://argo-workflows.readthedocs.io/en/stable/workflow-archive/

開啟 GC 功能之後,會自動清理 Workflow 以保證 etcd 不被佔滿,但是也無法查詢之前的記錄了。

ArgoWorkflow 也提供了流水線歸檔功能,來解決該問題。

透過將 Workflow 記錄到外部 Postgres 資料庫來實現持久化,從而滿足查詢歷史記錄的需求。

部署 Postgres

首先,簡單使用 helm 部署一個 AIO 的Postgres

REGISTRY_NAME=registry-1.docker.io
REPOSITORY_NAME=bitnamicharts
storageClass="local-path"
# postgres 賬號的密碼
adminPassword="postgresadmin"

helm install pg-aio oci://$REGISTRY_NAME/$REPOSITORY_NAME/postgresql \
--set global.storageClass=$storageClass \
--set global.postgresql.auth.postgresPassword=$adminPassword \
--set global.postgresql.auth.database=argo

配置流水線歸檔

同樣的,在 argo 配置檔案中增加 persistence 相關配置即可:

persistence: 
  archive: true
  postgresql:
    host: pg-aio-postgresql.default.svc.cluster.local
    port: 5432
    database: postgres
    tableName: argo_workflows
    userNameSecret:
      name: argo-postgres-config
      key: username
    passwordSecret:
      name: argo-postgres-config
      key: password

argo-workflows-workflow-controller-configmap 完整內容如下:

apiVersion: v1
data:
  retentionPolicy: |
    completed: 3
    failed: 3
    errored: 3
  persistence: |
    archive: true
    archiveTTL: 180d
    postgresql:
      host: pg-aio-postgresql.default.svc.cluster.local
      port: 5432
      database: argo
      tableName: argo_workflows
      userNameSecret:
        name: argo-postgres-config
        key: username
      passwordSecret:
        name: argo-postgres-config
        key: password
kind: ConfigMap
metadata:
  name: argo-workflows-workflow-controller-configmap
  namespace: argo

然後還要建立一個 secret

kubectl create secret generic argo-postgres-config -n argo --from-literal=password=postgresadmin --from-literal=username=postgres

可能還需要給 rbac,否則 Controller 無法查詢 secret

kubectl create clusterrolebinding argo-workflow-controller-admin --clusterrole=admin --serviceaccount=argo:argo-workflows-workflow-controller

然後重啟 argo-workflow-controller 和 argo-server

kubectl -n argo rollout restart deploy argo-workflows-server
kubectl -n argo rollout restart deploy argo-workflows-workflow-controller

在啟用存檔的情況下啟動工作流控制器時,將在資料庫中建立以下表:

  • argo_workflows
  • argo_archived_workflows
  • argo_archived_workflows_labels
  • schema_history

歸檔記錄 GC

配置檔案中的 archiveTTL 用於指定壓縮到 Postgres 中的 Workflow 記錄存活時間,argo Controller 會根據該配置自動刪除到期的記錄,若不指定該值則不會刪除。

具體如下:

func (r *workflowArchive) DeleteExpiredWorkflows(ttl time.Duration) error {
	rs, err := r.session.SQL().
		DeleteFrom(archiveTableName).
		Where(r.clusterManagedNamespaceAndInstanceID()).
		And(fmt.Sprintf("finishedat < current_timestamp - interval '%d' second", int(ttl.Seconds()))).
		Exec()
	if err != nil {
		return err
	}
	rowsAffected, err := rs.RowsAffected()
	if err != nil {
		return err
	}
	log.WithFields(log.Fields{"rowsAffected": rowsAffected}).Info("Deleted archived workflows")
	return nil
}

不過刪除任務預設每天執行一次,因此就算配置為 1m 分鐘也不會立即刪除。

func (wfc *WorkflowController) archivedWorkflowGarbageCollector(stopCh <-chan struct{}) {
	defer runtimeutil.HandleCrash(runtimeutil.PanicHandlers...)

	periodicity := env.LookupEnvDurationOr("ARCHIVED_WORKFLOW_GC_PERIOD", 24*time.Hour)
	if wfc.Config.Persistence == nil {
		log.Info("Persistence disabled - so archived workflow GC disabled - you must restart the controller if you enable this")
		return
	}
	if !wfc.Config.Persistence.Archive {
		log.Info("Archive disabled - so archived workflow GC disabled - you must restart the controller if you enable this")
		return
	}
	ttl := wfc.Config.Persistence.ArchiveTTL
	if ttl == config.TTL(0) {
		log.Info("Archived workflows TTL zero - so archived workflow GC disabled - you must restart the controller if you enable this")
		return
	}
	log.WithFields(log.Fields{"ttl": ttl, "periodicity": periodicity}).Info("Performing archived workflow GC")
	ticker := time.NewTicker(periodicity)
	defer ticker.Stop()
	for {
		select {
		case <-stopCh:
			return
		case <-ticker.C:
			log.Info("Performing archived workflow GC")
			err := wfc.wfArchive.DeleteExpiredWorkflows(time.Duration(ttl))
			if err != nil {
				log.WithField("err", err).Error("Failed to delete archived workflows")
			}
		}
	}
}

需要設定環境變數 ARCHIVED_WORKFLOW_GC_PERIOD 來調整該值,修改 argo-workflows-workflow-controller 增加 env,就像這樣:

        env:
        - name: ARCHIVED_WORKFLOW_GC_PERIOD
          value: 1m

測試

接下來建立 Workflow 看下是否測試

for ((i=1; i<=10; i++)); do
cat <<EOF | kubectl create -f -
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: hello-world-
spec:
  entrypoint: whalesay
  templates:
  - name: whalesay
    container:
      image: docker/whalesay
      command: [cowsay]
      args: ["hello world $i"]
EOF
done

檢視下是 postgres 中是否生成歸檔記錄

export POSTGRES_PASSWORD=postgresadmin

kubectl run postgresql-dev-client --rm --tty -i --restart='Never' --namespace default --image docker.io/bitnami/postgresql:14.1.0-debian-10-r80 --env="PGPASSWORD=$POSTGRES_PASSWORD" --command -- psql --host pg-aio-postgresql -U postgres -d argo -p 5432

按 Enter 進入 Pod 後直接查詢即可

# 查詢表
argo-# \dt
                     List of relations
 Schema |              Name              | Type  |  Owner
--------+--------------------------------+-------+----------
 public | argo_archived_workflows        | table | postgres
 public | argo_archived_workflows_labels | table | postgres
 public | argo_workflows                 | table | postgres
 public | schema_history                 | table | postgres
(4 rows)

# 查詢記錄
argo=# select name,phase from argo_archived_workflows;
       name        |   phase
-------------------+-----------
 hello-world-s8v4f | Succeeded
 hello-world-6pl5w | Succeeded
 hello-world-qvv7d | Succeeded
 hello-world-vgjqr | Succeeded
 hello-world-g2s8f | Succeeded
 hello-world-jghdm | Succeeded
 hello-world-fxtvk | Succeeded
 hello-world-tlv9k | Succeeded
 hello-world-bxcg2 | Succeeded
 hello-world-f6mdw | Succeeded
 hello-world-dmvj6 | Succeeded
 hello-world-btknm | Succeeded
(12 rows)

# \q 退出
argo=# \q

可以看到,Postgres 中已經儲存好了歸檔的 Workflow,這樣需要查詢歷史記錄時到 Postgres 查詢即可。

將 archiveTTL 修改為 1 分鐘,然後重啟 argo,等待 1 至2 分鐘後,再次檢視

argo=#  select name,phase from argo_archived_workflows;
 name | phase
------+-------
(0 rows)

argo=#

可以看到,所有記錄都因為 TTL 被清理了,這樣也能保證外部 Postgres 中的資料不會越累積越多。

4. Pod 日誌歸檔

https://argo-workflows.readthedocs.io/en/stable/configure-archive-logs/

流水線歸檔實現了流水線持久化,即使把叢集中的 Workflow 物件刪除了,也可以從 Postgres 中查詢到記錄以及狀態等資訊。

但是流水線執行的日誌卻分散在對應 Pod 中的,如果 Pod 被刪除了,日誌就無法檢視了,因此我們還需要做日誌歸檔。

配置 Pod 歸檔

全域性配置

在 argo 配置檔案中開啟 Pod 日誌歸檔並配置好 S3 資訊。

具體配置如下:

和第三篇配置的 artifact 一樣,只是多了一個 archiveLogs: true

artifactRepository:
  archiveLogs: true
  s3:
    endpoint: minio.default.svc:9000
    bucket: argo
    insecure: true
    accessKeySecret:
      name: my-s3-secret
      key: accessKey
    secretKeySecret:
      name: my-s3-secret
      key: secretKey

完整配置如下:

apiVersion: v1
data:
  retentionPolicy: |
    completed: 3
    failed: 3
    errored: 3
  persistence: |
    archive: true
    postgresql:
      host: pg-aio-postgresql.default.svc.cluster.local
      port: 5432
      database: argo
      tableName: argo_workflows
      userNameSecret:
        name: argo-postgres-config
        key: username
      passwordSecret:
        name: argo-postgres-config
        key: password
  artifactRepository: |
    archiveLogs: true
    s3:
      endpoint: minio.default.svc:9000
      bucket: argo
      insecure: true
      accessKeySecret:
        name: my-s3-secret
        key: accessKey
      secretKeySecret:
        name: my-s3-secret
        key: secretKey
kind: ConfigMap
metadata:
  name: argo-workflows-workflow-controller-configmap
  namespace: argo

注意:根據第三篇分析 artifact,argo 中關於 artifactRepository 的資訊包括三種配置方式:

  • 1)全域性配置
  • 2)名稱空間預設配置
  • 3)Workflow 中指定配置

這裡是用的全域性配置方式,如果 Namespace 級別或者 Workflow 級別也配置了 artifactRepository 並指定了不開啟日誌歸檔,那麼也不會歸檔的。

然後重啟 argo

kubectl -n argo rollout restart deploy argo-workflows-server
kubectl -n argo rollout restart deploy argo-workflows-workflow-controller

在 Workflow & template 中配置

配置整個工作流都需要歸檔

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: archive-location-
spec:
  archiveLogs: true
  entrypoint: whalesay
  templates:
  - name: whalesay
    container:
      image: docker/whalesay:latest
      command: [cowsay]
      args: ["hello world"]

配置工作流中的某一個 template 需要歸檔。

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: archive-location-
spec:
  entrypoint: whalesay
  templates:
  - name: whalesay
    container:
      image: docker/whalesay:latest
      command: [cowsay]
      args: ["hello world"]
    archiveLocation:
      archiveLogs: true

小結

3 個地方都可以配置是否歸檔,就還挺麻煩的,根據官方文件,各個配置優先順序如下:

workflow-controller config (on) > workflow spec (on/off) > template (on/off)

Controller Config Map Workflow Spec Template are we archiving logs?
true true true true
true true false true
true false true true
true false false true
false true true true
false true false false
false false true true
false false false false

對應的程式碼實現:

// IsArchiveLogs determines if container should archive logs
// priorities: controller(on) > template > workflow > controller(off)
func (woc *wfOperationCtx) IsArchiveLogs(tmpl *wfv1.Template) bool {
	archiveLogs := woc.artifactRepository.IsArchiveLogs()
	if !archiveLogs {
		if woc.execWf.Spec.ArchiveLogs != nil {
			archiveLogs = *woc.execWf.Spec.ArchiveLogs
		}
		if tmpl.ArchiveLocation != nil && tmpl.ArchiveLocation.ArchiveLogs != nil {
			archiveLogs = *tmpl.ArchiveLocation.ArchiveLogs
		}
	}
	return archiveLogs
}

建議配置全域性的就行了。

測試

接下來建立 Workflow 看下是否測試

cat <<EOF | kubectl create -f -
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: hello-world-
spec:
  entrypoint: whalesay
  templates:
  - name: whalesay
    container:
      image: docker/whalesay
      command: [cowsay]
      args: ["hello world"]
EOF

等待 Workflow 執行完成

# k get po
NAME                     READY   STATUS      RESTARTS   AGE
hello-world-6pl5w        0/2     Completed   0          53s
# k get wf
NAME                STATUS      AGE   MESSAGE
hello-world-6pl5w   Succeeded   55s

到 S3 檢視是否有日誌歸檔檔案

argo-archive-log.png

可以看到,在指定 bucket 裡已經儲存了一個日誌檔案,以$bucket/$workflowName/$stepName 格式命名。

正常一個 Workflow 都會有多個 Step,每一個 step 分一個目錄儲存

內容就是 Pod 日誌,具體如下:

 _____________ 
< hello world >
 ------------- 
    \
     \
      \     
                    ##        .            
              ## ## ##       ==            
           ## ## ## ##      ===            
       /""""""""""""""""___/ ===        
  ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~   
       \______ o          __/            
        \    \        __/             
          \____\______/   

5. 小結


【ArgoWorkflow 系列】持續更新中,搜尋公眾號【探索雲原生】訂閱,閱讀更多文章。


總結一下,本文主要分析了以下 3 部分內容:

  • 1)開啟 GC,自動清理執行完成的 Workflow 記錄,避免佔用 etcd 空間
  • 2)開啟流水線歸檔,將 Workflow 記錄儲存到外部 Postgres,便於查詢歷史記錄
  • 3)開啟 Pod 日誌歸檔,將流水線每一步 Pod 日誌記錄到 S3,便於查詢,否則 Pod 刪除就無法查詢了

生產使用,一般都建議開啟相關的清理和歸檔功能,如果全儲存到 etcd,難免會影響到叢集效能和穩定性。

相關文章