Admission Controllers 在 Kubernetes 中的作用:安全与策略强制执行

Kubernetes 的守护神:Admission Controllers,安全与策略的守护者 (一段轻松幽默的讲解)

各位朋友们,各位Kubernetes爱好者,大家好!我是你们的老朋友,也是你们在云原生世界里的向导,今天咱们来聊聊Kubernetes集群里一个非常关键,但又常常被忽视的角色——Admission Controllers,也就是准入控制器

想象一下,你的Kubernetes集群就像一个繁华的城市,里面住着各种各样的Pod,跑着各式各样的应用。如果没有交通规则,没有警察叔叔维持秩序,那会乱成什么样?恐怕早就堵成一锅粥了! 🤯

Admission Controllers就像这个城市里的警察局,他们负责检查每一个想要进入城市(也就是集群)的请求,看看它是否符合咱们制定的规则。如果符合,放行!如果不符合,对不起,请回吧! 👮‍♂️

所以,简单来说,Admission Controllers的作用就是:在对象被持久化到Kubernetes之前,拦截并修改或拒绝API请求。

让我们深入了解一下这个“警察局”是如何运作的。

1. 为什么我们需要Admission Controllers? (安全、策略,一个都不能少!)

你可能会问,Kubernetes本身不是已经有RBAC(基于角色的访问控制)了吗?为什么还需要Admission Controllers? 这个问题问得非常好!👏

RBAC主要负责身份验证授权,它决定了“谁”可以对“什么”资源执行“什么”操作。 然而,RBAC并不能保证操作的内容是否符合我们的策略。

举个例子:

  • RBAC: 允许用户“张三”创建Pod。
  • Admission Controller: 检查“张三”创建的Pod是否符合资源限制,是否使用了允许的镜像仓库,是否设置了必要的安全上下文等等。

你看,RBAC管的是“谁能做”,Admission Controller管的是“怎么做”。两者分工合作,才能构建一个安全、可靠的Kubernetes集群。

更具体地说,Admission Controllers可以帮助我们实现以下目标:

  • 安全性增强: 阻止不安全的配置,例如使用特权容器、暴露敏感端口等。
  • 策略强制执行: 确保所有部署都符合公司的安全策略和最佳实践。
  • 资源管理: 限制Pod的资源使用,防止资源滥用。
  • 合规性满足: 满足各种行业合规性要求,例如HIPAA、PCI DSS等。
  • 自定义行为: 根据自己的需求,定制各种准入控制策略。

简而言之,Admission Controllers就像一道安全屏障,可以有效地防止错误配置和恶意攻击,守护我们的Kubernetes集群。

2. Admission Controllers 的工作方式:拦截、修改、放行!

Admission Controllers本质上是一些HTTP回调,它们会在API请求到达Kubernetes API Server后,但在对象被持久化到etcd之前被调用。

具体流程如下:

  1. API请求: 用户或其他组件向Kubernetes API Server发送一个API请求,例如创建一个Pod。
  2. 身份验证和授权: API Server首先进行身份验证和授权,确认请求者有权限执行该操作。
  3. Admission Controller调用: API Server调用配置好的Admission Controllers。
  4. 拦截和处理: Admission Controllers根据自己的配置,对请求进行检查和处理。
    • Mutating Admission Controller: 可以修改请求的内容,例如自动添加标签、设置默认值等。
    • Validating Admission Controller: 只能验证请求的内容,不能修改。
  5. 决策: Admission Controllers根据检查结果,决定是否允许请求通过。
    • 允许: 请求被API Server接受,对象被持久化到etcd。
    • 拒绝: 请求被API Server拒绝,并返回错误信息给请求者。

可以用一个表格来更清晰地说明:

阶段 描述 Admission Controller 类型 示例
认证之后 API Server 接收到请求,进行身份验证和授权。 N/A 检查用户是否有权限创建Pod。
准入控制 在对象被持久化之前,拦截请求,进行验证或修改。 Mutating, Validating 自动添加资源限制,验证镜像来源,拒绝特权容器。
对象持久化 如果准入控制允许,则对象被持久化到 etcd。 N/A 将Pod的定义存储到etcd。

3. Admission Controller 的类型:Mutating vs. Validating

