Kubernetes / Linux Note / 运维笔记

 Kubernetes Admission Webhook Part 1

Einic Yeo · 2月12日 · 2022年 · · · · ·

一、简介

admission webhooks

正如您在上面看到的,发送到 kube-apiserver 的每个请求都会经历几个阶段。第一个是验证和授权,以查看客户端是谁以及它是否可以执行此类操作。

在对象被持久化到 etcd 之前,请求将转到不同的准入控制器。准入控制器是拦截请求并根据其目的对对象执行修改或验证的插件。例如,LimitRanger准入控制器会保证创建对象后不会超出命名空间的资源限制;AlwaysPullImages将图像拉取策略修改为Always. 在撰写本文时,有 40 多个准入控制器

您可能会问,这些与 admission webhook 有什么关系?

在所有这些准入控制器中,有两个控制器称为MutatingAdmissionWebhookValidatingAdmissionWebhook。你猜对了,他们会调用你注册的 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 WebhookValidating Admission Webhook
Can allow or reject request?YesYes
Can modify object?YesNo
Receive object at its final state?No.
May be invoked mutiple times after other mutating admission webhooks.
Yes
Common use casesSidecar injectionRegistry modificationAdd annotations…Ensure resouce are setEnsure label/ann版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!otations are set版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!Ensure no privileged/host level settings are set…

当您需要modifyensure传入的对象满足某些条件时,您可以依靠 admission webhook。除非您有一些需要自己实现的要求,否则上述策略引擎可以涵盖大多数场景。

在详细了解如何编写其中之一之前,让我们先解释一下,它们是什么,我们可以用它们做什么。

准入控制器是一段代码,它在对象持久化之前截取对Kubernetes API服务器的请求,但在请求经过身份验证和授权之后。Kubernetes附带了许多准入插件,您可以查看列表以获取有关它们的更多详细信息。值得注意的是,它们被编译成“kube-apiserver”二进制文件,它们可能只能由集群管理员配置,它们也有自己的逻辑,例如,“NamespaceLifecycle”准入控制器强制执行Namespace终止的在其中创建了新对象,并确保不存在的请求Namespace被拒绝,因此,我们需要一种方法来扩展或增强Kubernetes准入控制器的能力,此时,“动态准入控制器” 应运而生。

版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!

我们都知道Kubernetes默认是高度可配置和可扩展的,这种可扩展性特性是Kubernetes的另一个强大之处。Kubernetes中有很多可扩展的点和组件,如果你想了解更多关于扩展Kubernetes的细节,可以点击这个链接

因此,“动态准入控制器” 就是其中之一。它们可以作为扩展开发并作为运行时配置的webhook运行。因此,有两种特殊的Kubernetes准入控制器负责它们。这些是“ValidatingAdmissionWebhook”和“MutatingAdmissionWebhook”准入控制器。这些是特殊的并且功能是无限的,因为它们本身并不像编译到“kube-apiserver”二进制文件中的任何其他准入插件那样实现任何策略决策逻辑。相反,相应的操作是从集群内运行的服务的REST端点(webhook)获得的。

我认为我们提供了足够的关于它们的信息,所以,这篇文章的目的不是解释它们。有很多关于它们的好文档。我将在这篇文章的参考部分中删除其中大多数的链接。

要开始编写自己的admission webhook可能既乏味又困难。因为它应该通过TLSAPI 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版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!ontroller-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来处理我们的webhookTLS管理,因此,我们也将在我们的集群中安装一个cert-manager

cert-manager是一个原生的Kubernetes证书管理控制器。它可以帮助从各种来源颁发证书,例如Let's EncryptHashiCorp VaultVenafi、简单的签名密钥对或自签名。

我们走吧!

先决条件

  • 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官方页面中关于创建基于Gooperator快速入门指南

让我们从创建将保存我们的项目代码库的文件夹开始。

$ 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大量使用KustomizeMakefile来管理构建、推送镜像、部署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”文件,您应该注意到为我们生成了一些样板代码来实现MutatingValidating 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 是否正常工版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!作,我们应该应用我们的 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(例如DeploymentPod等)创建webhook。但我们可以通过以下指南做到这一点。所以,在这篇博文的second part,我将向你展示我们这次如何使用kubebuilder来实现这种针对核心类型(如DeploymentPod等)的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/

0 条回应