准入控制器

简而言之,Kubernetes准入控制器是管理和强制执行集群使用方式的插件。它们可以被认为是拦截(经过身份验证的)API 请求并可能更改请求对象或完全拒绝请求的看门人。准入控制过程有两个阶段:首先执行变更阶段,然后是验证阶段。

Kubernetes 准入控制器阶段:
在这里插入图片描述
准入控制器是一个软件,它在对象(如 PodDeploymentServicek8s 资源) etcd 数据库中持久化之前,但在请求被认证和授权之后,拦截对 Kubernetes API 服务器的请求. 使用准入控制器,我们可以验证或“改变”传入请求的资源。例如,想象以下用例:

  • 应用一个简单的安全验证,即集群中的所有容器都不能使用该latest标签。
  • 可能想要设置一些默认值,例如annotationslabels在您部署的每个资源中。

Kubernetes中有两种类型的准入控制器。他们正在验证准入控制器和变异准入控制器。变异准入控制器首先被调用并且可以“修改”对象。在所有对象修改完成后,在传入对象被 API 服务器验证后,验证准入控制器被调用。可以拒绝执行自定义策略的请求。

注意:我将“修改”和“变异”这两个词放在引号中,因为我们并没有真正修改资源本身。我们使用 JSON Patch 格式告诉 Kubernetes 它应该对对象“K8s 资源”进行哪些修改。

变异准入控制器可以充当变异或验证控制器。它可以同时对请求执行两种操作。但是,请记住,变异准入控制器首先执行。为确保您将验证对象的最后状态,应该使用验证准入控制器。

注册准入控制器 webhook

创建了这个存储库,其中包含此示例的所有代码以及 Go GitHub中准入控制器的简单样板。
https://github.com/douglasmakey/admissioncontroller
可以测试此示例的集群必须运行 Kubernetes 1.9.0 或更高版本。确保启用 admissionregistration.k8s.io/v1beta1 API。可以使用以下命令进行验证:

[root@VM-2-29-centos 101]# kubectl api-versions |grep admissionreg
admissionregistration.k8s.io/v1
admissionregistration.k8s.io/v1beta1

应该检查MutatingAdmissionWebhook并ValidatingAdmissionWebhook在您的集群中激活检查kube-apiserver.
--enable-admission-plugins=..,MutatingAdmissionWebhook,ValidatingAdmissionWebhook.."

为了实现我们的准入控制器,Kubernetes API服务器需要知道何时何地将传入的请求发送到我们的准入控制器。必须在 Kubernetes 中创建一个ValidatingWebhookConfigurationMutatingWebhookConfiguration对象,这取决于我们想要什么。例如,以下配置是注册一个ValidatingWebhookConfiguration以应用一些验证来创建一个Pod.

apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
  name: pod-validation
webhooks:
  - name: pod-validation.default.svc
    clientConfig:
      service:
        name: admission-server
        namespace: default
        path: "/validate/pods"
      caBundle: "${CA_BUNDLE}"
    rules:
      - operations: ["CREATE"]
        apiGroups: [""]
        apiVersions: ["v1"]
        resources: ["pods"]

这里的主要部分是:

  • clientConfig:准入控制器服务器的配置。
  • service.nameservice准入控制器服务器的名称。
  • service.namespace: 准入控制器服务器在哪个命名空间中。
  • service.path:准入控制器将接收此 webhook请求的路径。
  • rules: 包含你要拦截的操作、资源、资源的api版本和组
  • operations:您希望为这些资源拦截的操作。例如, CREATE, DELETE,UPDATED
  • resources: 你想在这个 webhook 上拦截的资源。例如,PodsDeploymentService等。
  • apiGroups以及apiVersions这些资源。

Kubernetes API服务器向给定的服务和 URL 路径发出 HTTPS POST 请求。由于必须通过 HTTPS提供 webhook,因此我们需要为服务器提供适当的证书。这些证书可以是自签名的,但需要 Kubernetes在与 webhook服务器通信时指示相应的 CA证书。为此,您可以caBundle在配置中看到 。

