K8S CRD、webhook、operator开发
K8s定制化开发完整笔记
一、K8s定制化开发整体概述
K8s核心使用逻辑本质是调用集群内置的各类资源类型,每一种原生资源类型都对应专属控制器,由控制器自动化完成资源的状态维护、生命周期管理。当集群内置资源和管控逻辑无法满足个性化业务需求时,就需要通过定制化开发实现业务适配。
K8s定制化开发主要分为三大核心方向,覆盖全部自定义扩展场景:
1、Operator开发:基于CRD自定义全新资源类型,并开发专属控制器,实现自定义资源的全生命周期管控。本课程仅讲解Operator核心原理与开发流程,后续会通过专属实战项目讲解完整源码开发。
2、Webhook准入开发:拦截K8s API请求,对资源实现动态修改、合规校验,实现请求链路的定制化处理,是集群资源规范化管控的核心扩展手段。
1.1 K8s资源与控制器核心关系
K8s所有资源的管理逻辑可以类比数据库结构,可清晰区分资源类型与具体资源实例的对应关系,理解该关系是掌握K8s定制化开发的基础:
资源类型:等同于数据库的数据表,是资源的模板定义,例如Pod、Deployment、Service都是K8s内置的资源类型,规定了资源的字段、属性、约束规则。
资源实例:等同于数据表中的单条数据记录,用户通过YAML清单创建的具体Pod、Deployment,就是对应资源类型下的实例。
YAML清单的核心作用:仅作为数据声明工具,用于向etcd数据库写入资源配置数据,本身不具备任何资源管控、调度、修复能力。
控制器的核心作用:隐藏在集群后台的逻辑程序,持续监听对应资源的数据变化,根据etcd中的配置数据,自动完成资源创建、更新、删除、状态修复等运维工作,保障资源始终维持用户定义的预期状态。
二、CRD自定义资源机制(Operator开发基础)
2.1 CRD核心概念
CRD全称Custom Resource Define,即自定义资源定义,是K8s1.7版本后正式引入的官方资源扩展机制,核心作用是突破原生资源限制,允许用户自定义全新的资源类型,补齐原生资源的业务短板。
CRD与CR的对应关系:
CRD:自定义资源类型,相当于在etcd数据库中新建一张数据表,定义了新资源的字段结构、属性规则、校验约束,是自定义资源的模板。
CR(自定义资源):基于CRD模板创建的具体资源实例,通过YAML声明资源,将kind字段指定为自定义的CRD名称,即可在etcd中生成一条自定义资源数据记录。
自定义控制器:每一种自定义CRD资源类型,都需要配套专属控制器,负责监听该CRD资源的变更事件,自动化维护资源状态,管控逻辑与K8s原生资源控制器完全一致。
2.2 CRD机制的应用意义
K8s原生内置的Pod、Deployment、StatefulSet等资源,仅能满足通用容器编排需求。面对个性化业务、中间件专属运维、自定义集群管控等特殊场景,原生资源的字段和管控逻辑无法适配。此时通过CRD自定义新资源类型,结合自定义控制器,即可实现业务场景的专属自动化运维能力,让K8s适配各类垂直业务场景。
2.3 CRD完整使用流程
1、编写CRD类型YAML清单并应用,在K8s集群中注册全新的自定义资源类型,完成etcd数据表的创建与注册;
2、基于client-go、kubebuilder等框架,开发对应CRD的自定义控制器,编写专属的资源调谐、生命周期管控逻辑;
3、编写CR实例YAML清单,基于自定义CRD创建具体资源,控制器通过监听机制捕获资源变更,自动执行调谐逻辑,维护资源状态。
完整实操演示:
定义CRD资源清单,新建 crd.yaml 文件,注册自定义mysqlCluster资源类型:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
# 自定义资源名称,格式:资源名.分组名
name: mysqlclusters.egonlin.com
spec:
# 资源分组
group: egonlin.com
# 资源版本
versions:
- name: v1
served: true
storage: true
# 资源作用域,Namespaced为命名空间级别,Cluster为集群级别
scope: Namespaced
# 资源名称配置
names:
kind: MySQLCluster
listKind: MySQLClusterList
singular: mysqlcluster
plural: mysqlclusters
执行命令注册CRD资源:
kubectl apply -f crd.yaml
kubectl get crd
新建CR资源实例文件 cr.yaml,创建自定义mysqlCluster资源:
apiVersion: egonlin.com/v1
kind: MySQLCluster
metadata:
name: test-mysql
namespace: default
spec:
replicas: 1
version: "8.0"
storageSize: 10Gi
执行命令创建自定义资源:
kubectl apply -f cr.yaml
此时集群仅会注册并存储该自定义资源,不会自动创建MySQL容器、服务等相关资源,资源无实际运行效果。核心原因是仅定义了CRD资源模板和CR资源实例,缺少配套的自定义控制器,无法实现资源的自动化管控。后续需要开发对应控制器,完成资源的调谐与运维逻辑。
三、自定义控制器开发框架与底层原理
3.1 三大控制器开发框架层级关系
K8s自定义控制器开发拥有三层递进框架,从底层原生封装到高层一键生成,适配不同开发场景,层级由低到高、封装程度由浅到深:
1、Client-Go:最底层原生开发库,完全基于K8s API原生封装,无多余抽象层,灵活性最高,支持深度定制控制器逻辑,适合极致个性化的开发场景,缺点是开发成本高、需要手写大量底层逻辑。
2、Controller-Runtime:基于Client-Go二次封装的高层框架,屏蔽了底层复杂的informer、队列、监听逻辑,提供标准化控制器开发模板,大幅简化开发流程,适配绝大多数常规自定义控制器开发场景。
3、Kubebuilder:最高层级一体化开发工具链,基于Controller-Runtime构建,支持一键生成CRD模板、控制器代码、部署YAML,无需记忆复杂字段和底层原理,适合快速开发、快速部署上线的场景,是目前企业主流开发方案。
3.2 Client-Go核心Informer机制(底层核心原理)
Informer是Client-Go的核心机制,也是所有K8s自定义控制器、集群组件的底层核心。核心设计目的是避免控制器直接频繁请求API Server和etcd,通过本地缓存机制降低集群压力,提升集群整体性能与稳定性。
Informer整体定义:自带缓存、索引、事件监听机制的客户端库,能够实时监听集群资源变更,自动缓存资源数据、建立索引,同时触发自定义业务处理逻辑。
3.2.1 Informer完整工作流程
上半部分:资源监听与缓存封装流程(框架全自动实现,无需开发者编码)
1、Reflector组件通过List&Watch长连接机制持续监听API Server,实时捕获集群资源的新增、更新、删除变更事件;
2、Reflector捕获变更对象后,通过watchHandler函数将事件数据推入Delta FIFO队列,实现事件缓冲;
3、Informer作为核心桥梁,持续从Delta FIFO队列中取出事件对象,一方面将数据更新至Indexer本地缓存并建立索引,避免重复请求API Server;另一方面提取资源唯一key,触发自定义控制器的事件回调。
下半部分:控制器自定义处理流程(开发者手动实现核心业务逻辑)
1、Informer将资源key推送至控制器的workqueue工作队列,等待消费;
2、控制器循环从工作队列中读取待处理的资源key;
3、控制器根据key从Indexer本地缓存中读取完整资源对象,对比资源预期状态与当前实际状态,执行自定义调谐逻辑,修复资源差异。
核心总结:Informer上半部分底层监听、缓存、队列逻辑均已封装完成,开发者只需实现下半部分的资源调谐业务逻辑,即可完成定制化控制器开发。
3.2.2 Informer核心组件作用总结
Reflector:对接API Server的核心监听组件,依托List&Watch长连接机制实现资源实时监听,是所有资源事件的数据来源。
DeltaFIFO:事件缓冲队列,用于暂存资源变更事件,对事件进行去重、排序,实现事件的有序消费,避免事件丢失、重复处理。
Informer:承上启下的核心枢纽,衔接底层监听缓存与上层业务逻辑,负责事件分发、缓存更新、回调触发。
Indexer:本地缓存与索引组件,缓存集群资源全量数据,提供快速查询能力,彻底规避频繁请求API Server的性能问题。
四、Operator开发模型与实战流程
4.1 Operator核心概念
Operator由红帽旗下CoreOS公司推出,是为了解决原生Client-Go开发繁琐、底层逻辑复杂的问题,应运而生的标准化开发模型。
Operator是K8s标准化的自定义资源开发模型,核心组合公式:Operator = 自定义CRD资源类型 + 自定义控制器调谐逻辑。
其本质是将业务专属的资源定义、生命周期管控逻辑封装为标准化组件,实现业务资源的自动化运维,是中间件、数据库、微服务专属运维的主流企业级方案。
4.2 Operator框架核心优势
原生手动基于Client-Go开发CRD和控制器,需要记忆大量字段、手写底层监听缓存逻辑,开发门槛极高。Operator框架实现了高度封装,大幅降低开发难度:
1、自动生成CRD YAML:开发者无需记忆CRD复杂字段,只需按照框架规范定义自定义资源的字段属性,框架可自动生成标准CRD清单;
2、标准化控制器逻辑:框架封装通用的Informer监听、队列、缓存底层逻辑,开发者只需在固定函数中填充业务调谐代码,无需关注底层原理。
4.3 Operator标准开发与部署步骤
1、环境准备:安装Kubebuilder/Operator-SDK开发框架,配置本地Go开发环境与集群连接环境;
2、资源定义:在框架指定配置文件中,定义自定义CRD资源的字段、属性、约束规则,框架自动生成CRD.yaml资源清单;
3、业务逻辑开发:在框架固定回调函数中编写控制器调谐逻辑,核心伪代码循环执行:获取资源预期状态、获取集群当前实际状态、对比状态差异、执行资源变更修复;
for {
desired := getDesiredState() // 获取用户定义的预期状态
current := getCurrentState() // 获取集群当前实际状态
makeChanges(desired, current) // 对比差异,修复资源状态
}
4、控制器容器化打包:原生裸跑控制器不便于集群运维管理,将控制器代码打包为镜像,以Deployment+Pod的形式部署在K8s集群中,实现常驻运行、集群托管;
5、资源测试验证:编写CR实例YAML并应用到集群,控制器通过Informer机制监听资源变更,自动执行调谐逻辑,持续保障资源始终处于用户预期状态。
五、Admission Webhook准入机制开发
5.1 两种Webhook核心分类
Admission Webhook即准入钩子,是K8s API Server的请求拦截扩展机制,作用于资源请求写入etcd数据库之前,相当于集群资源写入的前置关卡,可对所有资源创建、更新请求进行拦截和定制化处理。
Webhook分为两类核心类型,覆盖集群资源准入的所有场景:
Mutating Webhook(修改型准入):拦截用户的资源请求,自动修改资源配置、补充字段、注入属性,修改完成后将请求放回原链路,继续执行后续准入流程。
Validating Webhook(校验型准入):拦截资源请求,按照自定义规则校验资源配置,校验合规则放行写入etcd,不合规则直接拒绝请求、返回自定义错误信息。
5.2 Webhook执行时机与优先级
所有Webhook请求的统一触发时机:API Server接收用户请求、完成基础权限校验之后,资源持久化写入etcd数据库之前。
执行优先级:Mutating Webhook 优先于 Validating Webhook 执行,先完成资源自动修改,再对修改后的资源配置进行合规校验,避免修改后的资源触发校验失败,保证校验逻辑的准确性。
5.3 Webhook整体运行流程
1、用户通过kubectl apply、API接口调用等方式提交资源YAML请求;
2、API Server接收请求,完成基础认证、鉴权校验后,匹配集群中配置的Webhook规则;
3、若请求匹配Webhook规则,API Server将原始请求封装为标准AdmissionReview格式;
4、API Server通过HTTPS协议,将AdmissionReview请求转发至集群内的Webhook服务;
5、Webhook服务接收请求,执行自定义修改或校验逻辑,返回AdmissionResponse响应;
6、API Server根据响应结果,放行或拒绝请求,放行后的资源最终写入etcd数据库,完成资源创建。
5.4 Webhook典型应用场景
Mutating Webhook场景:自动为Pod注入Sidecar容器、自动补充资源限制配额、批量注入标签和注解、自动配置环境变量、统一初始化资源配置。
Validating Webhook场景:强制资源必须携带指定标签、限制仅允许私有仓库镜像、禁止容器开启特权模式、校验资源配额合规性、统一集群安全策略、拦截违规资源创建。
5.5 Mutating Webhook实战案例(自动为Pod打环境标签)
项目需求:集群所有新建Pod,自动补充environment: production环境标签,无需用户手动在YAML中声明,实现资源标签统一初始化。
5.5.1 开发Webhook服务程序
基于Flask框架开发HTTPS Webhook接口,实现Pod标签自动注入逻辑,新建 webhook.py 文件,完整代码如下:
from flask import Flask, request, jsonify
import json
import ssl
import base64
app = Flask(__name__)
def create_patch(metadata):
"""
创建JSON Patch补丁,为Pod添加环境标签
"""
if 'labels' in metadata:
dic = metadata['labels']
else:
dic = {}
patch = [
{'op': 'add', 'path': '/metadata/labels', 'value': dic},
{'op': 'add', 'path': '/metadata/labels/environment', 'value': 'production'}
]
patch_json = json.dumps(patch)
patch_base64 = base64.b64encode(patch_json.encode('utf-8')).decode('utf-8')
return patch_base64
@app.route('/mutate', methods=['POST'])
def mutate():
"""
处理K8s准入修改请求
"""
admission_review = request.get_json()
if 'request' not in admission_review or 'object' not in admission_review['request']:
return jsonify({
'kind': 'AdmissionReview',
'apiVersion': 'admission.k8s.io/v1',
'response': {
'allowed': False,
'status': {'message': 'Invalid AdmissionReview format'}
}
})
req = admission_review['request']
metata = req['object']['metadata']
patch_json = create_patch(metata)
admission_response = {
'kind': 'AdmissionReview',
'apiVersion': 'admission.k8s.io/v1',
'response': {
'uid': req['uid'],
'allowed': True,
'patchType': 'JSONPatch',
'patch': patch_json
}
}
return jsonify(admission_response)
if __name__ == '__main__':
# 加载SSL证书,开启HTTPS服务
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain('/certs/tls.crt', '/certs/tls.key')
app.run(host='0.0.0.0', port=443, ssl_context=context)
5.5.2 编写Dockerfile打包镜像
新建Dockerfile文件,用于构建Webhook服务镜像:
# 基于python3.9基础镜像
FROM python:3.9-slim
WORKDIR /app
COPY webhook.py .
# 安装依赖
RUN pip install Flask
# 启动服务
CMD ["python", "webhook.py"]
镜像构建、打标签、推送镜像仓库命令:
docker build -t egon-mute-webhook:v1.0 .
docker tag egon-mute-webhook:v1.0 registry.cn-shanghai.aliyuncs.com/egon-k8s-test/egon-mute-webhook:v1.0
docker push registry.cn-shanghai.aliyuncs.com/egon-k8s-test/egon-mute-webhook:v1.0
5.5.3 生成Webhook SSL证书与Secret
K8s Webhook强制要求HTTPS协议,需要手动生成自签名证书,并通过Secret挂载到Pod中:
1、生成CA证书
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -subj "/CN=webhook-service.default.svc" -days 36500 -out ca.crt
2、编写证书配置文件webhook-openssl.cnf
[req]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = req_ext
distinguished_name = dn
[ dn ]
C = CN
ST = Shanghai
L = Shanghai
O = egonlin
OU = egonlin
CN = webhook-service.default.svc
[ req_ext ]
subjectAltName = @alt_names
[alt_names]
DNS.1 = webhook-service
DNS.2 = webhook-service.default
DNS.3 = webhook-service.default.svc
DNS.4 = webhook-service.default.svc.cluster.local
[req_distinguished_name]
CN = webhook-service.default.svc
[v3_req]
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[ v3_ext ]
authorityKeyIdentifier=keyid,issuer:always
basicConstraints=CA:FALSE
keyUsage=keyEncipherment,dataEncipherment
extendedKeyUsage=serverAuth,clientAuth
subjectAltName=@alt_names
3、生成Webhook服务证书
openssl genrsa -out webhook.key 2048
openssl req -new -key webhook.key -out webhook.csr -config webhook-openssl.cnf
4、将证书存入K8s Secret,供Webhook服务挂载使用
kubectl delete secrets webhook-certs
kubectl create secret tls webhook-certs \
--cert=webhook.crt \
--key=webhook.key \
--namespace=default --dry-run=client -o yaml | kubectl apply -f -
5.5.4 部署Webhook服务(Deployment+Service)
编写部署YAML,实现Webhook服务容器化部署与集群暴露:
apiVersion: apps/v1
kind: Deployment
metadata:
name: webhook-deployment
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: webhook
template:
metadata:
labels:
app: webhook
spec:
containers:
- name: webhook
image: registry.cn-shanghai.aliyuncs.com/egon-k8s-test/egon-mute-webhook:v1.0
volumeMounts:
- name: webhook-certs
mountPath: /certs
readOnly: true
volumes:
- name: webhook-certs
secret:
secretName: webhook-certs
---
apiVersion: v1
kind: Service
metadata:
name: webhook-service
namespace: default
spec:
ports:
- port: 443
targetPort: 443
selector:
app: webhook
5.5.5 配置MutatingWebhookConfiguration规则
编写脚本自动填充CA证书,生成准入规则YAML,绑定Webhook拦截规则:
#!/bin/bash
base64 -w 0 ca.crt > ca.crt.base64
ca_base64_content=$(cat "ca.crt.base64" | tr -d '\n')
cat <<EOF > m-w-c.yaml
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: example-mutating-webhook
webhooks:
- name: example.webhook.com
clientConfig:
service:
name: webhook-service
namespace: default
path: "/mutate"
caBundle: "$ca_base64_content"
rules:
- operations: ["CREATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
admissionReviewVersions: ["v1"]
sideEffects: None
EOF
执行脚本生成并应用规则:
bash 脚本名.sh
kubectl apply -f m-w-c.yaml
5.5.6 功能测试
新建测试Pod资源 test.yaml:
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- name: nginx
image: nginx:1.18
应用资源并查看标签:
kubectl apply -f test.yaml
kubectl get pod test-pod -o yaml
结果验证:Pod会自动生成 environment: production 标签,无需手动配置,修改型Webhook功能生效。
5.6 Validating Webhook实战案例(强制校验Pod环境标签)
项目需求:禁止创建无environment环境标签的Pod,实现集群资源标签规范化校验,统一集群资源规范。
新建校验服务 validating-webhook.py,完整代码如下:
from flask import Flask, request, jsonify
import ssl
import logging
app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
@app.route('', methods=['POST'])
def validate():
admission_review = request.get_json()
if 'request' not in admission_review or 'object' not in admission_review['request']:
return jsonify({
'kind': 'AdmissionReview',
'apiVersion': 'admission.k8s.io/v1',
'response': {
'allowed': False,
'status': {'message': 'Invalid AdmissionReview format'}
}
})
req = admission_review['request']
# 仅校验Pod资源
if req['kind']['kind'] == 'Pod':
pod = req['object']
labels = pod.get('metadata', {}).get('labels', {})
# 校验必须包含环境标签
if 'environment' not in labels:
return jsonify({
'kind': 'AdmissionReview',
'apiVersion': 'admission.k8s.io/v1',
'response': {
'uid': req['uid'],
'allowed': False,
'status': {
'metadata': {},
'code': 400,
'message': 'Pod must have an "environment" label'
}
}
})
return jsonify({
'kind': 'AdmissionReview',
'apiVersion': 'admission.k8s.io/v1',
'response': {
'uid': req['uid'],
'allowed': True,
'status': {
'metadata': {},
'code': 200
}
}
})
return jsonify({
'kind': 'AdmissionReview',
'apiVersion': 'admission.k8s.io/v1',
'response': {
'allowed': True,
'status': {
'metadata': {},
'code': 200
}
}
})
if __name__ == '__main__':
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain('/certs/tls.crt', '/certs/tls.key')
app.run(host='0.0.0.0', port=443, ssl_context=context)
配置ValidatingWebhookConfiguration规则,补全空缺配置文件,新建 v-w-c.yaml:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: example-validating-webhook
webhooks:
- name: validate.webhook.com
clientConfig:
service:
name: webhook-service
namespace: default
path: "/"
caBundle: "$ca_base64_content"
rules:
- operations: ["CREATE","UPDATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
admissionReviewVersions: ["v1"]
sideEffects: None
部署流程与Mutating Webhook完全一致:重新打包镜像、更新部署、替换CA证书、应用准入规则,最终测试无environment标签的Pod会被集群直接拒绝创建。
六、K8s核心组件定制化开发补充
除了Operator自定义资源开发、Webhook准入拦截开发两大主流定制化方案外,K8s支持对原生核心组件进行二次定制开发,适配企业特殊集群架构与专属业务场景。
核心定制场景:改造API Server实现专属鉴权逻辑、自定义调度器实现业务专属调度策略、改造Controller Manager实现特殊资源管控、自定义监控采集组件适配业务指标。
开发特点:该方案基于K8s原生源码二次开发,自由度最高、适配性最强,可深度改造集群底层逻辑,但开发难度大、对Go语言功底要求高、版本兼容性要求严格,多用于大型企业专属集群架构改造,普通业务场景极少使用。
七、整体流程总结
1、K8s原生资源与管控逻辑仅适配通用场景,可通过三类定制化开发扩展集群能力:核心组件源码改造、Operator自定义资源运维、Webhook请求准入拦截。
2、CRD+Operator是业务定制的核心方案,依托Informer缓存监听机制,实现自定义资源的自动化生命周期运维,是中间件、数据库等业务组件容器化运维的主流方案。
3、Webhook作为API请求前置关卡,通过修改、校验两种核心能力,实现集群资源的标准化初始化、安全合规管控,保障集群资源统一规范。
4、所有K8s定制化组件最终均以容器化方式部署为Pod、Service资源,融入K8s原生调度、运维体系,实现对集群的无侵入扩展。

浙公网安备 33010602011771号