欢送拜访我的GitHub

https://github.com/zq2599/blog_demos

内容:所有原创文章分类汇总及配套源码,波及Java、Docker、Kubernetes、DevOPS等;

本篇概览

  • 本文是《kubebuilder实战》系列的第七篇,之前的文章咱们实现了一个Operator的设计、开发、部署、验证过程,为了让整个过程放弃简洁并且篇幅不收缩,实战中刻意跳过了一个重要的知识点:<font color="red">webhook</font>,现在是时候学习它了,这是个很重要的性能;
  • 本篇由以下局部形成:
  • 介绍webhook;
  • 联合后面的elasticweb我的项目,设计一个应用webhook的场景;
  • 筹备工作
  • 生成webhook
  • 开发(配置)
  • 开发(编码)
  • 部署
  • 验证Defaulter(增加默认值)
  • 验证Validator(合法性校验)

对于webhook

  • 相熟java开发的读者大多晓得过滤器(Servlet Filter),如下图,内部申请会先达到过滤器,做一些对立的操作,例如转码、校验,而后才由真正的业务逻辑解决申请:

  • Operator中的webhook,其作用与上述过滤器相似,内部对CRD资源的变更,在Controller解决之前都会交给webhook提前解决,流程如下图,该图来自《Getting Started with Kubernetes | Operator and Operator Framework》:

  • 再来看看webhook具体做了哪些事件,如下图,kubernetes官网博客明确指出webhook能够做两件事:批改(mutating)和验证(validating)
  • kubebuilder为咱们提供了生成webhook的根底文件和代码的工具,与制作API的工具相似,极大地简化了工作量,咱们只需聚焦业务实现即可;
  • 基于kubebuilder制作的webhook和controller,如果是同一个资源,那么<font color="red">它们在同一个过程中</font>;

设计实战场景

  • 为了让实战有意义,咱们为后面的elasticweb我的项目上减少需要,让webhook施展理论作用;
  1. 如果用户遗记输出总QPS,零碎webhook负责设置默认值<font color="red">1300</font>,操作如下图:

  1. 为了爱护零碎,给单个pod的QPS设置下限<font color="red">1000</font>,如果内部输出的singlePodQPS值超过1000,<font color="blue">就创立资源对象失败</font>,如下图所示:

源码下载

  • 本篇实战中的残缺源码可在GitHub下载到,地址和链接信息如下表所示(https://github.com/zq2599/blo...:
名称链接备注
我的项目主页https://github.com/zq2599/blo...该我的项目在GitHub上的主页
git仓库地址(https)https://github.com/zq2599/blo...该我的项目源码的仓库地址,https协定
git仓库地址(ssh)mailto:git@github.com:zq2599/blog_demos.git该我的项目源码的仓库地址,ssh协定
  • 这个git我的项目中有多个文件夹,kubebuilder相干的利用在<font color="blue">kubebuilder</font>文件夹下,如下图红框所示:

  • kubebuilder文件夹下有多个子文件夹,本篇对应的源码在<font color="blue">elasticweb</font>目录下,如下图红框所示:

筹备工作

  • 和controller相似,webhook既能在kubernetes环境中运行,也能在kubernetes环境之外运行;
  • 如果webhook在kubernetes环境之外运行,是有些麻烦的,须要将证书放在所在环境,默认地址是:
/tmp/k8s-webhook-server/serving-certs/tls.{crt,key}
  • 为了省事儿,也为了更靠近生产环境的用法,接下来的实战的做法是<font color="red">将webhook部署在kubernetes环境中</font>
  • 为了让webhook在kubernetes环境中运行,咱们要做一点筹备工作<font color="blue">装置cert manager</font>,执行以下操作:
  • kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.2.0/cert-manager.yaml
  • 上述操作实现后会新建很多资源,如namespace、rbac、pod等,以pod为例如下:
[root@hedy ~]# kubectl get pods --all-namespacesNAMESPACE        NAME                                       READY   STATUS    RESTARTS   AGEcert-manager     cert-manager-6588898cb4-nvnz8              1/1     Running   1          5d14hcert-manager     cert-manager-cainjector-7bcbdbd99f-q645r   1/1     Running   1          5d14hcert-manager     cert-manager-webhook-5fd9f9dd86-98tm9      1/1     Running   1          5d14h...
  • 操作实现后,筹备工作完结,能够开始实战了;

生成webhook

  • 进入elasticweb工程下,执行以下命令创立webhook:
kubebuilder create webhook \--group elasticweb \--version v1 \--kind ElasticWeb \--defaulting \--programmatic-validation
  • 上述命令执行结束后,先去看看<font color="blue">main.go</font>文件,如下图红框1所示,主动减少了一段代码,作用是让webhook失效:

  • 上图红框2中的<font color="blue">elasticweb_webhook.go</font>就是新增文件,内容如下:
package v1import (    "k8s.io/apimachinery/pkg/runtime"    ctrl "sigs.k8s.io/controller-runtime"    logf "sigs.k8s.io/controller-runtime/pkg/log"    "sigs.k8s.io/controller-runtime/pkg/webhook")// log is for logging in this package.var elasticweblog = logf.Log.WithName("elasticweb-resource")func (r *ElasticWeb) SetupWebhookWithManager(mgr ctrl.Manager) error {    return ctrl.NewWebhookManagedBy(mgr).        For(r).        Complete()}// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!// +kubebuilder:webhook:path=/mutate-elasticweb-com-bolingcavalry-v1-elasticweb,mutating=true,failurePolicy=fail,groups=elasticweb.com.bolingcavalry,resources=elasticwebs,verbs=create;update,versions=v1,name=melasticweb.kb.iovar _ webhook.Defaulter = &ElasticWeb{}// Default implements webhook.Defaulter so a webhook will be registered for the typefunc (r *ElasticWeb) Default() {    elasticweblog.Info("default", "name", r.Name)    // TODO(user): fill in your defaulting logic.}// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.// +kubebuilder:webhook:verbs=create;update,path=/validate-elasticweb-com-bolingcavalry-v1-elasticweb,mutating=false,failurePolicy=fail,groups=elasticweb.com.bolingcavalry,resources=elasticwebs,versions=v1,name=velasticweb.kb.iovar _ webhook.Validator = &ElasticWeb{}// ValidateCreate implements webhook.Validator so a webhook will be registered for the typefunc (r *ElasticWeb) ValidateCreate() error {    elasticweblog.Info("validate create", "name", r.Name)    // TODO(user): fill in your validation logic upon object creation.    return nil}// ValidateUpdate implements webhook.Validator so a webhook will be registered for the typefunc (r *ElasticWeb) ValidateUpdate(old runtime.Object) error {    elasticweblog.Info("validate update", "name", r.Name)    // TODO(user): fill in your validation logic upon object update.    return nil}// ValidateDelete implements webhook.Validator so a webhook will be registered for the typefunc (r *ElasticWeb) ValidateDelete() error {    elasticweblog.Info("validate delete", "name", r.Name)    // TODO(user): fill in your validation logic upon object deletion.    return nil}
  • 上述代码有两处须要留神,第一处和填写默认值无关,如下图:

  • 第二处和校验无关,如下图:

  • 咱们要实现的业务需要就是通过批改上述<font color="blue">elasticweb_webhook.go</font>的内容来实现,不过代码稍后再写,先把配置都改好;

开发(配置)

  • 关上文件<font color="blue">config/default/kustomization.yaml</font>,下图四个红框中的内容本来都被正文了,当初请将正文符号都删掉,使其失效:

  • 还是文件<font color="blue">config/default/kustomization.yaml</font>,节点<font color="red">vars</font>上面的内容,本来全副被正文了,当初请全副放开,放开后的成果如下图:

  • 配置曾经实现,能够编码了;

开发(编码)

  • 关上文件<font color="blue">elasticweb_webhook.go</font>
  • 新增依赖:
apierrors "k8s.io/apimachinery/pkg/api/errors"
  • 找到<font color="blue">Default</font>办法,改成如下内容,可见代码很简略,判断TotalQPS是否存在,若不存在就写入默认值,另外还加了两行日志:
func (r *ElasticWeb) Default() {    elasticweblog.Info("default", "name", r.Name)    // TODO(user): fill in your defaulting logic.    // 如果创立的时候没有输出总QPS,就设置个默认值    if r.Spec.TotalQPS == nil {        r.Spec.TotalQPS = new(int32)        *r.Spec.TotalQPS = 1300        elasticweblog.Info("a. TotalQPS is nil, set default value now", "TotalQPS", *r.Spec.TotalQPS)    } else {        elasticweblog.Info("b. TotalQPS exists", "TotalQPS", *r.Spec.TotalQPS)    }}
  • 接下来开发校验性能,咱们把校验性能封装成一个validateElasticWeb办法,而后在新增和批改的时候各调用一次,如下,可见最终是调用<font color="blue">apierrors.NewInvalid</font>生成谬误实例的,而此办法承受的是多个谬误,因而要为其筹备切片做入参,当然了,如果是多个参数校验失败,能够都放入切片中:
func (r *ElasticWeb) validateElasticWeb() error {    var allErrs field.ErrorList    if *r.Spec.SinglePodQPS > 1000 {        elasticweblog.Info("c. Invalid SinglePodQPS")        err := field.Invalid(field.NewPath("spec").Child("singlePodQPS"),            *r.Spec.SinglePodQPS,            "d. must be less than 1000")        allErrs = append(allErrs, err)        return apierrors.NewInvalid(            schema.GroupKind{Group: "elasticweb.com.bolingcavalry", Kind: "ElasticWeb"},            r.Name,            allErrs)    } else {        elasticweblog.Info("e. SinglePodQPS is valid")        return nil    }}
  • 再找到新增和批改资源对象时被调用的办法,在外面调用validateElasticWeb:
// ValidateCreate implements webhook.Validator so a webhook will be registered for the typefunc (r *ElasticWeb) ValidateCreate() error {    elasticweblog.Info("validate create", "name", r.Name)    // TODO(user): fill in your validation logic upon object creation.    return r.validateElasticWeb()}// ValidateUpdate implements webhook.Validator so a webhook will be registered for the typefunc (r *ElasticWeb) ValidateUpdate(old runtime.Object) error {    elasticweblog.Info("validate update", "name", r.Name)    // TODO(user): fill in your validation logic upon object update.    return r.validateElasticWeb()}
  • 编码实现,可见非常简单,接下来,咱们把以前实战遗留的货色清理一下,再开始新的部署和验证;

清理工作

  • 如果您是随着《kubebuilder实战》系列一路操作下来,此时零碎上应该积攒了之前遗留的内容,能够通过以下步骤实现清理:
  • 删除elasticweb资源对象:
kubectl delete -f config/samples/elasticweb_v1_elasticweb.yaml
  1. 删除controller
kustomize build config/default | kubectl delete -f -
  1. 删除CRD
make uninstall
  • 当初万事俱备,能够部署webhook了;

部署

  1. 部署CRD
make install
  1. 构建镜像并推送到仓库(我终于受够了<font color="blue">hub.docker.com</font>的龟速,改为阿里云镜像仓库):
make docker-build docker-push IMG=registry.cn-hangzhou.aliyuncs.com/bolingcavalry/elasticweb:001
  1. 部署集成了webhook性能的controller:
make deploy IMG=registry.cn-hangzhou.aliyuncs.com/bolingcavalry/elasticweb:001
  1. 查看pod,确认启动胜利:
zhaoqin@zhaoqindeMBP-2 ~ % kubectl get pods --all-namespacesNAMESPACE           NAME                                             READY   STATUS    RESTARTS   AGEcert-manager        cert-manager-6588898cb4-nvnz8                    1/1     Running   1          5d21hcert-manager        cert-manager-cainjector-7bcbdbd99f-q645r         1/1     Running   1          5d21hcert-manager        cert-manager-webhook-5fd9f9dd86-98tm9            1/1     Running   1          5d21helasticweb-system   elasticweb-controller-manager-7dcbfd4675-898gb   2/2     Running   0          20s

验证Defaulter(增加默认值)

  • 批改文件<font color="blue">config/samples/elasticweb_v1_elasticweb.yaml</font>,批改后的内容如下,可见<font color="blue">totalQPS</font>字段曾经被正文掉了:
apiVersion: v1kind: Namespacemetadata:  name: dev  labels:    name: dev---apiVersion: elasticweb.com.bolingcavalry/v1kind: ElasticWebmetadata:  namespace: dev  name: elasticweb-samplespec:  # Add fields here  image: tomcat:8.0.18-jre8  port: 30003  singlePodQPS: 500  # totalQPS: 600
  • 创立一个elasticweb资源对象:
kubectl apply -f config/samples/elasticweb_v1_elasticweb.yaml
  • 此时单个pod的QPS是500,如果webhook的代码失效的话,总QPS就是1300,而对应的pod数应该是3个,接下来咱们看看是否合乎预期;
  • 先看elasticweb、deployment、pod等资源对象是否失常,如下所示,全副合乎预期:
zhaoqin@zhaoqindeMBP-2 ~ % kubectl get elasticweb -n dev                                                                 NAME                AGEelasticweb-sample   89szhaoqin@zhaoqindeMBP-2 ~ % kubectl get deployments -n devNAME                READY   UP-TO-DATE   AVAILABLE   AGEelasticweb-sample   3/3     3            3           98szhaoqin@zhaoqindeMBP-2 ~ % kubectl get service -n dev    NAME                TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGEelasticweb-sample   NodePort   10.105.125.125   <none>        8080:30003/TCP   106szhaoqin@zhaoqindeMBP-2 ~ % kubectl get pod -n dev    NAME                                 READY   STATUS    RESTARTS   AGEelasticweb-sample-56fc5848b7-5tkxw   1/1     Running   0          113selasticweb-sample-56fc5848b7-blkzg   1/1     Running   0          113selasticweb-sample-56fc5848b7-pd7jg   1/1     Running   0          113s
  • 用<font color="blue">kubectl describe</font>命令查看elasticweb资源对象的详情,如下所示,TotalQPS字段被webhook设置为1300,RealQPS也计算正确:
zhaoqin@zhaoqindeMBP-2 ~ % kubectl describe elasticweb elasticweb-sample -n devName:         elasticweb-sampleNamespace:    devLabels:       <none>Annotations:  <none>API Version:  elasticweb.com.bolingcavalry/v1Kind:         ElasticWebMetadata:  Creation Timestamp:  2021-02-27T16:07:34Z  Generation:          2  Managed Fields:    API Version:  elasticweb.com.bolingcavalry/v1    Fields Type:  FieldsV1    fieldsV1:      f:metadata:        f:annotations:          .:          f:kubectl.kubernetes.io/last-applied-configuration:      f:spec:        .:        f:image:        f:port:        f:singlePodQPS:    Manager:      kubectl-client-side-apply    Operation:    Update    Time:         2021-02-27T16:07:34Z    API Version:  elasticweb.com.bolingcavalry/v1    Fields Type:  FieldsV1    fieldsV1:      f:status:        f:realQPS:    Manager:         manager    Operation:       Update    Time:            2021-02-27T16:07:34Z  Resource Version:  687628  UID:               703de111-d859-4cd2-b3c4-1d201fb7bd7dSpec:  Image:           tomcat:8.0.18-jre8  Port:            30003  Single Pod QPS:  500  Total QPS:       1300Status:  Real QPS:  1500Events:      <none>
  • 再来看看controller的日志,其中的webhook局部是否合乎预期,如下图红框所示,发现TotalQPS字段为空,就将设置为默认值,并且在检测的时候SinglePodQPS的值也没有超过1000:

  • 最初别忘了用浏览器验证web服务是否失常,我这里的残缺地址是:http://192.168.50.75:30003/
  • 至此,咱们实现了webhook的Defaulter验证,接下来验证Validator

验证Validator

  • 接下来该验证webhook的参数校验性能了,先验证批改时的逻辑;
  • 编辑文件config/samples/update_single_pod_qps.yaml,值如下:
spec:  singlePodQPS: 1100
  • 用patch命令使之失效:
kubectl patch elasticweb elasticweb-sample \-n dev \--type merge \--patch "$(cat config/samples/update_single_pod_qps.yaml)"
  • 此时,控制台会输入错误信息:
Error from server (ElasticWeb.elasticweb.com.bolingcavalry "elasticweb-sample" is invalid: spec.singlePodQPS: Invalid value: 1100: d. must be less than 1000): admission webhook "velasticweb.kb.io" denied the request: ElasticWeb.elasticweb.com.bolingcavalry "elasticweb-sample" is invalid: spec.singlePodQPS: Invalid value: 1100: d. must be less than 1000
  • 再用<font color="blue">kubectl describe</font>命令查看elasticweb资源对象的详情,如下图红框,仍然是500,可见webhook曾经失效,阻止了谬误的产生:

  • 再去看controller日志,如下图红框所示,和代码对应上了:

  • 接下来再试试webhook在新增时候的校验性能;
  • 清理后面创立的elastic资源对象,执行命令:
kubectl delete -f config/samples/elasticweb_v1_elasticweb.yaml
  • 批改文件,如下图红框所示,咱们将singlePodQPS的值改为超过<font color="red">1000</font>,看看webhook是否能查看到这个谬误,并阻止资源对象的创立:

  • 执行以下命令开始创立elasticweb资源对象:
kubectl apply -f config/samples/elasticweb_v1_elasticweb.yaml
  • 控制台提醒以下信息,蕴含了咱们代码中写入的谬误形容,证实elasticweb资源对象创立失败,证实webhook的Validator性能曾经失效:
namespace/dev createdError from server (ElasticWeb.elasticweb.com.bolingcavalry "elasticweb-sample" is invalid: spec.singlePodQPS: Invalid value: 1500: d. must be less than 1000): error when creating "config/samples/elasticweb_v1_elasticweb.yaml": admission webhook "velasticweb.kb.io" denied the request: ElasticWeb.elasticweb.com.bolingcavalry "elasticweb-sample" is invalid: spec.singlePodQPS: Invalid value: 1500: d. must be less than 1000
  • 不释怀的话执行kubectl get命令检查一下,发现空洞无物:
zhaoqin@zhaoqindeMBP-2 elasticweb % kubectl get elasticweb -n dev       No resources found in dev namespace.zhaoqin@zhaoqindeMBP-2 elasticweb % kubectl get deployments -n devNo resources found in dev namespace.zhaoqin@zhaoqindeMBP-2 elasticweb % kubectl get service -n devNo resources found in dev namespace.zhaoqin@zhaoqindeMBP-2 elasticweb % kubectl get pod -n devNo resources found in dev namespace.
  • 还要看下controller日志,如下图红框所示,合乎预期:

  • 至此,operator的webhook的开发、部署、验证咱们就实现了,整个elasticweb也算是根本功能齐全,心愿能为您的operator开发提供参考;

你不孤独,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列

欢送关注公众号:程序员欣宸

微信搜寻「程序员欣宸」,我是欣宸,期待与您一起畅游Java世界...
https://github.com/zq2599/blog_demos