一、知识储备
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)