Welcome to my GitHub

Github.com/zq2599/blog…

Content: all original article classification summary and supporting source code, involving Java, Docker, Kubernetes, DevOPS, etc.;

This paper gives an overview of

  • This article is the seventh in the series of “KubeBuilder Actual Combat”. In the previous article, we completed the design, development, deployment and verification process of an Operator. In order to keep the whole process concise and not to expand the length, an important knowledge point was deliberately skipped in the actual combat: Webhook, now is the time to learn it, this is an important feature;
  • This article is composed of the following parts:
  1. Introduce webhook;
  2. Design a webhook scenario based on the elasticWeb project.
  3. The preparatory work
  4. Generate webhook
  5. Development (Configuration)
  6. Development (coding)
  7. The deployment of
  8. Validate Defaulter(add default values)
  9. Validator Validation

About webhook

  • For those familiar with Java development, Servlet filters are known as Servlet filters. External requests first arrive at the Filter and do some common operations, such as transcoding and validation, before the request is processed by the real business logic:

  • The function of Webhook in Operator is similar to the above filter. External changes to CRD resources will be handed to WebHook for processing in advance before being processed by Controller. The flow is as follows. From the “Getting Started with Kubernetes | Operator and Operator Framework” :

  • As the kubernetes blog points out, Webhook can do two things: mutating and validating.

  • Kubebuilder provides us with tools to generate the basic files and code of Webhook, similar to the tools for making API, greatly simplifying the workload, we only need to focus on business implementation;

  • Webhook and Controller based on KubeBuilder are in the same process if they are the same resource;

Design actual combat scene

  • Let’s add requirements for elasticWeb and make Webhook work.
  1. If the user forgets to enter the total QPS, the system Webhook is responsible for setting the default value 1300. The operation is as follows:

  1. To protect the system, set an upper limit of 1000 on the QPS of a single pod. If the external input singlePodQPS value exceeds 1000, the resource object will fail to be created, as shown below:

Download the source code

  • The full source code for this article can be downloaded at GitHub with the following address and link information (github.com/zq2599/blog…
The name of the link note
Project home page Github.com/zq2599/blog… The project’s home page on GitHub
Git repository address (HTTPS) Github.com/zq2599/blog… The project source warehouse address, HTTPS protocol
Git repository address (SSH) [email protected]:zq2599/blog_demos.git The project source warehouse address, SSH protocol
  • The git project has multiple folders, and the kubeBuilder related applications are in the KubeBuilder folder, as shown in the red box below:

  • The kubeBuilder folder has several sub-folders. The source code for this article is in the elasticWeb directory, as shown in the red box below:

The preparatory work

  • Like Controller, Webhook can run in or out of Kubernetes;
  • If webhook is running outside the Kubernetes environment, it is a bit cumbersome. You need to put the certificate in the environment. The default address is:
/tmp/k8s-webhook-server/serving-certs/tls.{crt,key}
Copy the code
  • The next step in the field is to deploy Webhooks in kubernetes for ease of use and closer to production usage
  • In order for Webhook to run in Kubernetes, we need to do a little preparation to install cert Manager and do the following:
Kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.2.0/cert-manager.yamlCopy the code
  • After the above operations are complete, many resources will be created, such as namespace, RBAC, and POD. Pod is used as an 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
...
Copy the code
  • After the completion of the operation, the preparation work is over, and the actual combat can begin.

Generate webhook

  • Go to the ElasticWeb project and run the following command to create a Webhook:
kubebuilder create webhook \
--group elasticweb \
--version v1 \
--kind ElasticWeb \
--defaulting \
--programmatic-validation
Copy the code
  • After executing the command above, take a look at the main.go file, as shown in red box 1 below, which automatically adds a piece of code to make webhook work:

  • Elasticweb_webhook. go is the new file shown in red box 2 above.
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(a) {
	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(a) 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(a) error {
	elasticweblog.Info("validate delete"."name", r.Name)

	// TODO(user): fill in your validation logic upon object deletion.
	return nil
}
Copy the code
  • There are two things to note about this code. The first has to do with filling in the default values, as shown below:

  • The second is related to verification, as shown below:

  • (elasticWeb_webhook. go) (elasticWeb_webhook. go) (elasticWeb_webhook. go)

Development (Configuration)

  • Open the config file/default/kustomization yaml, below the contents of the four red box were originally annotation, the annotation symbols are deleted, now, please make it take effect:

  • Or config files/default/kustomization yaml, node vars the content below, originally all is commented out, all now, please let go, let go after the effect of the diagram below:

  • The configuration is complete and you are ready to code;

Development (coding)

  • Open the file elasticweb_webhook.go

  • New dependencies:

apierrors "k8s.io/apimachinery/pkg/api/errors"
Copy the code
  • If TotalQPS exists, write the Default value if TotalQPS does not exist, and add two lines of log:
func (r *ElasticWeb) Default(a) {
	elasticweblog.Info("default"."name", r.Name)

	// TODO(user): fill in your defaulting logic.
	// If total QPS was not entered at the time of creation, set the default value
	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)
	}
}
Copy the code
  • Call apiErrors. NewInvalid () to generate an instance of an error. This method accepts multiple errors. Therefore, slices should be prepared for input. Of course, if multiple parameters fail verification, they can be put into slices:
