上一篇我們分析了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 檢視是否有日誌歸檔檔案
可以看到,在指定 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,難免會影響到叢集效能和穩定性。