231 lines
8.2 KiB
Markdown
231 lines
8.2 KiB
Markdown
`AdmissionWebhook` 是 Kubernetes 中的一种动态准入控制器,用于在请求进入 Kubernetes API 服务器时对其进行修改或验证。准入控制器是 Kubernetes 安全模型的一部分,负责拦截请求并对其进行特定的检查或修改,以确保集群的安全性和策略的一致性。
|
||
|
||
### AdmissionWebhook 的作用
|
||
|
||
1. **验证(Validating Webhooks)**:
|
||
- 这些 Webhook 在资源被创建、更新或删除之前对请求进行检查,以确保请求符合某些策略或规则。
|
||
- 如果验证失败,请求将被拒绝,并返回给用户一个错误信息。
|
||
|
||
2. **变更(Mutating Webhooks)**:
|
||
- 这些 Webhook 可以在请求到达 API 服务器时修改请求对象。
|
||
- 例如,可以自动填充某些字段,或者根据预定义的规则调整资源配置。
|
||
|
||
### 使用场景
|
||
|
||
- **策略实施**:确保所有部署的应用程序都符合公司或组织的安全和合规策略。例如,确保所有 Pod 都具有指定的标签或资源限制。
|
||
- **自动化变更**:自动为资源添加或修改某些属性,例如为每个 Pod 添加一个特定的环境变量。
|
||
- **资源验证**:在资源被持久化之前,对其进行额外的验证,以防止错误配置。
|
||
- **审计和日志**:记录和监控特定请求,以帮助进行审计和故障排查。
|
||
|
||
### 工作原理
|
||
|
||
1. **注册 Webhook**:
|
||
- 使用 `MutatingWebhookConfiguration` 和 `ValidatingWebhookConfiguration` 资源在 Kubernetes 中注册 Webhook。
|
||
|
||
2. **处理请求**:
|
||
- 当一个请求到达 Kubernetes API 服务器时,配置的 Webhook 会被调用。
|
||
- Webhook 服务接收到请求对象,执行逻辑并返回一个响应,指示是否允许、拒绝或修改请求。
|
||
|
||
3. **响应处理**:
|
||
- 对于 Mutating Webhook,API 服务器将根据 Webhook 的响应修改请求对象。
|
||
- 对于 Validating Webhook,API 服务器将检查 Webhook 的响应以决定是否执行请求。
|
||
|
||
### 实现示例
|
||
|
||
要实现一个 AdmissionWebhook,通常需要以下步骤:
|
||
|
||
1. **编写 Webhook 服务器**:
|
||
- 使用编程语言(例如 Go)编写一个 HTTP 服务器,能够处理 Kubernetes 的 AdmissionReview 请求。
|
||
|
||
2. **部署 Webhook 服务**:
|
||
- 将 Webhook 服务器作为一个 Pod 部署到 Kubernetes 集群中。
|
||
|
||
3. **创建 Webhook 配置**:
|
||
- 编写并应用 `MutatingWebhookConfiguration` 或 `ValidatingWebhookConfiguration`,指向你的 Webhook 服务。
|
||
|
||
### 注意事项
|
||
|
||
- **安全性**:确保 Webhook 服务的安全性,使用 TLS 进行通信,确保只有经过身份验证的请求才能访问。
|
||
- **性能影响**:Webhook 会影响 API 请求的延迟,因为请求需要经过 Webhook 的处理。尽量优化 Webhook 的处理逻辑以减少开销。
|
||
- **故障处理**:设计 Webhook 时需要考虑故障处理,确保 Webhook 不会因为某些错误而导致集群无法正常工作。
|
||
|
||
通过使用 AdmissionWebhook,Kubernetes 提供了一个强大的机制来实施自定义策略和自动化配置,帮助运维人员更好地管理和控制集群行为。
|
||
|
||
|
||
|
||
### 举例
|
||
|
||
当然!下面是一个简单的示例,展示如何实现一个 Kubernetes Admission Webhook。这个示例将创建一个简单的 Mutating Admission Webhook,它会在每个 Pod 创建之前向其中添加一个环境变量。
|
||
|
||
### 步骤 1: 编写 Webhook 服务器
|
||
|
||
我们将用 Go 编写一个简单的 HTTP 服务器,作为我们的 Webhook 服务。
|
||
|
||
```go
|
||
package main
|
||
|
||
import (
|
||
"encoding/json"
|
||
"net/http"
|
||
"log"
|
||
|
||
admissionv1 "k8s.io/api/admission/v1"
|
||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||
corev1 "k8s.io/api/core/v1"
|
||
)
|
||
|
||
func handleAdmission(w http.ResponseWriter, r *http.Request) {
|
||
var admissionReviewReq admissionv1.AdmissionReview
|
||
if err := json.NewDecoder(r.Body).Decode(&admissionReviewReq); err != nil {
|
||
log.Printf("Could not decode request: %v", err)
|
||
http.Error(w, "Bad request", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
|
||
if admissionReviewReq.Request.Resource != podResource {
|
||
log.Printf("Expect resource to be %s", podResource)
|
||
http.Error(w, "Bad request", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
raw := admissionReviewReq.Request.Object.Raw
|
||
pod := corev1.Pod{}
|
||
if err := json.Unmarshal(raw, &pod); err != nil {
|
||
log.Printf("Could not unmarshal raw object: %v", err)
|
||
http.Error(w, "Bad request", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
// 添加环境变量
|
||
env := corev1.EnvVar{Name: "ADDED_BY_WEBHOOK", Value: "true"}
|
||
pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, env)
|
||
|
||
// 创建补丁操作
|
||
patchBytes, err := json.Marshal([]map[string]interface{}{
|
||
{"op": "add", "path": "/spec/containers/0/env", "value": pod.Spec.Containers[0].Env},
|
||
})
|
||
if err != nil {
|
||
log.Printf("Could not marshal patch: %v", err)
|
||
http.Error(w, "Bad request", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
admissionReviewResponse := admissionv1.AdmissionReview{
|
||
Response: &admissionv1.AdmissionResponse{
|
||
UID: admissionReviewReq.Request.UID,
|
||
Allowed: true,
|
||
Patch: patchBytes,
|
||
PatchType: func() *admissionv1.PatchType {
|
||
pt := admissionv1.PatchTypeJSONPatch
|
||
return &pt
|
||
}(),
|
||
},
|
||
}
|
||
|
||
respBytes, err := json.Marshal(admissionReviewResponse)
|
||
if err != nil {
|
||
log.Printf("Could not marshal response: %v", err)
|
||
http.Error(w, "Bad request", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
w.Header().Set("Content-Type", "application/json")
|
||
w.Write(respBytes)
|
||
}
|
||
|
||
func main() {
|
||
http.HandleFunc("/mutate", handleAdmission)
|
||
log.Println("Starting server on :8080")
|
||
if err := http.ListenAndServeTLS(":8080", "/path/to/tls.crt", "/path/to/tls.key", nil); err != nil {
|
||
log.Fatalf("Failed to start server: %v", err)
|
||
}
|
||
}
|
||
```
|
||
|
||
### 步骤 2: 部署 Webhook 服务
|
||
|
||
1. **编译和打包服务**:
|
||
- 将上述代码编译为二进制文件,创建一个 Docker 镜像,并将其推送到容器镜像仓库。
|
||
|
||
2. **创建 Kubernetes Deployment 和 Service**:
|
||
编写一个 YAML 文件,将 Webhook 服务部署到 Kubernetes 集群中。
|
||
|
||
```yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: webhook-server
|
||
spec:
|
||
replicas: 1
|
||
selector:
|
||
matchLabels:
|
||
app: webhook-server
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: webhook-server
|
||
spec:
|
||
containers:
|
||
- name: webhook-server
|
||
image: <your-docker-image>
|
||
ports:
|
||
- containerPort: 8080
|
||
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
|
||
spec:
|
||
ports:
|
||
- port: 443
|
||
targetPort: 8080
|
||
selector:
|
||
app: webhook-server
|
||
```
|
||
|
||
### 步骤 3: 创建 Webhook 配置
|
||
|
||
1. **创建 TLS 证书**:
|
||
- 为 Webhook 服务创建 TLS 证书,并将其存储在 Kubernetes Secret 中。
|
||
|
||
2. **创建 `MutatingWebhookConfiguration`**:
|
||
编写一个 YAML 文件,为 Kubernetes 注册 Webhook。
|
||
|
||
```yaml
|
||
apiVersion: admissionregistration.k8s.io/v1
|
||
kind: MutatingWebhookConfiguration
|
||
metadata:
|
||
name: pod-mutator
|
||
webhooks:
|
||
- name: pod-mutator.example.com
|
||
clientConfig:
|
||
service:
|
||
name: webhook-server
|
||
namespace: default
|
||
path: "/mutate"
|
||
caBundle: <base64-encoded-ca-cert>
|
||
rules:
|
||
- operations: ["CREATE"]
|
||
apiGroups: [""]
|
||
apiVersions: ["v1"]
|
||
resources: ["pods"]
|
||
admissionReviewVersions: ["v1"]
|
||
sideEffects: None
|
||
```
|
||
|
||
### 注意事项
|
||
|
||
- 确保 Webhook 服务的 TLS 证书正确配置,并与 `MutatingWebhookConfiguration` 中的 `caBundle` 匹配。
|
||
- 此示例只是一个简单的实现,生产环境中需要考虑安全性、性能优化和错误处理等方面。
|
||
- `caBundle` 应该是你的 CA 证书的 base64 编码。
|
||
|
||
通过上述步骤,你可以实现一个简单的 Admission Webhook,它在 Pod 创建时自动添加一个环境变量。这个示例可以作为实现更复杂逻辑的起点。 |