前面提到,Admission Controllers分为两种类型:

  • Mutating Admission Controller (变更准入控制器): 它可以修改API请求的内容。例如,它可以自动为Pod添加标签,设置默认的资源限制,注入Sidecar容器等等。
  • Validating Admission Controller (验证准入控制器): 它只能验证API请求的内容,不能修改。例如,它可以检查Pod是否符合特定的安全策略,是否使用了允许的镜像仓库等等。

区分这两种类型的关键在于:是否可以修改请求。 Mutating Admission Controller 可以“动手脚”,而 Validating Admission Controller 只能“动嘴皮子”。

举个例子:

假设我们需要自动为所有Pod添加一个owner=team-a的标签。 那么,我们可以使用一个Mutating Admission Controller来实现。 这个Controller会在Pod被创建之前,自动添加这个标签。

又假设我们需要禁止所有Pod使用特权容器。 那么,我们可以使用一个Validating Admission Controller来实现。 这个Controller会在Pod被创建之前,检查是否设置了securityContext.privileged=true,如果设置了,就拒绝请求。

4. Kubernetes 内置的 Admission Controllers:自带的“警察叔叔”

Kubernetes自带了很多内置的Admission Controllers,它们默认情况下已经启用,并提供了一些基本的安全和策略强制执行功能。

以下是一些常用的内置 Admission Controllers:

  • NamespaceLifecycle: 防止删除kube-system等关键命名空间。
  • LimitRanger: 强制执行命名空间级别的资源限制。
  • ServiceAccount: 自动为Pod设置ServiceAccount。
  • DefaultStorageClass: 自动为PersistentVolumeClaim设置默认的StorageClass。
  • DefaultTolerationSeconds: 为Pod设置默认的容忍度。
  • PodSecurityPolicy: (已弃用,推荐使用Pod Security Admission) 根据PodSecurityPolicy限制Pod的安全上下文。
  • ResourceQuota: 强制执行命名空间级别的资源配额。
  • AlwaysPullImages: 强制每次启动容器时都拉取镜像。

这些内置的Admission Controllers就像是Kubernetes自带的“警察叔叔”,它们默默地守护着集群的安全和稳定。

查看已启用的 Admission Controllers:

你可以通过以下命令查看当前集群中已启用的Admission Controllers:

kubectl api-resources | grep admissionregistration.k8s.io

5. 自定义 Admission Controllers:打造专属的“超级警察”

除了内置的Admission Controllers,我们还可以根据自己的需求,创建自定义的Admission Controllers。这就像是为我们的城市打造一支专属的“超级警察”队伍,他们可以执行更加复杂的任务,满足我们个性化的需求。

自定义Admission Controllers有两种实现方式:

  • Webhook Admission Controller: 通过HTTP回调调用外部服务来处理API请求。
  • Admission Plugin: 编译到kube-apiserver二进制文件中,需要重新编译和部署API Server。 (不推荐使用,因为比较复杂)

Webhook Admission Controller 是目前最常用的方式,因为它更加灵活、易于开发和部署。

Webhook Admission Controller 的工作流程:

  1. 配置: 创建一个MutatingWebhookConfigurationValidatingWebhookConfiguration对象,配置Webhook的URL、匹配规则等信息。
  2. API请求: 用户或其他组件向Kubernetes API Server发送一个API请求。
  3. API Server调用Webhook: API Server根据Webhook的配置,将API请求发送到Webhook的URL。
  4. Webhook处理请求: Webhook服务接收到API请求后,根据自己的逻辑进行处理。
    • Mutating Webhook: 可以修改请求的内容,并返回修改后的对象。
    • Validating Webhook: 可以验证请求的内容,并返回允许或拒绝的决定。
  5. API Server处理响应: API Server根据Webhook的响应,决定是否允许请求通过。

举个例子:

假设我们需要实现一个Webhook Admission Controller,它可以自动为所有Deployment添加一个app.kubernetes.io/managed-by=my-custom-controller的标签。

