从零开始实现k8s_admission_webhook
【摘要】 实现一个简单的webhook程序,帮助大家了解开发K8S Admission Webhook的大致流程。webhook的原理请看这篇:https://bbs.huaweicloud.com/blogs/418973
案例说明
校验 Pod 创建请求,如果pod中的任意 Container声明了环境变量,就拒绝它。
代码实现
- 首先构建一个 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) }
- 将项目编译成二进制文件,并将二进制文件拷贝至集群内节点
GOOS=linux go build -o denyenv-validating-admission-webhook ./main.go
部署过程
- 使用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 .
- 使用如下脚本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
- 使用上一步生成的ca证书进行base64编码
cat ca.crt | base64 -w 0
- 创建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
- 在集群内部署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
验证
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)