1. WebHook介绍

我们知道访问Kubernetes API有好几种方式,比如使用kubectl命令、使用client-go之类的开发库、直接通过REST请求等。不管是一个使用kubectl的真人用户,还是一个Service Account,都可以通过API访问认证,这个过程官网有一张图描述得很直观
Operator 开发实践 四 (WebHook)-LMLPHP

当一个访问请求发送到API Server的时候,会依次经过认证、鉴权、准入控制三个主要的过程。Admission Webhook就是这里提到的“准入控制”的范畴.
准入控制(Admission Control) 模块能够实现更改一个请求的内容或者决定是否拒绝一个请求的功能。准入控制主要是在一个对象发生变更时生效,变更包括创建、更新、删除等动作,也就是不包含查询动作。如果配置了多个准入控制模块,那么这些模块是按顺序工作的。
关于拒绝请求这个能力,一个请求在多个准入控制模块中有一个模块拒绝,这个请求就会被拒绝,这和认证或者鉴权模块明显不一样.而更改一个请求内容的能力,主要用于给一些请求字段设置默认值。准入控制器基本都是在kube-apiserver中实现的,所以它们的启用也是通过在kube-apiserver的启动参数上添加相应配置,比如:

kude-apiserver --enable-admission-plugins=NamespaceLifecycle,LimitRanger ...

可以在 https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#what-does-each-admission-controller-do看到目前有哪些准入控制器以及它们的作用。这里的多数准入控制器只能决定它们的启用或者禁用,除了这类在kube-apiserver内部实现的准入控制器外,我们可以看到有两个特殊的准入控制器:ValidatingAdmissionWebhook 和 MutatingAdmissionWebhook。这是Kubernetes提供的一种拓展机制,使我们能够通过Webhook的方式独立于kube-apiserver运行自己的准入控制逻辑。

顾名思义,Admission Webhook是一个HTTP回调钩子,可以用来接收“准入请求”,然后对这个请求做相应的逻辑处理。
Admission Webhook有两种:
ValidatingAdmissionWebhook
MutatingAdmissionWebhook

先执行的是MutatingAdmissionWebhook,这个准入控制器可以修改请求对象,主要用来注入自定义字段;当这个对象被API Server校验时,就会回调ValidatingAdmissionWebhook,然后相应的自定义校验策略就会被执行,以决定这个请求能否被通过

2. WebHook的实现

我们可以通过operator的create webhook命令来生成实现Admission Webhook的代码脚手架:

operator-sdk create webhook --group apps --version v1 --kind Atom --defaulting --programmatic-validation

这个命令执行完成后,可以看到项目内多了文件。打开api/v1/atom_webhook.go源文件,可以看到里面有一个Default()方法。在Default()方法中就可以完成MutatingAdmissionWebhook的相关逻辑。

实现MutatingAdmissionWebhook

以Replicas默认值注入为例,比如用户提交的Atom配置中没有给出Replicas的大小,那么就注入一个默认值3,代码如下:

func (r *Atom) Default() {
	atomlog.Info("default", "name", r.Name)
	// 如果没有设置副本数,则默认为3
	if r.Spec.Deployment.Replicas == nil {
		r.Spec.Deployment.Replicas = new(int32)
		*r.Spec.Deployment.Replicas = 3
	}
}
实现ValidatingAdmissionWebhook

在atom_webhook.go源文件中继续往后看,可以发现有3个Validatexxx()方法,分 别 是 ValidateCreate、ValidateUpdate 和ValidateDelete。顾名思义,这几个Validate方法的触发条件分别是相应对象在创建、更新、删除的时候。删除时不需要做什么校验逻辑,而创建和更新的校验逻辑几乎是样的,所以我们将创建和更新时所需的校验逻辑封装一下,编写一个validateAtom()方法:

// ValidateAtom validates the Atom
func (r *Atom) ValidateAtom() error {
	if *r.Spec.Deployment.Replicas > 10 {
		return fmt.Errorf("replicas too many error")
	}
	return nil
}

这里简单地校验Replicas是不是设置得过大了,其他业务逻辑也是类似的校验方法,如果觉得条件不满足,就返回一个error,反之返回nil就行。然后就在几个 Validate xxx方法中调用这个ValidateAtom()方法:

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

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

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

检查一下atom_types.go中Atom结构体的注解, 需要是下面这样

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:resource:path=atoms,singular=atom,scope=Namespaced,shortName=at

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

	Spec   AtomSpec   `json:"spec,omitempty"`
	Status AtomStatus `json:"status,omitempty"`
}

3. cert-manager

在部署Webhook之前需要先安装cert-manager,用来实现证书签发功能。关于cert-manager的详细介绍大家可以参考官方文档:https://cert-manager.io/docs/,现在只介绍怎么部署cert-manager。cert-manager提供了helm Chart包方式部署:

helm repo add jetstack https://charts.jetstack.io
helm search repo jetstack
helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --version v1.12.3 --set installCRDs=true

出现以下内容代表部署成功

# kgpo -n cert-manager

