Welcome to my GitHub

https://github.com/zq2599/blog_demos

Content: all the original article classification summary and supporting source code, involving Java, Docker, Kubernetes, DevOps and so on;

This paper gives an overview of

  • This is the seventh article in the KubeBuilder series. In the previous article, we completed the design, development, deployment, and verification of an Operator. In order to keep the whole process simple and not bloated, we deliberately skipped an important knowledge point: webhook >
  • This paper consists of the following parts:
  • Introduce webhook;
  • Combined with the previous ElasticWeb project, design a scenario using Webhook;
  • The preparatory work
  • Generate webhook
  • Development (configuration)
  • Development (coding)
  • The deployment of
  • Verify Defaulter(add default values)
  • Validation of Validator

About webhook

  • Readers familiar with Java development are mostly familiar with the Servlet Filter, as shown in the figure below. External requests reach the Filter first and perform some uniform operations, such as transcoding and validation, before the real business logic processes the request:

  • The webhook in Operator is similar to the above filter. Any external changes to the CRD resource will be handed over to the webhook before being processed by the Controller. The process is shown in the following figure. From the “Getting Started with Kubernetes | Operator and Operator Framework” :

  • Looking at what the Webhook does in detail, as shown in the figure below, the official Kubernetes blog clearly states that a Webhook can do two things: mutating and validating.
  • KubeBuilder provides us with the tools to generate the basic files and code of Webhook, which is similar to the tools for making API, greatly simplifies the workload. We only need to focus on the business implementation.
  • ; ; ;

Design actual combat scenarios

  • To make real sense, let’s add some requirements to ElasticWeb projects and make Webhook work in real life.
  1. < FONT color=”red”>1300

    < FONT color=”red”>1300

  1. In order to protect the system, to a single pod QPS cap < font color = “red” > 1000 < / font >, if the external input singlePodQPS value of more than 1000, < font color = “blue” > just failed to create object resources < / font >, as shown in the figure below:

