k8s

k8s的webhook开发

axing
2023-08-20 / 0 评论 / 11 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2024年09月29日,已超过115天没有更新,若内容或图片失效,请留言反馈。

一、知识储备
1、Admission Webhooks是什么

        请求链路
            ------------|--------|-------------》etcd
                      关卡      关卡
        webhook指的是中途拦击请求做定制化处理,包含
            (1)mutate:截胡请求之后做修改,改完之后扔回原链路
            (2)validate:对请求的数据做校验,不符合规定的直接扔掉
        Admission翻译为准入,入指的是入到etcd数据库中

2、webhook的执行时机

    Admission Webhook 本质是 api-server 的一个 webhook 调用,
    总结出两种webhook的触发时机
    1、总提来说,Admission Webhooks 机制是在 API Server 接收到请求、执行授权检查后和将请求持久化到 etcd 之前触发。
    2、详细的看,Mutating Webhook 在 Validating Webhook 之前触发,因此可以先对资源进行修改,然后再进行验证。
    3、Admission Webhook的运行流程
    
        
                                                ┌──────────────────────────────────┐
                 ┌─────────────────┐            │                                  │
        apply    │                 │    read    │  validatingwebhookconfiguration  │
    ────────────►│    api-server   │◄───────────┤                                  │
                 │                 │            │  mutatingwebhookconfiguration    │
                 └────────┬────────┘            │                                  │
                          │                     └──────────────────────────────────┘
                          │
                          │  回调
                          │
                          │
                 ┌────────▼────────┐
                 │                 │
                 │  webhookservice │
                 │                 │
                 └─────────────────┘

4、Admission Webhook的应用场景

mutating webhook应用场景举例        
自动注入sidecar容器
自动配置资源限制
注入配置或者标签
        
Validating Webhook应用场景举例
强制标签和注解策略
必须用私有仓库的镜像
安全策略审计:例如不允许使用特权模式

二、mutating webhook实战案例
1.储备知识:基于flask框架开发一个web程序,对外提供api接口

# 安装python3解释器
# 为解释器环境安装flask代码包: pip3 install flask
    from flask import Flask

    app = Flask(__name__)  # 创建一个 Flask 应用实例

    @app.route('/xxx', methods=['GET'])  # http://192.168.71.2:8888/xxx
    def mutate():
        print("run....................")

        return "hello"

    @app.route('/yyy', methods=['GET'])  # http://192.168.71.2:8888/xxx
    def test():
        print("run22222222222222222....................")

        return "hello2222222222222222222222222"

    if __name__ == '__main__':

        app.run(host='0.0.0.0', port=8888,)

2.需求:为所有新创建的pod打上标签:environment: production
(1)先准备webhook程序

cat > webhook.py << 'EOF'
from flask import Flask, request, jsonify  # 导入 Flask 框架、请求处理和 JSON 响应模块
import json
import ssl
import base64

app = Flask(__name__)  # 创建一个 Flask 应用实例


def create_patch(metadata):
    """
    创建 JSON Patch 以添加 'mutate' 注释。
    如果 metadata.annotations 不存在,则首先创建该路径。
    """
    if 'labels' in metadata:
        dic = metadata['labels']
    else:
        dic = {}

    patch = [
        # 添加 'labels' 键,如果不存在
        {'op': 'add', 'path': '/metadata/labels', 'value': dic},
        # 添加 'environment' 标签
        {'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'])  # https://webhook-service.default.svc:443/mutate
def mutate():
    """
    处理 Mutating Webhook 的请求,对 Pod 对象应用 JSON Patch。
    """
    admission_review = request.get_json()  # 从请求中提取 AdmissionReview 对象

    # 验证 AdmissionReview 格式是否正确
    # admission_review['request']['object']
    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']  # 提取请求对象
    print('--->',req)
    # 生成 JSON Patch
    metata = req['object']['metadata']
    patch_json = create_patch(metata)

    # 准备 AdmissionResponse 响应
    admission_response = {
        'kind': 'AdmissionReview',
        'apiVersion': 'admission.k8s.io/v1',
        'response': {
            'uid': req['uid'],
            'allowed': True,
            'patchType': 'JSONPatch',
            'patch': patch_json  # 直接包含 Patch 数据作为 JSON 字符串
        }
    }

    print(admission_response)
    return jsonify(admission_response)


