Skip to content
🧸A demo about Kubernetes AdmissionWebhook by kubebuilder.
Go Makefile Dockerfile
Branch: master
Clone or download

Latest commit

Fetching latest commit…
Cannot retrieve the latest commit at this time.

Files

Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
api/v1
config
controllers
hack
.gitignore
Dockerfile
LICENSE
Makefile
PROJECT
README.md
go.mod
go.sum
main.go

README.md

KubeAdmissionWebhookDemo

这里使用一个简单的场景做一个演示,我们自定义一个名为 App 资源,当用户创建一个 App 实例时,我们根据用户的描述创建出一个 Deployment。

然后我们添加一个 MutatingAdmissionWebhook,当用户通过 App 创建 Deployment 时,自动添加一个 sidecar 容器到 Pod 中(这里使用 nginx 作为 sidecar)。

初始化 API 及 Controller

第一步是创建出 CRD 及其 Controller,几行命令就能搞定:

$ export GO111MODULE=on

$ mkdir $GOPATH/src/zww-app
$ cd $GOPATH/src/zww-app
$ kubebuilder init --domain o0w0o.cn --owner "zwwhdls"

$ kubebuilder create api --group app --version v1 --kind App

我这里做的比较简单,AppSpec 只定义了一个 deploy 属性(就是 appsv1.DeploymentSpec),Controller 中会根据 deploy 属性生成对应的 Deployment:

type AppSpec struct {
	// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
	// Important: Run "make" to regenerate code after modifying this file
	Deploy appsv1.DeploymentSpec `json:"deploy,omitempty"`
}

在完善了 AppSpec 和 Controller 的 Reconcile 函数后,使 Kubebuilder 重新生成代码,并将 config/crd 下的 CRD yaml 应用到当前集群:

make
make install

创建 Webhook Server

接下来就是用 Kubebuilder 来生成 Webhooks 了:

kubebuilder create webhook --group app --version v1 --kind App 

在路径 api/v1 下生成了一个名为 app_webhook.go 的文件。可以看到 Kubebuilder 已经帮你定义了两个变量:

var _ webhook.Defaulter = &App{}
var _ webhook.Validator = &App{}

这两个变量分别表示 MutatingWebhookServer 和 ValidatingWebhookServer,在程序启动的时候,这两个 Server 会 run 起来。

对于 MutatingWebhookServer,Kubebuilder 预留了 Default() 函数,让用户来填写自己的逻辑:

// Default implements webhook.Defaulter so a webhook will be registered for the type
func (r *App) Default() {
	applog.Info("default", "name", r.Name)

	// TODO(user): fill in your defaulting logic.
}

对于我们希望 Webhook 在资源发生什么样的变化时触发,可以通过这条注释修改:

// +kubebuilder:webhook:path=/mutate-app-o0w0o-cn-v1-app,mutating=true,failurePolicy=fail,groups=app.o0w0o.cn,resources=apps,verbs=create;update,versions=v1,name=mapp.kb.io

对应的参数为:

  • failurePolicy:表示 ApiServer 无法与 webhook server 通信时的失败策略,取值为 "ignore" 或 "fail";
  • groups:表示这个 webhook 在哪个 Api Group 下会收到请求;
  • mutating:这个参数是个 bool 型,表示是否是 mutating 类型;
  • name:webhook 的名字,需要与 configuration 中对应;
  • path:webhook 的 path;
  • resources:表示这个 webhook 在哪个资源发生变化时会收到请求;
  • verbs:表示这个 webhook 在资源发生哪种变化时会收到请求,取值为 “create“, "update", "delete", "connect", 或 "*" (即所有);
  • versions:表示这个 webhook 在资源的哪个 version 发生变化时会收到请求;

对于 ValidatingWebhookServer,Kubebuilder 的处理与 MutatingWebhookServer 一致,这里不再赘述。

方便起见,我只定义了 MutatingWebhookServer 的 Default 函数,为每个 App 类型资源的 pod 注入一个 nginx sidecar 容器:

func (r *App) Default() {
	applog.Info("default", "name", r.Name)
	var cns []core.Container
	cns = r.Spec.Deploy.Template.Spec.Containers

	container := core.Container{
		Name:  "sidecar-nginx",
		Image: "nginx:1.12.2",
	}

	cns = append(cns, container)
	r.Spec.Deploy.Template.Spec.Containers = cns
}

