2023年6月30日发(作者:)
(翻)如何动⼿写⼀个kubernetesoperator⽂章源地址请移步writing-a-controller-for-pod-labels样例代码k8s中的operator是什么?operator旨在简化基于k8s部署有状态服务(例如:ceph集群、skywalking集群)可以利⽤Operator SDK 构建⼀个operator,operator使扩展k8s及实现⾃定义调度变得更加简单。尽管Operator SDK 适合构建功能齐全的operator,但也可以使⽤它来编写单个控制器。这篇⽂章将指导您在Go中编写⼀个Kubernetes控制器,该控制器将向具有特定注释的pod添加pod-name标签为什么我们需要⼀个控制器呢?最近我们项⽬中有这么个需求:通过⼀个service将流量路由⾄同⼀ReplicaSet中指定pod内(service对应⼀个或多个pod)⽽原⽣k8s并不能实现该功能,因为原⽣service只能通过label与Pod匹配,并且同⼀ReplicaSet内,Pod具有相同标签。上述需求有两种解决⽅案:1. 创建service时不指定标签选择器,⽽是利⽤Endpoints或EndpointSlices关联pod此时我们需要写⼀个⾃定义控制器,⽤于插⼊指定pod的端点地址⾄Endpoints或EndpointSlices对象2. 为每个Pod添加具有唯⼀value的标签,接下来我们就可以利⽤标签选择器进⾏service与Pod的关联。由于k8s中的控制器实质是个控制循环程序,控制器可以对k8s的资源(Resource,⽐如namespace、service等)进⾏监听追踪。此时如果我们创建⼀个控制器,仅监听Pod资源,针对指定Pod进⾏label处理,就可实现上述需求。当然k8s原⽣资源StatefulSets也是可以实现这⼀功能的,但假设我们不想/不能使⽤StatefulSets类型去实现呢?⼀般情况下,我们很少直接创建Pod类型,⽽是通过Deployment, ReplicaSet间接创建Pod。我们可以指定标签添加到PodSpec中的每个Pod,但不能使⽤动态值,因此⽆法复制StatefulSet的pod-name标签。我们尝试使⽤mutating admission webhook实现。当任何⼈创建Pod时,webhook会⾃动注⼊⼀个包含Pod名称的标签对Pod进⾏修改。遗憾的是这种⽅式并不能实现我们的需求: 并不是所有的Pod在创建前都有名字。举个例⼦:当ReplicaSet控制器创建⼀个Pod时,他向kube-apiserver发送⼀个请求,获取⼀个namePrefix⽽⾮namekubeapi-server在将新的Pod持久化到etcd之前⽣成⼀个唯⼀的名称,这个过程发⽣于在调⽤我们的许可webhook之后。所以在⼤多数情况下,我们⽆法知道⼀个带有mutating webhook的Pod的名字⼀旦Pod持久化⾄K8s集群中时,它⼏乎不会发⽣变更,但我们仍然可以通过以下⽅式,添加labelkubectl label my-pod my-label-key=my-label-value我们需要观察Kubernetes API中任何Pod的变化,并添加我们想要的标签。我们将编写⼀个控制器来为我们做这件事,⽽不是⼿动做这件事利⽤Operator SDK构建⼀个控制器控制器是⼀个协调循环,它从Kubernetes API中读取期望的资源状态,并采取⾏动使集群的实际状态达到期望状态安装配置1.安装Operator SDK下载⼆进制sudo curl -LO /operator-framework/operator-sdk/releases/download/v1.12.0/operator-sdk_linux_amd64sudo mv operator-sdk_linux_amd64 /usr/local/bin/operator-sdk2.构建⼯程mkdir label-operator && cd label-operator3.初始化⼯程export GOPROXY=ator-sdk init --domain= --repo=/weiliang-ms/label-operator4.创建控制器接下来我们创建⼀个控制器,这个控制器将会处理Pod资源,⽽⾮⾃定义资源,所以不需要⽣成资源代码。operator-sdk create api --group=core --version=v1 --kind=Pod --controller=true --resource=false初始化编码controllers/pod_解析现在我们拥有了⼀个新⽂件:
controllers/pod_。该⽂件包含了PodReconciler类型,该类型包含两个⽅法:Reconcile函数:func (r *PodReconciler) Reconcile(ctx t, req t) (, error) { _ = ntext(ctx) // your logic here return {}, nil}当创建、更新、或删除Pod时会调⽤Reconcile⽅法,Pod名称与命名空间作为函数⼊参,存于t对象之中SetupWithManager函数:func (r *PodReconciler) SetupWithManager(mgr r) error { return trollerManagedBy(mgr). For(&{}). Complete(r)}operator会在启动时执⾏SetupWithManager函数,SetupWithManager函数⽤于⽣命监听资源类型因为我们只想要监听Pod资源变化,所以监听资源这部分代码不动RBAC配置接下来为我们的控制器配置RBAC权限,代码⽣成器⽣成的默认权限如下://+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;create;update;patch;delete//+kubebuilder:rbac:groups=core,resources=pods/status,verbs=get;update;patch//+kubebuilder:rbac:groups=core,resources=pods/finalizers,verbs=update显然我们并不需要以上全部权限,我们控制器从不会CRUD
Pod的status与finalizers字段。控制器需要的仅仅是对Pod的读权限与更新权限,本着最⼩原则,我们调整权限如下// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;update;patch此时我们已经编写好了控制器的基本调⽤逻辑。实现Reconcile函数我们希望Reconcile实现以下功能:1. 通过⼊参t中的Pod名称与命名空间字段,请求k8s api获取Pod对象2. 如果Pod拥有add-pod-name-label注解,给这个Pod添加⼀个pod-name标签3. 将上⼀步Pod的变更回写k8s中接下来我们为注解与标签定义⼀些常量const ( addPodNameLabelAnnotation = "/add-pod-name-label" podNameLabel = "/pod-name")根据⼊参获取Pod⾸先我们根据⼊参信息,去k8s api获取Pod实例func (r *PodReconciler) Reconcile(ctx t, req t) (, error) { l := ntext(ctx)
var pod if err := (ctx, acedName, &pod); err != nil { (err, "unable to fetch Pod") return {}, err } return {}, nil}异常处理当创建、更新或删除⼀个Pod时,会触发我们控制器的Reconcile⽅法但当事件为'删除事件'时,()会返回⼀个指定错误对象,接下来我们通过引⽤下⾯的包来处理这个异常。package controllersimport ( // apierrors "/apimachinery/pkg/api/errors" // )// func (r *PodReconciler) Reconcile(ctx t, req t) (, error) { l := ntext(ctx) var pod if err := (ctx, acedName, &pod); err != nil { if ound(err) { // we'll ignore not-found errors, since we can get them on deleted requests. return {}, nil } (err, "unable to fetch Pod") return {}, err } return {}, nil}// 编辑Pod,判断注解、标签是否存在此时我们已经获取到了这个Pod对象(创建、更新事件),接下来我们获取Pod的注解元数据,判断是否需要添加标签... /* Step 1: 添加或移除标签. */
// 判断Pod是否存在注解 -> /add-pod-name-label: true labelShouldBePresent := tions[addPodNameLabelAnnotation] == "true" // 判断Pod是否存在标签 -> /pod-name: Pod名称 labelIsPresent := [podNameLabel] ==
// 如果期望状态与实际状态⼀致(含有上述标签、注解),返回 if labelShouldBePresent == labelIsPresent { ("no update required") return {}, nil }
// 存在注解 -> /add-pod-name-label: true if labelShouldBePresent { // 判断标签map是否为空 if == nil { // 为空创建 = make(map[string]string) } // 添加标签 -> /pod-name: Pod名称 [podNameLabel] = ("adding label") } else { // 不存在注解 -> /add-pod-name-label: true // 移除标签 delete(, podNameLabel) ("removing label") }...回写Pod⾄k8s /* Step 2: Update the Pod in the Kubernetes API. */ if err := (ctx, &pod); err != nil { (err, "unable to update Pod") return {}, err }当我们回写Pod变更⾄k8s时存在以下风险:集群内的Pod与我们获取到的Pod已经不⼀致(可能通过其他渠道变更了该Pod)在编写⼀个k8s控制器时,我们应该明⽩⼀个问题:我们编写的控制器并不是唯⼀能操作k8s资源对象的实例(其他控制器、kubectl等亦能操作k8s资源对象)当发⽣这种情况时,最好的做法是通过重新排队事件,从头开始处理。 if err := (ctx, &pod); err != nil { if lict(err) { // The Pod has been updated since we read it. // Requeue the Pod to try to reconciliate again. return {Requeue: true}, nil } if ound(err) { // The Pod has been deleted since we read it. // Requeue the Pod to try to reconciliate again. return {Requeue: true}, nil } (err, "unable to update Pod") return {}, err}在k8s集群内运⾏该控制器本⼈本地开发环境为windows10 +
Ubuntu 20本地ubuntu安装Kubectl并配置kube-config集群信息weiliang@DESKTOP-O8QG6I5:/mnt/d/github/label-operator$ kubectl get nodeNAME STATUS ROLES AGE VERSIONnode1 Ready master,worker 62d v1.18.6node2 Ready master,worker 62d v1.18.6node3 Ready master,worker 62d v1.18.6node4 Ready worker 62d v1.18.6label-operator下执⾏shell⽬录weiliang@DESKTOP-O8QG6I5:/mnt/d/github/label-operator$ pwd/mnt/d/github/label-operator运⾏operatorexport GOPROXY= run运⾏⼀个nginx服务Pod新建⼀个ubuntu shell窗⼝执⾏kubectl run --image=nginx:1.20.0 my-nginx查看Pod信息weiliang@DESKTOP-O8QG6I5:/mnt/d/github/label-operator$ kubectl get podNAME READY STATUS RESTARTS AGEmy-nginx 1/1 Running 0 78s此时运⾏operator的窗⼝会输出如下信息,说明监听成功2021-09-24T11:52:10.588+0800 INFO no update required {"reconciler group": "", "reconciler kind": "Pod", "name": "my-nginx", "namespace": "default"}2021-09-24T11:52:10.597+0800 INFO no update required {"reconciler group": "", "reconciler kind": "Pod", "name": "my-nginx", "namespace": "default"}2021-09-24T11:52:10.630+0800 INFO no update required {"reconciler group": "", "reconciler kind": "Pod", "name": "my-nginx", "namespace": "default"}查看Pod标签weiliang@DESKTOP-O8QG6I5:/mnt/d/github/label-operator$ kubectl get pod my-nginx --show-labelsNAME READY STATUS RESTARTS AGE LABELSmy-nginx 1/1 Running 0 4m38s run=my-nginx此时我们给该Pod打上以下注解,并查看是否已⾃动添加新的标签weiliang@DESKTOP-O8QG6I5:/mnt/d/github/label-operator$ kubectl annotate pod my-nginx /add-pod-name-label=truepod/my-nginx annotated查看标签weiliang@DESKTOP-O8QG6I5:/mnt/d/github/label-operator$ kubectl get pod my-nginx --show-labelsNAME READY STATUS RESTARTS AGE LABELSmy-nginx 1/1 Running 0 6m39s /pod-name=my-nginx,run=my-nginx成功了! 我们成功编写⼀个简单的operator,实现上⾯的需求
发布者:admin,转转请注明出处:http://www.yc00.com/web/1688055614a72083.html
评论列表(0条)