Download the source code

  • This may be downloaded in making to combat the full source code, address and link information shown in the following table (https://github.com/zq2599/blo… :
The name of the link note
Project home page https://github.com/zq2599/blo… The project’s home page on GitHub
Git repository address (HTTPS) https://github.com/zq2599/blo… The project source warehouse address, HTTPS protocol
Git repository address (SSH) mailto:[email protected]:zq2599/blog_demos.git The project source warehouse address, SSH protocol
  • kubebuilder

  • elasticweb

The preparatory work

  • Similar to Controller, Webhooks can run inside or outside the Kubernetes environment;
  • If Webhook is running outside of Kubernetes environment, it is a bit troublesome. You need to place the certificate in the environment. The default address is:
/tmp/k8s-webhook-server/serving-certs/tls.{crt,key}
  • For easy and for closer to the use of the production environment, the practice of the next combat is < font color = “red” > will be deployed in the environment of kubernetes webhook < / font >
  • install cert manager
  • Kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.2.0/cert-manager.yaml
  • After the above operation is completed, many new resources will be created, such as namespace, RBAC, pod, etc. Take pod as the following example:
[root@hedy ~]# kubectl get pods --all-namespaces
NAMESPACE        NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager     cert-manager-6588898cb4-nvnz8              1/1     Running   1          5d14h
cert-manager     cert-manager-cainjector-7bcbdbd99f-q645r   1/1     Running   1          5d14h
cert-manager     cert-manager-webhook-5fd9f9dd86-98tm9      1/1     Running   1          5d14h
...
  • After the operation is completed, the preparation is over and the actual combat can begin.

Generate webhook

  • Go to ElasticWeb and create a webhook.
kubebuilder create webhook \
--group elasticweb \
--version v1 \
--kind ElasticWeb \
--defaulting \
--programmatic-validation
  • After the above command execution, first to see < font color = “blue” > main. Go < / font > file, shown in the figure 1 red box below, add a piece of code automatically, role is to make effective webhook:

  • < FONT color=”blue”>elasticweb_webhook. Go
    =”blue”>elasticweb_webhook.
package v1 import ( "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=ela sticweb.com.bolingcavalry,resources=elasticwebs,verbs=create; update,versions=v1,name=melasticweb.kb.io var _ webhook.Defaulter = &ElasticWeb{} // Default implements webhook.Defaulter so a webhook will be registered for the type func (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.io var _ webhook.Validator = &ElasticWeb{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type func (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 type func (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 type func (r *ElasticWeb) ValidateDelete() error { elasticweblog.Info("validate delete", "name", r.Name) // TODO(user): fill in your validation logic upon object deletion. return nil }
  • There are two things to note about the above code. The first is about filling in the default values, as shown in the figure below:

  • The second is related to validation, as shown in the figure below:

  • elasticweb_webhook. Go

Development (configuration)

  • Open the file < font color = “blue” > config/default/kustomization yaml < / font >, below the contents of the four red box were originally annotation, the annotation symbols are deleted, now, please make it take effect:

  • Or file < font color = “blue” > config/default/kustomization yaml < / font >, node < font color = “red” > vars < / font > the content below, originally all is commented, all now, please let go, The effect after release is as follows:

  • The configuration is complete and the code is ready;

Development (coding)

  • elasticweb_webhook. Go
  • New dependencies:
apierrors "k8s.io/apimachinery/pkg/api/errors"
  • < FONT color=”blue”>Default

    >

    >

    >

    >

    >

func (r *ElasticWeb) Default() { elasticweblog.Info("default", "name", r.Name) // TODO(user): Fill in your defaulting logic. // If you did not enter the total QPS at the time of creation, If R.Spec. TotalQps == nil {R.Spec. TotalQps = new(Int32) * R.Spec. TotalQps = 1300 ElasticWeLog.info ("a. TotalQps  is nil, set default value now", "TotalQPS", *r.Spec.TotalQPS) } else { elasticweblog.Info("b. TotalQPS exists", "TotalQPS", *r.Spec.TotalQPS) } }
  • Let’s wrap the validation function into a validateElasticWeb method, and call it once when we add and modify it, as follows: Visible in the end is called < font color = “blue” > apierrors. NewInvalid < / font > instance to generate errors, and this method is acceptable is multiple errors, so be prepared for the sliced into arguments, and, of course, if it is a multiple parameter validation fails, can be all in the section:
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 = validateElasticWeb = validateElasticWeb = validateElasticWeb = validateElasticWeb = validateElasticWeb = validateElasticWeb
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (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 type
func (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()
}
  • The coding is completed, so it is very simple. Next, let’s clean up the things left by the actual combat before, and then start the new deployment and verification.

The cleanup

  • If you’ve been working with the KubeBuilder in Action series, you should have some leftover content on your system. You can clean it up by following these steps:
  • Delete ElasticWeb resource object:
kubectl delete -f config/samples/elasticweb_v1_elasticweb.yaml
  1. Remove the controller
kustomize build config/default | kubectl delete -f -
  1. Delete the CRD
make uninstall
  • Now you’re ready to deploy Webhook;

The deployment of

  1. The deployment of CRD
make install
  1. < FONT color=”blue”>hub.docker.com
    =”blue” hub.docker.com
    =”blue” hub.docker.com
make docker-build docker-push IMG=registry.cn-hangzhou.aliyuncs.com/bolingcavalry/elasticweb:001
  1. Deploy Controller with Webhook functionality:
make deploy IMG=registry.cn-hangzhou.aliyuncs.com/bolingcavalry/elasticweb:001
  1. Check POD to confirm successful startup:
zhaoqin@zhaoqindeMBP-2 ~ % kubectl get pods --all-namespaces
NAMESPACE           NAME                                             READY   STATUS    RESTARTS   AGE
cert-manager        cert-manager-6588898cb4-nvnz8                    1/1     Running   1          5d21h
cert-manager        cert-manager-cainjector-7bcbdbd99f-q645r         1/1     Running   1          5d21h
cert-manager        cert-manager-webhook-5fd9f9dd86-98tm9            1/1     Running   1          5d21h
elasticweb-system   elasticweb-controller-manager-7dcbfd4675-898gb   2/2     Running   0          20s

Verify Defaulter(add default values)

  • Modify the file < font color = “blue” > config/samples/elasticweb_v1_elasticweb yaml < / font >, the modified content as follows, totalQps field has been commented out:
apiVersion: v1 kind: Namespace metadata: name: dev labels: name: dev --- apiVersion: elasticweb.com.bolingcavalry/v1 kind: ElasticWeb metadata: namespace: dev name: elasticweb-sample spec: # Add fields here image: tomcat:8.0.18-jre8 port: 30003 singlePodQPS: 500 # Add fields here image: tomcat:8.0.18-jre8 port: 30003 singlePodQPS: 500 # totalQPS: 600
  • Create an ElasticWeb resource object:
kubectl apply -f config/samples/elasticweb_v1_elasticweb.yaml
  • At this point, the QPS of a single pod is 500. If the Webhook code takes effect, the total QPS will be 1300, and the corresponding number of pods should be 3. Let’s see if it meets the expectation.
  • First check whether the ElasticWeb, Deployment, POD and other resource objects are normal, as shown below, they all meet expectations:
zhaoqin@zhaoqindeMBP-2 ~ % kubectl get elasticweb -n dev                                                                 
NAME                AGE
elasticweb-sample   89s
zhaoqin@zhaoqindeMBP-2 ~ % kubectl get deployments -n dev
NAME                READY   UP-TO-DATE   AVAILABLE   AGE
elasticweb-sample   3/3     3            3           98s
zhaoqin@zhaoqindeMBP-2 ~ % kubectl get service -n dev    
NAME                TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
elasticweb-sample   NodePort   10.105.125.125   <none>        8080:30003/TCP   106s
zhaoqin@zhaoqindeMBP-2 ~ % kubectl get pod -n dev    
NAME                                 READY   STATUS    RESTARTS   AGE
elasticweb-sample-56fc5848b7-5tkxw   1/1     Running   0          113s
elasticweb-sample-56fc5848b7-blkzg   1/1     Running   0          113s
elasticweb-sample-56fc5848b7-pd7jg   1/1     Running   0          113s
  • kubectl describe
zhaoqin@zhaoqindeMBP-2 ~ % kubectl describe elasticweb elasticweb-sample -n dev Name: elasticweb-sample Namespace: dev Labels: <none> Annotations: <none> API Version: elasticweb.com.bolingcavalry/v1 Kind: ElasticWeb Metadata: 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-1d201fb7bd7d Spec: Image: Tomcat :8.0.18-jre8 Port: 30003 Single Pod QPS: 500 Total QPS: 1300 Status: Real QPS: 1500 Events: <none>
  • If the totalQps field is found to be empty, it will be set to the default value and the singlePodQps value will not exceed 1000 at the time of checking:

  • Finally, don’t forget to use browser validation of web services is normal, my complete address is here: http://192.168.50.75:30003/
  • Now that we have completed the Webhook Defaulter validation, we will validate the Validator

Verify the Validator

  • Next, it is time to verify the parameter verification function of Webhook. First, it is necessary to verify the logic of modification.
  • Edit the file config/samples/update_single_pod_qps.yaml with the following values:
spec:
  singlePodQPS: 1100
  • Enable it with patch:
kubectl patch elasticweb elasticweb-sample \
-n dev \
--type merge \
--patch "$(cat config/samples/update_single_pod_qps.yaml)"
  • At this point, the console will print an error message:
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
  • kubectl describe

  • Looking at the Controller log, as shown in the red box below, it corresponds to the code:

  • Next, try Webhook validation when added;
  • Clean up the Elastic resource object created earlier and execute the command:
kubectl delete -f config/samples/elasticweb_v1_elasticweb.yaml
  • Let’s change the value of singlePodQps to more than 1000 and see if Webhook detects this error and prevents the creation of the resource object:

  • Execute the following command to create the ElasticWeb resource object:
kubectl apply -f config/samples/elasticweb_v1_elasticweb.yaml
  • Console prompt the following information, contains the code we write the wrong description, prove elasticweb resource object creation failed, prove webhook Validator function in effect:
namespace/dev created
Error 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 = kubectl get = kubectl get = 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 dev
No resources found in dev namespace.
zhaoqin@zhaoqindeMBP-2 elasticweb % kubectl get service -n dev
No resources found in dev namespace.
zhaoqin@zhaoqindeMBP-2 elasticweb % kubectl get pod -n dev
No resources found in dev namespace.
  • Also take a look at the Controller log, as shown in the red box below, as expected:

  • At this point, the operator of the webhook let’s complete development, deployment, validation, the entire elasticweb is basic function is well-found, the hope can operator development to provide the reference for you;

You are not alone, Xin Chen original all the way

  1. Java series
  2. Spring series
  3. The Docker series
  4. Kubernetes series
  5. Database + middleware series
  6. The conversation series

Welcome to pay attention to the public number: programmer Xin Chen

WeChat search “programmer Xin Chen”, I am Xin Chen, looking forward to traveling with you in the JAVA world…


https://github.com/zq2599/blog_demos