在 controllerruntime 控制器中配置资源 webhook

目录

controllerruntime webhook 接口概述

K8s 支持 mutatingWebhookConfiguration 以及 validatingWebhookConfiguration 资源,前者主要用于配置一些字段的默认值,或者修改某些字段(比如替换镜像),可用在资源的 create 以及 update 阶段;后者主要是执行一些校验,可以校验资源的 create/update/delete 操作。

kubernetes-sigs/controller-runtime 项目中,资源用户自定以 validator 被定义为 CustomValidator 接口。ValidateDelete 看似没什么用,但是可以阻止特定的资源被删除,比如业务 namespace。用户自定义 defaulter 被定义为 CustomDefaulter 接口。

// 需要被校验的 obj 被以参数的形式传递给校验方法
type CustomValidator interface {
	ValidateCreate(ctx context.Context, obj runtime.Object) (warnings Warnings, err error)
	ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (warnings Warnings, err error)
	ValidateDelete(ctx context.Context, obj runtime.Object) (warnings Warnings, err error)
}

type CustomDefaulter interface {
	Default(ctx context.Context, obj runtime.Object) error
}

所以如果我们想实现 MutatingWebhookConfiguration(关注资源的 Create/Update 操作)以及 ValidatingWebhookConfiguration(关注资源的 Create/Update/Delete 操作)只要实现上面四个接口就行,下面具体看下怎么做。

controller 注册 webhook

kubebuilder 官方文档 Implementing defaulting/validating webhooks 中介绍了通过 kubebuilder 生成 webhook 代码的方式,不过在大多数情况下,我们是直接在代码中注册一个 webhook,并且可以注册任意资源类型的 webhook。

假设下面 PodHook 结构体实现上面四个接口,那我们可以通过下面方式注册 webhook。在初始化 Controller Manager 的时候,指定 webhook server 的监听地址为 8082,这里没有指定 cert 地址,根据 kubebuilder 文档 默认地址为 /tmp/k8s-webhook-server/serving-certs,这意味着我们需要将 certificate 对应的 secret 挂载到这个目录,后面会介绍怎么通过 cert-manager 为webhook自动签发证书。

注意,我们在注册地址时,一定要以分隔符 / 开头,要不然虽然能注册成功,但是在访问的时候,总是报 404 错误。

mgr, _ := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
    Scheme:             scheme,
    MetricsBindAddress: ":8090",
    WebhookServer: webhook.NewServer(webhook.Options{
        Port: 8082,
    }),
})
ph := &PodHook{}
mgr.GetWebhookServer().Register("/mutating", admission.WithCustomDefaulter(scheme, &corev1.Pod{}, ph))
mgr.GetWebhookServer().Register("/validating", admission.WithCustomValidator(scheme, &corev1.Pod{}, ph))

自动签发证书

K8s Apiserver 在跟 webhook 通信时,是通过 https 进行的,Apiserver 需要验证 webhook server 证书,因此需要我们提供一个 ca 证书,通过这个证书来验证 webhook server 的cert。我们在 NewServer 的时候,可以手动传入一个证书目录,包含 tls.crt 和 tls.key 两个文件。同时,也必须在 MutatingWebhookConfiguration 中写入一个 caBundle,用来让 Apiserver 验证 tls.crt 以及 tls.key。这个过程非常繁琐,要生成证书,并要 renew 证书。幸好 cert-manager 可以帮我们做这个事情,即通过 cert-manager 自动生成证书。

用 cert-manager 生成证书需要一个本地 pki,具体包括:issuer、certificate。这里用 SelfSigned issuer 就可以,certificate 生成的 secret,我们需要挂载到我们的控制器 deployment 中,挂到默认的目录 /tmp/k8s-webhook-server/serving-certs 就可以。对于 MutatingWebhookConfiguration 配置,需要需要一个额外的 annotation 配置,让 cert-manager 自动找到对应的 certificate 并注入 cabundle:cert-manager.io/inject-ca-from: test/cluster-webhook,其中 test 是证书的命名空间,cluster-webhook 是证书的名字。

关于 selfsigned issuer,默认情况下其签发的证书之间是没有关系的,各个证书是用自己的私钥自签名的,所以叫自签名证书。但是我们可以通过 selfsigned issuer 签发一个 ca certificate(其 isCA 字段为 true),然后再创建一个 selfsigned issuer B,并且在 issuer B 中指定 CA 为 ca certificate。这样就具有一个私有的 ca 了,用 issuer B 签发的证书,就具有相同的 ca。

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: cluster-mutating-webhook
  namespace: test
  annotations:
    cert-manager.io/inject-ca-from: test/cluster-webhook
webhooks:
- admissionReviewVersions:
  - v1
  - v1beta1
  clientConfig:
    service:
      name: cluster-webhook
      namespace: test
      path: /clusters/defaulting
  failurePolicy: Fail
  name: mutating.cluster.test
  rules:
  - apiGroups:
    - core.matrixone-cloud
    apiVersions:
    - v1alpha1
    operations:
    - CREATE
    - UPDATE
    resources:
    - clusters
  sideEffects: None

另外,访问 webhook 的时候遇到 failed to call webhook: Post "https://cluster-webhook.test.svc:443/clusters/validating?timeout=10s": EOF 错误,大概率是程序 panic 了。

配置 webhook 不拦截自身 pod

如果 webhook 拦截自身可能会导致 pod 无法启动。这个因为如果 K8s 创建了 MutatingWebhookConfiguration 资源,但是 pod 还没有启动,此时访问这个 pod 的 service 就会失败。因此我们一般会配置 webhook 不拦截自身。这个是通过资源的 objectSelector 字段配置的,这个字段通过 label 来选择需要拦截的资源;当使用 NotIn operator 时,也可以配置不拦截的资源。比如下面配置表示不拦截含有 app.kubernetes.io/hook-excluded=test-image-hook 标签的资源。

webhooks:
- admissionReviewVersions:
  # ...
  # ...
  sideEffects: None
  timeoutSeconds: 30
  objectSelector:
    matchExpressions:
      - key: app.kubernetes.io/hook-excluded
        operator: NotIn
        values:
          - test-image-hook