我们可以按照以下步骤进行操作:

  1. 编写Webhook服务: 使用任何编程语言编写一个HTTP服务,该服务接收Kubernetes API Server发送的AdmissionReview对象,并根据自己的逻辑进行处理。 在这个例子中,我们需要修改Deployment的metadata.labels字段,添加app.kubernetes.io/managed-by=my-custom-controller标签。
  2. 部署Webhook服务: 将Webhook服务部署到Kubernetes集群中。
  3. 创建TLS证书: Webhook服务需要使用TLS证书进行加密通信。
  4. 创建WebhookConfiguration: 创建一个MutatingWebhookConfiguration对象,配置Webhook的URL、CA证书、匹配规则等信息。
  5. 测试Webhook: 创建一个Deployment,检查是否自动添加了app.kubernetes.io/managed-by=my-custom-controller标签。

一个简单的 Mutating Webhook 代码示例 (Go):

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"

    admissionv1 "k8s.io/api/admission/v1"
    appsv1 "k8s.io/api/apps/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/runtime/serializer"
)

var (
    universalDeserializer = serializer.NewCodecFactory(runtime.NewScheme()).UniversalDeserializer()
)

func main() {
    http.HandleFunc("/mutate", mutateHandler)

    log.Println("Starting webhook server on :8443")
    err := http.ListenAndServeTLS(":8443", "/path/to/tls.crt", "/path/to/tls.key", nil)
    if err != nil {
        log.Fatalf("Failed to listen and serve: %v", err)
    }
}

func mutateHandler(w http.ResponseWriter, r *http.Request) {
    log.Println("Received mutation request")

    body, err := ioutil.ReadAll(r.Body)
    if err != nil {
        log.Printf("Failed to read request body: %v", err)
        http.Error(w, "Failed to read request body", http.StatusBadRequest)
        return
    }

    admissionReview := admissionv1.AdmissionReview{}
    _, _, err = universalDeserializer.Decode(body, nil, &admissionReview)
    if err != nil {
        log.Printf("Failed to decode request body: %v", err)
        http.Error(w, "Failed to decode request body", http.StatusBadRequest)
        return
    }

    if admissionReview.Request == nil {
        log.Println("AdmissionReview request is nil")
        http.Error(w, "AdmissionReview request is nil", http.StatusBadRequest)
        return
    }

    if admissionReview.Request.Kind.Kind != "Deployment" {
        log.Printf("Ignoring request of kind: %s", admissionReview.Request.Kind.Kind)
        admissionReview.Response = &admissionv1.AdmissionResponse{
            Allowed: true,
        }
        respond(w, admissionReview)
        return
    }

    deployment := appsv1.Deployment{}
    err = json.Unmarshal(admissionReview.Request.Object.Raw, &deployment)
    if err != nil {
        log.Printf("Failed to unmarshal deployment: %v", err)
        admissionReview.Response = &admissionv1.AdmissionResponse{
            Result: &metav1.Status{
                Message: err.Error(),
                Code:    http.StatusBadRequest,
            },
            Allowed: false,
        }
        respond(w, admissionReview)
        return
    }

    // Mutate the deployment
    if deployment.ObjectMeta.Labels == nil {
        deployment.ObjectMeta.Labels = make(map[string]string)
    }
    deployment.ObjectMeta.Labels["app.kubernetes.io/managed-by"] = "my-custom-controller"

    marshaledDeployment, err := json.Marshal(deployment)
    if err != nil {
        log.Printf("Failed to marshal deployment: %v", err)
        admissionReview.Response = &admissionv1.AdmissionResponse{
            Result: &metav1.Status{
                Message: err.Error(),
                Code:    http.StatusInternalServerError,
            },
            Allowed: false,
        }
        respond(w, admissionReview)
        return
    }

    admissionReview.Response = &admissionv1.AdmissionResponse{
        Allowed: true,
        PatchType: func() *admissionv1.PatchType {
            pt := admissionv1.PatchTypeJSONPatch
            return &pt
        }(),
        Patch: createPatch(admissionReview.Request.Object.Raw, marshaledDeployment),
    }

    respond(w, admissionReview)
}

func createPatch(original, modified []byte) []byte {
    patch := fmt.Sprintf(`[
        {"op": "replace", "path": "/metadata/labels", "value": %s}
    ]`, string(modified[len(`{"apiVersion":"apps/v1","kind":"Deployment","metadata":`):len(modified)-1]))
    return []byte(patch)
}