if __name__ == '__main__':
    # 加载 SSL 证书和私钥
    context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    context.load_cert_chain('/certs/tls.crt', '/certs/tls.key')

    # Run the Flask application with SSL
    app.run(host='0.0.0.0', port=443, ssl_context=context)

EOF

python webhook.py

(2)制作镜像(包含webhook.py的运行环境,依赖python3解释器、依赖flask框架)
文件dockerfile内容如下

# 使用官方 Python 镜像作为基础镜像
FROM python:3.9-slim

# 设置工作目录
WORKDIR /app

# 将当前目录的所有文件复制到容器的 /app 目录
COPY webhook.py .

# 安装 Flask 及其依赖
RUN pip install Flask

# 启动 Flask 应用
CMD ["python", "webhook.py"]

然后构建镜像:

docker build -t egon-mute-webhook:v1.0 .

打标签上传

docker tag c0f805ffaa01 registry.cn-guangzhou.aliyuncs.com/xingcangku/egon-mute-webhook:v1.0
docker push registry.cn-guangzhou.aliyuncs.com/xingcangku/egon-mute-webhook:v1.0

(3)配置 Webhook 的 Secret

# 生成 CA 私钥
openssl genrsa -out ca.key 2048

# 生成自签名 CA 证书,有效期为 100 年
openssl req -x509 -new -nodes -key ca.key -subj  "/CN=webhook-service.default.svc" -days 36500 -out ca.crt

创建证书请求的配置文件

cat > webhook-openssl.cnf << 'EOF'
[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

EOF

使用webhook-openssl.cnf这个配置文件生成 CSR:

# 生成 Webhook 服务的私钥
openssl genrsa -out webhook.key 2048
# 使用 OpenSSL 配置文件生成 CSR
openssl req -new -key webhook.key -out webhook.csr -config webhook-openssl.cnf
openssl x509 -req -in webhook.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out webhook.crt -days 36500 -extensions v3_ext -extfile webhook-openssl.cnf
#最终得到webhook.crt、webhook.key

将生成的证书和私钥存储在 Kubernetes Secret 中

#可能有残留可以先删一下
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 -

3.创建deployment来部署webhook服务

cat > webhook-deployment.yaml << 'EOF'
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-guangzhou.aliyuncs.com/xingcangku/axingcangku:v2.0
        command: [ "/bin/sh", "-c", "tail -f /dev/null" ]
        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
    
EOF

4.基于kind: mutatingwebhookconfiguration 该资源类型创建出一个资源,相当于于一道关卡
在该资源中声明把请求转给的目标webhook程序的api地址
基于脚本来生成yaml:
cat a.sh

#!/bin/bash

base64 -w 0 ca.crt > ca.crt.base64

# 定义文件路径
ca_base64_file="ca.crt.base64"
yaml_file="m-w-c.yaml"

# 读取 ca.crt.base64 的内容
ca_base64_content=$(cat "$ca_base64_file" | tr -d '\n')

# 生成替换后的 YAML 文件内容
# 将 base64 内容插入到 YAML 文件中
cat <<EOF > "$yaml_file"
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"
      # 替换为 cat ca.crt.base64的内容
      caBundle: "$ca_base64_content"
    rules:
      - operations: ["CREATE"]
        apiGroups: [""]
        apiVersions: ["v1"]
        resources: ["pods"]
    admissionReviewVersions: ["v1"]
    sideEffects: None
EOF

echo "YAML 文件已更新。"

5、创建pod进行测试

[root@k8s-master-01 word]# cat test.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
  - name: nginx
    image: nginx:1.18
0

评论 (0)

取消