运行 Webhook Server

首先需要将 MutatingWebhookConfiguration 稍作修改,使得 ApiServer 能够与 Webhook Server 通信。具体方法如下:

配置 Server Path

本文仅分享本地开发测试的调试方案,线上部署方案请参考官方文档

第一步,配置 Server Path;将 service 去掉,换成 url: https://<server_ip>:9443/mutate-app-o0w0o-cn-v1-app ,其中 server_ip 是 Webhook Server 的 ip,如果运行在本地,就是本地的 ip。需要注意的是 url 中的 path 要与 app_webhook.go 中定义的保持一致。

配置证书

第二步,配置 caBundle;由于在 Kube 里,所有与 ApiServer 交互的组件都需要与 ApiServer 进行双向 TLS 认证,我们这里需要先手动签发自签名 CA 证书:

$ openssl genrsa -out ca.key 2048
$ openssl req -x509 -new -nodes -key ca.key -subj "/CN=<server_ip>" -days 10000 -out ca.crt
$ openssl genrsa -out server.key 2048
$ cat << EOF >csr.conf
> [ req ]
> default_bits = 2048
> prompt = no
> default_md = sha256
> req_extensions = req_ext
> distinguished_name = dn
> 
> [ dn ]
> C = <country>
> ST = <state>
> L = <city>
> O = <organization>
> OU = <organization unit>
> CN = <server_ip>
> 
> [ req_ext ]
> subjectAltName = @alt_names
> 
> [ alt_names ]
> IP.1 = <server_ip>
> 
> [ v3_ext ]
> authorityKeyIdentifier=keyid,issuer:always
> basicConstraints=CA:FALSE
> keyUsage=keyEncipherment,dataEncipherment
> extendedKeyUsage=serverAuth,clientAuth
> subjectAltName=@alt_names
> EOF
$ openssl req -new -key server.key -out server.csr -config csr.conf
$ openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 10000 -extensions v3_ext -extfile csr.conf

证书生成后将 server.keyserver.crt 拷贝到 Kubebuilder 设置的 webhook server 的私钥和证书路径下:

webhook server 的私钥路径:$(TMPDIR)/k8s-webhook-server/serving-certs/tls.key webhook server 的证书路径:$(TMPDIR)/k8s-webhook-server/serving-certs/tls.crt

注:如果 $(TMPDIR) 为空,则默认路径为 "/tmp/k8s-webhook-server/...",但 android 系统默认路径为 "/data/local/tmp/k8s-webhook-server/..."

而 MutatingWebhookConfiguration 中的 caBundle 为 ca.crt 的 base64 编码结果。最终 yaml 结果为:

apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
  creationTimestamp: null
  name: mutating-webhook-configuration
webhooks:
- clientConfig:
  caBundle: LS0tLS1CRUdJTiBDRVJ...FLS0tLS0=
  url: https://<server_ip>:9443/mutate-app-o0w0o-cn-v1-app
  failurePolicy: Fail
  name: mapp.kb.io
  rules:
    ...

ValidatingWebhookConfiguration 的修改与 MutatingWebhookConfiguration 类似,只需要注意 server path 与 app_webhook.go 中一致即可。两个配置文件都修改好之后在集群中 apply 一下即可。

运行

最后直接在本地运行 CRD Controller 及 Webhook Server:

make run

验证

简单运行一个 app 试试:

apiVersion: app.o0w0o.cn/v1
kind: App
metadata:
  name: app-sample
spec:
  deploy:
    selector:
      matchLabels:
        app: app-sample
    template:
      metadata:
        name: sample
        labels:
          app: app-sample
      spec:
        containers:
          - name: cn
            image: daocloud.io/library/redis:4.0.14-alpine

查看是否已经注入了 sidecar 容器:

$ kubectl apply -f config/samples/app_v1_app.yaml
$ kubectl get app
NAME         AGE
app-sample   43s
$ kubectl get deploy
NAME                READY   UP-TO-DATE   AVAILABLE   AGE
app-sample-deploy   0/1     1            0           43s
$ kubectl get po
NAME                                 READY   STATUS              RESTARTS   AGE
app-sample-deploy-5b5cfb9c9b-z8jk5   0/2     ContainerCreating   0          43s
You can’t perform that action at this time.