一、为什么需要自定义准入控制Webhook
在Kubernetes集群中,默认的准入控制器已经能处理大部分场景,比如资源配额检查、Pod安全策略等。但实际生产环境中,我们经常需要实现一些特定的业务规则验证,这时候就需要开发自定义的准入控制Webhook了。
举个例子,假设我们有个电商平台,所有部署的微服务都需要打上特定的业务标签(比如department: ecommerce),否则就不允许部署。这种业务特定的规则,Kubernetes原生的准入控制器可帮不上忙,就得靠我们自己来实现。
二、Webhook工作原理浅析
准入控制Webhook本质上就是个HTTP回调服务,Kubernetes API Server在遇到需要准入控制的请求时,会向这个Webhook发起HTTP请求,询问"这个操作能不能通过?"。
整个过程分为两个阶段:
- 变更阶段(Mutating):可以修改请求内容,比如自动给Pod注入sidecar容器
- 验证阶段(Validating):只能回答"允许"或"拒绝",不能修改请求内容
这就像公司门禁系统,变更阶段相当于保安帮你整理下着装(比如提醒你戴工牌),验证阶段则是检查你是否真的有权限进入。
三、手把手开发Validating Webhook
下面我们用Go语言开发一个简单的Validating Webhook,它会检查所有新创建的Pod是否带有特定标签。
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
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 AdmissionResponse struct {
Allowed bool `json:"allowed"`
Message string `json:"message,omitempty"`
}
// 处理准入控制请求
func handleAdmissionReview(w http.ResponseWriter, r *http.Request) {
// 1. 读取请求体
body, err := os.ReadAll(r.Body)
if err != nil {
http.Error(w, fmt.Sprintf("读取请求失败: %v", err), http.StatusBadRequest)
return
}
// 2. 反序列化请求
var admissionReview admissionv1.AdmissionReview
if _, _, err := deserializer.Decode(body, nil, &admissionReview); err != nil {
http.Error(w, fmt.Sprintf("反序列化失败: %v", err), http.StatusBadRequest)
return
}
// 3. 处理请求
response := validatePod(admissionReview.Request)
// 4. 构造响应
admissionReview.Response = response
respBytes, err := json.Marshal(admissionReview)
if err != nil {
http.Error(w, fmt.Sprintf("序列化响应失败: %v", err), http.StatusInternalServerError)
return
}
// 5. 返回响应
w.Header().Set("Content-Type", "application/json")
w.Write(respBytes)
}
// 验证Pod标签
func validatePod(request *admissionv1.AdmissionRequest) *admissionv1.AdmissionResponse {
// 只处理Pod创建请求
if request.Kind.Kind != "Pod" || request.Operation != "CREATE" {
return &admissionv1.AdmissionResponse{Allowed: true}
}
// 解析Pod对象
pod := corev1.Pod{}
if err := json.Unmarshal(request.Object.Raw, &pod); err != nil {
return &admissionv1.AdmissionResponse{
Allowed: false,
Message: fmt.Sprintf("解析Pod失败: %v", err),
}
}
// 检查必须的标签
if pod.Labels["department"] != "ecommerce" {
return &admissionv1.AdmissionResponse{
Allowed: false,
Message: "所有Pod必须包含标签: department=ecommerce",
}
}
return &admissionv1.AdmissionResponse{Allowed: true}
}
func main() {
http.HandleFunc("/validate", handleAdmissionReview)
fmt.Println("Starting webhook server on :8443")
http.ListenAndServeTLS(
":8443",
"/etc/webhook/certs/tls.crt",
"/etc/webhook/certs/tls.key",
nil,
)
}
这个Webhook做了以下几件事:
- 监听8443端口的/validate路径
- 只拦截Pod的创建请求
- 检查Pod是否带有department=ecommerce标签
- 没有标签就拒绝创建
四、部署Webhook到Kubernetes
开发完Webhook服务后,我们需要把它部署到集群中。这里给出对应的Kubernetes资源配置:
# webhook-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: admission-webhook
labels:
app: admission-webhook
spec:
replicas: 1
selector:
matchLabels:
app: admission-webhook
template:
metadata:
labels:
app: admission-webhook
spec:
containers:
- name: webhook
image: my-registry/admission-webhook:v1
ports:
- containerPort: 8443
volumeMounts:
- name: webhook-certs
mountPath: /etc/webhook/certs
readOnly: true
volumes:
- name: webhook-certs
secret:
secretName: webhook-certs
---
# webhook-service.yaml
apiVersion: v1
kind: Service
metadata:
name: admission-webhook
labels:
app: admission-webhook
spec:
ports:
- port: 443
targetPort: 8443
selector:
app: admission-webhook
---
# webhook-configuration.yaml
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: pod-label-validator
webhooks:
- name: validator.example.com
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
scope: "*"
clientConfig:
service:
name: admission-webhook
path: "/validate"
port: 443
caBundle: ${CA_BUNDLE}
admissionReviewVersions: ["v1"]
sideEffects: None
timeoutSeconds: 5
部署步骤:
- 先创建包含证书的Secret
- 部署Webhook服务
- 创建ValidatingWebhookConfiguration资源
五、Mutating Webhook开发实战
有时候我们不仅想验证请求,还想自动修改请求内容。下面展示一个Mutating Webhook的示例,它会自动给Pod注入一个日志收集的sidecar容器。
func mutatePod(request *admissionv1.AdmissionRequest) *admissionv1.AdmissionResponse {
// 只处理Pod创建请求
if request.Kind.Kind != "Pod" || request.Operation != "CREATE" {
return &admissionv1.AdmissionResponse{Allowed: true}
}
// 解析Pod对象
pod := corev1.Pod{}
if err := json.Unmarshal(request.Object.Raw, &pod); err != nil {
return &admissionv1.AdmissionResponse{
Allowed: false,
Message: fmt.Sprintf("解析Pod失败: %v", err),
}
}
// 创建patch操作
var patches []map[string]interface{}
// 1. 添加sidecar容器
sidecar := corev1.Container{
Name: "log-collector",
Image: "fluentd:latest",
VolumeMounts: []corev1.VolumeMount{
{
Name: "varlog",
MountPath: "/var/log",
},
},
}
patches = append(patches, map[string]interface{}{
"op": "add",
"path": "/spec/containers/-",
"value": sidecar,
})
// 2. 添加共享volume
volume := corev1.Volume{
Name: "varlog",
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{
Path: "/var/log",
},
},
}
patches = append(patches, map[string]interface{}{
"op": "add",
"path": "/spec/volumes/-",
"value": volume,
})
// 序列化patch
patchBytes, err := json.Marshal(patches)
if err != nil {
return &admissionv1.AdmissionResponse{
Allowed: false,
Message: fmt.Sprintf("创建patch失败: %v", err),
}
}
// 返回允许并携带patch
return &admissionv1.AdmissionResponse{
Allowed: true,
Patch: patchBytes,
PatchType: func() *admissionv1.PatchType {
pt := admissionv1.PatchTypeJSONPatch
return &pt
}(),
}
}
这个Mutating Webhook会:
- 检查到Pod创建请求
- 自动注入fluentd sidecar容器
- 添加共享volume用于日志收集
六、应用场景与技术选型
自定义准入控制Webhook的典型应用场景包括:
- 资源验证:确保所有资源都有必要的标签或注解
- 安全策略:检查容器是否以非root用户运行
- 配置管理:自动注入sidecar或环境变量
- 成本控制:限制资源请求量过大
技术选型建议:
- 语言:Go是首选,因为Kubernetes本身就是用Go写的,生态完善
- 框架:可以使用controller-runtime等专业库简化开发
- 部署:建议使用Kubernetes Deployment部署Webhook服务
七、注意事项与最佳实践
在开发和使用Webhook时,需要注意以下几点:
性能考虑:
- Webhook会增加API Server的延迟
- 保持处理逻辑简单高效
- 设置合理的超时时间(默认10秒)
高可用:
- Webhook服务必须有多个副本
- 正确处理启动顺序问题(Webhook可能依赖其他服务)
幂等性:
- Mutating Webhook要确保多次应用patch结果一致
- 避免造成无限递归(比如Webhook修改了自己的Deployment)
测试策略:
- 单元测试:测试业务逻辑
- 集成测试:使用kind或minikube测试完整流程
- 混沌测试:模拟Webhook服务不可用的情况
八、总结
自定义准入控制Webhook是扩展Kubernetes能力的强大工具,就像给Kubernetes装上了"业务规则检查器"。通过本文的讲解,你应该已经掌握了:
- Webhook的基本工作原理
- 如何开发Validating和Mutating Webhook
- 如何部署和配置Webhook
- 实际生产中的注意事项
记住,能力越大责任越大。Webhook可以拦截所有API请求,一旦出问题可能导致整个集群不可用。所以一定要充分测试,并准备好回滚方案。
评论