什么是 MutatingWebhook?
Kubernetes 中的 MutatingWebhook 是一种 Webhook,允许在将资源保留在 Kubernetes API 服务器中之前对其进行修改。这可用于在创建或更新资源时对资源实施某些策略或配置。
例如,MutatingWebhook 可用于自动向 Kubernetes pod 添加特定标签或注释,或将环境变量注入容器。Webhook 由特定事件(例如创建或更新)触发,并在持久化之前修改对象。
MutatingWebhooks 在 Kubernetes MutatingWebhookConfiguration 对象中定义,该对象指定 Webhook 应用的资源类型、调用 Webhook 的端点以及触发 Webhook 的操作类型(创建、更新、删除)。
一旦定义了 webhook 并将其注册到 API 服务器,当创建、更新或删除匹配的资源时,它将自动调用。它可以根据其验证过程接受或拒绝资源。
以下是向 pod 添加标签的 MutatingWebhook 清单文件示例:
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: add-label-webhook
webhooks:
- name: add-label.example.com
clientConfig:
url: https://webhook.example.com/add-label
caBundle:
...
rules:
- operations: [ "CREATE", "UPDATE" ]
apiGroups: ["", "apps", "batch"]
apiVersions: ["v1", "v1beta1", "v1beta2"]
resources: ["pods"]
failurePolicy: Ignore
值得注意的是,这只是一个示例,确切的配置将取决于具体的用例和要求。
MutatingWebhook 的 Kubernetes 清单文件通常包含以下字段:
apiVersion:资源所属的 Kubernetes API 的版本。
kind:清单中定义的资源类型。对于 MutatingWebhook,这将是“MutatingWebhookConfiguration”。
元数据:有关资源的元数据,包括其名称和标签。
webhooks:webhooks 数组,每个 webhooks 包含以下字段:
- name : webhook 的唯一名称
- clientConfig:调用 webhook 的客户端的配置,包括 url 端点和用于验证 webhook 的服务器证书的 caBundle。caBundle 是一个 PEM 编码的 CA 包,将用于验证 webhook 服务器的证书。
- Rules:用于触发 Webhook 的规则数组,包括 Webhook 所应用的操作(创建、更新、删除)和资源。
- failurePolicy:处理失败的策略,例如 Ignore 或 Fail。
在 Kubernetes 中改变 Webhook 的一些其他用例包括:
- 自动 sidecar 注入:mutating webhook 可用于自动将 sidecar 容器注入到 pod 中,例如提供日志记录或监控功能。
- 自动配置注入:mutating Webhook 可用于自动将配置文件或环境变量注入容器中,例如提供数据库连接详细信息或 API 密钥。
- 自动安全增强:mutating Webhook 可用于自动向 pod 添加安全相关配置,例如添加安全上下文或配置网络策略。
- 自动缩放:可以使用 mutating webhook 根据 pod 的资源使用情况自动配置水平 pod 自动缩放器 (HPA)。
- 自动修补:mutating Webhook 可用于自动修补运行易受攻击的软件版本的容器。
- 自动验证:mutating Webhook 可用于在创建 pod 或服务之前自动验证其配置,如果不满足某些条件则拒绝请求。
以下是 YAML 中的变异 Webhook 清单文件的示例,该文件将标签注入到 my-namespace 命名空间中的 pod 中:
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: inject-labels-webhook
webhooks:
- name: inject-labels.example.com
rules:
- apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
namespaceSelector:
matchExpressions:
- {key: "name", operator: "In", values: ["core-services"]}
failurePolicy: Fail
clientConfig:
service:
name: webhook-service
namespace: webhook-namespace
path: "/inject"
caBundle: <base64 encoded ca bundle>
此清单创建一个名为ject-labels-webhook_的MutatingWebhook 配置和一个名为_inject-labels.example.com_的Webhook 。webhook 配置为仅对**v1 apiGroup 中的 pod 进行变异,如果变异失败则失败。Webhook 还配置为仅改变 my-namespace 命名空间中的 pod。Webhook 由 webhook-namespace 命名空间中名为 webhook-service 的服务支持,并调用该服务上的/inject路径。clientConfig 还包含一个 caBundle 字段,该字段应包含用于保护 Webhook 服务的证书的 Base64 编码 CA 捆绑包。 此清单使用 namespaceSelector 字段来指定 webhook 应该仅改变 my-namespace 命名空间中的 pod。matchExpressions 字段用于指定名称标签应使用 In 运算符与值 my-namespace 匹配。这意味着 webhook 只会在 my-namespace 命名空间中的 pod 资源上调用。
以下是Go 中 mutating Webhook 示例,它将标签注入到 pod 中:
package main
import (
"encoding/json"
"fmt"
"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()
)
func main() {
http.HandleFunc("/inject", inject)
http.ListenAndServe(":8080", nil)
}
func inject(w http.ResponseWriter, r *http.Request) {
var body []byte
if r.Body != nil {
if data, err := ioutil.ReadAll(r.Body); err == nil {
body = data
}
}
if len(body) == 0 {
http.Error(w, "no body found", http.StatusBadRequest)
return
}
contentType := r.Header.Get("Content-Type")
if contentType != "application/json" {
http.Error(w, "invalid Content-Type, want `application/json`", http.StatusUnsupportedMediaType)
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 = mutatePods(ar)
}
admissionReview := admissionv1.AdmissionReview{}
if admissionResponse != nil {
admissionReview.Response = admissionResponse
if ar.Request != nil {
admissionReview.Response.UID = ar.Request.UID
}
}
resp, err := json.Marshal(admissionReview)
if err != nil {
http.Error(w, fmt.Sprintf("could not encode response: %v", err), http.StatusInternalServerError)
}
if _, err := w.Write(resp); err != nil {
http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
}
}
func mutatePods(ar admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
req := ar.Request
var pod corev1.Pod