上一篇我們分析了argo-workflow 中的 archive,包括 流水線GC、流水線歸檔、日志歸檔等功能。本篇主要分析 Workflow 中的幾種觸發(fā)方式,包括手動(dòng)觸發(fā)、定時(shí)觸發(fā)、Event 事件觸發(fā)等。 1. 概述 Argo Workflows 的流水線有多種觸發(fā)方式: 手動(dòng)觸發(fā):手動(dòng)提交一
上一篇我們分析了argo-workflow 中的 archive,包括 流水線GC、流水線歸檔、日志歸檔等功能。本篇主要分析 Workflow 中的幾種觸發(fā)方式,包括手動(dòng)觸發(fā)、定時(shí)觸發(fā)、Event 事件觸發(fā)等。
Argo Workflows 的流水線有多種觸發(fā)方式:
CronWorkflow
本質(zhì)上就是一個(gè) Workflow + Cron Spec。
設(shè)計(jì)上參考了 k8s 中的 CronJob
一個(gè)簡單的 CronWorkflow 如下:
apiVersion: argoproj.io/v1alpha1
kind: CronWorkflow
metadata:
name: test-cron-wf
spec:
schedule: "* * * * *"
concurrencyPolicy: "Replace"
startingDeadlineSeconds: 0
workflowSpec:
entrypoint: whalesay
templates:
- name: whalesay
container:
image: alpine:3.6
command: [sh, -c]
args: ["date; sleep 90"]
apply 一下,可以看到創(chuàng)建出來的 Workflow 命名為
$cronWorkflowName-xxx
[root@lixd-argo workdir]# k get cwf
NAME AGE
test-cron-wf 116s
[root@lixd-argo workdir]# k get wf
NAME STATUS AGE MESSAGE
test-cron-wf-1711852560 Running 47s
由于 template 中運(yùn)行任務(wù)是
sleep 90s
因此,整個(gè)任務(wù)耗時(shí)肯定是超過 60s 的,根據(jù)設(shè)置的 concurrencyPolicy 為 Replace ,因此 60s 后,第二個(gè) Workflow 被創(chuàng)建出來,第一個(gè)就會(huì)被停止掉。
[root@lixd-argo workdir]# k get wf
NAME STATUS AGE MESSAGE
test-cron-wf-1711852560 Failed 103s Stopped with strategy 'Terminate'
test-cron-wf-1711852620 Running 43s
支持的具體參數(shù)如下:
type CronWorkflowSpec struct {
// WorkflowSpec is the spec of the workflow to be run
WorkflowSpec WorkflowSpec `json:"workflowSpec" protobuf:"bytes,1,opt,name=workflowSpec,casttype=WorkflowSpec"`
// Schedule is a schedule to run the Workflow in Cron format
Schedule string `json:"schedule" protobuf:"bytes,2,opt,name=schedule"`
// ConcurrencyPolicy is the K8s-style concurrency policy that will be used
ConcurrencyPolicy ConcurrencyPolicy `json:"concurrencyPolicy,omitempty" protobuf:"bytes,3,opt,name=concurrencyPolicy,casttype=ConcurrencyPolicy"`
// Suspend is a flag that will stop new CronWorkflows from running if set to true
Suspend bool `json:"suspend,omitempty" protobuf:"varint,4,opt,name=suspend"`
// StartingDeadlineSeconds is the K8s-style deadline that will limit the time a CronWorkflow will be run after its
// original scheduled time if it is missed.
StartingDeadlineSeconds *int64 `json:"startingDeadlineSeconds,omitempty" protobuf:"varint,5,opt,name=startingDeadlineSeconds"`
// SuccessfulJobsHistoryLimit is the number of successful jobs to be kept at a time
SuccessfulJobsHistoryLimit *int32 `json:"successfulJobsHistoryLimit,omitempty" protobuf:"varint,6,opt,name=successfulJobsHistoryLimit"`
// FailedJobsHistoryLimit is the number of failed jobs to be kept at a time
FailedJobsHistoryLimit *int32 `json:"failedJobsHistoryLimit,omitempty" protobuf:"varint,7,opt,name=failedJobsHistoryLimit"`
// Timezone is the timezone against which the cron schedule will be calculated, e.g. "Asia/Tokyo". Default is machine's local time.
Timezone string `json:"timezone,omitempty" protobuf:"bytes,8,opt,name=timezone"`
// WorkflowMetadata contains some metadata of the workflow to be run
WorkflowMetadata *metav1.ObjectMeta `json:"workflowMetadata,omitempty" protobuf:"bytes,9,opt,name=workflowMeta"`
}
內(nèi)容可以分為 3 部分:
WorkflowSpec 和 WorkflowMetadata 沒太大區(qū)別,就不贅述了,分析一下 Cron Spec 相關(guān)的幾個(gè)字段:
* * * * *
每分鐘創(chuàng)建一次
大部分字段和 K8s CronJob 一致
apiVersion: argoproj.io/v1alpha1
kind: CronWorkflow
metadata:
name: my-cron
spec:
schedule: "* * * * *"
concurrencyPolicy: "Replace"
startingDeadlineSeconds: 0
workflowSpec:
entrypoint: whalesay
templates:
- name: whalesay
container:
image: alpine:3.6
command: [sh, -c]
args: ["date; sleep 10"]
workflowMetadata:
labels:
from: cron
增加了 metadata,測試一下
[root@lixd-argo workdir]# k get wf my-cron-1711853400 -oyaml|grep labels -A 1
labels:
from: cron
可以看到,創(chuàng)建出來的 Workflow 確實(shí)攜帶上了,在 CronWorkflow 中指定的 label。
argo 提供了一個(gè) Event API:
/api/v1/events/{namespace}/{discriminator}
,該 API 可以接受任意 json 數(shù)據(jù)。
通過 event API 可以創(chuàng)建 Workflow ,類似于 Webhook。
具體請求長這樣:
curl https://localhost:2746/api/v1/events/argo/ \
-H "Authorization: $ARGO_TOKEN" \
-d '{"message": "hello"}'
或者這樣:
curl https://localhost:2746/api/v1/events/argo/my-discriminator \
-H "Authorization: $ARGO_TOKEN" \
-d '{"message": "hello"}'
創(chuàng)建 RBAC 相關(guān)對象,role、rolebinding、sa,其中 role 只需要提供最小權(quán)限即可。
直接創(chuàng)建在 default 命名空間
kubectl apply -f - <
serviceaccount 和 rolebinding
kubectl create sa test
kubectl create rolebinding test --role=test --serviceaccount=default:test
然后創(chuàng)建一個(gè) Secret
kubectl apply -f - <
最后就可以查詢 Secret 解析 Token 了
ARGO_TOKEN="Bearer $(kubectl get secret test.service-account-token -o=jsonpath='{.data.token}' | base64 --decode)"
echo $ARGO_TOKEN
Bearer ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNkltS...
測試,能否正常使用
ARGO_SERVER=$(kubectl get svc argo-workflows-server -n argo -o=jsonpath='{.spec.clusterIP}')
curl http://$ARGO_SERVER:2746/api/v1/workflow-event-bindings/default -H "Authorization: $ARGO_TOKEN"
為了接收 Event,可以創(chuàng)建 WorkflowEventBinding 對象,具體如下:
apiVersion: argoproj.io/v1alpha1
kind: WorkflowEventBinding
metadata:
name: event-consumer
spec:
event:
# metadata header name must be lowercase to match in selector
selector: payload.message != "" && metadata["x-argo-e2e"] == ["true"] && discriminator == "my-discriminator"
submit:
workflowTemplateRef:
name: my-wf-tmple
arguments:
parameters:
- name: message
valueFrom:
event: payload.message
spec.event 指定了該 Binding 該如何匹配收到的 Event,比如這里的條件就是:
如果匹配則會(huì)使用 submit 下面指定的內(nèi)容創(chuàng)建 Workflow:
至于創(chuàng)建出的 Workflow 則是由 my-wf-tmple 定義了,先創(chuàng)建這個(gè) Template
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: my-wf-tmple
spec:
templates:
- name: main
inputs:
parameters:
- name: message
value: "{{workflow.parameters.message}}"
container:
image: docker/whalesay:latest
command: [cowsay]
args: ["{{inputs.parameters.message}}"]
entrypoint: main
最后我們就可以發(fā)送 API 來觸發(fā) event 實(shí)現(xiàn) Workflow 的創(chuàng)建
curl $ARGO_SERVER:2746/api/v1/events/default/my-discriminator \
-H "Authorization: $ARGO_TOKEN" \
-H "X-Argo-E2E: true" \
-d '{"message": "hello events"}'
測試一下:
{}[root@lixd-argo workdir]# curl $ARGO_SERVER:2746/api/v1/events/default/my-discriminator \
> -H "Authorization: $ARGO_TOKEN" \
> -H "X-Argo-E2E: true" \
> -d '{"message": "hello events"}'
{}[root@lixd-argo workdir]# k get wf
NAME STATUS AGE MESSAGE
my-wf-tmple-ea81n Running 5s
[root@lixd-argo workdir]# k get wf my-wf-tmple-ea81n -oyaml|grep parameters -A 5
parameters:
- name: message
value: hello events
可以看到,Workflow 已經(jīng)創(chuàng)建出來了,而且參數(shù)也是我們發(fā)請求時(shí)給的 hello events。
默認(rèn)情況下 argo-server 可以同時(shí)處理 64 個(gè)事件,再多就會(huì)直接返回 503 了,可以通過以下參數(shù)進(jìn)行調(diào)整:
前面 Event 章節(jié)提到了可以通過發(fā)送 HTTP 請求的方式來創(chuàng)建觸發(fā) event 以 Workflow,但是需要客戶端提供 AuthToken。
問題來了,對于一些不能指定 Token 的客戶端來說就比較麻煩了,比如 Github、Gitlab 等 Git 倉庫,都可以配置 Webhook,在收到 commit 的時(shí)候調(diào)用 Webhook 來觸發(fā)流水線。
此時(shí),這些發(fā)送過來的請求肯定是沒有帶 Token 的,因此需要額外配置來進(jìn)行驗(yàn)證,保證 argo 只處理來自 Github、Gitlab 等等平臺(tái)的 Webhook 請求。
第一步 Token 和 Event 章節(jié)一致,就不在贅述了,主要是第二步。
上一步,創(chuàng)建 RBAC 對象,準(zhǔn)備好 Secret 之后,一般客戶端都是解析 Secret 中的 Token,然后帶上該 Token 發(fā)送請求,就像這樣:
ARGO_SERVER=$(kubectl get svc argo-workflows-server -n argo -o=jsonpath='{.spec.clusterIP}')
ARGO_TOKEN="Bearer $(kubectl get secret jenkins.service-account-token -o=jsonpath='{.data.token}' | base64 --decode)"
curl https://$ARGO_SERVER:2746/api/v1/events/default/ \
-H "Authorization: $ARGO_TOKEN" \
-d '{"message": "hello"}'
但是,對于 Webhook 客戶端來說,是沒辦法這樣指定 token 的,因此需要通過
argo-workflows-webhook-clients
配置來告訴 argo,哪個(gè) Webhook 使用哪個(gè) Secret 中的 token。
創(chuàng)建一個(gè)名為
argo-workflows-webhook-clients
的 Secret,內(nèi)容大致是這樣的:
kind: Secret
apiVersion: v1
metadata:
name: argo-workflows-webhook-clients
# The data keys must be the name of a service account.
stringData:
# https://support.atlassian.com/bitbucket-cloud/docs/manage-webhooks/
bitbucket.org: |
type: bitbucket
secret: "my-uuid"
# https://confluence.atlassian.com/bitbucketserver/managing-webhooks-in-bitbucket-server-938025878.html
bitbucketserver: |
type: bitbucketserver
secret: "shh!"
# https://developer.github.com/webhooks/securing/
github.com: |
type: github
secret: "shh!"
# https://docs.gitlab.com/ee/user/project/integrations/webhooks.html
gitlab.com: |
type: gitlab
secret: "shh!"
以 Github 具體,secret 配置如下:
在添加 Webhook 時(shí)可以填一個(gè) Secret 配置,實(shí)際就是一串加密字符,隨便填什么都可以。
這樣 Github 發(fā)送 Webhook 請求時(shí)就會(huì)攜帶上這個(gè) Secret 信息,Argo 收到后就根據(jù)
argo-workflows-webhook-clients
的 Secret 里配置的 type=github 的 secret 字段進(jìn)行對比,如果匹配上就處理,否則就忽略該請求。
如果能匹配上就從對應(yīng)的 Serviceaccount 中解析 Token 作為 Authorization 信息。
Webhook 這一塊,官方文檔不是很詳細(xì),一筆帶過了,因此翻了下源碼。
這塊邏輯以一個(gè) Interceptor 的形式出現(xiàn),對于所有 Event API 都會(huì)經(jīng)過該邏輯, 用于為沒有攜帶 Authorization 的請求添加 Authorization 信息 。
// Interceptor creates an annotator that verifies webhook signatures and adds the appropriate access token to the request.
func Interceptor(client kubernetes.Interface) func(w http.ResponseWriter, r *http.Request, next http.Handler) {
return func(w http.ResponseWriter, r *http.Request, next http.Handler) {
err := addWebhookAuthorization(r, client)
if err != nil {
log.WithError(err).Error("Failed to process webhook request")
w.WriteHeader(403)
// hide the message from the user, because it could help them attack us
_, _ = w.Write([]byte(`{"message": "failed to process webhook request"}`))
} else {
next.ServeHTTP(w, r)
}
}
}
調(diào)用 addWebhookAuthorization 嘗試添加認(rèn)證信息。
func addWebhookAuthorization(r *http.Request, kube kubernetes.Interface) error {
// try and exit quickly before we do anything API calls
if r.Method != "POST" || len(r.Header["Authorization"]) > 0 || !strings.HasPrefix(r.URL.Path, pathPrefix) {
return nil
}
parts := strings.SplitN(strings.TrimPrefix(r.URL.Path, pathPrefix), "/", 2)
if len(parts) != 2 {
return nil
}
namespace := parts[0]
secretsInterface := kube.CoreV1().Secrets(namespace)
ctx := r.Context()
webhookClients, err := secretsInterface.Get(ctx, "argo-workflows-webhook-clients", metav1.GetOptions{})
if err != nil {
return fmt.Errorf("failed to get webhook clients: %w", err)
}
// we need to read the request body to check the signature, but we still need it for the GRPC request,
// so read it all now, and then reinstate when we are done
buf, _ := io.ReadAll(r.Body)
defer func() { r.Body = io.NopCloser(bytes.NewBuffer(buf)) }()
serviceAccountInterface := kube.CoreV1().ServiceAccounts(namespace)
for serviceAccountName, data := range webhookClients.Data {
r.Body = io.NopCloser(bytes.NewBuffer(buf))
client := &webhookClient{}
err := yaml.Unmarshal(data, client)
if err != nil {
return fmt.Errorf("failed to unmarshal webhook client \"%s\": %w", serviceAccountName, err)
}
log.WithFields(log.Fields{"serviceAccountName": serviceAccountName, "webhookType": client.Type}).Debug("Attempting to match webhook request")
ok := webhookParsers[client.Type](client.Secret, r)
if ok {
log.WithField("serviceAccountName", serviceAccountName).Debug("Matched webhook request")
serviceAccount, err := serviceAccountInterface.Get(ctx, serviceAccountName, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("failed to get service account \"%s\": %w", serviceAccountName, err)
}
tokenSecret, err := secretsInterface.Get(ctx, secrets.TokenNameForServiceAccount(serviceAccount), metav1.GetOptions{})
if err != nil {
return fmt.Errorf("failed to get token secret \"%s\": %w", tokenSecret, err)
}
r.Header["Authorization"] = []string{"Bearer " + string(tokenSecret.Data["token"])}
return nil
}
}
return nil
}
具體流程如下:
第三步會(huì)直接使用 key 作為 serviceaccount,這也就是為什么配置
argo-workflows-webhook-clients
時(shí)需要把 serviceaccount 名稱做為 key。
【ArgoWorkflow 系列】 持續(xù)更新中,搜索公眾號(hào)【 探索云原生 】訂閱,閱讀更多文章。
本文主要分析了 Argo 中的 Workflow 的幾種觸發(fā)方式。
argo-workflows-webhook-clients
配置好不同來源的 Webhook 使用的 Secret 以實(shí)現(xiàn)認(rèn)證,這樣就可以把 Event API 用作 Webhook 端點(diǎn) 配置到 Github、Gitlab 等環(huán)境了。
機(jī)器學(xué)習(xí):神經(jīng)網(wǎng)絡(luò)構(gòu)建(下)
閱讀華為Mate品牌盛典:HarmonyOS NEXT加持下游戲性能得到充分釋放
閱讀實(shí)現(xiàn)對象集合與DataTable的相互轉(zhuǎn)換
閱讀鴻蒙NEXT元服務(wù):論如何免費(fèi)快速上架作品
閱讀算法與數(shù)據(jù)結(jié)構(gòu) 1 - 模擬
閱讀5. Spring Cloud OpenFeign 聲明式 WebService 客戶端的超詳細(xì)使用
閱讀Java代理模式:靜態(tài)代理和動(dòng)態(tài)代理的對比分析
閱讀Win11筆記本“自動(dòng)管理應(yīng)用的顏色”顯示規(guī)則
閱讀本站所有軟件,都由網(wǎng)友上傳,如有侵犯你的版權(quán),請發(fā)郵件[email protected]
湘ICP備2022002427號(hào)-10 湘公網(wǎng)安備:43070202000427號(hào)© 2013~2025 haote.com 好特網(wǎng)