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原生调度、运维体系,实现对集群的无侵入扩展。

posted @ 2026-07-14 10:54  cuckooyang  阅读(22)  评论(0)    收藏  举报