Kubernetes Webhook 详解与实践
约 1253 字大约 4 分钟
2025-09-03
什么是 Webhook
Webhook 是 Kubernetes 中的一种扩展机制,允许在 API 请求到达 Kubernetes API Server 之前或之后进行拦截和处理。它提供了一种无需修改 Kubernetes 核心代码就能扩展和自定义 Kubernetes 行为的方式。
Webhook 的类型
1. Admission Webhook
- Mutating Webhook: 在资源被持久化之前修改资源对象
- Validating Webhook: 验证资源对象是否符合要求,可以拒绝请求
2. Authorization Webhook
- 用于自定义授权决策
3. Authentication Webhook
- 用于自定义身份验证
使用场景
- 资源验证: 检查 Pod 标签、资源限制等
- 资源修改: 自动添加 sidecar 容器、标签等
- 安全策略: 检查镜像来源、网络策略等
- 合规性检查: 确保资源符合公司政策
实现步骤
步骤 1: 创建 Webhook 服务
首先创建一个简单的 webhook 服务:
// main.go
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"time"
admissionv1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
)
var (
runtimeScheme = runtime.NewScheme()
codecs = serializer.NewCodecFactory(runtimeScheme)
deserializer = codecs.UniversalDeserializer()
)
type WebhookServer struct {
server *http.Server
}
type patchOperation struct {
Op string `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value,omitempty"`
}
func (whsvr *WebhookServer) mutate(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
req := ar.Request
var pod corev1.Pod
if err := json.Unmarshal(req.Object.Raw, &pod); err != nil {
return &admissionv1.AdmissionResponse{
Result: &metav1.Status{
Message: err.Error(),
},
}
}
// 检查 Pod 是否已经有特定标签
if pod.Labels["webhook-demo"] == "true" {
return &admissionv1.AdmissionResponse{
Allowed: true,
}
}
// 创建 patch 操作
patch := []patchOperation{
{
Op: "add",
Path: "/metadata/labels",
Value: map[string]string{"webhook-demo": "true"},
},
}
patchBytes, err := json.Marshal(patch)
if err != nil {
return &admissionv1.AdmissionResponse{
Result: &metav1.Status{
Message: err.Error(),
},
}
}
return &admissionv1.AdmissionResponse{
Allowed: true,
Patch: patchBytes,
PatchType: func() *admissionv1.PatchType {
pt := admissionv1.PatchTypeJSONPatch
return &pt
}(),
}
}
func (whsvr *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
var body []byte
if r.Body != nil {
if data, err := io.ReadAll(r.Body); err == nil {
body = data
}
}
// 验证 Content-Type
if contentType := r.Header.Get("Content-Type"); contentType != "application/json" {
http.Error(w, "invalid Content-Type", http.StatusBadRequest)
return
}
var admissionResponse *admissionv1.AdmissionResponse
ar := admissionv1.AdmissionReview{}
if _, _, err := deserializer.Decode(body, nil, &ar); err != nil {
admissionResponse = &admissionv1.AdmissionResponse{
Result: &metav1.Status{
Message: err.Error(),
},
}
} else {
admissionResponse = whsvr.mutate(&ar)
}
ar.Response = admissionResponse
resp, err := json.Marshal(ar)
if err != nil {
http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)
}
w.Header().Set("Content-Type", "application/json")
w.Write(resp)
}
func main() {
webhookServer := &WebhookServer{
server: &http.Server{
Addr: ":8443",
TLSConfig: &tls.Config{},
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
},
}
mux := http.NewServeMux()
mux.HandleFunc("/mutate", webhookServer.serve)
webhookServer.server.Handler = mux
fmt.Println("Starting webhook server on :8443")
if err := webhookServer.server.ListenAndServeTLS("/etc/webhook/certs/tls.crt", "/etc/webhook/certs/tls.key"); err != nil {
panic(err)
}
}步骤 2: 创建 Dockerfile
# Dockerfile
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o webhook-server .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/webhook-server .
COPY --from=builder /app/certs /etc/webhook/certs
EXPOSE 8443
CMD ["./webhook-server"]步骤 3: 创建 Kubernetes 资源
创建命名空间和 RBAC
# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: webhook-demo# rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: webhook-sa
namespace: webhook-demo
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: webhook-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: webhook-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: webhook-role
subjects:
- kind: ServiceAccount
name: webhook-sa
namespace: webhook-demo创建 Deployment 和 Service
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: webhook-server
namespace: webhook-demo
spec:
replicas: 1
selector:
matchLabels:
app: webhook-server
template:
metadata:
labels:
app: webhook-server
spec:
serviceAccountName: webhook-sa
containers:
- name: webhook-server
image: your-registry/webhook-server:latest
ports:
- containerPort: 8443
volumeMounts:
- name: webhook-certs
mountPath: /etc/webhook/certs
readOnly: true
volumes:
- name: webhook-certs
secret:
secretName: webhook-certs
---
apiVersion: v1
kind: Service
metadata:
name: webhook-server
namespace: webhook-demo
spec:
ports:
- port: 443
targetPort: 8443
selector:
app: webhook-server创建 MutatingWebhookConfiguration
# webhook-config.yaml
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: webhook-demo
webhooks:
- name: webhook-demo.example.com
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
scope: "Namespaced"
clientConfig:
service:
namespace: webhook-demo
name: webhook-server
path: "/mutate"
port: 443
admissionReviewVersions: ["v1"]
sideEffects: None
timeoutSeconds: 5步骤 4: 生成 TLS 证书
# 生成私钥
openssl genrsa -out webhook-server-tls.key 2048
# 生成证书签名请求
openssl req -new -key webhook-server-tls.key -subj "/CN=webhook-server.webhook-demo.svc" -out webhook-server-tls.csr
# 生成自签名证书
openssl x509 -req -in webhook-server-tls.csr -signkey webhook-server-tls.key -out webhook-server-tls.crt -days 365
# 创建 Kubernetes Secret
kubectl create secret tls webhook-certs \
--key webhook-server-tls.key \
--cert webhook-server-tls.crt \
--namespace webhook-demo步骤 5: 部署和测试
# 部署所有资源
kubectl apply -f namespace.yaml
kubectl apply -f rbac.yaml
kubectl apply -f deployment.yaml
kubectl apply -f webhook-config.yaml
# 测试 webhook
kubectl run test-pod --image=nginx --namespace=webhook-demo
# 检查 Pod 是否被修改
kubectl get pod test-pod -n webhook-demo -o yaml | grep -A 5 labels高级功能
1. 条件性修改
// 只在特定命名空间应用 webhook
if pod.Namespace != "webhook-demo" {
return &admissionv1.AdmissionResponse{
Allowed: true,
}
}2. 复杂验证逻辑
// 验证镜像来源
for _, container := range pod.Spec.Containers {
if !strings.HasPrefix(container.Image, "your-registry.com/") {
return &admissionv1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Message: "Only images from your-registry.com are allowed",
},
}
}
}3. 错误处理和重试
// 添加重试逻辑
if err != nil {
log.Printf("Error processing webhook: %v", err)
return &admissionv1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Code: 500,
Message: "Internal server error",
},
}
}注意事项
- 性能: Webhook 会增加 API 请求的延迟,应保持轻量级
- 可用性: Webhook 服务不可用会影响整个集群
- 安全性: 使用 TLS 证书,限制访问权限
- 监控: 监控 webhook 的性能和错误率
- 测试: 在测试环境中充分测试 webhook 逻辑
总结
Kubernetes Webhook 提供了强大的扩展能力,允许在不修改核心代码的情况下自定义集群行为。通过合理的设计和实现,可以构建出功能强大、安全可靠的 webhook 服务。
关键要点:
- 选择合适的 webhook 类型(Mutating/Validating)
- 实现正确的 admission 逻辑
- 配置适当的 RBAC 权限
- 使用 TLS 证书确保安全
- 监控和维护 webhook 服务
更新日志
2025/9/3 12:45
查看所有更新日志
4a0d5-add webhook于