GitHub存储库中,您将找到demo/deploy.sh将创建自签名证书并demo/webhooks.yaml为此示例创建 webhook的脚本。要在生产中正确管理您的证书,您可以使用类似https://cert-manager.io的东西。

创建准入控制器服务器

注意:这里的所有代码示例都经过简化,以便于阅读。如需完整实施,请访问存储库。
https://github.com/douglasmakey/admissioncontroller
开始创建一个简单的 HTTPS。它应该具有我们为 admission webhook 定义的路径的端点。

// http/server.go
func NewServer(port string) *http.Server {
    // Instances hooks
    podsValidation := pods.NewValidationHook()
    // Routers
    ah := newAdmissionHandler()
    mux := http.NewServeMux()
    mux.Handle("/validate/pods", ah.Serve(podsValidation))
    return &http.Server{
        Addr:    fmt.Sprintf(":%s", port),
        Handler: mux,
    }
}
// cmd/main.go
func main() {
    // flags
    // ...
    server := http.NewServer(port)
    if err := server.ListenAndServeTLS(tlscert, tlskey); err != nil{
        log.Errorf("Failed to listen and serve: %v", err)
    }
}

然后必须创建admissionHandler以接收来自我们的 webhook的所有请求。这些请求在请求正文中带有 JSON编码的AdmissionReview(请求字段已填写)。响应应该是填写了响应字段的 JSON AdmissionReview

// http/handlers.go
type admissionHandler struct {...}
// Serve returns a http.HandlerFunc for an admission webhook
func (h *admissionHandler) Serve(hook admissioncontroller.Hook) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // HTTP validations
        // ...
        body, err := io.ReadAll(r.Body)
        if err != nil {...}
        var review admission.AdmissionReview
        if _, _, err := h.decoder.Decode(body, nil, &review); err != nil {...}
        result, err := hook.Execute(review.Request)
        if err != nil {...}
        admissionResponse := v1beta1.AdmissionReview{
            Response: &v1beta1.AdmissionResponse{
                UID:     review.Request.UID,
                Allowed: result.Allowed,
                Result:  &meta.Status{Message: result.Msg},
            },
        }
        //...
        res, err := json.Marshal(admissionResponse)
        if err != nil {...}
        w.WriteHeader(http.StatusOK)
        w.Write(res)
    }
}

正如上面的代码中注意到的那样,处理程序接收一个Hook结构并调用其Execute方法来处理请求。在Hook结构中,可以AdmitFunc为每个允许的操作注册一个。

// AdmitFunc defines how to process an admission request
type AdmitFunc func(request *admission.AdmissionRequest) (*Result, error)
// Hook represents the set of functions for each operation in an admission webhook.
type Hook struct {
    Create  AdmitFunc
    Delete  AdmitFunc
    Update  AdmitFunc
    Connect AdmitFunc
}
// Execute evaluates the request and try to execute the function for operation specified in the request.
func (h *Hook) Execute(r *admission.AdmissionRequest) (*Result, error) {
    switch r.Operation {
    case admission.Create:
        return wrapperExecution(h.Create, r)
    .....
    }
    return &Result{Msg: fmt.Sprintf("Invalid operation: %s", r.Operation)}, nil
}

现在,只关注我们的业务逻辑。需要为注册的 webhook编写代码。将为我们在资源中ValidatingWebhook注册的CREATE操作实现逻辑。我们想要验证 pod的容器都不能使用latest其镜像中的标签。

// pods/pods.go
// NewValidationHook creates a new instance of pods validation hook
func NewValidationHook() admissioncontroller.Hook {
    return admissioncontroller.Hook{
        Create: validateCreate(),
    }
}
// validateImages validates that none of the containers use the `latest` tag.
func validateImages() admissioncontroller.AdmitFunc {
    return func(r *v1beta1.AdmissionRequest) (*admissioncontroller.Result, error) {
        pod, err := parsePod(r.Object.Raw)
        if err != nil {
            return &admissioncontroller.Result{Msg: err.Error()}, nil
        }
        for _, c := range pod.Spec.Containers {
            if strings.HasSuffix(c.Image, ":latest") {
                return &admissioncontroller.Result{Msg: "You cannot use the tag 'latest' in a container."}, nil
            }
        }
        return &admissioncontroller.Result{Allowed: true}, nil
    }
}

