从零开始实现k8s_admission_webhook

举报
可以交个朋友 发表于 2023/12/28 14:36:06 2023/12/28
【摘要】 实现一个简单的webhook程序,帮助大家了解开发K8S Admission Webhook的大致流程。webhook的原理请看这篇:https://bbs.huaweicloud.com/blogs/418973

案例说明

校验 Pod 创建请求,如果pod中的任意 Container声明了环境变量,就拒绝它。
image.png

代码实现

  1. 首先构建一个 HTTPS服务,监听8000端口,通过/validate接口接收校验请求。main.go内容如下
    package main 
    import ( 
        "encoding/json" 
        "fmt" 
        "log" 
        "net/http" 
        "os" 
        "path/filepath" 
        admissionv1 "k8s.io/api/admission/v1" 
        corev1 "k8s.io/api/core/v1" 
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 
    ) 
    const ( 
        tlsKeyName  = "tls.key" 
        tlsCertName = "tls.crt" 
    ) 
    func main() { 
        mux := http.NewServeMux() 
        mux.HandleFunc("/validate", validate) 
        if certDir := os.Getenv("CERT_DIR"); certDir != "" { 
            certFile := filepath.Join(certDir, tlsCertName) 
            keyFile := filepath.Join(certDir, tlsKeyName) 
            log.Println("serving https on 0.0.0.0:8000") 
            log.Fatal(http.ListenAndServeTLS(":8000", certFile, keyFile, mux)) 
        }
    } 
    func validate(w http.ResponseWriter, r *http.Request) { 
        var ( 
            reviewReq, reviewResp admissionv1.AdmissionReview 
            pd                    corev1.Pod 
        ) 
        dec := json.NewDecoder(r.Body) 
        if err := dec.Decode(&reviewReq); err != nil { 
            http.Error(w, err.Error(), http.StatusInternalServerError) 
            return 
        } 
        // Get pod object from request 
        if err := json.Unmarshal(reviewReq.Request.Object.Raw, &pd); err != nil { 
            http.Error(w, err.Error(), http.StatusInternalServerError) 
            return 
        } 
        log.Println("validating pod", pd.Name) 
        reviewResp.TypeMeta = reviewReq.TypeMeta 
        reviewResp.Response = &admissionv1.AdmissionResponse{ 
            UID:     reviewReq.Request.UID, // write the unique identifier back 
            Allowed: true, 
            Result:  nil, 
        } 
        for _, ctr := range pd.Spec.Containers { 
            if len(ctr.Env) > 0 { 
                reviewResp.Response.Allowed = false 
                reviewResp.Response.Result = &metav1.Status{ 
                    Status:  "Failure", 
                    Message: fmt.Sprintf("%s is using env vars", ctr.Name), 
                    Reason:  metav1.StatusReason(fmt.Sprintf("%s is using env vars", ctr.Name)), 
                    Code:    402, 
                } 
                break 
            } 
        } 
        js, err := json.Marshal(reviewResp) 
        if err != nil { 
            http.Error(w, err.Error(), http.StatusInternalServerError) 
            return 
        } 
        w.Header().Set("Content-Type", "application/json") 
        w.Write(js) 
    }
    
    
  2. 将项目编译成二进制文件,并将二进制文件拷贝至集群内节点
    GOOS=linux go build -o denyenv-validating-admission-webhook ./main.go
    

