使用Admission Webhook修改pod信息

目標

本篇文章我們將參照官方的測試實例來一步步添加一個Admission Webhook,從而修改POD的信息

配置

webhook證書生成

由上節的配置我們可以看出,我們的webhook必須使用https,所以我們需要生成一個自簽名的https證書,
ca可以使用自定義的也可以共用apiserver的。
腳本可以參照istio的證書生成腳本: webhook-create-signed-cert.sh

tmpdir=$(mktemp -d) #生成臨時目錄
service=sidecar-injector-webhook-svc #指定webhook對應的svc name
namespace=default #指定 運行的命名空間
secret=sidecar-injector-webhook-certs # 指定secret name
csrName=${service}.${namespace} #指定CertificateSigningRequest 的名稱
cat <> ${tmpdir}/csr.conf
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
[req_distinguished_name]
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = ${service}
DNS.2 = ${service}.${namespace}
DNS.3 = ${service}.${namespace}.svc
EOF
openssl genrsa -out server-key.pem 2048 #生成私鑰
openssl req -new -key ${tmpdir}/server-key.pem -subj "/CN=${service}.${namespace}.svc" -out ${tmpdir}/server.csr -config ${tmpdir}/csr.conf # 生成證書籤名請求
通過k8s CSR生成證書
cat <

認證apiserver配置

如果我們的webhook需要對請求進行身份認證,那麼我們需要對apiserver進行以下配置:

--admission-control-config-file=/etc/kubernetes/admission-control.conf

/etc/kubernetes/admission-control.conf 文件內容如下:

apiVersion: apiserver.k8s.io/v1alpha1
kind: AdmissionConfiguration
plugins:
- name: MutatingAdmissionWebhook
 configuration:
 apiVersion: apiserver.config.k8s.io/v1alpha1
 kind: WebhookAdmission
 kubeConfigFile: /etc/kubeconfig/mutating-admission-webhook.kubeconfig

/etc/kubeconfig/mutating-admission-webhook.kubeconfig 配置如下:

apiVersion: v1
kind: Config
users:
- name: 'sidecar-injector-webhook-svc.default.svc'
 user:
 token: "testtoken"

MutatingWebhookConfiguration 配置

apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
 name: sidecar-injector-webhook-cfg
 labels:
 app: sidecar-injector
webhooks:
 - name: sidecar-injector.morven.me
 clientConfig:
 service:
 name: sidecar-injector-webhook-svc
 namespace: default
 path: "/mutating-pods"
 rules:
 - operations: [ "CREATE" ]
 apiGroups: [""]
 apiVersions: ["v1"]
 resources: ["pods"]
 namespaceSelector:
 matchLabels:
 sidecar-injector: enabled

代碼實現

接下來我們參照官方的測試實例實現一個 sidecar inject MutatingAdmissionWebhook。