func respond(w http.ResponseWriter, admissionReview admissionv1.AdmissionReview) {
    resp, err := json.Marshal(admissionReview)
    if err != nil {
        log.Printf("Failed to marshal response: %v", err)
        http.Error(w, "Failed to marshal response", http.StatusInternalServerError)
        return
    }

    w.Header().Set("Content-Type", "application/json")
    _, err = w.Write(resp)
    if err != nil {
        log.Printf("Failed to write response: %v", err)
    }
}

需要注意的是,这只是一个非常简单的示例,实际的Webhook服务可能需要处理更加复杂的逻辑,例如身份验证、授权、错误处理等等。

6. Pod Security Admission (PSA): 新一代的安全卫士

Pod Security Policy (PSP) 曾经是 Kubernetes 中用于定义 Pod 安全策略的标准方法。 然而,PSP 存在一些缺陷,例如配置复杂、难以管理等。 因此,Kubernetes 从 v1.21 版本开始弃用 PSP,并推出了 Pod Security Admission (PSA) 作为替代方案。

PSA 基于 Kubernetes 的标签机制,通过在命名空间上设置标签,来定义该命名空间内 Pod 的安全级别。

PSA 定义了三个安全级别:

  • Privileged: 允许所有操作,无任何限制。
  • Baseline: 提供最小化的限制,可以满足大多数用户的需求。
  • Restricted: 提供最严格的限制,适用于对安全性要求较高的场景。

通过在命名空间上设置pod-security.kubernetes.io/enforcepod-security.kubernetes.io/auditpod-security.kubernetes.io/warn三个标签,我们可以控制PSA的行为。

  • enforce: 强制执行指定的安全级别。 如果Pod不符合该安全级别,则会被拒绝创建。
  • audit: 记录违反安全级别的Pod的事件。
  • warn: 在Pod创建时,显示违反安全级别的警告信息。

举个例子:

假设我们需要将my-namespace命名空间设置为Restricted安全级别,并强制执行该级别。 我们可以执行以下命令:

kubectl label namespace my-namespace pod-security.kubernetes.io/enforce=restricted --overwrite

这样,所有在my-namespace命名空间中创建的Pod都必须符合Restricted安全级别的要求,否则会被拒绝创建。

PSA 的优势:

  • 易于配置和管理: 通过简单的标签设置,即可定义Pod的安全级别。
  • 与Kubernetes原生集成: 无需安装额外的组件,即可使用PSA功能。
  • 灵活性高: 可以根据不同的命名空间,设置不同的安全级别。

7. Admission Controllers 的最佳实践:安全与便捷的平衡

在使用Admission Controllers时,我们需要注意以下几点:

  • 尽早启用: 越早启用Admission Controllers,就能越早发现和解决安全问题。
  • 谨慎配置: 错误的配置可能会导致服务中断。 建议先在测试环境中进行充分的测试,然后再应用到生产环境中。
  • 监控和告警: 监控Admission Controllers的运行状态,及时发现和解决问题。
  • 保持更新: 及时更新Admission Controllers的版本,以获取最新的安全补丁和功能。
  • 平衡安全与便捷: 过度严格的策略可能会影响开发效率。 需要在安全性和便捷性之间找到一个平衡点。
  • 使用工具: 可以使用一些现成的工具,例如OPA Gatekeeper、Kyverno等,来简化Admission Controllers的开发和管理。

8. 总结:守护 Kubernetes 集群的“门神”

Admission Controllers 是 Kubernetes 集群中至关重要的组件,它们负责在对象被持久化之前,拦截并修改或拒绝API请求,从而实现安全策略的强制执行。

通过内置的Admission Controllers和自定义的Webhook Admission Controllers,我们可以构建一个安全、可靠、合规的Kubernetes集群。

希望今天的讲解能让你对Admission Controllers有更深入的了解。 记住,他们是 Kubernetes 集群的“门神”,守护着我们的应用安全。 让我们一起努力,打造更加安全、可靠的云原生世界! 🚀

感谢大家的聆听! 如果你有任何问题,欢迎随时提问! 👍

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注