mutatingwebhook的简单实例

news/2025/3/16 20:36:28/文章来源:https://www.cnblogs.com/buttertofree/p/18775588

一. k8s集群准备

这里不再赘述k8s集群搭建。主要注意参数:

kubectl get po kube-apiserver-server -n kube-system -o yaml | grep plugin

 

预期结果为:- --enable-admission-plugins=NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook

至少要拥有两个参数:MutatingAdmissionWebhook,ValidatingAdmissionWebhook 这是控制webhook特性启用的。

修改一下启动配置文件,即进入/etc/kubernetes/manifests/kube-apiserver.yaml,修改对应的启动项为预期值。
 
 
二.生成自签证书
使用下面的脚本
#!/bin/bash# 配置参数
NAMESPACE="default"
SERVICE_NAME="webhook-svc"
SECRET_NAME="webhook-tls"
CN="${SERVICE_NAME}.${NAMESPACE}.svc"
SAN="DNS:${CN},DNS:${SERVICE_NAME}.${NAMESPACE}.svc.cluster.local"# 清理旧文件并创建目录
rm -rf certs
mkdir -p certs
cd certs# 步骤1: 生成 CA 私钥和证书
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -days 3650 -out ca.crt -subj "/CN=Webhook Root CA"# 步骤2: 生成服务器私钥和 CSR(含 SAN 扩展)
cat <<EOF > openssl.cnf
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
prompt = no[req_distinguished_name]
CN = ${CN}[v3_req]
keyUsage = keyEncipherment, digitalSignature
extendedKeyUsage = serverAuth
subjectAltName = @alt_names[alt_names]
DNS.1 = ${SERVICE_NAME}
DNS.2 = ${SERVICE_NAME}.${NAMESPACE}
DNS.3 = ${SERVICE_NAME}.${NAMESPACE}.svc
DNS.4 = ${SERVICE_NAME}.${NAMESPACE}.svc.cluster.local
EOF# 生成服务器私钥和 CSR
openssl genrsa -out tls.key 2048
openssl req -new -key tls.key -out server.csr -config openssl.cnf# 步骤3: 使用 CA 签发服务器证书
openssl x509 -req -in server.csr \-CA ca.crt \-CAkey ca.key \-CAcreateserial \-out tls.crt \-days 365 \-extensions v3_req \-extfile openssl.cnf# 步骤4: 创建 Secret(包含 CA 和服务器证书)
kubectl create secret generic ${SECRET_NAME} \--from-file=tls.crt=tls.crt \--from-file=tls.key=tls.key \--from-file=ca.crt=ca.crt \-n ${NAMESPACE} --dry-run=client -o yaml > ./deploy/secret.yamlecho "Secret YAML 已保存到 certs/secret.yaml"
echo "请执行以下步骤:"
echo "1. 部署 Secret: kubectl apply -f certs/secret.yaml"
echo "2. 在 MutatingWebhookConfiguration 中设置 caBundle: $(cat ca.crt | base64 -w0)"

根据自签发ca生成证书。

目录为./certs,实际有3个文件是我们使用到的。

其中ca.crt是要后续设置在MutatingWebhookConfiguration资源中的caBundle字段(base64 -d)

tls.crt和tls.key作为secret挂载到webhookpod中,作为服务端的tls证书。

 

3.代码

