controller-runtime 实现准入 webhook

Posted by Luffyao on Sunday, May 17, 2020

前言

上一篇 client-go 实现准入 webhook 讲的是直接用 client-go 去编写 webhook server. 这篇将讲述如何使用 controller-runtime 快速的写一个 webhook server.

编写 webhook server

这次是直接用 controller-runtime 的 webhook, 自己写一个 handle 处理逻辑就好了,所以实现起来也是比 client-go 版本更加简单明了。下面贴出主要处理逻辑,完整代码可参考 validating handler.

// ValidatingHandler validates deployment,scale
type ValidatingHandler struct {
    Client      client.Client
    decoder     *admission.Decoder
    serviceName string
}

func NewValidatingHandler(c client.Client, servicename string) *ValidatingHandler {
    return &ValidatingHandler{
        Client:      c,
        serviceName: servicename,
    }
}

func (vh *ValidatingHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
    switch req.Kind.Kind {
    case deploymentKind:
        isAllow := false
        if isAllow {
            return admission.Allowed("")
        }
        return admission.Denied(fmt.Sprintf("The %s service doesn't allow update", req.Name))
    case scaleKind:
        isAllow := false
        if isAllow {
            return admission.Allowed("")
        }
        return admission.Denied(fmt.Sprintf("The %s service doesn't allow update", req.Name))
    }

    return admission.Allowed("")
}

// InjectDecoder injects the decoder.
func (vh *ValidatingHandler) InjectDecoder(d *admission.Decoder) error {
    vh.decoder = d
    return nil
}

然后 你就可以直接在 main 函数中像下面这个调用即可。完整代码请参考 main.go.

    mgr, err := manager.New(config.GetConfigOrDie(), manager.Options{
        Port:    *webhookServerPort,
        CertDir: *certPath,
    })
    if err != nil {
        panic(err.Error())
    }

    hookServer := mgr.GetWebhookServer()
    hookServer.Register("/validate", &webhook.Admission{
        Handler: webhookserver.NewValidatingHandler(mgr.GetClient(), *serviceName),
    })

    if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
        panic(err.Error())
    }
}

编译代码并重新生成 docker image

可以参考我提供的简单 build.sh 脚本编译代码和使用 buildimage.sh 脚本去构建 docker 镜像。

NOTE: buildimage.sh 脚本中,我做了些处理,首先需要传入 docker_user(如果你没有,你也可以随意指定一个即可)和构建的 image 的 tag。然后我注释掉了 docker push 这个命令,防止有些读者还没有 docker hub 或其他仓库的账号。

添加相应的 k8s 资源

安装相应的 k8s 资源即可,具体请参考 resources.

NOTE: 这里我为了方便管理这些资源,我使用了 helm 去管理了,如果你有 helm 相关的环境,你可直接使用 helm install 命令安装资源,如果你没有或者不熟悉,你可以使用 kubectl apply 命令一个个的部署,但是你需要修改资源中的 Release.Namespace 到你安装的那个 namespace 下,例如如果你安装时没有指定 namespace, 则默认是 default。

测试

例子中 default 的实现是拒绝任何对 deployment 和 scale 的修改。所以可以使用 kubectl edit 命令修改 deployment,或者使用 kubectl scale 命令修改 replicas。如果命令修改失败并提示"The %s service doesn’t allow update",则说明自定义的 validating webhook 是可以工作的。然后可以加上自己的处理逻辑即可。

参考