当然,这是一个非常基本的示例,可以根据用例实现更复杂的验证。例如,在存储库上,我们有另一个有趣的例子。使用MutattingWebhookJSON补丁,我们将一个容器作为 sidecar注入到我们的 pod中。

注意:Istio使用类似的方法来注入其 sidecar容器。

func mutateCreate() admissioncontroller.AdmitFunc {
    return func(r *v1beta1.AdmissionRequest) (*admissioncontroller.Result, error) {
        var operations []admissioncontroller.PatchOperation
        pod, err := parsePod(r.Object.Raw)
        if err != nil {
            return &admissioncontroller.Result{Msg: err.Error()}, nil
        }
        // Very simple logic to inject a new "sidecar" container.
        if pod.Namespace == "special" {
            var containers []v1.Container
            containers = append(containers, pod.Spec.Containers...)
            sideC := v1.Container{
                Name:    "test-sidecar",
                Image:   "busybox:stable",
                Command: []string{"sh", "-c", "while true; do echo 'I am a container injected by mutating webhook'; sleep 2; done"},
            }
            containers = append(containers, sideC)
            operations = append(operations, admissioncontroller.ReplacePatchOperation("/spec/containers", containers))
        }
        return &admissioncontroller.Result{
            Allowed:  true,
            PatchOps: operations,
        }, nil
    }
}

部署和测试

Rundemo/deploy.sh将为服务器和 webhook 创建一个自签名 CA、证书和私有 CA。它还将创建以下资源:

  • Secret TLS
  • 准入服务器的部署使用 demo/deployment.yaml
  • 我们的admission service
  • 所有的Admission webhooks

注意:demo/deploy.sh 仅用于开发/测试环境。它不是用于生产的。您可以看到所有创建的资源:

kubectl get svc
NAME               TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
admission-server   ClusterIP   10.43.120.27   <none>        443/TCP   1h
kubectl get deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
admission-server   1/1     0            1           1h
kubectl get secret
NAME                  TYPE                                  DATA   AGE
admission-tls         kubernetes.io/tls                     2      1h
kubectl get mutatingwebhookconfigurations
NAME           WEBHOOKS   AGE
pod-mutation   1          1h
kubectl get validatingwebhookconfigurations
NAME                    WEBHOOKS   AGE
deployment-validation   1          1h
pod-validation          1          1h

现在,我们可以测试我们的 webhook。如果我们尝试Pod使用以下清单创建一个将失败。

# demo/pods/01_fail_pod_creation_test.yaml 
apiVersion: v1 
kind: Pod 
metadata: 
  name: webserver 
spec: 
  containers: 
    - name: webserver 
      image: nginx:latest 
      ports: 
        - containerPort: 80

注意:您可以在内部使用不同的清单demo/podsdemo/deployments测试验证和突变。

kubectl create -f pods/01_fail_pod_creation_test.yaml 
Error from server: error when creating "pods/01_fail_pod_creation_test.yaml": admission webhook "pod-validation.default.svc" denied the request: You cannot use the tag 'latest' in a container.

结语

如上所见,准入控制器是一个强大的功能,它允许我们为 k8s 资源实现自定义规则和默认值。这是您可以扩展 k8s 行为的一种方式。我希望你喜欢这篇文章,任何反馈都会得到很好的接受。

参考链接

https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/#why-do-i-need-admission-controllers
https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers
https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/
https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/
http://jsonpatch.com
https://douglasmakey.medium.com/implementing-a-simple-k8s-admission-controller-in-go-87ae84408cb2

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