本文介绍了如何使用Operator-SDK开发Kubernetes Operator

Operator

什么是Operator

参考:https://www.redhat.com/zh/topics/containers/what-is-a-kubernetes-operator

Kubernetes Operator 是一种特定于应用的控制器,可扩展 Kubernetes API 的功能,来为 Kubernetes 用户创建、配置和管理复杂应用的实例。

它基于基本 Kubernetes Resource与Controller概念构建,又涵盖了特定于域或应用的知识,用于实现其所管理软件的整个生命周期的自动化;可以看作是CRD与Custom Controller的组合模式的封装及最佳实践。

对比通过定义CRD与基于client-go去实现Custom Controller;Operator在这个基础上进行了标准化的流程规范、高级API抽象、快速启动脚手架与代码生成工具、RBAC控制、持续监控、数据备份、故障恢复、自动化升级Operator的CICD等。

目前比较主流的Operator框架是如下两个:

  • operator-framework: 由CoreOS开发和维护,包含operator-sdk与operator-lifecycle-manager。
  • kubebuilder:由 Kubernetes SIGs 开发和维护。

Operator-SDK Vs Kubebuilder

目前来看,operator-sdkkubebuilder都是类似的支持快速创建和管理 Operator 的框架,这两个项目都广泛使用了controller-runtimecontroller-tools 项目,因此有着类似的 Go 源文件、包结构,而且目前operator-sdk也与kubebuilder进行了深度整合,可以参考kubebuilder的github projects 与 operator-sdk的faq

  • operator-sdk-go 底层基于kuberbuilder进行操作。
  • operator-sdk在Kubebuilder提供的基本项目脚手架之上提供了其他功能,如operator-sdk init会生成。
  • operator-sdk支持 Go 以外的 operator types,如Ansible 和 Helm。
  • operator-sdk与kubebuilder的文档是通用的。

Operator 工作模式

Operator基本工作模式

  1. 用户创建一个CRD,并提交给到 kube-apiserver。
  2. kube-apiserver 根据自己注册的一个 pass 列表,把该 CRD 的请求转发给 webhook。
  3. webhook完成该CRD 的缺省值与参数检验,处理完成后,相应的 CR 会被持久化到etcd,后响应用户。
  4. controller会在后台监测该自定义资源,按照业务逻辑,处理与该自定义资源相关联的特殊操作。

可以看到,与前文所示的sample-controller方式的处理流程大致相同。

Operator 应用场景

  • 分布式应用的自动化运维:etcd-operator、 mongodb-enterprise-kubernetes、prometheus-operator、kafka-operator…
  • 扩展Kubernetes:istio、kruise、kubeedge…

Kubernetes Admission Controller

参考:

https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/

https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/

Admission Controller(准入控制器)

准入控制器是在对象持久化之前用于对 Kubernetes API Server 的请求进行拦截的代码段。准入控制器可能是validatingmutating或者两者都有,Mutating 控制器可以修改他们的处理的资源对象,Validating 控制器不会,如果任何一个阶段中的任何控制器拒绝了请求,则会立即拒绝整个请求,并将错误返回给最终的用户。

我们可以编写自己的准入控制器,但是准入控制器需要被编译进 kube-apiserver,并且在 kube-apiserver 启动时启动。为了增加扩展性,而不是和 apiserver 耦合在一起,便有了Admission webhooks这种通过一种动态配置webhook的方式。

admission webhooks

在 kube-apiserver 中包含两个特殊的准入控制器:MutatingAdmissionWebhookValidatingAdmissionWebhook。这两个控制器支持webhook模式,将发送准入请求到外部的 HTTP 服务并接收一个准入响应;创建admission webhook的流程可以参考官方文档。

image

Operator开发实践

参考:

https://sdk.operatorframework.io/docs/

https://olm.operatorframework.io/docs/

https://book.kubebuilder.io/

Operator-Framework

Operator-Framework主要包含如下两个部分:

Operator-SDK

编写Operator的核心工具包,提供高级API,可以更直观地编写Controller逻辑;提供用于快速启动新项目的脚手架与代码生成工具。

Go-Operator工作流程:

  1. 使用 SDK 命令行界面 (CLI) 创建Operator项目。
  2. 通过添加CRD 来定义新的资源 API。
  3. 定义控制器来监视和协调资源。
  4. 使用 SDK 和 controller-runtime APIs 为控制器编写协调逻辑。
  5. 使用 SDK CLI 构建和生成Operator部署清单。

Operator能力等级:

标准化了Operator的生命周期管理能力方面不同的成熟度级别(基本安装、无缝升级、全生命周期、深度可观测、自动伸缩扩展),如下图所示:

image

Go-Operator-SDK架构图

uTools_1644509865678

Operator-Lifecycle-Manager(OLM)

OLM 扩展了 Kubernetes,以提供一种声明式的方式来安装、管理和升级 Operator 及其在集群中的依赖项;提供的功能如下:

  • Over-the-Air Updates and Catalogs:OLM 有一个 Catalogs概念,Operator 可以从中安装并保持更新。
  • Dependency Model:使用 OLMs 打包格式,可以表达对平台其他Operator的依赖关系。
  • Discoverability:可发现的Operator,OLM 将已安装的 Operator 及其服务通告到租户的命名空间中,以达到自动发现可安装的Operator。
  • Cluster Stability :集群稳定性保证,Operator声明对其API的所有权,OLM 将防止拥有相同 API 的 Operator 发生冲突,确保集群稳定性。
  • 声明式 UI 控件

基于Operator-SDK开发Sidecar-Operator

Sidecar-Operator的功能

与Istio的自动注入Envoy代理方式类似,sidcar-operator支持自动为集群中符合条件的Pod注入sidecar容器,并负责更新sidecarSet自定义资源的状态。

安装Operator-SDK

这里使用的是Ubuntu系统进行开发,安装可以参考官方文档:

export ARCH=$(case $(uname -m) in x86_64) echo -n amd64 ;; aarch64) echo -n arm64 ;; *) echo -n $(uname -m) ;; esac)
export OS=$(uname | awk '{print tolower($0)}')
export OPERATOR_SDK_DL_URL=https://github.com/operator-framework/operator-sdk/releases/download/v1.16.0
curl -LO ${OPERATOR_SDK_DL_URL}/operator-sdk_${OS}_${ARCH}
chmod +x operator-sdk_${OS}_${ARCH} && sudo mv operator-sdk_${OS}_${ARCH} /usr/local/bin/operator-sdk

初始化项目

# 创建项目
mkdir -p sidecar-operator && cd sidecar-operator
# 初始化项目,这里的domain指定了后续注册CRD对象的Group域名,init初始化后会生成代码框架、Makefile工具文件、拉取依赖代码库
operator-sdk init --domain chinalhr.github.io --repo github.com/ChinaLHR/sidecar-operator
 
# 查看项目结构
tree -L 2
.
├── Dockerfile
├── LICENSE
├── Makefile
├── PROJECT
├── README.md
├── config
│   ├── default
│   ├── manager
│   ├── manifests
│   ├── prometheus
│   ├── rbac
│   └── scorecard
├── go.mod
├── go.sum
├── hack
│   └── boilerplate.go.txt
└── main.go
  • go.mod :项目基本依赖,client-go,controller-runtime…
  • Makefile:构建,部署Operator
  • PROJECT:用于搭建新组件的 Kubebuilder 元数据
  • config目录:启动配置,包含在集群上启动控制器所需的Kustomize YAML 定义,还会保存CRD、RBAC与Webhook配置
  • main.go:初始化并运行Manager

创建Custom Resource 与 Custom Controller模板代码

operator-sdk create api --group apps --version v1alpha1 --kind SidecarSet --resource --controller

通过operator-sdk tool生成 SidecarSet API资源 api/v1alpha1/sidecarset_types.go和控制器controllers/sidecarset_controller.go 模板。

自定义资源结构修改

这里我们需要根据我们自身的需求去定义我们自定义资源的结构,即修改controllers/sidecarset_controller.go,如下所示:

/*
Copyright 2022.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.

// SidecarSetSpec defines the desired state of SidecarSet
type SidecarSetSpec struct {
	// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
	// Important: Run "make" to regenerate code after modifying this file

	//query over pods that should be injected sidecar
	Selector   *metav1.LabelSelector `json:"selector,omitempty"`
	Containers []SidecarContainer    `json:"containers,omitempty"`
}

type SidecarContainer struct {
	v1.Container `json:",inline"`
}

// SidecarSetStatus defines the observed state of SidecarSet
type SidecarSetStatus struct {
	// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
	// Important: Run "make" to regenerate code after modifying this file

	//the number of Pods whose labels are matched with this SidecarSet's selector
	MatchedPods int32 `json:"matchedPods"`
	//the number of matched Pods that are injected with the latest SidecarSet's
	UpdatedPods int32 `json:"updatedPods"`
	//the number of matched Pods that have a ready condition
	ReadyPods int32 `json:"readyPods"`
}

// +genclient
// +genclient:nonNamespaced
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster
// +kubebuilder:printcolumn:name="MATCHED",type="integer",JSONPath=".status.matchedPods",description="The number of pods matched."
// +kubebuilder:printcolumn:name="UPDATED",type="integer",JSONPath=".status.updatedPods",description="The number of pods matched and updated."
// +kubebuilder:printcolumn:name="READY",type="integer",JSONPath=".status.readyPods",description="the number of matched Pods that have a ready condition."

// SidecarSet is the Schema for the sidecarsets API
type SidecarSet struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   SidecarSetSpec   `json:"spec,omitempty"`
	Status SidecarSetStatus `json:"status,omitempty"`
}

//+kubebuilder:object:root=true

// SidecarSetList contains a list of SidecarSet
type SidecarSetList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`
	Items           []SidecarSet `json:"items"`
}

func init() {
	//将类型注册到API组中
	SchemeBuilder.Register(&SidecarSet{}, &SidecarSetList{})
}

这里主要是更新了SidecarSet的Spec结构,增加了SelectorContainers属性,用于匹配需要注入sidecar的Pod以及sidecar注入容器的定义。更新了SidecarSet的Status结构,增加了MatchedPodsUpdatedPodsReadyPods描述匹配的Pod数量与已注入sidecar的Pod数量与已注入成功并且就绪的Pod数量。

这里还补充了code generator依赖注释,如:+genclient:nonNamespaced生成非namespace对象、+kubebuilder:resource:scope=Cluster指定资源的范围、kubebuilder:printcolumn... 用于kubectl获取数据展示…

最后,我们需要运行代码生成命令make generate 为资源类型更新\生成代码,该命令会调用controller-gen程序来更新api/v1alpha1/zz_generated.deepcopy.go文件。

make generate

自定义控制器模板

生成的controllers/sidecarset_controller.go模板

  • SetupWithManager()函数:为构建控制器以监视 CR 和该控制器拥有和管理的其他资源。
// SetupWithManager sets up the controller with the Manager.
func (r *SidecarSetReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).	//控制器构建器
		For(&appsv1alpha1.SidecarSet{}).		//监控的主资源,对于每个SidecarSet类型的Add/Update/Delete事件,将发送给协调循环(Reconcile)
		Complete(r)								
}
  • Reconcile()函数:协调循环(Reconcile loop),接受Request参数,该参数为(命名空间/名称键),用于从缓存中查找主要资源对象如Sidecarset;实现特定类型协调逻辑(如repliceset的reconcile即在其创建的时候去创建关联的pod)并返回结果、异常或者重试。
func (r *SidecarSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	_ = log.FromContext(ctx)
	// TODO(user): your logic here
	return ctrl.Result{}, nil
}

创建Custom Resouces Admission Webhook模板代码

Admission Webhooks分为如下两种:

  • Validating admission webhook: 用于执行超出 OpenAPI 模式验证功能的验证,注意这里可以拒绝请求,但不能修改在请求中接收的对象。
  • Mutating admission webhook: 用于默认设置(在创建时为资源中未设置的字段添加默认值),也可以通过创建一个补丁来修改对象。

通过如下命令创建Validating Admission Webhook模板代码

# --programmatic-validation参数创建Validating admission webhook所需要的资源
# --defaulting 参数创建Mutating admission webhook所需要的资源
operator-sdk create webhook --group apps --version v1alpha1 --kind SidecarSet --programmatic-validation

生成的api/v1alpha1/sidecarset_webhook.go模板

func (r *SidecarSet) ValidateCreate() error {
	sidecarsetlog.Info("validate create", "name", r.Name)
	// TODO(user): fill in your validation logic upon object creation.
	return nil
}

func (r *SidecarSet) ValidateUpdate(old runtime.Object) error {
	sidecarsetlog.Info("validate update", "name", r.Name)
	// TODO(user): fill in your validation logic upon object update.
	return nil
}

func (r *SidecarSet) ValidateDelete() error {
	sidecarsetlog.Info("validate delete", "name", r.Name)
	// TODO(user): fill in your validation logic upon object deletion.
	return nil
}

可以看到生成了ValidateCreateValidateUpdateValidateDelete三个模板方法,分别在CR创建、更新、删除时进行回调。

核心逻辑梳理

  1. 在用户创建SidecarSet Custom Resouce的时候进行配置项校验:通过实现SidecarSet Validating admission webhook 校验逻辑进行处理。
  2. 在用户创建Pod的时候,自动匹配对应的SidecarSet,如果匹配到SidecarSet的话解析出SidecarSet对应的sidecar容器注入到Pod中:这里需要通过实现Pod的Mutating admission webhook达到目的,由于 kubebuilder 不支持核心类型的 webhook 自动生成,所以需要使用 controller-runtime 的库来处理。
  3. 实时监测Pod信息,更新SidecarSet Status: 这里就需要实现sidecarset_controllerReconcile逻辑进行处理。

完整流程如下图所示:

SIdecarSet运行流程 (1)

实现Pod类型的Mutating Admission Webhook

//+kubebuilder:webhook:path=/mutate-sidecar-pod,mutating=true,failurePolicy=fail,groups="",resources=pods,verbs=create;update,versions=v1,name=mpod.kb.io,admissionReviewVersions=v1,sideEffects=None

type podHandler struct {
	Client  client.Client
	Decoder *admission.Decoder
}

var (
	SidecarEnvKey = "IS_INJECTED"
	SidecarAnnotationHashKey = "chinalhr.github.io/sidecar-hash"
)

func (r *SidecarSet) Register(mgr ctrl.Manager) error {
	mgr.GetWebhookServer().Register("/mutate-sidecar-pod", &webhook.Admission{Handler: &podHandler{Client: mgr.GetClient()}})
	return nil
}

func (handler *podHandler) Handle(ctx context.Context, request admission.Request) admission.Response {
	log := log.FromContext(ctx)
	pod := &v1.Pod{}
	err := handler.Decoder.Decode(request, pod)
	copy := pod.DeepCopy()

	log.V(0).Info("begin to process sidecarSet webhook")
	if err != nil {
		return admission.Errored(http.StatusInternalServerError, err)
	}

	log.V(0).Info("sidecarSet webhook hand pod %v", "pod", pod.Name)
	err = handler.mutatingPodFunc(ctx, copy)
	if err != nil {
		return admission.Errored(http.StatusInternalServerError, err)
	}

	marshaledPod, err := json.Marshal(copy)
	return admission.PatchResponseFromRaw(request.Object.Raw, marshaledPod)
}

func (handler *podHandler) mutatingPodFunc(ctx context.Context, pod *v1.Pod) error {
	//1. get sidecarSet resource list
	sidecarSets := &SidecarSetList{}
	if err := handler.Client.List(ctx, sidecarSets); err != nil {
		return err
	}

	var sidecarContainers []v1.Container
	sidecarSetHash := make(map[string]string)

	matchNothing := true
	for _, sidecarSet := range sidecarSets.Items {
		needInject, err := PodSidecarSetMatch(pod, sidecarSet)
		if err != nil {
			return err
		}

		if !needInject {
			continue
		}

		matchNothing = false
		sidecarSetHash[sidecarSet.Name] = sidecarSet.Annotations[SidecarAnnotationHashKey]

		for i := range sidecarSet.Spec.Containers {
			sidecarContainer := &sidecarSet.Spec.Containers[i]
			sidecarContainer.Env = append(sidecarContainer.Env, v1.EnvVar{Name: SidecarEnvKey, Value: "true"})
			sidecarContainers = append(sidecarContainers, sidecarContainer.Container)
		}
	}

	if matchNothing {
		return nil
	}

	pod.Spec.Containers = append(pod.Spec.Containers, sidecarContainers...)
	if pod.Annotations == nil {
		pod.Annotations = make(map[string]string)
	}
	if len(sidecarSetHash) != 0 {
		encodedStr, err := json.Marshal(sidecarSetHash)
		if err != nil {
			return err
		}
		pod.Annotations[SidecarAnnotationHashKey] = string(encodedStr)
	}

	return nil
}

func PodSidecarSetMatch(pod *v1.Pod, sidecarSet SidecarSet) (bool, error) {
	selector, err := metav1.LabelSelectorAsSelector(sidecarSet.Spec.Selector)
	if err != nil {
		return false, err
	}

	if !selector.Empty() && selector.Matches(labels.Set(pod.Labels)) {
		return true, nil
	}
	return false, nil
}

// InjectDecoder injects the decoder.
func (handler *podHandler) InjectDecoder(d *admission.Decoder) error {
	handler.Decoder = d
	return nil
}

这里实现包括:

  1. InjectDecoder方法注入解码器,用以解析admission request;实现 admission.Handler 接口的Handle方法,解析出Pod 资源的信息,获取当前sidecarSet列表,根据sidecarSet的selector匹配Pod的labels,匹配成功则进行sidecar的注入,将sidecarSet的Container注入到当前的Pod中,并对已注入的Pod进行annotations标识(后续SidecarSet Controller进行识别监控);最后通过PatchResponseFromRaw 合并更新Pod的信息相应admission controller。
  2. Register方法注册当前的mutating webhook。
  3. 注释kubebuilder标记,让controller-gen可以生成对应的webhook 配置

实现SidecarSet Validating Admission Webhook逻辑

// log is for logging in this package.
var sidecarsetlog = logf.Log.WithName("sidecarset-resource")

func (r *SidecarSet) SetupWebhookWithManager(mgr ctrl.Manager) error {
	return ctrl.NewWebhookManagedBy(mgr).
		For(r).
		Complete()
}

//+kubebuilder:webhook:path=/validate-apps-chinalhr-github-io-v1alpha1-sidecarset,mutating=false,failurePolicy=fail,sideEffects=None,groups=apps.chinalhr.github.io,resources=sidecarsets,verbs=create;update,versions=v1alpha1,name=vsidecarset.kb.io,admissionReviewVersions=v1

var _ webhook.Validator = &SidecarSet{}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *SidecarSet) ValidateCreate() error {
	sidecarsetlog.Info("validate create", "name", r.Name)
	return r.validateSpec()
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (r *SidecarSet) ValidateUpdate(old runtime.Object) error {
	sidecarsetlog.Info("validate update", "name", r.Name)
	return r.validateSpec()
}

func (r *SidecarSet) validateSpec() *apierrors.StatusError {
	spec := r.Spec
	var allErrs field.ErrorList

	if spec.Selector == nil {
		allErrs = append(allErrs, field.Required(field.NewPath("spec").Child("selector"), "no selector defined for sidecarset"))
	} else {
		if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 {
			allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("selector"), spec.Selector, "empty selector is not valid for sidecarset."))
		}
	}
	if len(allErrs) == 0 {
		return nil
	}

	return apierrors.NewInvalid(
		schema.GroupKind{Group: "apps.chinalhr.github.io", Kind: "SidecarSet"},
		r.Name, allErrs)
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (r *SidecarSet) ValidateDelete() error {
	sidecarsetlog.Info("validate delete", "name", r.Name)

	// TODO(user): fill in your validation logic upon object deletion.
	return nil
}

这里主要是实现了validateSpec方法,支持对SidecarSet创建/更新的请求,进行specSelector属性的校验;如果校验失败,则返回异常拒绝资源的创建/更新。

实现控制器协调逻辑

//+kubebuilder:rbac:groups=apps.chinalhr.github.io,resources=sidecarsets,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=apps.chinalhr.github.io,resources=sidecarsets/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=apps.chinalhr.github.io,resources=sidecarsets/finalizers,verbs=update
//+kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups="",resources=pods/status,verbs=get

func (r *SidecarSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := log.FromContext(ctx)
	//next reconcile
	reconcileResult := ctrl.Result{RequeueAfter: time.Second * 5, Requeue: true}

	//fetch the SidecarSet instance
	sidecarSet := &appsv1alpha1.SidecarSet{}
	err := r.Get(context.TODO(), req.NamespacedName, sidecarSet)
	if err != nil {
		if errors.IsNotFound(err) {
			log.Error(err, "process sidecarSet error : object not found")
			return reconcileResult, nil
		}
		log.Error(err, "process sidecarSet error : reading object error")
		return reconcile.Result{}, err
	}

	log.V(0).Info("begin to process sidecarSet [%s]", sidecarSet.Name)
	//list matched pod
	selector, err := metav1.LabelSelectorAsSelector(sidecarSet.Spec.Selector)
	if err != nil {
		log.Error(err, "process sidecarSet error")
		return reconcile.Result{}, err
	}

	listOpts := &client.ListOptions{LabelSelector: selector}

	matchedPods := &v1.PodList{}
	if err := r.Client.List(context.TODO(), matchedPods, listOpts); err != nil {
		log.Error(err, "list matched pods error")
		return reconcile.Result{}, err
	}
	log.V(0).Info("process sidecarSet matchedPods", "pods", matchedPods)

	// ignore inactive pods
	var filteredPods []*v1.Pod
	for i := range matchedPods.Items {
		pod := &matchedPods.Items[i]
		if IsPodActive(pod) {
			filteredPods = append(filteredPods, pod)
		}
	}

	//calculateSidecarSetStatus
	status, err := calculateSidecarSetStatus(sidecarSet, filteredPods)
	if err != nil {
		log.Error(err, "calculateSidecarSetStatus error")
		return reconcile.Result{}, err
	}

	//updateSidecarSetStatus
	err = r.updateSidecarSetStatus(log, sidecarSet, status)
	if err != nil {
		log.Error(err, "updateSidecarSetStatus error")
		return reconcile.Result{}, err
	}
	return reconcileResult, nil
}

这里实现包括:

  1. 获取到sidecarSet资源,根据sidecarSet的selector获取匹配的Pod集合,根据Pod的状态计算出sidecarSet的Status,即已匹配的Pod数量更新MatchedPods ,已经注入sidecar容器的Pod数量更新UpdatedPods,已经注入sidecar容器并且处于Running状态的Pod数量更新ReadyPods
  2. 添加kubebuilder注释标记,增加对Pod,Pod Status对应的权限。

部署Operator

  • 生成CRD/Webhook manifests
# 使用spec/status字段与标记和CRD validation标记定义API,以及新增了其他资源的Webhook,需要使用make manifests生成/更新CRD与webhook的 manifests
make manifests
  • 部署cert-manager

因为需要为webhook server配置证书,这里需要在Kubernetes集群中部署cert manager作证书管理,可以参考https://cert-manager.io/docs/

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.7.1/cert-manager.yaml

cert-manager的CA injector组件会将 CA 包注入到 Mutating|ValidatingWebhookConfiguration 中,operator-sdk已经为我们生成了对应的kustomize patch文件在config/default/webhookcainjection_patch.yaml

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: mutating-webhook-configuration
  annotations:
    cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: validating-webhook-configuration
  annotations:
    cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
  • 通过 kustomize 启用 webhook 和 cert-manager 配置config/default/kustomization.yaml与config/crd/kustomization.yaml,根据注释进行配置
  • 通过make docker-build docker-push进行容器镜像打包与推送;通过make deploy部署Operator到集群中
make docker-build docker-push IMG=ccr.ccs.tencentyun.com/open-operator/sidecar-operator:1.0.0
make deploy IMG=ccr.ccs.tencentyun.com/open-operator/sidecar-operator:1.0.0

代码仓库

https://github.com/ChinaLHR/sidecar-operator