package main
import (
 "crypto/tls"
 "encoding/json"
 "flag"
 "fmt"
 "io/ioutil"
 "net/http"
 "k8s.io/api/admission/v1beta1"
 admissionv1beta1 "k8s.io/api/admission/v1beta1"
 admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
 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"
 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 "k8s.io/klog"
 // TODO: try this library to see if it generates correct json patch
)
var scheme = runtime.NewScheme() //用於序列化
var codecs = serializer.NewCodecFactory(scheme) //提供檢索序列化方式的函數
const (
 //返回給apiserver的patcher
 podsInjectContainerPatch string = `{"op":"add","path":"/spec/containers/-","value":[{"image":"envoy","name":"envoy","resources":{}}]}`
 addInjectAnnotationPatch string = `{"op":"add","path":"/metadata/annotations","value": {"sidecar-injector-webhook/status": "true"}}`
 updateInjectAnnotationPatch string = `{"op":"replace","path":"/metadata/annotations/sidecar-injector-webhook/status","value":"true"}`
 injectAnnotation string = "sidecar-injector-webhook/status"
)
func init() {
 // 將數據類型註冊到scheme
 utilruntime.Must(corev1.AddToScheme(scheme))
 utilruntime.Must(admissionv1beta1.AddToScheme(scheme))
 utilruntime.Must(admissionregistrationv1beta1.AddToScheme(scheme))
}
// 創建AdmissionResponse
func toAdmissionResponse(err error) *v1beta1.AdmissionResponse {
 return &v1beta1.AdmissionResponse{
 Result: &metav1.Status{
 Message: err.Error(),
 },
 }
}
type admitFunc func(v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
// 使用對應http.handlefunc 處理請求
func serve(w http.ResponseWriter, r *http.Request, admit admitFunc) {
 var body []byte
 if r.Body != nil {
 if data, err := ioutil.ReadAll(r.Body); err == nil {
 body = data
 }
 }

 // 校驗token
 token:=r.Header.Get("Authorization")
 if token!=fmt.Sprintf("Bearer %s",bearToken) {
 klog.Errorf("unexpect bear token: %s", token)
 return
 }
 // 校驗content type 只接收json
 contentType := r.Header.Get("Content-Type")
 if contentType != "application/json" {
 klog.Errorf("contentType=%s, expect application/json", contentType)
 return
 }
 klog.V(2).Info(fmt.Sprintf("handling request: %s", body))
 // 接收到的AdmissionReview
 requestedAdmissionReview := v1beta1.AdmissionReview{}
 // 用於返回的AdmissionReview
 responseAdmissionReview := v1beta1.AdmissionReview{}
 deserializer := codecs.UniversalDeserializer() //返回runtime.Decoder 用於轉換成為k8s的runtime.Object
 if _, _, err := deserializer.Decode(body, nil, &requestedAdmissionReview); err != nil { //將body轉換為AdmissionReview
 klog.Error(err)
 responseAdmissionReview.Response = toAdmissionResponse(err) //返回包含錯誤的AdmissionResponse
 } else {
 klog.Info("s1")
 responseAdmissionReview.Response = admit(requestedAdmissionReview) //通過對應handlefunc處理
 }
 responseAdmissionReview.Response.UID = requestedAdmissionReview.Request.UID //響應 UUID需要同請求UUID相同
 klog.V(2).Info(fmt.Sprintf("sending response: %v", responseAdmissionReview.Response))
 respBytes, err := json.Marshal(responseAdmissionReview) //序列化為JSON
 if err != nil {
 klog.Error(err)
 }
 if _, err := w.Write(respBytes); err != nil {
 klog.Error(err)
 }
}
func serveMutatePods(w http.ResponseWriter, r *http.Request) {
 serve(w, r, mutatePods)
}
func main() {
 klog.InitFlags(nil)
 flag.Parse()
 http.HandleFunc("/mutating-pods", serveMutatePods)
 sCert, err := tls.LoadX509KeyPair("/etc/certs/cert.pem", "/etc/certs/key.pem")
 if err != nil {
 klog.Fatal(err)
 }
 server := &http.Server{
 Addr: ":443",
 TLSConfig: &tls.Config{
 Certificates: []tls.Certificate{sCert},
 },
 }
 server.ListenAndServeTLS("", "")
}
func mutatePods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
 klog.V(2).Info("mutating pods")
 podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
 if ar.Request.Resource != podResource { //判斷類型是否相同
 klog.Errorf("expect resource to be %s %s", podResource,ar.Request.Resource)
 return nil
 }
 raw := ar.Request.Object.Raw
 pod := corev1.Pod{}
 deserializer := codecs.UniversalDeserializer()
 if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil { //反序列化為pod
 klog.Error(err)
 return toAdmissionResponse(err)
 }
 reviewResponse := v1beta1.AdmissionResponse{}
 reviewResponse.Allowed = true
 klog.Info(reviewResponse)
 annotations:=pod.Annotations
 if annotations==nil {
 annotations = map[string]string{}
 }
 if v,ok:=annotations[injectAnnotation];ok { // 沒有或不是true 進行注入
 klog.Info(v)
 if v!="true" {
 reviewResponse.Patch = []byte(fmt.Sprintf("{%s,%s}",podsInjectContainerPatch,updateInjectAnnotationPatch)) //設置patcher
 pt := v1beta1.PatchTypeJSONPatch
 reviewResponse.PatchType = &pt
 }
 }else {
 reviewResponse.Patch = []byte(fmt.Sprintf("[%s,%s]",podsInjectContainerPatch,addInjectAnnotationPatch)) //設置patcher
 pt := v1beta1.PatchTypeJSONPatch
 reviewResponse.PatchType = &pt
 }
 klog.Info(string(reviewResponse.Patch))
 return &reviewResponse
}


分享到:


相關文章: