在 Kubernetes 的 Webhook 系統中,我們可以針對不同的資源操作進行精確控制。Webhook 可以監聽並干預各種資源的生命週期事件,包括:
webhook:
- name: validate-resource-operations.k8s.io
rules:
- operations:
- CREATE
- UPDATE
- DELETE
- CONNECT
apiGroups:
- "" # 核心 API 群組
apiVersions:
- "*" # 所有 API 版本
resources:
- pods
- services
- configmaps
- secrets
failurePolicy: Fail # 處理失敗時的策略
上面的 YAML 設定了一個定義 Webhook 的規則部分,它指定了:
- 操作型別:這個 Webhook 會攔截資源的建立、更新、刪除和連線操作
- API 群組:空字串
""
代表核心 API 群組,包含最基本的 Kubernetes 資源 - API 版本:
"*"
表示所有版本的 API - 資源型別:明確列出了需要攔截的資源型別,包括 Pod、Service、ConfigMap 和 Secret
- 失敗策略:設定為
Fail
,表示當 Webhook 處理失敗時,相關的 API 請求也會失敗
失敗策略的重要性
failurePolicy
是 Webhook 設定中的關鍵引數,它決定了當 Webhook 服務不可用或發生錯誤時系統的行為:
failurePolicy: Ignore # 可選值: Fail 或 Ignore
這個設定有兩個可能的值:
- Fail:當 Webhook 呼叫失敗時,API 請求會被拒絕。這是較為嚴格的設定,確保所有資源變更都必須透過 Webhook 驗證。
- Ignore:當 Webhook 呼叫失敗時,API 請求會被允許繼續處理。這提供了較高的可用性,但可能會在 Webhook 服務中斷時允許不符合規則的資源進入叢集。
Webhook 型別與應用場景
Kubernetes 支援兩種主要型別的 Webhook:
1. 驗證型 Webhook (Validating Webhook)
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: pod-policy-validator
webhooks:
- name: validate-pods.example.com
rules:
- apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
operations: ["CREATE", "UPDATE"]
clientConfig:
service:
namespace: webhook-system
name: webhook-service
path: "/validate-pods"
caBundle: <base64-encoded-ca-cert>
failurePolicy: Fail
sideEffects: None
admissionReviewVersions: ["v1"]
timeoutSeconds: 5
這個設定了一個定義驗證型 Webhook,它的主要功能是:
- 只針對 Pod 資源的建立和更新操作進行驗證
- 指定了 Webhook 服務的位置(namespace、service name 和路徑)
- 包含了 CA 憑證,用於 TLS 連線
- 設定了 5 秒的超時間
sideEffects: None
表示這個 Webhook 不會產生任何副作用admissionReviewVersions
指定了支援的 AdmissionReview API 版本
2. 修改型 Webhook (Mutating Webhook)
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: pod-modifier
webhooks:
- name: mutate-pods.example.com
rules:
- apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
operations: ["CREATE"]
clientConfig:
service:
namespace: webhook-system
name: webhook-service
path: "/mutate-pods"
caBundle: <base64-encoded-ca-cert>
failurePolicy: Ignore
sideEffects: None
admissionReviewVersions: ["v1"]
timeoutSeconds: 3
reinvocationPolicy: Never
這個設定了一個定義修改型 Webhook,它的特點是:
- 只針對 Pod 資源的建立操作進行修改
- 失敗策略設為
Ignore
,表示 Webhook 失敗時不會阻止 Pod 的建立 - 超時間設為 3 秒,比驗證型 Webhook 更短
reinvocationPolicy: Never
表示這個 Webhook 在同一個 AdmissionReview 請求中只會被呼叫一次
Webhook 執行順序與優先順序
在 Kubernetes 中,Webhook 的執行順序非常重要,特別是當有多個 Webhook 時:
- 所有的修改型 Webhook 先執行,按照它們的
metadata.name
字母順序排序 - 然後執行所有的驗證型 Webhook,同樣按照名稱排序
這意味著如果你需要控制 Webhook 的執行順序,可以透過命名約定來實作:
metadata:
name: "01-inject-sidecars" # 會先執行
metadata:
name: "02-validate-security" # 後執行
高階 Webhook 設定選項
1. 名稱空間選擇器
你可以使用名稱空間選擇器來限制 Webhook 只對特定名稱空間的資源生效:
namespaceSelector:
matchExpressions:
- key: environment
operator: In
values: ["production", "staging"]
這個選擇器會使 Webhook 只處理帶有 environment: production
或 environment: staging
標籤的名稱空間中的資源。這對於實作環境隔離的策略非常有用。
2. 物件選擇器
物件選擇器允許你根據資源的標籤來決定是否應用 Webhook:
objectSelector:
matchLabels:
app: critical-service
這個設定使 Webhook 只處理帶有 app: critical-service
標籤的資源,忽略其他資源。這對於只對特定應用程式實施策略非常有用。
3. 比對條件
在 Kubernetes v1.25+ 中,你可以使用更強大的比對條件:
matchConditions:
- name: "exclude-system-namespaces"
expression: "!(object.metadata.namespace.startsWith('kube-'))"
這個條件使用 CEL (Common Expression Language) 表示式來排除所有 kube-
開頭的名稱空間中的資源。這提供了比簡單選擇器更靈活的過濾機制。
Webhook 安全性考量
Webhook 在 Kubernetes 中扮演著關鍵的角色,因此安全性至關重要:
TLS 加密:所有 Webhook 通訊都應該使用 TLS 加密,透過
caBundle
欄位提供 CA 證書最小許可權原則:Webhook 服務應該只有執行其功能所需的最小許可權
超時設定:合理設定
timeoutSeconds
以避免 Webhook 延遲影響整個 API 伺服器高用性:關鍵的 Webhook 應該佈署為高用性設定,避免單點故障
稽核日誌:啟用稽核日誌來追蹤 Webhook 的決策和操作
實際應用案例:Pod 安全策略實施
以下是一個完整的例子,展示如何使用 Webhook 來實施 Pod 安全策略:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: pod-security-validator
webhooks:
- name: podsecurity.webhook.example.com
rules:
- apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
operations: ["CREATE", "UPDATE"]
clientConfig:
service:
namespace: security
name: pod-security-webhook
path: "/validate"
caBundle: <base64-encoded-ca-cert>
failurePolicy: Fail
namespaceSelector:
matchExpressions:
- key: pod-security.kubernetes.io/enforce
operator: In
values: ["baseline", "restricted"]
sideEffects: None
admissionReviewVersions: ["v1"]
timeoutSeconds: 2
這個設定建立了一個驗證型 Webhook,用於實施 Pod 安全策略:
- 它只針對 Pod 的建立和更新操作
- 只在帶有特定安全標籤的名稱空間中生效
- 失敗策略設為
Fail
,確保不符合安全要求的 Pod 無法被建立 - 超時間設為 2 秒,保證快速回應
- 透過服務設定指向實際執行驗證邏輯的 Webhook 服務
Webhook 服務實作範例
下面是一個簡單的 Go 語言 Webhook 服務實作,用於驗證 Pod 安全性:
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
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()
)
// 驗證 Pod 是否符合安全要求
func validatePod(pod *corev1.Pod) (bool, string) {
// 檢查是否有特權容器
for _, container := range pod.Spec.Containers {
if container.SecurityContext != nil &&
container.SecurityContext.Privileged != nil &&
*container.SecurityContext.Privileged {
return false, fmt.Sprintf("特權容器不允許: %s", container.Name)
}
}
// 檢查是否掛載了敏感主機路徑
for _, volume := range pod.Spec.Volumes {
if volume.HostPath != nil {
return false, fmt.Sprintf("不允許掛載主機路徑: %s", volume.Name)
}
}
return true, ""
}
// 處理 Webhook 請求
func handleValidate(w http.ResponseWriter, r *http.Request) {
var body []byte
if r.Body != nil {
if data, err := ioutil.ReadAll(r.Body); err == nil {
body = data
}
}
// 驗證內容型別
contentType := r.Header.Get("Content-Type")
if contentType != "application/json" {
http.Error(w, "無效的 Content-Type", http.StatusUnsupportedMediaType)
return
}
// 解析 AdmissionReview 請求
var admissionReview admissionv1.AdmissionReview
if _, _, err := deserializer.Decode(body, nil, &admissionReview); err != nil {
http.Error(w, fmt.Sprintf("無法解析請求: %v", err), http.StatusBadRequest)
return
}
// 確保請求有效
if admissionReview.Request == nil {
http.Error(w, "無效的 AdmissionReview 請求", http.StatusBadRequest)
return
}
// 解析 Pod 物件
var pod corev1.Pod
if err := json.Unmarshal(admissionReview.Request.Object.Raw, &pod); err != nil {
http.Error(w, fmt.Sprintf("無法解析 Pod: %v", err), http.StatusBadRequest)
return
}
// 驗證 Pod
allowed, reason := validatePod(&pod)
// 準備回應
response := admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{
Kind: "AdmissionReview",
APIVersion: "admission.k8s.io/v1",
},
Response: &admissionv1.AdmissionResponse{
UID: admissionReview.Request.UID,
Allowed: allowed,
},
}
// 如果不允許,增加原因
if !allowed {
response.Response.Result = &metav1.Status{
Message: reason,
}
}
// 回傳 JSON 回應
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func main() {
http.HandleFunc("/validate", handleValidate)
fmt.Println("啟動 Webhook 服務,監聽 8443 連線埠...")
if err := http.ListenAndServeTLS(":8443", "/certs/tls.crt", "/certs/tls.key", nil); err != nil {
panic(err)
}
}
這個 Go 程式實作了一個簡單的驗證型 Webhook 服務:
- 它接收 Kubernetes 的 AdmissionReview 請求
- 解析請求中的 Pod 物件
- 檢查 Pod 是否符合安全要求(不允許特權容器和主機路徑掛載)
- 回傳一個 AdmissionResponse,指示是否允許該 Pod
- 如果拒絕,提供拒絕的原因
- 服務使用 TLS 加密通訊,證書和金鑰從掛載的卷中讀取
Webhook 佈署最佳實踐
為了確保 Webhook 服務的可靠性和安全性,以下是一些佈署最佳實踐:
- 使用 Deployment 佈署:確保 Webhook 服務有多個副本,提高用性
apiVersion: apps/v1
kind: Deployment
metadata:
name: webhook-service
namespace: webhook-system
spec:
replicas: 2
selector:
matchLabels:
app: webhook-service
template:
metadata:
labels:
app: webhook-service
spec:
containers:
- name: webhook
image: webhook-image:latest
ports:
- containerPort: 8443
volumeMounts:
- name: webhook-certs
mountPath: /certs
readOnly: true
resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
readinessProbe:
httpGet:
path: /health
port: 8080
scheme: HTTP
volumes:
- name: webhook-certs
secret:
secretName: webhook-certs
- 使用 PodDisruptionBudget:確保在節點維護期間 Webhook 服務仍然可用
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: webhook-pdb
namespace: webhook-system
spec:
minAvailable: 1
selector:
matchLabels:
app: webhook-service
- 使用 NetworkPolicy:限制對 Webhook 服務的網路存取
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: webhook-network-policy
namespace: webhook-system
spec:
podSelector:
matchLabels:
app: webhook-service
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: TCP
port: 8443
- 自動證書管理:使用 cert-manager 自動管理 Webhook 的 TLS 證書
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: webhook-cert
namespace: webhook-system
spec:
secretName: webhook-certs
duration: 8760h # 1 年
renewBefore: 720h # 30 天
subject:
organizations:
- Example Org
isCA: false
privateKey:
algorithm: RSA
encoding: PKCS1
size: 2048
usages:
- server auth
dnsNames:
- webhook-service.webhook-system.svc
- webhook-service.webhook-system.svc.cluster.local
issuerRef:
name: webhook-issuer
kind: ClusterIssuer
故障排除與監控
有效監控和故障排除對於維護 Webhook 系統至關重要:
- 日誌收集:確保 Webhook 服務的日誌被收集並可搜尋
containers:
- name: webhook
image: webhook-image:latest
args:
- "--log-level=info"
- "--log-format=json"
- 指標監控:暴露 Prometheus 指標以監控 Webhook 效能
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
webhookRequests = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "webhook_requests_total",
Help: "Webhook 請求總數",
},
[]string{"operation", "resource", "allowed"},
)
webhookLatency = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "webhook_latency_seconds",
Help: "Webhook 處理延遲(秒)",
Buckets: prometheus.DefBuckets,
},
[]string{"operation", "resource"},
)
)
func init() {
prometheus.MustRegister(webhookRequests)
prometheus.MustRegister(webhookLatency)
}
func main() {
// ... 其他初始化程式碼 ...
// 暴露 Prometheus 指標
http.Handle("/metrics", promhttp.Handler())
// ... 其他程式碼 ...
}
- 告警設定:設定告警以在 Webhook 失敗時通知管理員
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: webhook-alerts
namespace: monitoring
spec:
groups:
- name: webhook.rules
rules:
- alert: WebhookHighErrorRate
expr: sum(rate(webhook_requests_total{allowed="false"}[5m])) / sum(rate(webhook_requests_total[5m])) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "Webhook 錯誤率高"
description: "Webhook 拒絕率超過 10%,請檢查日誌"
- alert: WebhookHighLatency
expr: histogram_quantile(0.95, sum(rate(webhook_latency_seconds_bucket[5m])) by (le)) > 0.5
for: 5m
labels:
severity: warning
annotations:
summary: "Webhook 延遲高"
description: "Webhook 95% 延遲超過 500ms,可能影響 API 伺服器效能"
進階 Webhook 使用案例
1. 動態資源修改
修改型 Webhook 可以在資源建立時自動注入設定,例如為所有 Pod 增加 sidecar 容器:
func createPatch(pod *corev1.Pod) ([]byte, error) {
// 建立一個 sidecar 容器
sidecar := corev1.Container{
Name: "logging-sidecar",
Image: "fluent/fluent-bit:latest",
Resources: corev1.ResourceRequirements{
Limits: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("100m"),
corev1.ResourceMemory: resource.MustParse("50Mi"),
},
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("10m"),
corev1.ResourceMemory: resource.MustParse("25Mi"),
},
},
}
// 建立 JSON 補丁
var patch []patchOperation
patch = append(patch, patchOperation{
Op: "add",
Path: "/spec/containers/-",
Value: sidecar,
})
return json.Marshal(patch)
}
2. 自定義策略實施
Webhook 可以實施組織特定的策略,例如強制要求所有資源都有特定標籤:
func validateLabels(obj metav1.Object) (bool, string) {
labels := obj.GetLabels()
// 檢查必要的標籤
if _, hasTeam := labels["team"]; !hasTeam {
return false, "缺少必要的 'team' 標籤"
}
if _, hasApp := labels["app"]; !hasApp {
return false, "缺少必要的 'app' 標籤"
}
if _, hasEnv := labels["environment"]; !hasEnv {
return false, "缺少必要的 'environment' 標籤"
}
return true, ""
}
3. 成本控制與資源限制
Webhook 可以用於實施資源限制,控制叢整合本:
func validateResourceLimits(pod *corev1.Pod) (bool, string) {
namespace := pod.Namespace
// 根據名稱空間確定資源限制
var maxCPU, maxMemory resource.Quantity
switch {
case strings.HasPrefix(namespace, "dev-"):
maxCPU = resource.MustParse("500m")
maxMemory = resource.MustParse("1Gi")
case strings.HasPrefix(namespace, "staging-"):
maxCPU = resource.MustParse("2000m")
maxMemory = resource.MustParse("4Gi")
case strings.HasPrefix(namespace, "prod-"):
maxCPU = resource.MustParse("4000m")
maxMemory = resource.MustParse("8Gi")
default:
maxCPU = resource.MustParse("200m")
maxMemory = resource.MustParse("512Mi")
}
// 檢查每個容器的資源請求
for _, container := range pod.Spec.Containers {
if container.Resources.Requests == nil {
return false, fmt.Sprintf("容器 %s 缺少資源請求", container.Name)
}
cpuRequest := container.Resources.Requests[corev1.ResourceCPU]
memoryRequest := container.Resources.Requests[corev1.ResourceMemory]
if cpuRequest.Cmp(maxCPU) > 0 {
return false, fmt.Sprintf("容器 %s CPU 請求 %s 超過限制 %s",
container.Name, cpuRequest.String(), maxCPU.String())
}
if memoryRequest.Cmp(maxMemory) > 0 {
return false, fmt.Sprintf("容器 %s 記憶體請求 %s 超過限制 %s",
container.Name, memoryRequest.String(), maxMemory.String())
}
}
return true, ""
}
Kubernetes Webhook 系統提供了強大的擴充套件機制,讓管理員和開發者能夠實施自定義的策略和自動化。透過正確設定 Webhook 的操作範圍、API 群組、資源型別和失敗策略,可以精確控制 Webhook 的行為。在實際佈署中,應該注意 Webhook 的效能、可用性和安全性,確保它們不會成為 Kubernetes API 伺服器的瓶頸。透過遵循本文介紹的最佳實踐,你可以構建強大、可靠的 Webhook 系統,為你的 Kubernetes 叢集增加自定義的控制和自動化能力。
深入解析 Kubernetes 准入控制器:原理、實作與最佳實踐
准入控制器的核心概念
准入控制器是 Kubernetes API 伺服器中的關鍵元件,負責在資源持久化到 etcd 之前對請求進行攔截和檢查。它們就像是 Kubernetes 叢集的守門員,確保只有符合特定規則和政策的資源才能被建立、修改或刪除。
准入控制器的工作流程分為兩個階段:
- 變更准入控制(Mutating Admission Control):可以修改請求的資源物件
- 驗證准入控制(Validating Admission Control):只能驗證請求的資源物件,不能修改
這種雙階段設計使得 Kubernetes 能夠在保證安全性的同時提供靈活的資源處理機制。
准入控制器的設計體現了 Kubernetes 的防禦縱深策略。透過將控制分為變更和驗證兩個階段,系統能夠先對資源進行必要的修改(如注入 sidecar 容器),再進行嚴格的政策驗證。這種分離設計也符合單一職責原則,使得每個控制器的邏輯更加清晰和可維護。
准入控制器的型別
Kubernetes 中的准入控制器可以分為兩大類別:
- 內建准入控制器:直接編譯到 API 伺服器中,透過啟動引數設定
- 動態准入控制器:以 Webhook 形式佈署,可以在不重啟 API 伺服器的情況下動態設定
內建准入控制器包括 LimitRanger
、ResourceQuota
、ServiceAccount
等,而動態准入控制器則透過 MutatingWebhookConfiguration
和 ValidatingWebhookConfiguration
資源進行設定。
內建准入控制器雖然功能強大,但缺乏靈活性,每次修改都需要重啟 API 伺服器。而動態准入控制器(Webhook)則彌補了這一缺陷,使得開發者可以根據自身需求實作自定義的准入邏輯,並且能夠在執行時動態調整。這種設計反映了 Kubernetes 的擴充套件性理念,允許使用者在不修改核心程式碼的情況下擴充套件系統功能。
實作自定義准入 Webhook
讓我們來實作一個簡單的驗證准入 Webhook,它將確保所有 Pod 都有特定的標籤。
首先,我們需要建立一個 Webhook 伺服器:
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
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()
)
// 定義 Webhook 伺服器
type WebhookServer struct {
server *http.Server
}
// 處理准入請求
func (ws *WebhookServer) validate(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
req := ar.Request
// 解析 Pod 物件
var pod corev1.Pod
if err := json.Unmarshal(req.Object.Raw, &pod); err != nil {
return &admissionv1.AdmissionResponse{
Result: &metav1.Status{
Message: err.Error(),
},
}
}
// 檢查是否有必要的標籤
if _, ok := pod.Labels["app"]; !ok {
return &admissionv1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Message: "Pod 必須包含 'app' 標籤",
},
}
}
// 允許請求
return &admissionv1.AdmissionResponse{
Allowed: true,
}
}
// 處理 HTTP 請求
func (ws *WebhookServer) serveValidate(w http.ResponseWriter, r *http.Request) {
var body []byte
if r.Body != nil {
if data, err := ioutil.ReadAll(r.Body); err == nil {
body = data
}
}
// 驗證 Content-Type
contentType := r.Header.Get("Content-Type")
if contentType != "application/json" {
http.Error(w, "Content-Type 必須是 application/json", http.StatusUnsupportedMediaType)
return
}
// 解析 AdmissionReview
var admissionReview admissionv1.AdmissionReview
if _, _, err := deserializer.Decode(body, nil, &admissionReview); err != nil {
http.Error(w, fmt.Sprintf("無法解析請求: %v", err), http.StatusBadRequest)
return
}
// 處理請求
var responseObj runtime.Object
if admissionReview.Request != nil {
admissionResponse := ws.validate(&admissionReview)
admissionReview.Response = admissionResponse
admissionReview.Response.UID = admissionReview.Request.UID
responseObj = &admissionReview
}
// 傳送回應
resp, err := json.Marshal(responseObj)
if err != nil {
http.Error(w, fmt.Sprintf("無法編碼回應: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(resp)
}
func main() {
ws := &WebhookServer{
server: &http.Server{
Addr: ":8443",
},
}
// 設定路由
mux := http.NewServeMux()
mux.HandleFunc("/validate", ws.serveValidate)
ws.server.Handler = mux
// 啟動伺服器
fmt.Println("啟動伺服器...")
if err := ws.server.ListenAndServeTLS("/etc/webhook/certs/tls.crt", "/etc/webhook/certs/tls.key"); err != nil {
fmt.Printf("啟動伺服器失敗: %v", err)
}
}
這段程式碼實作了一個基本的驗證准入 Webhook 伺服器。它接收來自 Kubernetes API 伺服器的 AdmissionReview 請求,解析出 Pod 物件,然後檢查該 Pod 是否包含 “app” 標籤。如果沒有,則拒絕請求並回傳錯誤訊息。
關鍵部分包括:
- 使用
deserializer.Decode()
解析 AdmissionReview 物件 - 使用
json.Unmarshal()
將原始 Pod 資料轉換為結構化物件 - 檢查 Pod 標籤並回傳相應的 AdmissionResponse
- 設定 HTTPS 伺服器以接收 Webhook 請求
這個實作展示了准入 Webhook 的基本工作流程,但在生產環境中還需要考慮錯誤處理、日誌記錄、監控等方面。
設定 Webhook
接下來,我們需要在 Kubernetes 中設定 ValidatingWebhookConfiguration 來啟用我們的 Webhook:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: pod-label-validator
webhooks:
- name: pod-label.example.com
clientConfig:
service:
name: pod-label-validator
namespace: default
path: "/validate"
caBundle: ${CA_BUNDLE}
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
scope: "Namespaced"
sideEffects: None
admissionReviewVersions: ["v1"]
failurePolicy: Fail
timeoutSeconds: 5
這個 YAML 設定了一個定義 ValidatingWebhookConfiguration 資源,它告訴 Kubernetes API 伺服器在建立或更新 Pod 時呼叫我們的 Webhook。
關鍵設定包括:
clientConfig
:指定 Webhook 伺服器的位置,這裡使用 Service 參照rules
:定義哪些資源和操作會觸發 WebhooksideEffects
:宣告 Webhook 沒有副作用,這是 v1 版本的必要欄位failurePolicy
:定義當 Webhook 不可用時的行為,這裡設定為 Fail(拒絕請求)timeoutSeconds
:設定 API 伺服器等待 Webhook 回應的最長時間
${CA_BUNDLE}
需要替換為 Webhook 伺服器 TLS 證書的 CA 證書,通常透過 base64 編碼後注入。
佈署 Webhook 伺服器
為了佈署我們的 Webhook 伺服器,我們需要建立以下 Kubernetes 資源:
apiVersion: v1
kind: Service
metadata:
name: pod-label-validator
namespace: default
spec:
selector:
app: pod-label-validator
ports:
- port: 443
targetPort: 8443
apiVersion: v1
kind: ConfigMap
metadata:
name: opa-policies
namespace: opa
data:
pod-security.rego: |
package kubernetes.admission
deny[msg] {
input.request.kind.kind == "Pod"
input.request.operation == "CREATE"
container := input.request.object.spec.containers[_]
container.securityContext.privileged == true
msg := sprintf("privileged container is not allowed: %v", [container.name])
}
deny[msg] {
input.request.kind.kind == "Pod"
input.request.operation == "CREATE"
not input.request.object.metadata.labels.app
msg := "pod must have app label"
}
---
apiVersion: v1
kind: Service
metadata:
name: opa
namespace: opa
spec:
selector:
app: opa
ports:
- port: 443
targetPort: 8181
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: opa-validating-webhook
webhooks:
- name: validating-webhook.openpolicyagent.org
clientConfig:
service:
name: opa
namespace: opa
path: "/v1/data/kubernetes/admission/deny"
caBundle: ${CA_BUNDLE}
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
scope: "Namespaced"
sideEffects: None
admissionReviewVersions: ["v1"]
failurePolicy: Fail
這個 YAML 設定展示瞭如何佈署 OPA 作為准入控制器:
- 建立一個執行 OPA 的 Deployment
- 使用 ConfigMap 儲存 Rego 策略
- 建立一個 Service 暴露 OPA API
- 設定 ValidatingWebhookConfiguration 將准入請求傳送到 OPA
Rego 策略定義了兩個規則:
- 禁止使用特權容器
- 要求 Pod 必須有 app 標籤
使用 OPA 的優勢在於策略可以與程式碼分離,並使用宣告式語言定義,使得非開發人員也能理解和修改策略。
與 Kyverno 整合
Kyverno 是一個專為 Kubernetes 設計的策略引擎,提供了更簡單的策略定義方式:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
spec:
validationFailureAction: enforce
rules:
- name: require-app-label
match:
resources:
kinds:
- Pod
validate:
message: "Pod 必須包含 app 標籤"
pattern:
metadata:
labels:
app: "?*"
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-privileged
spec:
validationFailureAction: enforce
rules:
- name: no-privileged-containers
match:
resources:
kinds:
- Pod
validate:
message: "不允許使用特權容器"
pattern:
spec:
containers:
- name: "*"
securityContext:
privileged: "false"
這個 YAML 設定展示瞭如何使用 Kyverno 定義策略:
- 建立一個要求 Pod 包含 app 標籤的 ClusterPolicy
- 建立一個禁止使用特權容器的 ClusterPolicy
與 OPA 相比,Kyverno 的策略定義更接近 Kubernetes 資源的結構,使用模式比對而非程式設計語言,更容易上手。Kyverno 還提供了變更策略、生成策略等功能,可以實作更複雜的自動化。
准入控制器的未來發展
隨著 Kubernetes 的不斷發展,准入控制器也在不斷演進:
- CEL 表示式支援:Kubernetes 1.23 引入了對 CEL(Common Expression Language)的支援,使得可以在 ValidatingAdmissionPolicy 中使用表示式進行驗證,無需編寫 Webhook
- 策略引擎整合:更多的策略引擎(如 OPA、Kyverno)與 Kubernetes 的整合,提供更豐富的策略管理功能
- 安全性增強:更多關注於安全性的准入控制器,如 Pod Security Admission
- 自動化工具:更多工具用於生成和管理准入控制器設定
准入控制器作為 Kubernetes 安全和治理的關鍵元件,將繼續在雲原生態系統中發揮重要作用。透過深入理解和靈活運用准入控制器,我們可以構建更安全、更可靠的 Kubernetes 環境。
在實際應用中,准入控制器應該是多層防禦策略的一部分,與網路政策、RBAC、Pod 安全標準等機制結合使用,共同構建完整的安全架構。同時,應該根據組織的需求和安全策略,選擇適當的准入控制器實作方式,並確保其高用性和可靠性。
透過本文的介紹,玄貓希望能幫助讀者理解 Kubernetes 准入控制器的工作原理、實作方法和最佳實踐,從而更好地利用這一強大機制來保護和管理 Kubernetes 環境。
物件被接受
當 Kubernetes 的 Webhook 驗證透過時,物件會被接受並進入系統。這是 Webhook 處理流程中的關鍵環節,也是確保系統安全性和一致性的重要步驟。
物件接受後的處理流程
當一個物件透過 Webhook 驗證並被接受後,Kubernetes 會繼續進行以下處理:
- 將物件持久化到 etcd 資料函式庫
- 通知相關控制器進行後續處理
- 更新系統狀態以反映新物件的存在
- 觸發任何相關的事件和通知
這個過程確保了只有符合系統要求的物件才能進入 Kubernetes 生態系統,維護了整體系統的穩定性和安全性。
副作用(Side Effects)標記
sideEffects
欄位用於標記 Webhook 是否可能產生副作用。這是一個重要的設定引數,它告訴 Kubernetes API 伺服器如何處理對 Webhook 的呼叫。
sideEffects: None
副作用值的選項
Webhook 的 sideEffects
欄位可以設定為以下值:
- none:表示 Webhook 不會產生任何副作用,API 伺服器可以安全地多次呼叫它
- NoneOnDryRun:表示當請求包含
dryRun
引數時,Webhook 不會產生副作用 - Some:表示 Webhook 可能會產生副作用,即使是在
dryRun
模式下
這個設定非常重要,因為它決定了 API 伺服器如何處理 Webhook 呼叫。如果設定為 None
,API 伺服器知道可以安全地多次呼叫 Webhook,這在高用性環境中特別有用。而 NoneOnDryRun
則允許在正常操作中有副作用,但在 dryRun
模式下不會產生副作用,這對於測試和驗證非常有用。
實際應用中的副作用處理
在實際應用中,副作用處理是一個重要的考量因素。以下是一個範例,說明如何在 ValidatingWebhookConfiguration 中正確設定副作用:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: validation-webhook
webhooks:
- name: validation.example.com
clientConfig:
service:
namespace: webhook-namespace
name: webhook-service
path: "/validate"
caBundle: <base64-encoded-ca-cert>
rules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
scope: "Namespaced"
sideEffects: None
admissionReviewVersions: ["v1", "v1beta1"]
timeoutSeconds: 5
在這個設定中,我們明確指定了 sideEffects: None
,這告訴 Kubernetes API 伺服器這個 Webhook 不會產生任何副作用。這意味著 API 伺服器可以安全地多次呼叫這個 Webhook,例如在重試或高用性場景中。這對於保持系統的可靠性和一致性非常重要。
副作用與系統穩定性
正確處理副作用對於維護 Kubernetes 系統的穩定性至關重要。如果 Webhook 可能產生副作用(如更新外部系統或傳送通知),但被標記為 None
,可能會導致意外的行為,因為 API 伺服器可能會多次呼叫 Webhook。
相反,如果 Webhook 實際上沒有副作用,但被標記為 Some
,則可能會導致不必要的限制,影響系統的效能和可用性。
最佳實踐
在設計和實作 Webhook 時,應遵循以下最佳實踐:
- 明確定義副作用:清楚瞭解你的 Webhook 是否會產生副作用
- 正確設定 sideEffects:根據 Webhook 的實際行為正確設定
sideEffects
欄位 - 設計無副作用的 Webhook:盡可能設計無副作用的 Webhook,以提高系統的可靠性和可用性
- 處理重複呼叫:如果 Webhook 可能被多次呼叫,確保它能夠正確處理重複呼叫
超時設定與失敗策略
在 Webhook 設定中,超時設定和失敗策略是確保系統穩定性的關鍵因素。
超時設定
timeoutSeconds
欄位定義了 API 伺服器等待 Webhook 回應的最長時間。如果 Webhook 在指定時間內沒有回應,API 伺服器會根據 failurePolicy
的設定來決定如何處理請求。
timeoutSeconds: 5
這個設定表示 API 伺服器會等待最多 5 秒鐘來取得 Webhook 的回應。
超時設定是一個關鍵的效能和可靠性引數。設定太短可能導致正常的 Webhook 處理被錯誤地視為超時;設定太長則可能在 Webhook 出現問題時導致 API 伺服器等待過長時間,影響整體系統效能。通常建議設定一個合理的值,如 5 秒,這在大多數情況下足夠 Webhook 完成處理,同時又不會在出現問題時導致過長的等待。
失敗策略
failurePolicy
欄位定義了當 Webhook 呼叫失敗時(例如網路問題或超時)API 伺服器應該採取的行動。
failurePolicy: Fail
可選的值包括:
- Fail:如果 Webhook 呼叫失敗,則拒絕請求
- Ignore:如果 Webhook 呼叫失敗,則忽略 Webhook 並允許請求繼續
失敗策略的選擇取決於 Webhook 在系統中的重要性。如果 Webhook 執行的是關鍵的安全檢查,那麼 Fail
可能是更安全的選擇,因為它確保只有經過 Webhook 驗證的請求才能被處理。然而,這也意味著如果 Webhook 服務出現問題,可能會影響整個系統的可用性。
相反,如果 Webhook 執行的是非關鍵的檢查,或者在某些情況下可以被安全地跳過,那麼 Ignore
可能是更好的選擇,因為它提高了系統的可用性。
完整設定範例
以下是一個包含超時設定和失敗策略的完整 ValidatingWebhookConfiguration 範例:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: validation-webhook
webhooks:
- name: validation.example.com
clientConfig:
service:
namespace: webhook-namespace
name: webhook-service
path: "/validate"
caBundle: <base64-encoded-ca-cert>
rules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
scope: "Namespaced"
sideEffects: None
admissionReviewVersions: ["v1", "v1beta1"]
timeoutSeconds: 5
failurePolicy: Fail
這個設定設定了一個驗證 Webhook,它會檢查 apps/v1
API 組中的 Deployment 資源的建立和更新操作。Webhook 沒有副作用,API 伺服器會等待最多 5 秒鐘來取得回應,如果呼叫失敗,請求將被拒絕。這種設定適合執行關鍵安全檢查的 Webhook,因為它確保只有經過 Webhook 驗證的請求才能被處理。