一、简介
admission webhooks

正如您在上面看到的,发送到 kube-apiserver 的每个请求都会经历几个阶段。第一个是验证和授权,以查看客户端是谁以及它是否可以执行此类操作。
在对象被持久化到 etcd 之前,请求将转到不同的准入控制器。准入控制器是拦截请求并根据其目的对对象执行修改或验证的插件。例如,LimitRanger
准入控制器会保证创建对象后不会超出命名空间的资源限制;AlwaysPullImages
将图像拉取策略修改为Always
. 在撰写本文时,有 40 多个准入控制器。
您可能会问,这些与 admission webhook 有什么关系?
在所有这些准入控制器中,有两个控制器称为MutatingAdmissionWebhook
和ValidatingAdmissionWebhook
。你猜对了,他们会调用你注册的 HTTP webhook。
MutatingAdmissionWebhook
位于上图中的变异阶段。它可以允许或拒绝此请求,并且可以另外改变请求。ValidatingAdmissionWebhook
而是处于验证阶段。此阶段的准入控制器只能“验证”请求并决定允许或拒绝它们。此阶段的对象处于其最终状态,这意味着它们之后不会改变。
Which applications use admission webhooks
There are actually plenty of apps that use this mechanism, such as:
- Sidecar injection:
- Istio uses a mutating admission webhook to inject its sidecar.
- Policy Engines: This allows you to write policies to make sure everything in the cluster is compliant.
- ingress-nginx also uses validating webhooks to ensure config’s correctness.
- …and many, many others.
TL; DR
Mutating Admission Webhook | Validating Admission Webhook | |
---|---|---|
Can allow or reject request? | Yes | Yes |
Can modify object? | Yes | No |
Receive object at its final state? | No. May be invoked mutiple times after other mutating admission webhooks. | Yes |
Common use cases | Sidecar injectionRegistry modificationAdd annotations… | Ensure resouce are setEnsure label/annotations are setEnsure no privileged/host level settings are set… |
当您需要modify 与 ensure传入的对象满足某些条件时,您可以依靠 admission webhook。除非您有一些需要自己实现的要求,否则上述策略引擎可以涵盖大多数场景。
在详细了解如何编写其中之一之前,让我们先解释一下,它们是什么,我们可以用它们做什么。
准入控制器是一段代码,它在对象持久化之前截取对Kubernetes API
服务器的请求,但在请求经过身份验证和授权之后。Kubernetes
附带了许多准入插件,您可以查看列表以获取有关它们的更多详细信息。值得注意的是,它们被编译成“kube-apiserver
”二进制文件,它们可能只能由集群管理员配置,它们也有自己的逻辑,例如,“NamespaceLifecycle
”准入控制器强制执行Namespace
终止的在其中创建了新对象,并确保不存在的请求Namespace
被拒绝,因此,我们需要一种方法来扩展或增强Kubernetes
准入控制器的能力,此时,“动态准入控制器” 应运而生。
我们都知道Kubernetes
默认是高度可配置和可扩展的,这种可扩展性特性是Kubernetes
的另一个强大之处。Kubernetes
中有很多可扩展的点和组件,如果你想了解更多关于扩展Kubernetes
的细节,可以点击这个链接。
因此,“动态准入控制器” 就是其中之一。它们可以作为扩展开发并作为运行时配置的webhook
运行。因此,有两种特殊的Kubernetes
准入控制器负责它们。这些是“ValidatingAdmissionWebhook
”和“MutatingAdmissionWebhook
”准入控制器。这些是特殊的并且功能是无限的,因为它们本身并不像编译到“kube-apiserver
”二进制文件中的任何其他准入插件那样实现任何策略决策逻辑。相反,相应的操作是从集群内运行的服务的REST
端点(webhook
)获得的。
我认为我们提供了足够的关于它们的信息,所以,这篇文章的目的不是解释它们。有很多关于它们的好文档。我将在这篇文章的参考部分中删除其中大多数的链接。
要开始编写自己的admission webhook
可能既乏味又困难。因为它应该通过TLS
与API Server
通信,所以webhook
处理某种TLS
管理。此外,我们应该将我们的webhook
注册到集群。为了做到这一点,我们应该管理一个额外的Kubernetes
资源,称为“MutatingWebhookConfiguration
” 或“ValidatingWebhookConfiguration
”。
简而言之,我们的 webhook
只是一个普通的 HTTP
服务器,我们可以用 Go
开发并实现自己的逻辑,在端点后面公开这个逻辑,并将自己注册为 Mutating
或 Validating webhook
。
开始编写自己的admission webhook
的最简单方法是使用某种项目脚手架工具,例如“Kubebuilder
”或“Operator SDK
”。我们可能熟悉这些编写Kubernetes Operators
的工具,但它们也是编写admission webhook
的不错选择。本次demo我们将使用Operator SDK
,所以,让我们简单介绍一下这个工具。
Operator SDK
用于构建Kubernetes
应用程序。它提供高级API
、有用的抽象和项目脚手架。Operator SDK
使构建Kubernetes
原生应用程序变得更加容易,这个过程可能需要深入的、特定于应用程序的操作知识。它通过在后台使用controller-runtime来完成所有事情。
现在,我们都知道admission webhooks
是什么,以及Operator SDK
是什么了。所以,让我们跳到demo部分来制作我们的脚手架。
二、Demo 实践
在这个demo中,我们将开发“MutatingAdmissionWebhook
”来检查我们的Memcached
自定义资源的大小,然后,如果大小等于0
,我们将默认设置为3
。但是在创建我们的webhook
之前,我们应该先创建我们的Memcached Operator
,所以,在这个demo中,我们将创建一个operator
,然后,我们将为该operator
管理的自定义资源创建一个webhook
。
Operator SDK
将使用cert-manager CA injector
来处理我们的webhook
的TLS
管理,因此,我们也将在我们的集群中安装一个cert-manager
。
cert-manager
是一个原生的Kubernetes
证书管理控制器。它可以帮助从各种来源颁发证书,例如Let's Encrypt
、HashiCorp Vault
、Venafi
、简单的签名密钥对或自签名。
我们走吧!
先决条件
- Minikube v1.18.1
- Operator SDK v1.5.0
- kubectl v1.20.5
- Go v1.16.3
我将在macOS
环境中进行此demo,因此,您可以使用macOS
的包管理器“brew
”来安装上述所有工具。
三、Memcached operator
Kubernetes Operator
是一个特定于应用程序的控制器,它扩展了Kubernetes API
的功能,以代表Kubernetes
用户创建、配置和管理复杂应用程序的实例。
Kubernetes Operator
针对它创建的自定义资源实现自己的control-loop。自定义资源是Kubernetes
中的API
扩展机制。自定CRD
) 定义CR
并列出operator
用户可用的所有配置。
我们将遵循Redhat
官方页面中关于创建基于Go
的operator
的快速入门指南。
让我们从创建将保存我们的项目代码库的文件夹开始。
$ mkdir -p memcached-operator
$ cd memcached-operator
然后,通过 Operator SDK init
命令初始化项目。
$ operator-sdk init --repo=github.com/developer-guy/memcached-operator
Writing scaffold for you to edit…
Get controller runtime:
$ go get sigs.k8s.io/[email protected]
Update dependencies:
$ go mod tidy
Next: define a resource with:
$ operator-sdk create api
# Let's look at the folder structure after the project initialized, you should see similar output like the following:
$ tree -L 2 .
.
├── Dockerfile
├── Makefile
├── PROJECT
├── config
│ ├── certmanager
│ ├── default
│ ├── manager
│ ├── prometheus
│ ├── rbac
│ └── scorecard
├── go.mod
├── go.sum
├── hack
│ └── boilerplate.go.txt
└── main.go
项目初始化后,我们应该创建自己的API
来管理自定义资源,在这种情况下是Memcached API
。
# Let's watch the filesystem changes by using fswatch command utility before running "operator-sdk create api" command.
$ fswatch . | xargs -n 1 -I {} echo {}
...
...
...
# Open a second terminal and run this command below, and watch changes on the filesystem through first terminal
$ operator-sdk create api --group cache --version v1 --kind Memcached --resource=true --controller=true
Writing scaffold for you to edit...
api/v1/memcached_types.go
controllers/memcached_controller.go
Update dependencies:
$ go mod tidy
Running make:
$ make generate
go: creating new go.mod: module tmp
Downloading sigs.k8s.io/controller-tools/cmd/[email protected]
go get: added sigs.k8s.io/controller-tools v0.4.1
/Users/batuhan.apaydin/workspace/projects/personal/poc/operator-sdk-examples/memcached-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
Operator SDK
大量使用Kustomize
和Makefile
来管理构建、推送镜像、部署Operator
和生成清单等操作。所以,如果你需要对项目做一些事情,你应该总是参考Makefile
目标,例如,如果你想构建和推送你的项目镜像,你可以在Makefile
中运行“docker-build docker-push
”目标,或者如果您想对您的Memcache CR
进行一些更改,例如通过更改“api/memcached_types.go
”中的代码来添加或更新字段,您也应该运行“make
”来更新自定义资源定义规范。
让我们构建并推送我们项目的镜像。
# don't forget to add IMG variable, it should specify your DockerHub user id, mine is devopps.
$ make docker-build docker-push IMG=devopps/memcached-operator:v1
...
...
...
The push refers to repository [docker.io/devopps/memcached-operator]
7d8ae67ed609: Pushed
1a5ede0c966b: Layer already exists
v1: digest: sha256:9f017578347d1f861bce531e852d2d11293ee961bc072ee00f4c1f019a4bd858 size: 739
# let's check if image successfully pushed
$ crane ls devopps/memcached-operator
v1
$ crane digest devopps/memcached-operator:v1
sha256:9f017578347d1f861bce531e852d2d11293ee961bc072ee00f4c1f019a4bd858 <-- it should be same hash with the hash of docker push command output.
crane
是一个管理容器镜像的工具。它是由谷歌开发的。现在,我们已经准备好部署我们的operator
,我们应该在部署opera
之前通过Minikube
启动我们的本地Kubernetes
集群。
$ minikube start
minikube v1.18.1 on Darwin 10.15.7
✨ Using the virtualbox driver based on user configuration
Starting control plane node minikube in cluster minikube
Creating virtualbox VM (CPUs=3, Memory=8192MB, Disk=20000MB) ...
Preparing Kubernetes v1.20.2 on Docker 20.10.3 ...
▪ Generating certificates and keys ...
▪ Booting up control plane ...
▪ Configuring RBAC rules ...
Verifying Kubernetes components...
▪ Using image gcr.io/k8s-minikube/storage-provisioner:v4
Enabled addons: storage-provisioner, default-storageclass
Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
$ make install
...
customresourcedefinition.apiextensions.k8s.io/memcacheds.cache.my.domain created
# let's check if our custom resource definition created
$ kubectl get customresourcedefinitions.apiextensions.k8s.io
NAME CREATED AT
memcacheds.cache.my.domain 2021-04-05T07:23:44Z
$ make deploy IMG=devopps/memcached-operator:v1
...
namespace/memcached-operator-system created
customresourcedefinition.apiextensions.k8s.io/memcacheds.cache.my.domain configured
serviceaccount/memcached-operator-controller-manager created
role.rbac.authorization.k8s.io/memcached-operator-leader-election-role created
clusterrole.rbac.authorization.k8s.io/memcached-operator-manager-role created
clusterrole.rbac.authorization.k8s.io/memcached-operator-metrics-reader created
clusterrole.rbac.authorization.k8s.io/memcached-operator-proxy-role created
rolebinding.rbac.authorization.k8s.io/memcached-operator-leader-election-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/memcached-operator-manager-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/memcached-operator-proxy-rolebinding created
configmap/memcached-operator-manager-config created
service/memcached-operator-controller-manager-metrics-service created
deployment.apps/memcached-operator-controller-manager created
# check if controller are working on namespace memcached-operator-system
$ kubectl get pods --namespace memcached-operator-system
NAME READY STATUS RESTARTS AGE
memcached-operator-controller-manager-6c9655978-7smfl 2/2 Running 0 72s
让我们通过应用我们的自定义资源清单来测试operator
。
# this is our custom resource
$ cat config/samples/cache_v1_memcached.yaml
apiVersion: cache.my.domain/v1
kind: Memcached
metadata:
name: memcached-sample
spec:
# Add fields here
foo: bar
size: 0
$ kubectl apply -f config/samples/cache_v1_memcached.yaml
memcached.cache.my.domain/memcached-sample created
# now, we can get our CR like any other Kubernetes resource such as Deployment, Pod etc.
$ kubectl get memcacheds.cache.my.domain
NAME AGE
memcached-sample 63s
$ kubectl get memcacheds.cache.my.domain memcached-sample -oyaml
apiVersion: cache.my.domain/v1
kind: Memcached
metadata:
name: memcached-sample
spec:
# Add fields here
foo: bar
size: 0
好的,现在我们已经完成了创建 Kubernetes Operator
的一部分,让我们继续为我们的CR
创建一个 Kubernetes Admission Webhook
。
四、Memcached
MutatingAdmissionWebhook
在本节中,我们将为我们的webhook
生成一个代码模板,然后,我们将在我们的自定义资源中添加一个名为“size
”的新字段,然后,在webhook
逻辑中,我们将检查size
字段的值,如果它等于零,我们将大小更新为三。
让我们为我们的 webhook
生成代码模板
# --defaulting:此标志将为变异 webhook 提供所需的资源
# --programmatic-validation:此标志将搭建验证 webhook 所需的资源
$ operator-sdk create webhook --group cache --version v1 --kind Memcached --defaulting --programmatic-validation
编写脚手架供您编辑...
api/v1/memcached_webhook.go
如果您查看“api/v1/memcached_webhook.go
”文件,您应该注意到为我们生成了一些样板代码来实现Mutating
和Validating webhook
逻辑。
// Default implements webhook.Defaulter so a webhook will be registered for the type
func (r *Memcached) Default() {
memcachedlog.Info("default", "name", r.Name)
// TODO(user): fill in your defaulting logic.
}
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *Memcached) ValidateCreate() error {
memcachedlog.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 *Memcached) ValidateUpdate(old runtime.Object) error {
memcachedlog.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 *Memcached) ValidateDelete() error {
memcachedlog.Info("validate delete", "name", r.Name)
// TODO(user): fill in your validation logic upon object deletion.
return nil
}
这些是我们可以用来实现我们的 webhook
逻辑的上面的函数,但是这里我们只是要编辑Default
函数,因为我们对验证部分不感兴趣。因此,让我们通过添加以下代码来编辑文件:
if r.Spec.Size == 0 {
r.Spec.Size = 3
}
实现我们的 webhook
后,剩下的就是创建向WebhookConfigurationKubernetes
&nbwebhook
所需的清单:
$ make manifests
我们应该在这里做的最后一件事是启用和部署证书管理器,为了做到这一点,我们应该通过取消注释标记的部分和注释来编辑“config/default/kustomization.yaml
” 文件。[WEBHOOK]和[CERTMANAGER]
让我们将install-cert-manager
目标添加到Makefile
中,如下所示以部署cert-manager
:
不要忘记将
jetstack Helm
存储库添加到您的存储库中
$ helm repo add jetstack https://charts.jetstack.io
.PHONY: install-cert-manager ## Deploy cert-manager to the cluster
install-cert-manager:
helm install \
cert-manager jetstack/cert-manager \
--namespace cert-manager \
--version v1.2.0 \
--create-namespace \
--set installCRDs=true
.PHONY: install-cert-manager ## Deploy cert-manager to the cluster
install-cert-manager:
helm install \
cert-manager jetstack/cert-manager \
--namespace cert-manager \
--version v1.2.0 \
--create-namespace \
--set installCRDs=true
那我们安装吧。
$ make install-cert-manager
$ kubectl get pods --namespace cert-manager
NAME READY STATUS RESTARTS AGE
cert-manager-85f9bbcd97-fq8wn 1/1 Running 0 26s
cert-manager-cainjector-74459fcc56-z2h8b 1/1 Running 0 26s
cert-manager-webhook-57d97ccc67-466mq 1/1 Running 0 26s
因为我们更改了代码,所以我们应该再次构建并推送我们的镜像,这次使用v2tag。
$ make docker-build docker-push IMG=devopps/memcached-operator:v2
...
The push refers to repository [docker.io/devopps/memcached-operator]
b09d21af0c50: Pushed
1a5ede0c966b: Layer already exists
v2: digest: sha256:69fbcaaf3bc3bb00c542b47e5eca1e96586c5e22123d2edc0067c4904a73ba8d size: 739
现在,一切都按预期工作,最后一步是部署operator
。
$ make deploy IMG=devopps/memcached-operator:v2
...
namespace/memcached-operator-system unchanged
customresourcedefinition.apiextensions.k8s.io/memcacheds.cache.my.domain configured
serviceaccount/memcached-operator-controller-manager unchanged
role.rbac.authorization.k8s.io/memcached-operator-leader-election-role unchanged
clusterrole.rbac.authorization.k8s.io/memcached-operator-manager-role configured
clusterrole.rbac.authorization.k8s.io/memcached-operator-metrics-reader unchanged
clusterrole.rbac.authorization.k8s.io/memcached-operator-proxy-role unchanged
rolebinding.rbac.authorization.k8s.io/memcached-operator-leader-election-rolebinding unchanged
clusterrolebinding.rbac.authorization.k8s.io/memcached-operator-manager-rolebinding unchanged
clusterrolebinding.rbac.authorization.k8s.io/memcached-operator-proxy-rolebinding unchanged
configmap/memcached-operator-manager-config unchanged
service/memcached-operator-controller-manager-metrics-service unchanged
service/memcached-operator-webhook-service created
deployment.apps/memcached-operator-controller-manager configured
certificate.cert-manager.io/memcached-operator-serving-cert created
issuer.cert-manager.io/memcached-operator-selfsigned-issuer created
mutatingwebhookconfiguration.admissionregistration.k8s.io/memcached-operator-mutating-webhook-configuration created
validatingwebhookconfiguration.admissionregistration.k8s.io/memcached-operator-validating-webhook-configuration created
为了检查 webhook
是否正常工作,我们应该应用我们的 CR
清单,其中 size
字段的值为零,然后,我们应该在创建的对象端看到 size
字段的值为 3
。
$ cat << EOF | k apply -f -
apiVersion: cache.my.domain/v1
kind: Memcached
metadata:
name: memcached-sample
spec:
# Add fields here
foo: bar
size: 0
EOF
memcached.cache.my.domain/memcached-sample created
$ kubectl get memcacheds.cache.my.domain memcached-sample -oyaml | kubectl neat
apiVersion: cache.my.domain/v1
kind: Memcached
metadata:
name: memcached-sample
namespace: default
spec:
foo: bar
size: 3 <-- you should see the size as 3.
是的,我们证明它按预期工作。
五、最后总结
如果不使用这些脚手架工具,我们的生活可能会变得更加艰难。通过使用它们,我们可以轻松快速地创建Kubernetes Admission Webhooks
。
使用Operator SDK
创建webhook
部分的已知限制是我们只能为我们的CR
创建webhook
,因此默认情况下我们不能为现有的Kubernetes
(例如Deployment
、Pod
等)创建webhook
。但我们可以通过以下指南做到这一点。所以,在这篇博文的second part,我将向你展示我们这次如何使用kubebuilder
来实现这种针对核心类型(如Deployment
、Pod
等)的webhook
。
参考文献
https://medium.com/trendyol-tech/getting-started-to-write-your-first-kubernetes-admission-webhook-part-1-623f40c2adda
https://blog.wtcx.dev/2021/05/02/the-making-of-admission-webhooks-part-1-the-concept/