欢送拜访我的 GitHub
https://github.com/zq2599/blog_demos
内容:所有原创文章分类汇总及配套源码,波及 Java、Docker、Kubernetes、DevOPS 等;
本篇概览
- 本文是《kubebuilder 实战》系列的第七篇,之前的文章咱们实现了一个 Operator 的设计、开发、部署、验证过程,为了让整个过程放弃简洁并且篇幅不收缩,实战中刻意跳过了一个重要的知识点:<font color=”red”>webhook</font>,现在是时候学习它了,这是个很重要的性能;
- 本篇由以下局部形成:
- 介绍 webhook;
- 联合后面的 elasticweb 我的项目,设计一个应用 webhook 的场景;
- 筹备工作
- 生成 webhook
- 开发(配置)
- 开发(编码)
- 部署
- 验证 Defaulter(增加默认值)
- 验证 Validator(合法性校验)
对于 webhook
- 相熟 java 开发的读者大多晓得过滤器(Servlet Filter),如下图,内部申请会先达到过滤器,做一些对立的操作,例如转码、校验,而后才由真正的业务逻辑解决申请:
- Operator 中的 webhook,其作用与上述过滤器相似,内部对 CRD 资源的变更,在 Controller 解决之前都会交给 webhook 提前解决,流程如下图,该图来自《Getting Started with Kubernetes | Operator and Operator Framework》:
- 再来看看 webhook 具体做了哪些事件,如下图,kubernetes 官网博客明确指出 webhook 能够做两件事:批改 (mutating) 和验证(validating)
- kubebuilder 为咱们提供了生成 webhook 的根底文件和代码的工具,与制作 API 的工具相似,极大地简化了工作量,咱们只需聚焦业务实现即可;
- 基于 kubebuilder 制作的 webhook 和 controller,如果是同一个资源,那么 <font color=”red”> 它们在同一个过程中 </font>;
设计实战场景
- 为了让实战有意义,咱们为后面的 elasticweb 我的项目上减少需要,让 webhook 施展理论作用;
- 如果用户遗记输出总 QPS,零碎 webhook 负责设置默认值 <font color=”red”>1300</font>,操作如下图:
- 为了爱护零碎,给单个 pod 的 QPS 设置下限 <font color=”red”>1000</font>,如果内部输出的 singlePodQPS 值超过 1000,<font color=”blue”> 就创立资源对象失败 </font>,如下图所示:
源码下载
- 本篇实战中的残缺源码可在 GitHub 下载到,地址和链接信息如下表所示(https://github.com/zq2599/blo…:
名称 | 链接 | 备注 |
---|---|---|
我的项目主页 | https://github.com/zq2599/blo… | 该我的项目在 GitHub 上的主页 |
git 仓库地址(https) | https://github.com/zq2599/blo… | 该我的项目源码的仓库地址,https 协定 |
git 仓库地址(ssh) | mailto:git@github.com:zq2599/blog_demos.git | 该我的项目源码的仓库地址,ssh 协定 |
- 这个 git 我的项目中有多个文件夹,kubebuilder 相干的利用在 <font color=”blue”>kubebuilder</font> 文件夹下,如下图红框所示:
- kubebuilder 文件夹下有多个子文件夹,本篇对应的源码在 <font color=”blue”>elasticweb</font> 目录下,如下图红框所示:
筹备工作
- 和 controller 相似,webhook 既能在 kubernetes 环境中运行,也能在 kubernetes 环境之外运行;
- 如果 webhook 在 kubernetes 环境之外运行,是有些麻烦的,须要将证书放在所在环境,默认地址是:
/tmp/k8s-webhook-server/serving-certs/tls.{crt,key}
- 为了省事儿,也为了更靠近生产环境的用法,接下来的实战的做法是 <font color=”red”> 将 webhook 部署在 kubernetes 环境中 </font>
- 为了让 webhook 在 kubernetes 环境中运行,咱们要做一点筹备工作 <font color=”blue”> 装置 cert manager</font>,执行以下操作:
-
kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.2.0/cert-manager.yaml
- 上述操作实现后会新建很多资源,如 namespace、rbac、pod 等,以 pod 为例如下:
[root@hedy ~]# kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
cert-manager cert-manager-6588898cb4-nvnz8 1/1 Running 1 5d14h
cert-manager cert-manager-cainjector-7bcbdbd99f-q645r 1/1 Running 1 5d14h
cert-manager cert-manager-webhook-5fd9f9dd86-98tm9 1/1 Running 1 5d14h
...
- 操作实现后,筹备工作完结,能够开始实战了;
生成 webhook
- 进入 elasticweb 工程下,执行以下命令创立 webhook:
kubebuilder create webhook \
--group elasticweb \
--version v1 \
--kind ElasticWeb \
--defaulting \
--programmatic-validation
- 上述命令执行结束后,先去看看 <font color=”blue”>main.go</font> 文件,如下图红框 1 所示,主动减少了一段代码,作用是让 webhook 失效:
- 上图红框 2 中的 <font color=”blue”>elasticweb_webhook.go</font> 就是新增文件,内容如下:
package v1
import (
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)
// log is for logging in this package.
var elasticweblog = logf.Log.WithName("elasticweb-resource")
func (r *ElasticWeb) SetupWebhookWithManager(mgr ctrl.Manager) error {return ctrl.NewWebhookManagedBy(mgr).
For(r).
Complete()}
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// +kubebuilder:webhook:path=/mutate-elasticweb-com-bolingcavalry-v1-elasticweb,mutating=true,failurePolicy=fail,groups=elasticweb.com.bolingcavalry,resources=elasticwebs,verbs=create;update,versions=v1,name=melasticweb.kb.io
var _ webhook.Defaulter = &ElasticWeb{}
// Default implements webhook.Defaulter so a webhook will be registered for the type
func (r *ElasticWeb) Default() {elasticweblog.Info("default", "name", r.Name)
// TODO(user): fill in your defaulting logic.
}
// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
// +kubebuilder:webhook:verbs=create;update,path=/validate-elasticweb-com-bolingcavalry-v1-elasticweb,mutating=false,failurePolicy=fail,groups=elasticweb.com.bolingcavalry,resources=elasticwebs,versions=v1,name=velasticweb.kb.io
var _ webhook.Validator = &ElasticWeb{}
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *ElasticWeb) ValidateCreate() error {elasticweblog.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 *ElasticWeb) ValidateUpdate(old runtime.Object) error {elasticweblog.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 *ElasticWeb) ValidateDelete() error {elasticweblog.Info("validate delete", "name", r.Name)
// TODO(user): fill in your validation logic upon object deletion.
return nil
}
- 上述代码有两处须要留神,第一处和填写默认值无关,如下图:
- 第二处和校验无关,如下图:
- 咱们要实现的业务需要就是通过批改上述 <font color=”blue”>elasticweb_webhook.go</font> 的内容来实现,不过代码稍后再写,先把配置都改好;
开发(配置)
- 关上文件 <font color=”blue”>config/default/kustomization.yaml</font>,下图四个红框中的内容本来都被正文了,当初请将正文符号都删掉,使其失效:
- 还是文件 <font color=”blue”>config/default/kustomization.yaml</font>,节点 <font color=”red”>vars</font> 上面的内容,本来全副被正文了,当初请全副放开,放开后的成果如下图:
- 配置曾经实现,能够编码了;
开发(编码)
- 关上文件 <font color=”blue”>elasticweb_webhook.go</font>
- 新增依赖:
apierrors "k8s.io/apimachinery/pkg/api/errors"
- 找到 <font color=”blue”>Default</font> 办法,改成如下内容,可见代码很简略,判断 TotalQPS 是否存在,若不存在就写入默认值,另外还加了两行日志:
func (r *ElasticWeb) Default() {elasticweblog.Info("default", "name", r.Name)
// TODO(user): fill in your defaulting logic.
// 如果创立的时候没有输出总 QPS,就设置个默认值
if r.Spec.TotalQPS == nil {r.Spec.TotalQPS = new(int32)
*r.Spec.TotalQPS = 1300
elasticweblog.Info("a. TotalQPS is nil, set default value now", "TotalQPS", *r.Spec.TotalQPS)
} else {elasticweblog.Info("b. TotalQPS exists", "TotalQPS", *r.Spec.TotalQPS)
}
}
- 接下来开发校验性能,咱们把校验性能封装成一个 validateElasticWeb 办法,而后在新增和批改的时候各调用一次,如下,可见最终是调用 <font color=”blue”>apierrors.NewInvalid</font> 生成谬误实例的,而此办法承受的是多个谬误,因而要为其筹备切片做入参,当然了,如果是多个参数校验失败,能够都放入切片中:
func (r *ElasticWeb) validateElasticWeb() error {
var allErrs field.ErrorList
if *r.Spec.SinglePodQPS > 1000 {elasticweblog.Info("c. Invalid SinglePodQPS")
err := field.Invalid(field.NewPath("spec").Child("singlePodQPS"),
*r.Spec.SinglePodQPS,
"d. must be less than 1000")
allErrs = append(allErrs, err)
return apierrors.NewInvalid(schema.GroupKind{Group: "elasticweb.com.bolingcavalry", Kind: "ElasticWeb"},
r.Name,
allErrs)
} else {elasticweblog.Info("e. SinglePodQPS is valid")
return nil
}
}
- 再找到新增和批改资源对象时被调用的办法,在外面调用 validateElasticWeb:
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *ElasticWeb) ValidateCreate() error {elasticweblog.Info("validate create", "name", r.Name)
// TODO(user): fill in your validation logic upon object creation.
return r.validateElasticWeb()}
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (r *ElasticWeb) ValidateUpdate(old runtime.Object) error {elasticweblog.Info("validate update", "name", r.Name)
// TODO(user): fill in your validation logic upon object update.
return r.validateElasticWeb()}
- 编码实现,可见非常简单,接下来,咱们把以前实战遗留的货色清理一下,再开始新的部署和验证;
清理工作
- 如果您是随着《kubebuilder 实战》系列一路操作下来,此时零碎上应该积攒了之前遗留的内容,能够通过以下步骤实现清理:
- 删除 elasticweb 资源对象:
kubectl delete -f config/samples/elasticweb_v1_elasticweb.yaml
- 删除 controller
kustomize build config/default | kubectl delete -f -
- 删除 CRD
make uninstall
- 当初万事俱备,能够部署 webhook 了;
部署
- 部署 CRD
make install
- 构建镜像并推送到仓库(我终于受够了 <font color=”blue”>hub.docker.com</font> 的龟速,改为阿里云镜像仓库):
make docker-build docker-push IMG=registry.cn-hangzhou.aliyuncs.com/bolingcavalry/elasticweb:001
- 部署集成了 webhook 性能的 controller:
make deploy IMG=registry.cn-hangzhou.aliyuncs.com/bolingcavalry/elasticweb:001
- 查看 pod,确认启动胜利:
zhaoqin@zhaoqindeMBP-2 ~ % kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS RESTARTS AGE
cert-manager cert-manager-6588898cb4-nvnz8 1/1 Running 1 5d21h
cert-manager cert-manager-cainjector-7bcbdbd99f-q645r 1/1 Running 1 5d21h
cert-manager cert-manager-webhook-5fd9f9dd86-98tm9 1/1 Running 1 5d21h
elasticweb-system elasticweb-controller-manager-7dcbfd4675-898gb 2/2 Running 0 20s
验证 Defaulter(增加默认值)
- 批改文件 <font color=”blue”>config/samples/elasticweb_v1_elasticweb.yaml</font>,批改后的内容如下,可见 <font color=”blue”>totalQPS</font> 字段曾经被正文掉了:
apiVersion: v1
kind: Namespace
metadata:
name: dev
labels:
name: dev
---
apiVersion: elasticweb.com.bolingcavalry/v1
kind: ElasticWeb
metadata:
namespace: dev
name: elasticweb-sample
spec:
# Add fields here
image: tomcat:8.0.18-jre8
port: 30003
singlePodQPS: 500
# totalQPS: 600
- 创立一个 elasticweb 资源对象:
kubectl apply -f config/samples/elasticweb_v1_elasticweb.yaml
- 此时单个 pod 的 QPS 是 500,如果 webhook 的代码失效的话,总 QPS 就是 1300,而对应的 pod 数应该是 3 个,接下来咱们看看是否合乎预期;
- 先看 elasticweb、deployment、pod 等资源对象是否失常,如下所示,全副合乎预期:
zhaoqin@zhaoqindeMBP-2 ~ % kubectl get elasticweb -n dev
NAME AGE
elasticweb-sample 89s
zhaoqin@zhaoqindeMBP-2 ~ % kubectl get deployments -n dev
NAME READY UP-TO-DATE AVAILABLE AGE
elasticweb-sample 3/3 3 3 98s
zhaoqin@zhaoqindeMBP-2 ~ % kubectl get service -n dev
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
elasticweb-sample NodePort 10.105.125.125 <none> 8080:30003/TCP 106s
zhaoqin@zhaoqindeMBP-2 ~ % kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
elasticweb-sample-56fc5848b7-5tkxw 1/1 Running 0 113s
elasticweb-sample-56fc5848b7-blkzg 1/1 Running 0 113s
elasticweb-sample-56fc5848b7-pd7jg 1/1 Running 0 113s
- 用 <font color=”blue”>kubectl describe</font> 命令查看 elasticweb 资源对象的详情,如下所示,TotalQPS 字段被 webhook 设置为 1300,RealQPS 也计算正确:
zhaoqin@zhaoqindeMBP-2 ~ % kubectl describe elasticweb elasticweb-sample -n dev
Name: elasticweb-sample
Namespace: dev
Labels: <none>
Annotations: <none>
API Version: elasticweb.com.bolingcavalry/v1
Kind: ElasticWeb
Metadata:
Creation Timestamp: 2021-02-27T16:07:34Z
Generation: 2
Managed Fields:
API Version: elasticweb.com.bolingcavalry/v1
Fields Type: FieldsV1
fieldsV1:
f:metadata:
f:annotations:
.:
f:kubectl.kubernetes.io/last-applied-configuration:
f:spec:
.:
f:image:
f:port:
f:singlePodQPS:
Manager: kubectl-client-side-apply
Operation: Update
Time: 2021-02-27T16:07:34Z
API Version: elasticweb.com.bolingcavalry/v1
Fields Type: FieldsV1
fieldsV1:
f:status:
f:realQPS:
Manager: manager
Operation: Update
Time: 2021-02-27T16:07:34Z
Resource Version: 687628
UID: 703de111-d859-4cd2-b3c4-1d201fb7bd7d
Spec:
Image: tomcat:8.0.18-jre8
Port: 30003
Single Pod QPS: 500
Total QPS: 1300
Status:
Real QPS: 1500
Events: <none>
- 再来看看 controller 的日志,其中的 webhook 局部是否合乎预期,如下图红框所示,发现 TotalQPS 字段为空,就将设置为默认值,并且在检测的时候 SinglePodQPS 的值也没有超过 1000:
- 最初别忘了用浏览器验证 web 服务是否失常,我这里的残缺地址是:http://192.168.50.75:30003/
- 至此,咱们实现了 webhook 的 Defaulter 验证,接下来验证 Validator
验证 Validator
- 接下来该验证 webhook 的参数校验性能了,先验证批改时的逻辑;
- 编辑文件 config/samples/update_single_pod_qps.yaml,值如下:
spec:
singlePodQPS: 1100
- 用 patch 命令使之失效:
kubectl patch elasticweb elasticweb-sample \
-n dev \
--type merge \
--patch "$(cat config/samples/update_single_pod_qps.yaml)"
- 此时,控制台会输入错误信息:
Error from server (ElasticWeb.elasticweb.com.bolingcavalry "elasticweb-sample" is invalid: spec.singlePodQPS: Invalid value: 1100: d. must be less than 1000): admission webhook "velasticweb.kb.io" denied the request: ElasticWeb.elasticweb.com.bolingcavalry "elasticweb-sample" is invalid: spec.singlePodQPS: Invalid value: 1100: d. must be less than 1000
- 再用 <font color=”blue”>kubectl describe</font> 命令查看 elasticweb 资源对象的详情,如下图红框,仍然是 500,可见 webhook 曾经失效,阻止了谬误的产生:
- 再去看 controller 日志,如下图红框所示,和代码对应上了:
- 接下来再试试 webhook 在新增时候的校验性能;
- 清理后面创立的 elastic 资源对象,执行命令:
kubectl delete -f config/samples/elasticweb_v1_elasticweb.yaml
- 批改文件,如下图红框所示,咱们将 singlePodQPS 的值改为超过 <font color=”red”>1000</font>,看看 webhook 是否能查看到这个谬误,并阻止资源对象的创立:
- 执行以下命令开始创立 elasticweb 资源对象:
kubectl apply -f config/samples/elasticweb_v1_elasticweb.yaml
- 控制台提醒以下信息,蕴含了咱们代码中写入的谬误形容,证实 elasticweb 资源对象创立失败,证实 webhook 的 Validator 性能曾经失效:
namespace/dev created
Error from server (ElasticWeb.elasticweb.com.bolingcavalry "elasticweb-sample" is invalid: spec.singlePodQPS: Invalid value: 1500: d. must be less than 1000): error when creating "config/samples/elasticweb_v1_elasticweb.yaml": admission webhook "velasticweb.kb.io" denied the request: ElasticWeb.elasticweb.com.bolingcavalry "elasticweb-sample" is invalid: spec.singlePodQPS: Invalid value: 1500: d. must be less than 1000
- 不释怀的话执行 kubectl get 命令检查一下,发现空洞无物:
zhaoqin@zhaoqindeMBP-2 elasticweb % kubectl get elasticweb -n dev
No resources found in dev namespace.
zhaoqin@zhaoqindeMBP-2 elasticweb % kubectl get deployments -n dev
No resources found in dev namespace.
zhaoqin@zhaoqindeMBP-2 elasticweb % kubectl get service -n dev
No resources found in dev namespace.
zhaoqin@zhaoqindeMBP-2 elasticweb % kubectl get pod -n dev
No resources found in dev namespace.
- 还要看下 controller 日志,如下图红框所示,合乎预期:
- 至此,operator 的 webhook 的开发、部署、验证咱们就实现了,整个 elasticweb 也算是根本功能齐全,心愿能为您的 operator 开发提供参考;
你不孤独,欣宸原创一路相伴
- Java 系列
- Spring 系列
- Docker 系列
- kubernetes 系列
- 数据库 + 中间件系列
- DevOps 系列
欢送关注公众号:程序员欣宸
微信搜寻「程序员欣宸」,我是欣宸,期待与您一起畅游 Java 世界 …
https://github.com/zq2599/blog_demos