一、简介
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/ann |
当您需要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
原生应用程序变得更加容易,这个过程可能需要深入的、特定于应用程序的操作知识。它通过在后台使用c
现在,我们都知道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
,我们应该在部署operator
之前通过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
注册 webhook
所需的清单:
$ 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/