package mainimport ("encoding/json""flag""fmt""github.com/gin-gonic/gin""github.com/golang/glog""io"admissionv1 "k8s.io/api/admission/v1"admissionregistrationv1 "k8s.io/api/admissionregistration/v1"appsv1 "k8s.io/api/apps/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""k8s.io/apimachinery/pkg/types"utilruntime "k8s.io/apimachinery/pkg/util/runtime""log""net/http""strconv"
)var (scheme       = runtime.NewScheme()codecs       = serializer.NewCodecFactory(scheme)deserializer = codecs.UniversalDeserializer()
)const patchType = admissionv1.PatchTypeJSONPatchfunc init() {addToScheme(scheme)
}
func addToScheme(scheme *runtime.Scheme) {utilruntime.Must(corev1.AddToScheme(scheme))utilruntime.Must(appsv1.AddToScheme(scheme))utilruntime.Must(admissionv1.AddToScheme(scheme))utilruntime.Must(admissionregistrationv1.AddToScheme(scheme))
}type webhookServer struct {router  *gin.Engineport    inttlsCert stringtlsKey  string
}func newWebhookServer() *webhookServer {return &webhookServer{}
}func (ws *webhookServer) init() {ws.router = gin.Default()flag.IntVar(&ws.port, "port", 8443, "Webhook server port")//默认值先改一下//flag.StringVar(&ws.tlsCert, "tls-cert", "/etc/webhook/tls.cert", "TLS certificate")//flag.StringVar(&ws.tlsKey, "tls-key", "/etc/webhook/tls.key", "TLS key")flag.StringVar(&ws.tlsCert, "tls-cert", "./certs/tls.crt", "TLS certificate")flag.StringVar(&ws.tlsKey, "tls-key", "./certs/tls.key", "TLS key")flag.Parse()
}func main() {//初始化了webhook服务器配置包括证书svr := newWebhookServer()svr.init()log.Printf("run server...: port=%d, cert=%s, key=%s\n", svr.port, svr.tlsCert, svr.tlsKey)//注册路由 /mutate 逻辑在mutateHandler函数中svr.router.POST("/mutate", mutateHandler)//run httpserr := svr.router.RunTLS(":"+strconv.Itoa(svr.port), svr.tlsCert, svr.tlsKey)if err != nil {glog.Fatalf("webhook server run failed, error: %v", err)return}}func mutateHandler(c *gin.Context) {// 1. 读取原始请求体body, err := io.ReadAll(c.Request.Body)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": "failed to read request body"})return}log.Printf("Raw request body:\n%s", string(body))//校验 请求头中必须要带有 application/jsoncontentType := c.Request.Header.Get("Content-Type")if contentType != "application/json" {log.Printf("contentType=%s, expect application/json", contentType)return}// 2. 反序列化为 AdmissionReview 对象//req请求体反序列化之后是decodeObj runtime.object 所有k8s资源实现的接口decodeObj, gvk, err := deserializer.Decode(body, nil, nil)if err != nil {log.Printf("failed to deserialize object: %v", err)c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request format"})return}//构建响应var responseObj runtime.Object //集群中所有资源实现的接口//依据gvk判断不接受admissionReview以外的请求switch *gvk {case admissionv1.SchemeGroupVersion.WithKind("AdmissionReview")://将接口断言成*admissionReview对象reqAdmissionReview, ok := decodeObj.(*admissionv1.AdmissionReview)if !ok {log.Printf("expect AdmissionReview, but got %#v", gvk)c.JSON(http.StatusBadRequest, gin.H{"error": "failed to deserialize AdmissionReview"})return}respAdmissionReview := &admissionv1.AdmissionReview{Response: &admissionv1.AdmissionResponse{UID: reqAdmissionReview.Request.UID,},}respAdmissionReview.SetGroupVersionKind(*gvk)respAdmissionReview.Response = admitFunc(reqAdmissionReview)responseObj = respAdmissionReviewdefault:log.Printf("Unsupported gvk  %#v", gvk)c.JSON(http.StatusBadRequest, gin.H{"error": "failed to deserialize AdmissionReview"})return}responseByte, err := json.Marshal(responseObj)if err != nil {log.Println("Can't encode response:", err)c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to encode response"})return}c.Data(http.StatusOK, "application/json", responseByte)
}// 当创建deploy,sts, 控制器的时候,自动将replicas改成3
func admitFunc(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {if ar.Request == nil {return admissionErrorResponse("empty request.")}var patch []bytevar err errorswitch ar.Request.Kind.Kind {case "Deployment":patch, err = processReplicas(ar.Request.Object.Raw, 3, &appsv1.Deployment{})case "StatefulSet":patch, err = processReplicas(ar.Request.Object.Raw, 3, &appsv1.StatefulSet{})default:admissionAllowResp(ar.Request.UID)}if err != nil {return admissionErrorResponse(err.Error())}var pt admissionv1.PatchTypept = patchTypereturn &admissionv1.AdmissionResponse{Allowed:   true,UID:       ar.Request.UID,Patch:     patch,PatchType: &pt,}}func processReplicas(raw []byte, desiredReplicas int32, obj runtime.Object) ([]byte, error) {_, _, err := deserializer.Decode(raw, nil, obj)if err != nil {return nil, err}var currentReplicas *int32switch v := obj.(type) {case *appsv1.Deployment:currentReplicas = v.Spec.Replicascase *appsv1.StatefulSet:currentReplicas = v.Spec.Replicasdefault:return nil, fmt.Errorf("unknown type: %T", obj)}if *currentReplicas != desiredReplicas {patch := []map[string]interface{}{{"op":    "replace","path":  "/spec/replicas","value": desiredReplicas,},}return json.Marshal(patch)}return nil, nil}func admissionErrorResponse(msg string) *admissionv1.AdmissionResponse {return &admissionv1.AdmissionResponse{Allowed: false,Result: &metav1.Status{Code:    http.StatusBadRequest,Message: msg,},}
}func admissionAllowResp(uid types.UID) *admissionv1.AdmissionResponse {return &admissionv1.AdmissionResponse{Allowed: true,UID:     uid,}
}

这里实现的是将deployment,statefulset控制器的副本数强制改为3的webhook

webhook作为https服务器,接受apiserver的请求,请求体是admissionv1.AdmissionReview

实际的结构为:

type AdmissionReview struct {metav1.TypeMeta `json:",inline"`// Request describes the attributes for the admission request.// +optionalRequest *AdmissionRequest `json:"request,omitempty" protobuf:"bytes,1,opt,name=request"`// Response describes the attributes for the admission response.// +optionalResponse *AdmissionResponse `json:"response,omitempty" protobuf:"bytes,2,opt,name=response"`
}

可以看到实际资源其实就是将http的request和response放在一起组合成的。

request和response中的uid指向同一个值,返回的AdmissionReview中的response应该带有和request的同一个uid

 

四.部署

Dockerfile

FROM alpine:latest
USER rootCOPY replicasctrl /usr/local/bin/replicasctrl# set entrypoint
ENTRYPOINT ["/usr/local/bin/replicasctrl"]

MutatingWebhookConfiguration.yaml

kind: MutatingWebhookConfiguration
apiVersion: admissionregistration.k8s.io/v1
metadata:name: replicas-mutating-webhook
webhooks:- admissionReviewVersions:- v1clientConfig:caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURGVENDQWYyZ0F3SUJBZ0lVR3phSXQvQkt5enRlU1pJZVJRL0E3Sy9BMlVnd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0dqRVlNQllHQTFVRUF3d1BWMlZpYUc5dmF5QlNiMjkwSUVOQk1CNFhEVEkxTURNeE5URXlNekF4TUZvWApEVE0xTURNeE16RXlNekF4TUZvd0dqRVlNQllHQTFVRUF3d1BWMlZpYUc5dmF5QlNiMjkwSUVOQk1JSUJJakFOCkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQW1Jbm9SaWVETmQybEFIWkZiUWVOUkJuRWt0d1IKZXFjMUNLbHNTOFpPMDV3b3lFT0RlN2UwMVJWSXRUTU5NV2JsU3M4UVpVWnJNNTB2ZDdxK212Sm44NjlnNlg5cAoxNG0xYkx1c05jaDZQZUtTY2ZYQlJaNjNXWWgzbHFMbXhFMGtiTTVmbWhmZ3hnempHMGRnUGpEOWl3N3JBMzJnCm9wRi9SZFA2aTdqcHZ3bTU2a09UTlAwRWxOQ01MUHdVUEFzWFArdkN1anQxc1FOZUtyUHM5b1BVVlpmeWx4bFUKakhTM1p2bmxXOHpTSm5VSHhjbkkxR1VBbVRSN3UwNCs1TlF6aWpBT1Rxczl1YnNOc1lkV2xXdHJaTEpvSDhYMApPa0p3VzJaZDYydFpKTC9jNSs1OHN0Zm9mYVdmdmg2WkhQMTkwVGlVdTJjLzc2RCszd3dqcTdtckN3SURBUUFCCm8xTXdVVEFkQmdOVkhRNEVGZ1FVMWhJZTI3ejhmODlnVVBkdEI5TndsOHFtSHBRd0h3WURWUjBqQkJnd0ZvQVUKMWhJZTI3ejhmODlnVVBkdEI5TndsOHFtSHBRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBTkJna3Foa2lHOXcwQgpBUXNGQUFPQ0FRRUFjUExIVU93b3Y4ZjRPa3NoNGVVS2JqMTZZS1h2SFlJcnRKY3FZejZJcTdJK0dwUkNZbVp6CnM0MlZOSlpwcEwrUU5YUXY5SVBONXNvU3JjVjhaVWhETnFQSHRHWXIzbnM3aGpEY2QxWlVzSHVJM3lTWEJrQU8KQWNUYXFDeDNOODZLcTdqV0FiemV3b3hwTW9weTBMbnFhMnAxdmhjUWN0bG9Nd0pqNHc2OU1wK0luWjRVTERNdgo1Uk9HZ0RJcGFDWGtDazFzV1F2MWFzMWovRkxWekY0WUx0ZVNZbk5ScVRlMTNOVVVvZUlpTVJUZlR5akIyZlFUCm54dXUyKzFaL0RNMTNoaUgwTGdsYUwxNHV2TE5RM2I3SkVFeGZjV0tuUjlSU2ovNkNHM05zTkFySE01K1pOYkgKOElNL1l5Y3UxWjFnM3EzUHMxWFBOdlN5OTdlZDBLRHNuQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0Kservice:name: webhook-svcnamespace: defaultpath: /mutateport: 8443failurePolicy: FailmatchPolicy: Equivalentname: replicas.mutating.webhooknamespaceSelector: {}objectSelector: {}reinvocationPolicy: Neverrules:- apiGroups:- appsapiVersions:- v1operations:- CREATE- UPDATEresources:- deployments- statefulsetsscope: "*"sideEffects: NonetimeoutSeconds: 10

secret.yaml

apiVersion: v1
data:ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURGVENDQWYyZ0F3SUJBZ0lVR3phSXQvQkt5enRlU1pJZVJRL0E3Sy9BMlVnd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0dqRVlNQllHQTFVRUF3d1BWMlZpYUc5dmF5QlNiMjkwSUVOQk1CNFhEVEkxTURNeE5URXlNekF4TUZvWApEVE0xTURNeE16RXlNekF4TUZvd0dqRVlNQllHQTFVRUF3d1BWMlZpYUc5dmF5QlNiMjkwSUVOQk1JSUJJakFOCkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQW1Jbm9SaWVETmQybEFIWkZiUWVOUkJuRWt0d1IKZXFjMUNLbHNTOFpPMDV3b3lFT0RlN2UwMVJWSXRUTU5NV2JsU3M4UVpVWnJNNTB2ZDdxK212Sm44NjlnNlg5cAoxNG0xYkx1c05jaDZQZUtTY2ZYQlJaNjNXWWgzbHFMbXhFMGtiTTVmbWhmZ3hnempHMGRnUGpEOWl3N3JBMzJnCm9wRi9SZFA2aTdqcHZ3bTU2a09UTlAwRWxOQ01MUHdVUEFzWFArdkN1anQxc1FOZUtyUHM5b1BVVlpmeWx4bFUKakhTM1p2bmxXOHpTSm5VSHhjbkkxR1VBbVRSN3UwNCs1TlF6aWpBT1Rxczl1YnNOc1lkV2xXdHJaTEpvSDhYMApPa0p3VzJaZDYydFpKTC9jNSs1OHN0Zm9mYVdmdmg2WkhQMTkwVGlVdTJjLzc2RCszd3dqcTdtckN3SURBUUFCCm8xTXdVVEFkQmdOVkhRNEVGZ1FVMWhJZTI3ejhmODlnVVBkdEI5TndsOHFtSHBRd0h3WURWUjBqQkJnd0ZvQVUKMWhJZTI3ejhmODlnVVBkdEI5TndsOHFtSHBRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBTkJna3Foa2lHOXcwQgpBUXNGQUFPQ0FRRUFjUExIVU93b3Y4ZjRPa3NoNGVVS2JqMTZZS1h2SFlJcnRKY3FZejZJcTdJK0dwUkNZbVp6CnM0MlZOSlpwcEwrUU5YUXY5SVBONXNvU3JjVjhaVWhETnFQSHRHWXIzbnM3aGpEY2QxWlVzSHVJM3lTWEJrQU8KQWNUYXFDeDNOODZLcTdqV0FiemV3b3hwTW9weTBMbnFhMnAxdmhjUWN0bG9Nd0pqNHc2OU1wK0luWjRVTERNdgo1Uk9HZ0RJcGFDWGtDazFzV1F2MWFzMWovRkxWekY0WUx0ZVNZbk5ScVRlMTNOVVVvZUlpTVJUZlR5akIyZlFUCm54dXUyKzFaL0RNMTNoaUgwTGdsYUwxNHV2TE5RM2I3SkVFeGZjV0tuUjlSU2ovNkNHM05zTkFySE01K1pOYkgKOElNL1l5Y3UxWjFnM3EzUHMxWFBOdlN5OTdlZDBLRHNuQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0Ktls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURuVENDQW9XZ0F3SUJBZ0lVTkswVTNtL0wzYzVwQmwwSFdycWRtRU5kbVlvd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0dqRVlNQllHQTFVRUF3d1BWMlZpYUc5dmF5QlNiMjkwSUVOQk1CNFhEVEkxTURNeE5URXlNekF4TUZvWApEVEkyTURNeE5URXlNekF4TUZvd0lqRWdNQjRHQTFVRUF3d1hkMlZpYUc5dmF5MXpkbU11WkdWbVlYVnNkQzV6CmRtTXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFERlhOU3Zpa1g4c0R2a2NPR24KSndXdHVTT3JDRWQwRmpxR1FmZ3M3NWtPTjcyclI1My9XSzFRd3VwRnROOHRxcGZLYkJUVHpreURUMCtCZEsvcwp6Z1ZObkRRWVVzS01pVDJmRTNOR2Zuem1rdGRmc2o5bjhKRkdYRW1sZzVFcGFXVUxqL2RZM3N0d1Z6SHI5TkpOCmZTVUlZUlQrMXMrUW5lVktwVkpjaERPU2RlQ0xTL0xmU1p1ckJLamE2S3kzb2FaOGE0T2hHSFEzUWpvTmxlY3cKYnJ1czRrT3hoQTVDYXQ3L2k3Q2kvbHB0WUd5OTdiVjl6amNXejNZQjNEWklUSnVZdlVMbjVUcXUxejVwOXZrNgpEMHpZQUo2ZHo0cjNzTnZVRXh3TWQ5d09DK3hONXJPNkFUeWxhZUVsNHJIdW80aFVxU1ExVENaUGFmU0p6Tmc4Cmhyc3hBZ01CQUFHamdkSXdnYzh3Q3dZRFZSMFBCQVFEQWdXZ01CTUdBMVVkSlFRTU1Bb0dDQ3NHQVFVRkJ3TUIKTUdzR0ExVWRFUVJrTUdLQ0MzZGxZbWh2YjJzdGMzWmpnaE4zWldKb2IyOXJMWE4yWXk1a1pXWmhkV3gwZ2hkMwpaV0pvYjI5ckxYTjJZeTVrWldaaGRXeDBMbk4yWTRJbGQyVmlhRzl2YXkxemRtTXVaR1ZtWVhWc2RDNXpkbU11ClkyeDFjM1JsY2k1c2IyTmhiREFkQmdOVkhRNEVGZ1FVSnlndVBEb2JXRlhBdVJiSFhlNDZacGZFSHFJd0h3WUQKVlIwakJCZ3dGb0FVMWhJZTI3ejhmODlnVVBkdEI5TndsOHFtSHBRd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQgpBQkpkT2ZpbzE5bTBLeUF3VXgyb1VHNkNYemh6MW9rbm1PeUR4eTkrcXg2eTNIZXdTUTdLeWJCNUpJMWNGNUJMClFOYUN1dGVadWxnSGNydVFXUlJONng0L2E3TGF6V0RENWYzRmVPNEczbEppZk1IUVlVaDJOSlBuV3hKSU55b1MKWnlTcGhVL3Zmb2ozMVpuMXlqTFB0V3gzSVllcms4dnlQR0YrbkRRZE1lREM2Q3MyMXMyNDQwa01vaUVmVnhkcApScjRYUHp1R0FiMGNpcTRwdDlBTHpFUEh4RHhTYklsVmdEMFEvZGdIUEpuSkYzVDJ4ZVVyMDZJcmpmSjdQSkhpCkZJdEx1U3VQejhkdldUWEoxV2ZtWE9hN0ZIZUFvejRIbm5EdEhtc1VEZTRBNkR4L1BMVVc1K2kwclc4QTRncXAKRU5zbU9NclRDa3RkeGdUdTczSk9FdWM9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0Ktls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRREZYTlN2aWtYOHNEdmsKY09Hbkp3V3R1U09yQ0VkMEZqcUdRZmdzNzVrT043MnJSNTMvV0sxUXd1cEZ0Tjh0cXBmS2JCVFR6a3lEVDArQgpkSy9zemdWTm5EUVlVc0tNaVQyZkUzTkdmbnpta3RkZnNqOW44SkZHWEVtbGc1RXBhV1VMai9kWTNzdHdWekhyCjlOSk5mU1VJWVJUKzFzK1FuZVZLcFZKY2hET1NkZUNMUy9MZlNadXJCS2phNkt5M29hWjhhNE9oR0hRM1Fqb04KbGVjd2JydXM0a094aEE1Q2F0Ny9pN0NpL2xwdFlHeTk3YlY5empjV3ozWUIzRFpJVEp1WXZVTG41VHF1MXo1cAo5dms2RDB6WUFKNmR6NHIzc052VUV4d01kOXdPQyt4TjVyTzZBVHlsYWVFbDRySHVvNGhVcVNRMVRDWlBhZlNKCnpOZzhocnN4QWdNQkFBRUNnZ0VBRWZyZmkzQnk2TTdiWGZma3J0Z3d2YjlnbnZ1OW1yZE50SjU4OEFjUjhBZ20KK053bzZqTFhjMFNXbUN3ZXF1ZmdOVG84ZVlGUldpTVhFS21qUDFVVGlac0I2ZmRjTHZadnpUYTE2VVdydGt2SgpZRGY2YThzd1NQTXVhR3hBaEwvTHkwNWR6OVJZUDA5S1JuOUN5M2xycnNROVord0U4OTFXbnNMSjZwREdyQUIxCk5CeG1LVW03QXBVNVpMNExCYVVVWi9yZWQraTdJYTVTQU9yc2RQdEsvTDN5WHZmZXJ4KzdYakViSDNMN0RxTloKekN4SEFrT2IyT2Q2Q0o5SGN0eUdrNSsvVytJUERhdENJSzAyZFRDR0NvbXZqVzlyQW9vbDdNcXJtSTBvN0M4NApWeFlDSTV2N0srbW51QjBobnVUVkFJVWdsYU5PQSt6emljbnpjWkVXNHdLQmdRRGlGdXNTSEoxVk9SaUJNM3pzCmg2MEw2UkVjQXV4VEhVTVo2ZGxORmRzQ2tvbEVKQXMwd0JHSGNtYlZudDRDekV4UXk4bVhzM2xHVU5YZnlTSTgKUjlobXlVZEI4QTRaNGZvUHV3REdGYTRXV2VrdUl3Z21KTHBaZ2c1K2FXcW9EQUp1dUtReXBCZnBKTkovVXgxdgpRRDJYT2FUS3pRdTRTZHNOWkR4b2hMQXRBd0tCZ1FEZmVRRTZZMFp6Snh6WDRjSW9zWERzeE9oZjB0dzVzaEJLClFhc1hoa1pKcERPYXI5NzBzWnFFSURHREtHVndhRVVUK0RuNTRQSFREd1kzaGI0UmZodUVVZnpQc2tEejFWeWYKZWJiNG5saDB1Z0RMMHl3TkU3NTRQeXRXeGJwK1hUczZOWnRMT2ZkYlJJY0V5Z0l0NS9FYTZ0d3JKd291NVd4SAo2VEZ6NjFxZXV3S0JnUUNxM1hrd1NmSFpxM25LZ3hnQlJoUlFzUVplTGhOZVNQb2lSbW9VYU5VSW42Z2ZtRUhqCnp0Z3dqaFFMazdIaldYUy9oeFBHa3p1dkdYNVpUdytSa1JhSnI4b3JtZmwrTkJzZzhrb0dhZklVTUVVYXVoejUKZnI1YTBRQ1ZKcVFWZG1ZTU9YeldUTTlKUXF2VzBBQ3B5Rm9EeE91MjNMbmp2K1ZOdkpndXdVREg4d0tCZ0ZTNwpXOFRZdVhDV0J2Q3Y3OTlnRURJbUl2bWFTTmd6ZE12REJHMUNBMHFPME9ZNUF1K0NtOVMzSkM3WDFVWitzcHAwCnh2N0ExTkF5NVNlT05WZ0ttY0pkRjk5a2RnNDkrd1dZcjlDcXNWMW8zVDVyVGt1VERlZ29BM1crT1EwS3FwZFMKbGhRNjRWZ2dycFVaUnlSQ3luOXJSNW14RHNKalNPQW5RaEh5emdSYkFvR0FMNlZYSGFlbnlSM1BtUGM0Rk1hagptM3BkNDRXUmJmVUs2VzV4RXp3R1drREF5cXVwYW1mdm4yTTVYejFEZVh0Tzk5dTlrbU5WWlByWXJubXdVOElNCkZ2TjQyMTRDMlRhL2FlVGdxRHM1Q3FOWmFxTW1aY2F5ejdNR1h5S1ZYNTJmdm41YUloR3BSQjFBdWhiU3RKbFgKb3hKRkNxOURkTXU5NlJuTHFNMitDVjA9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
kind: Secret
metadata:creationTimestamp: nullname: webhook-tlsnamespace: default

service.yaml

# webhook-service.yaml
apiVersion: v1
kind: Service
metadata:name: webhook-svcnamespace: default
spec:ports:- port: 8443targetPort: 8443
#  selector:
#    app: admission-webhook-example

如果是在本地调试还未打镜像的情况下,需要配置ep到wsl或者虚拟机内部的监听端口

#调试情况下配置ep
apiVersion: v1
kind: Endpoints
metadata:name: webhook-svcnamespace: default
subsets:- addresses:- ip: 172.21.227.39  # 替换为步骤1获取的WSL IPports:- port: 8443

webhook镜像部署:

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:name: webhook
spec:progressDeadlineSeconds: 600replicas: 1revisionHistoryLimit: 10selector:matchLabels:service.cpaas.io/name: deployment-webhookstrategy:rollingUpdate:maxSurge: 25%maxUnavailable: 25%type: RollingUpdatetemplate:metadata:creationTimestamp: nulllabels:service.cpaas.io/name: deployment-webhookspec:affinity: {}containers:- args:- --tls-cert=/etc/webhook/certs/tls.crt- --tls-key=/etc/webhook/certs/tls.keyimage: 192.168.8.126:30080/wangao/webhook:v1imagePullPolicy: IfNotPresentname: webhookports:- containerPort: 8443protocol: TCPresources: {}terminationMessagePath: /dev/termination-logterminationMessagePolicy: FilevolumeMounts:- mountPath: /etc/webhook/certsname: certsreadOnly: truednsPolicy: ClusterFirstrestartPolicy: AlwaysschedulerName: default-schedulersecurityContext: {}terminationGracePeriodSeconds: 30volumes:- name: certssecret:defaultMode: 420secretName: replicas-webhook-tls

这样的话,apiserver就会根据mutatingwebhook中的配置,用ca去验证对应service+port的服务器证书,并且发送request,服务器返回符合apiserver要求的response,apiserver根据其response修改请求,最终持久化到etcd中。

另外,validatewebhook的逻辑类似,一般单纯用来验证请求,只是mutatingwebhook优先于validatewebhook生效。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/899971.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Tauri新手向 - 基于LSB隐写的shellcode加载器

此篇是记录自己初次学习tauri开发工具,包含遇到的一些问题以及基本的知识,也给想上手rust tauri的师傅们一些小小的参考。此项目为保持免杀性暂不开源,希望各位师傅多多支持,反响可以的话后续会放出代码大家一起交流学习。ShadowMeld - 基于图像隐写技术的载荷生成框架 通过…

P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G(缩点)

P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G 题目背景 本题测试数据已修复。 题目描述 每头奶牛都梦想成为牛棚里的明星。被所有奶牛喜欢的奶牛就是一头明星奶牛。所有奶牛都是自恋狂,每头奶牛总是喜欢自己的。奶牛之间的“喜欢”是可以传递的——如果 \(A\) 喜欢 \(B\),\(…

允许蜘蛛访问,屏蔽访客的php代码

大部分时候我们制作的泛目录需要屏蔽访客,php的优于js识别蜘蛛屏蔽,毕竟一个在服务器内运行后输出,一个在html中调用。 这里分享一段屏蔽游客查查看真实页面的php代码,直接命名为啥php文件,后在想要屏蔽游客的页面中引用(如:include /baidu.php;)就可以了,代码如下:&…

【程设の旅】第二次上机卡题复盘

python上机 其实很快就写完了,第五题有个坑,讲一下 05:奇偶ASCII值判断 描述 任意输入一个字符,判断其ASCII是否是奇数,若是,输出YES,否则,输出NO 例如,字符A的ASCII值是65,则输出YES,若输入字符B(ASCII值是66),则输出NO 输入 输入一个字符 输出 如果其ASCII值为奇数…

第二章课后习题

Tempconvent.py TempStr = input("请输入带有符号的温度值:") if TempStr[-1] in[F,f]: C = (eval(TempStr[0:-1])-32)/1.8 print("转换后的温度是{:.0f}C".format(C)) elif TempStr[-1] in[C,c]: F = 1.8*eval(TempStr[0:-1])+32 print("转换后的温…

30_正则练习题2

正则练习题正则含义1.1 基础正则 ^ 以什么开头,"^yuchao" 表示匹配以yuchao单词开头的行 $ 以什么结尾,"yuchao$",表示匹配以yuchao单词结尾的行 ^$ 组合符号,表示空行。逻辑解释就是以^开始,以$结尾的行 . 匹配任意且只有一个…

docker-compose 安装elasticsearch

安装版本 elasticsearch 8.14.1 拉取镜像 docker pull elasticsearch:8.14.1如需安装kibana 需要拉取对应版本的kibana镜像 docker pull kibana:8.14.1查看镜像是否安装成功 docker images配置docker-compose.yaml version: 3 services:es:# 镜像名称image: elasticsearch:8.14…

求阶乘

关于最少尾数0的个数,只有2的倍数与5的倍数相乘才能得到0,显然2的倍数比5的倍数多的多,现在目标找5的倍数的个数#include<bits/stdc++.h> #define endl "\n" #define int long long #define x first #define y second using namespace std; typedef long lo…

Visual Studio Code-设置展示多行TAB页

Visual Studio Code-设置展示多行TAB页 一、ctrl + shift + p -> 打开工作区设置(Open Workspace Settings 二、输入 ‘workbench.editor.wrapTabs’

Git-git生成SSH密钥

Git-git生成SSH密钥 一、生成 SSH 密钥 如果你还没有生成 SSH 密钥,可以使用以下命令生成新的 SSH 密钥对: 这里 -t rsa 指定密钥类型为 RSA,-b 4096 指定密钥长度为 4096 位,-C 添加一个注释,通常是你的电子邮件地址。 ssh-keygen -t rsa -b 4096 -C "your_email@ex…

鲜花:《一种基于错误的寻找重心方法的点分治的复杂度分析》注

原文:一种基于错误的寻找重心方法的点分治的复杂度分析 LCA 还是太神了,研究半天才看明白。 所以这里提供一种说人话版本。 为什么法一是错的? 原文提出了这样一个 hack:这是三个等长度的共端点的链,初始以红色箭头所指的点为根。 进行第一次分治:红色箭头所指的点是重心…