NAMESPACE      NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager   cert-manager-875c7579b-qnskb               1/1     Running   0          3m4s
cert-manager   cert-manager-cainjector-7bb6786867-vp8b7   1/1     Running   0          3m4s
cert-manager   cert-manager-webhook-89dc55877-tx8xj       1/1     Running   0          3m4s

4. WebHook部署运行

现在已经准备好了Webhook代码,接着就部署到环境中来看一下运行结果。

1.构建并推送镜像

执行以下两行命令来构建镜像,并把镜像加载到kind集群中:

make docker-build IMG=atom-operator:v0.1
kind load docker-image atom-operator:v0.1 --name dev
2. 部署CRD
make install
3. 配置相关证书

前面部署了cert-manager,但要使用cert-manager还需要做些配置。首先config/default/kustomization.yaml文件需要做一些调整,打开几行注释内容:

namespace: atom-operator-system
namePrefix: atom-operator-
bases:
- ../crd
- ../rbac
- ../manager
- ../webhook
- ../certmanager
patchesStrategicMerge:
- manager_auth_proxy_patch.yaml
- manager_webhook_patch.yaml
- webhookcainjection_patch.yaml
vars:
- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR
  objref:
    kind: Certificate
    group: cert-manager.io
    version: v1
    name: serving-cert # this name should match the one in certificate.yaml
  fieldref:
    fieldpath: metadata.namespace
- name: CERTIFICATE_NAME
  objref:
    kind: Certificate
    group: cert-manager.io
    version: v1
    name: serving-cert # this name should match the one in certificate.yaml
- name: SERVICE_NAMESPACE # namespace of the service
  objref:
    kind: Service
    version: v1
    name: webhook-service
  fieldref:
    fieldpath: metadata.namespace
- name: SERVICE_NAME
  objref:
    kind: Service
    version: v1
    name: webhook-service

修改config/crd/kustomization.yaml文件

resources:
- bases/apps.atom.com_atoms.yaml
#+kubebuilder:scaffold:crdkustomizeresource

patchesStrategicMerge:
- patches/webhook_in_atoms.yaml
#+kubebuilder:scaffold:crdkustomizewebhookpatch

- patches/cainjection_in_atoms.yaml
#+kubebuilder:scaffold:crdkustomizecainjectionpatch

configurations:
- kustomizeconfig.yaml

4. 部署控制器
make deploy  IMG=atom-operator:v0.1

5. WebHook测试

编写CRD文件

apiVersion: apps.atom.com/v1
kind: Atom
metadata:
  name: nginx-sample
  namespace: default
  labels:
    app: nginx
spec:
  deployment:
    replicas: 12
    selector:
      matchLabels:
        app: nginx
    template:
      spec:
        containers:
        - name: nginx
          image: nginx:1.14.2
          ports:
          - containerPort: 80
  service:
    type: NodePort
    ports:
      - name: nginx-http
        port: 80
        targetPort: 80
        nodePort: 30080

注意:我们这里给出的replicas为12,然后部署CRD看看Validate是否生效

# kubectl apply -f apps_v1_atom.yaml

Joxz nuew o07 seoTTdaI :1sanbaxaut patuep ,ot'qx'uorqeofTddeA, yoouqem uotsstupe : Twe/ uorteotTdde TA sdde. :oJn7Tnejep. :eoedsaweN .eTdues-xuTbu.. :eueN.uotteoTTddy-puTxTA/uo*nuTetuep'sdde.:putyuotsza dnoig suotqeotTdde-aoinosad "TA/uo'nutatuep'sdde,;aoinosay
:07
(([108: 7a0d73bxet.'08:7zod, 08008:7zodapou)l: sizodl:otatas '{{[([[08: qx0ajeutequoo,)]  sqxod "xutbu  eweu"2"hi'T;xutbu,  abewt]:.sTauTe7u0O.l:.oads.( ( ,xutbu, ' dde, l: sTaqeT. l: eqepezow ):o7eTdwe7.TT:,seoTdax, ):,quewkordep); oads,"(u{ll \zodepon\ edk\[108:7a0d7ebxe'08:zod "080088\qzodepou,\)lsqzod \) eotazas[{{[{[(08\qz0azauteuoo\)]sqzod "xutbu  oweu,\"\2'hT'T:xutbu\ abewT)l sxaurequoo \):\oads\[xutbu,\, dde,\):,sTogeT\): eepetou \) aeTduet,'(,xutbu  dde \)\sTegeruogew,); aogoatas,'tt seottdex\) quewhoTdep,\); oads,l tTnejapaoedsawpu," ardues-xutbu  aweu,',xutbu,  dde\): staqeT'();\suofte7ouue\):\eqepeqew"uorqeoTTddy\purx\\TA/uo*nu[oruep'sdde uorsjanTde,\)iuorteinbrjuoo-paridde-7seT/0r*seqauxaqny Tqoaqny,)isuorqeqouue,) eqepeqew):yoqed buthTdde uaum joxa :(joi uew 007 seotTdaa) jaazas woj Joxzg

符合预期,得到了一个replicas too many error错误。

接着将Replicas删除,使用同样的方式可以验证Defaulter能不能正常工作。结果是在不设置Replicas的情况下,Replicas默认值会变3

10-18 18:07