func (r *ElasticWeb) validateElasticWeb(a) 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}}Copy the code
  • Find the method called when adding and modifying the resource object and call validateElasticWeb:
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *ElasticWeb) ValidateCreate(a) 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()
}
Copy the code
  • Coding is complete, it is very simple, next, let’s clean up the things left over from the previous actual combat, and then start the new deployment and verification;

The cleanup

  • If you are following the KubeBuilder series all the way down, the system should have accumulated the previous legacy content, you can complete the following steps to clean up:
  1. Delete elasticWeb resource object:
kubectl delete -f config/samples/elasticweb_v1_elasticweb.yaml
Copy the code
  1. Remove the controller
kustomize build config/default | kubectl delete -f -
Copy the code
  1. Delete the CRD
make uninstall
Copy the code
  • Now everything is ready to deploy Webhook;

The deployment of

  1. The deployment of CRD
make install
Copy the code
  1. Build the image and push it to the repository (I finally got fed up with the slow pace of hub.docker.com and changed to the Ari Cloud repository) :
make docker-build docker-push IMG=registry.cn-hangzhou.aliyuncs.com/bolingcavalry/elasticweb:001
Copy the code
  1. Deploying controller with Webhook functionality:
make deploy IMG=registry.cn-hangzhou.aliyuncs.com/bolingcavalry/elasticweb:001
Copy the code
  1. Check pod and confirm startup success:
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
Copy the code

Validate Defaulter(add default values)

  • Modify the config file/samples/elasticweb_v1_elasticweb yaml, modified content as follows, visible totalQPS fields 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
  # totalQPS: 600

Copy the code
  • Create a ElasticWeb resource object:
kubectl apply -f config/samples/elasticweb_v1_elasticweb.yaml
Copy the code
  • At this point, the QPS of a single POD is 500. If the Webhook code is effective, the total QPS is 1300, and the corresponding POD number should be 3. Next, let’s see if it meets the expectation.
  • Resource objects (elasticWeb, Deployment, pod, etc.) are normal as shown below.
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 113sCopy the code
  • Kubectl describe (TotalQPS = 1300, RealQPS = 1300)
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 >Copy the code
  • If the TotalQPS field is null, the value of SinglePodQPS does not exceed 1000, as shown in the red box below:

  • 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’re done with webHook Defaulter validation, let’s validate the Validator

Verify the Validator

  • Next, verify the parameter verification function of Webhook, verify the logic when modifying first;
  • Edit the file config/samples/update_single_pod_qps.yaml with the following values:
spec:
  singlePodQPS: 1100
Copy the code
  • Run the patch command to make it effective:
kubectl patch elasticweb elasticweb-sample \
-n dev \
--type merge \
--patch "$(cat config/samples/update_single_pod_qps.yaml)"
Copy the code
  • At this point, the console prints 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
Copy the code
  • Kubectl describe = kubectl describe = kubectl describe

  • Then look at the controller log, as shown in the red box below, which corresponds to the code:

  • Next, try webhook’s new validation feature.
  • To clear the elastic resource object created earlier, run the following command:
kubectl delete -f config/samples/elasticweb_v1_elasticweb.yaml
Copy the code
  • To modify the file, as shown in the red box below, let’s change the value of singlePodQPS to more than 1000 and see if Webhook can detect this error and prevent the creation of the resource object:

  • Run the following command to create the ElasticWeb resource object:
kubectl apply -f config/samples/elasticweb_v1_elasticweb.yaml
Copy the code
  • The webhook Validator is now in effect for the elasticWeb resource object.
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
Copy the code
  • 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.
Copy the code
  • Also take a look at the Controller log, as shown in the red box below, as expected:

  • Operator webhook operator Webhook Operator Webhook Operator Webhook Operator Webhook Operator Webhook

You are not alone, Xinchen original accompany 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 enjoying the Java world with you…

Github.com/zq2599/blog…