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之前被调用。
具体流程如下:
- API请求: 用户或其他组件向Kubernetes API Server发送一个API请求,例如创建一个Pod。
- 身份验证和授权: API Server首先进行身份验证和授权,确认请求者有权限执行该操作。
- Admission Controller调用: API Server调用配置好的Admission Controllers。
- 拦截和处理: Admission Controllers根据自己的配置,对请求进行检查和处理。
- Mutating Admission Controller: 可以修改请求的内容,例如自动添加标签、设置默认值等。
- Validating Admission Controller: 只能验证请求的内容,不能修改。
- 决策: 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 的工作流程:
- 配置: 创建一个
MutatingWebhookConfiguration
或ValidatingWebhookConfiguration
对象,配置Webhook的URL、匹配规则等信息。 - API请求: 用户或其他组件向Kubernetes API Server发送一个API请求。
- API Server调用Webhook: API Server根据Webhook的配置,将API请求发送到Webhook的URL。
- Webhook处理请求: Webhook服务接收到API请求后,根据自己的逻辑进行处理。
- Mutating Webhook: 可以修改请求的内容,并返回修改后的对象。
- Validating Webhook: 可以验证请求的内容,并返回允许或拒绝的决定。
- API Server处理响应: API Server根据Webhook的响应,决定是否允许请求通过。
举个例子:
假设我们需要实现一个Webhook Admission Controller,它可以自动为所有Deployment添加一个app.kubernetes.io/managed-by=my-custom-controller
的标签。
我们可以按照以下步骤进行操作:
- 编写Webhook服务: 使用任何编程语言编写一个HTTP服务,该服务接收Kubernetes API Server发送的AdmissionReview对象,并根据自己的逻辑进行处理。 在这个例子中,我们需要修改Deployment的
metadata.labels
字段,添加app.kubernetes.io/managed-by=my-custom-controller
标签。 - 部署Webhook服务: 将Webhook服务部署到Kubernetes集群中。
- 创建TLS证书: Webhook服务需要使用TLS证书进行加密通信。
- 创建WebhookConfiguration: 创建一个
MutatingWebhookConfiguration
对象,配置Webhook的URL、CA证书、匹配规则等信息。 - 测试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/enforce
、pod-security.kubernetes.io/audit
和pod-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 集群的“门神”,守护着我们的应用安全。 让我们一起努力,打造更加安全、可靠的云原生世界! 🚀
感谢大家的聆听! 如果你有任何问题,欢迎随时提问! 👍