部署过程

  1. 使用Dockerfile制作镜像;Dockerfile文件内容如下
    FROM centos:7.6.1810   #基础容器可以修改 
    WORKDIR / 
    COPY denyenv-validating-admission-webhook /denyenv-validating-admission-webhook 
    ENTRYPOINT ["/denyenv-validating-admission-webhook"] 
    
    chmod 755 denyenv-validating-admission-webhook
    docker build -t denyenv-validating-admission-webhook:v1 .
    
  2. 使用如下脚本create-csr-cert.sh生成服务端证书;生成的ca证书和服务端证书有效期为27年,可自行修改脚本中-days的值。如果已有服务端证书和私钥,可以直接用服务端私钥和证书创建secret。
    脚本执行示例:
    sh create-csr-cert.sh --service denyenv --namespace default  --secret denyenv-tls-secret
    
    #!/bin/bash 
    
    set -ex 
    
    usage() { 
        cat <<EOF 
    usage: ${0} [OPTIONS] 
    
    The following flags are required. 
    
           --service          Service name of webhook. 
           --namespace        Namespace where webhook service and secret reside. 
           --secret           Secret name for CA certificate and server certificate/key pair. 
    EOF 
        exit 1 
    } 
    
    while [[ $# -gt 0 ]]; do 
        case ${1} in 
            --service) 
                service="$2" 
                shift 
                ;; 
            --secret) 
                secret="$2" 
                shift 
                ;; 
            --namespace) 
                namespace="$2" 
                shift 
                ;; 
            *) 
                usage 
                ;; 
        esac 
        shift 
    done 
    
    [ -z ${service} ] && echo "ERROR: --service flag is required" && exit 1 
    [ -z ${secret} ] && echo "ERROR: --secret flag is required" && exit 1 
    [ -z ${namespace} ] && namespace=default 
    
    if [ ! -x "$(command -v openssl)" ]; then 
        echo "openssl not found" 
        exit 1 
    fi 
    
    tmpdir=certs 
    echo "creating certs in ${pwd} " 
    mkdir -p ${tmpdir} 
    
    cat <<EOF >> ${tmpdir}/csr.conf 
    [req] 
    req_extensions = v3_req 
    distinguished_name = req_distinguished_name 
    
    [req_distinguished_name] 
    O = hw.dev/serving 
    CN = ${service}.${namespace}.svc 
    
    [ 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 
    IP.1 = 192.168.0.1  #如果使用ip访问webhook,替换为真实的ip地址 
    EOF 
    
    #生成CA私钥和ca根证书 
    openssl genrsa -out ca.key 2048 
    openssl req -x509 -new -nodes -key ca.key -subj "/CN=${service}.${namespace}.svc" -days 10000 -out ca.crt 
    
    #生成服务端私钥和证书签名请求,apiserver通过https访问准入webhook,需要服务端证书 
    openssl genrsa -out ${service}.key 2048 
    openssl req -new -key ${service}.key -subj "/CN=${service}.${namespace}.svc" -out ${tmpdir}/${service}.csr -config ${tmpdir}/csr.conf 
    
    #用CA私钥和根证书签发服务端证书 
    openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in ${tmpdir}/${service}.csr -days 10000 -out ${service}.crt -extfile ${tmpdir}/csr.conf -extensions v3_req 
    
    #用服务端私钥和服务端证书生成K8S集群的secret资源,挂载到webhook工作负载中   
    kubectl create secret generic ${secret} \ 
            --from-file=tls.key=${service}.key \ 
            --from-file=tls.crt=${service}.crt
    
  3. 使用上一步生成的ca证书进行base64编码
    cat ca.crt | base64 -w 0
    
  4. 创建CRD(ValidatingWebhookConfiguration)资源,向apiserver注册webhook信息(连接信息,匹配规则,ca证书等)
    apiVersion: admissionregistration.k8s.io/v1 
    kind: ValidatingWebhookConfiguration 
    metadata: 
      name: denyenv 
    webhooks: 
      - admissionReviewVersions: 
          - v1                  #webhook支持的admissionReview版本 
        clientConfig: 
          caBundle: ""           #为上一步ca证书base64编码后的内容 
          service:               #webhook的访问信息,如webhook部署在集群外,可用url代替service字段 
            name: denyenv 
            namespace: default 
            port: 443 
            path: /validate 
        failurePolicy: Fail      #若admission调用失败(访问异常或处理异常),Fail拒绝请求,Ignore允许请求
        matchPolicy: Exact 
        name: denyenv.dev 
        rules: 
          - apiGroups: 
              - "" 
            apiVersions: 
              - v1 
            operations: 
              - CREATE 
            resources: 
              - pods 
            scope: '*'    #如果使用objectSelector筛选,该字段不能设置为Namespaced,可以配置为"*"或者"Cluster" 
        namespaceSelector:  #命名空间级别的标签匹配;可以使用objectSelector对工作负载级别进行匹配 
          matchExpressions: 
          - key: kubernetes.io/metadata.name 
            operator: In 
            values: ["default", "test"] 
        sideEffects: None 
        timeoutSeconds: 3
    
  5. 在集群内部署webhook服务
    apiVersion: apps/v1 
    kind: Deployment 
    metadata: 
      labels: 
        app: denyenv 
      name: denyenv 
    spec: 
      replicas: 1 
      selector: 
        matchLabels: 
          app: denyenv 
      template: 
        metadata: 
          labels: 
            app: denyenv 
        spec: 
          containers: 
            - image: denyenv-validating-admission-webhook:v1 
              env: 
                - name: CERT_DIR 
                  value: "/etc/denyenv-webhook/certs" 
              name: denyenv 
              ports: 
                - containerPort: 8000 
                  protocol: TCP 
              volumeMounts: 
                - mountPath: /etc/denyenv-webhook/certs 
                  name: tls-cert 
          volumes: 
            - name: tls-cert 
              secret: 
                secretName: denyenv-tls-secret 
    
    --- 
    apiVersion: v1 
    kind: Service 
    metadata: 
      labels: 
        app: denyenv 
      name: denyenv 
    spec: 
      ports: 
        - name: https 
          port: 443 
          protocol: TCP 
          targetPort: 8000 
      selector: 
        app: denyenv 
      type: ClusterIP
    

验证

image.png

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。