Karmada 如何跨集群實(shí)現(xiàn)完整的自定義資源分發(fā)能力?
?本文轉(zhuǎn)自徐信釗的博客,原文:https://xinzhao.me/posts/guide-to-karmada-resource-interpreter-webhook/,版權(quán)歸原作者所有。歡迎投稿,投稿請?zhí)砑游⑿藕糜眩?strong style="color: rgb(50, 108, 229);">cloud-native-yang
Karmada 介紹
在開始講 Resource Interpreter Webhook 之前需要對 Karmada 的基礎(chǔ)架構(gòu)以及如何分發(fā)應(yīng)用等有一定的了解,但那一部分在之前的博客中已經(jīng)提到過了,所以這篇文章就不再贅述了,如果需要的話可以移步到 Kubernetes 多集群項(xiàng)目介紹[1]了解。
一個例子:創(chuàng)建一個 nginx 應(yīng)用
讓我們先從一個最簡單的例子開始,在 Karmada 中創(chuàng)建并分發(fā)一個 nginx 應(yīng)用;首先是準(zhǔn)備 nginx 的資源模板,這個就是原生的 K8s Deployment,不需要任何改變:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
再準(zhǔn)備一個 PropagationPolicy,用來控制 nginx 分發(fā)到哪些集群:
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
name: nginx-propagation
spec:
resourceSelectors:
- apiVersion: apps/v1
kind: Deployment
name: nginx
placement:
clusterAffinity:
clusterNames:
- member1
- member2
這里我們就直接將它分發(fā)到 member1 和 member2 集群。

member1 和 member2 集群分別有一個副本數(shù)為 2 的 nginx Deployment,所以該資源一共存在 4 個 Pod。
上面的例子非常簡單,直接在 member 集群根據(jù)模板原封不動創(chuàng)建 Deployment 就行了,但是大家知道 Karmada 是支持一些更高級的副本數(shù)調(diào)度策略的,比如下面這個例子:
replicaScheduling:
replicaDivisionPreference: Weighted
replicaSchedulingType: Divided
weightPreference:
staticWeightList:
- targetCluster:
clusterNames:
- member1
weight: 1
- targetCluster:
clusterNames:
- member2
weight: 1
應(yīng)用了該規(guī)則之后,會涉及到針對每個集群上資源副本數(shù)的動態(tài)調(diào)整,之后 Karmada 在 member 集群創(chuàng)建 Deployment 的時候就需要增加一個修改副本數(shù)的步驟。
針對 Deployment 這類 K8s 核心資源,因?yàn)槠浣Y(jié)構(gòu)是確定的,我們可以直接編寫修改其副本數(shù)的代碼,但是如果我有一個功能類似 Deployment 的 CRD 呢?我也需要副本數(shù)調(diào)度,Karmada 能正確地修改它的副本數(shù)嗎?答案是否定的,也正因此,Karmada 引入了一個新的特性來使其能深度支持自定義資源(CRD)。
Resource Interpreter Webhook
為了解決上面提到的問題,Karmada 引入了 Resource Interpreter Webhook,通過干預(yù)從 ResourceTemplate 到 ResourceBinding 到 Work 到 Resource 的這幾個階段來實(shí)現(xiàn)完整的自定義資源分發(fā)能力:

從一個階段到另一個都會經(jīng)過我們預(yù)定義的一個或多個接口,我們會在這些步驟中實(shí)現(xiàn)修改副本數(shù)等操作;用戶需要增加一個單獨(dú)的實(shí)現(xiàn)了對應(yīng)接口的 webhook server,Karmada 會在執(zhí)行到相應(yīng)步驟時通過配置去調(diào)用該 server 來完成操作。
下面我們將選四個具有代表性的 hook 點(diǎn)來逐一介紹,接下來都使用以下 CRD 作為示例:
// Workload is a simple Deployment.
type Workload struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec represents the specification of the desired behavior.
// +required
Spec WorkloadSpec `json:"spec"`
// Status represents most recently observed status of the Workload.
// +optional
Status WorkloadStatus `json:"status,omitempty"`
}
// WorkloadSpec is the specification of the desired behavior of the Workload.
type WorkloadSpec struct {
// Number of desired pods. This is a pointer to distinguish between explicit
// zero and not specified. Defaults to 1.
// +optional
Replicas *int32 `json:"replicas,omitempty"`
// Template describes the pods that will be created.
Template corev1.PodTemplateSpec `json:"template" protobuf:"bytes,3,opt,name=template"`
// Paused indicates that the deployment is paused.
// Note: both user and controllers might set this field.
// +optional
Paused bool `json:"paused,omitempty"`
}
// WorkloadStatus represents most recently observed status of the Workload.
type WorkloadStatus struct {
// ReadyReplicas represents the total number of ready pods targeted by this Workload.
// +optional
ReadyReplicas int32 `json:"readyReplicas,omitempty"`
}
它和 Deployment 很像,我們用來演示 Karmada 如何支持這類資源來進(jìn)行副本數(shù)調(diào)度等高級特性。
InterpretReplica

該 hook 點(diǎn)發(fā)生在從 ResourceTemplate 到 ResourceBinding 這個過程中,針對有 replica 功能的資源對象,比如類似 Deployment 的自定義資源,實(shí)現(xiàn)該接口來告訴 Karmada 對應(yīng)資源的副本數(shù)。
apiVersion: workload.example.io/v1alpha1
kind: Workload
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
針對我們示例的 Workload 資源,實(shí)現(xiàn)方式也非常簡單,直接在 webhook server 中返回副本數(shù)的值即可:
func (e *workloadInterpreter) responseWithExploreReplica(workload *workloadv1alpha1.Workload) interpreter.Response {
res := interpreter.Succeeded("")
res.Replicas = workload.Spec.Replicas
return res
}
?注:所有的示例均來自 Karmada 官方文檔,可以通過文章最后的 參考鏈接[2] 來查看完整的示例和代碼。
ReviseReplica

該 hook 點(diǎn)發(fā)生在從 ResourceBinding 到 Work 這個過程中,針對有 replica 功能的資源對象,需要按照 Karmada 發(fā)送的 request 來修改對象的副本數(shù)。Karmada 會通過調(diào)度策略把每個集群需要的副本數(shù)計算好,你需要做的只是把最后計算好的值賦給你的 CR 對象(因?yàn)?Karmada 并不知道該 CRD 的結(jié)構(gòu)):
func (e *workloadInterpreter) responseWithExploreReviseReplica(workload *workloadv1alpha1.Workload, req interpreter.Request) interpreter.Response {
wantedWorkload := workload.DeepCopy()
wantedWorkload.Spec.Replicas = req.DesiredReplicas
marshaledBytes, err := json.Marshal(wantedWorkload)
if err != nil {
return interpreter.Errored(http.StatusInternalServerError, err)
}
return interpreter.PatchResponseFromRaw(req.Object.Raw, marshaledBytes)
}
核心代碼也只有賦值那一行。
Workload 實(shí)現(xiàn)副本數(shù)調(diào)度
回到我們最初的那個問題,在了解了 InterpretReplica 和 ReviseReplica 兩個 hook 點(diǎn)之后,就能夠?qū)崿F(xiàn)自定義資源按副本數(shù)調(diào)度了,實(shí)現(xiàn) InterpretReplica hook 點(diǎn)以告知 Karmada 該資源的副本總數(shù),實(shí)現(xiàn) ReviseReplica hook 點(diǎn)來修改對象的副本數(shù),再配置一個 PropagationPolicy 就可以了,配置方法和 Deployment 等資源一樣:
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
name: nginx-workload-propagation
spec:
resourceSelectors:
- apiVersion: workload.example.io/v1alpha1
kind: Workload
name: nginx
placement:
clusterAffinity:
clusterNames:
- member1
- member2
replicaScheduling:
replicaDivisionPreference: Weighted
replicaSchedulingType: Divided
weightPreference:
staticWeightList:
- targetCluster:
clusterNames:
- member1
weight: 2
- targetCluster:
clusterNames:
- member2
weight: 1
效果如下:

Retain

該 hook 點(diǎn)發(fā)生在從 Work 到 Resource 這個過程中,針對 spec 內(nèi)容會在 member 集群單獨(dú)更新的情況,可以通過該 hook 告知 Karmada 保留某些字段的內(nèi)容。
apiVersion: workload.example.io/v1alpha1
kind: Workload
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 3
paused: false
以 paused 為例,該字段的功能是暫停 workload,member 集群的 controller 會單獨(dú)更新該字段,Retain hook 就是為了能更好地和 member 集群的 controller 協(xié)作,可以通過該 hook 來告知 Karmada 哪些字段是需要不用更新、需要保留的。
func (e *workloadInterpreter) responseWithExploreRetaining(desiredWorkload *workloadv1alpha1.Workload, req interpreter.Request) interpreter.Response {
if req.ObservedObject == nil {
err := fmt.Errorf("nil observedObject in exploreReview with operation type: %s", req.Operation)
return interpreter.Errored(http.StatusBadRequest, err)
}
observerWorkload := &workloadv1alpha1.Workload{}
err := e.decoder.DecodeRaw(*req.ObservedObject, observerWorkload)
if err != nil {
return interpreter.Errored(http.StatusBadRequest, err)
}
// Suppose we want to retain the `.spec.paused` field of the actual observed workload object in member cluster,
// and prevent from being overwritten by karmada controller-plane.
wantedWorkload := desiredWorkload.DeepCopy()
wantedWorkload.Spec.Paused = observerWorkload.Spec.Paused
marshaledBytes, err := json.Marshal(wantedWorkload)
if err != nil {
return interpreter.Errored(http.StatusInternalServerError, err)
}
return interpreter.PatchResponseFromRaw(req.Object.Raw, marshaledBytes)
}
核心代碼只有一行,更新 wantedWorkload 的 Paused 字段為之前版本的內(nèi)容。
AggregateStatus

該 hook 點(diǎn)發(fā)生在從 ResourceBinding 到 ResourceTemplate 這個過程中,針對需要將 status 信息聚合到 Resource Template 的資源類型,可通過實(shí)現(xiàn)該接口來更新 Resource Template 的 status 信息。
Karmada 會將各個集群 Resouce 的狀態(tài)信息統(tǒng)一收集到 ResourceBinding 中:

AggregateStatus hook 需要做的事情就是將 ResourceBinding 中 status 信息更新到 Resource Template 中:
func (e *workloadInterpreter) responseWithExploreAggregateStatus(workload *workloadv1alpha1.Workload, req interpreter.Request) interpreter.Response {
wantedWorkload := workload.DeepCopy()
var readyReplicas int32
for _, item := range req.AggregatedStatus {
if item.Status == nil {
continue
}
status := &workloadv1alpha1.WorkloadStatus{}
if err := json.Unmarshal(item.Status.Raw, status); err != nil {
return interpreter.Errored(http.StatusInternalServerError, err)
}
readyReplicas += status.ReadyReplicas
}
wantedWorkload.Status.ReadyReplicas = readyReplicas
marshaledBytes, err := json.Marshal(wantedWorkload)
if err != nil {
return interpreter.Errored(http.StatusInternalServerError, err)
}
return interpreter.PatchResponseFromRaw(req.Object.Raw, marshaledBytes)
}
邏輯也非常簡單,根據(jù) ResourceBinding 中的 status 信息來計算(聚合)出該資源總的 status 信息再更新到 Resource Template 中;效果和 Deployment 類似,可以直接查詢到該資源在所有集群匯總后的狀態(tài)信息:

參考鏈接
Resource Interpreter Webhook[3] custom resource interpreter example[4]
引用鏈接
Kubernetes 多集群項(xiàng)目介紹: https://xinzhao.me/posts/kubernetes-multi-cluster-projects/#karmada
[2]參考鏈接: https://xinzhao.me/posts/guide-to-karmada-resource-interpreter-webhook/#參考鏈接
[3]Resource Interpreter Webhook: https://github.com/karmada-io/karmada/tree/master/docs/proposals/resource-interpreter-webhook
[4]custom resource interpreter example: https://github.com/karmada-io/karmada/tree/master/examples#resource-interpreter


你可能還喜歡
點(diǎn)擊下方圖片即可閱讀
2022-11-16
2022-11-14
2022-11-11
2022-11-09

云原生是一種信仰 ??


點(diǎn)擊 "閱讀原文" 獲取更好的閱讀體驗(yàn)!
發(fā)現(xiàn)朋友圈變“安靜”了嗎?

