Admission Controller(准入控制器)
Admission Controller 是 Kubernetes 中一个强大的扩展机制,允许用户在 API 请求被处理之前对请求进行验证、修改或拒绝。通过准入控制器,用户可以实现各种自定义的业务逻辑,例如资源配额控制、安全策略检查、自动化配置注入等。
Kubernetes 提供了两种主要的准入控制器实现方式:Server-Side Admission 和 Webhook Admission。以下是这两种方式的详细介绍、实现细节、优缺点以及适用场景。
1. Server-Side Admission
1.1 实现细节
- 开发语言:Server-Side Admission 要求使用 Go 语言编写插件,因为 Kubernetes API Server 是用 Go 语言实现的。
- 插件开发:开发者需要实现 Kubernetes 的准入控制器接口,通常需要继承
admission.Interface
或admission.MutationInterface
等接口。 - 编译与部署:编写好的插件需要编译成共享库(
.so
文件),然后将该共享库放置在 Kubernetes API Server 的插件目录中,并在 API Server 的配置文件中加载该插件。
1.2 优点
- 高性能:由于插件直接运行在 API Server 的进程中,处理速度较快,延迟较低。
- 权限:插件可以访问 Kubernetes API Server 的内部功能和数据结构,适合需要高度集成的场景。
- 稳定性:插件与 API Server 有较强的耦合性,确保了插件的稳定性和可靠性。
1.3 缺点
- 耦合性:插件的更新和维护需要重新编译和重启 API Server,增加了操作的复杂性。
- 复杂性:需要深入了解 Kubernetes 的内部实现细节,开发和维护成本较高。
1.4 适用场景
- 高性能要求:需要对 API 请求进行快速处理的场景。
- 内部集成:需要与 Kubernetes API Server 的内部功能深度集成的场景。
- 核心功能扩展:需要扩展 Kubernetes 核心功能的场景。
2. Webhook Admission
2.1 实现细节
- 开发语言:Webhook Admission 允许使用任何语言(如 Python、Java、Go 等)编写 Webhook 服务。
- 服务暴露:开发者需要编写一个独立的 Web 服务,该服务需要支持 Kubernetes 的准入控制器协议(如
mutate
和validate
接口)。 - 配置与注册:在 Kubernetes API Server 的配置文件中注册 Webhook 服务,指定其处理的资源类型和动词(如
CREATE
、UPDATE
、DELETE
)。
2.2 优点
- 解耦:Webhook 服务独立于 Kubernetes API Server,不会影响 API Server 的性能和稳定性。
- 灵活性:可以使用不同的编程语言和框架实现 Webhook 服务,适合多样化的需求。
- 易于扩展:可以根据需要动态扩展 Webhook 服务的处理能力,支持高可用性部署。
2.3 缺点
- 延迟:由于需要通过网络调用 Webhook 服务,可能会增加请求的处理时间。
- 复杂性:需要管理 Webhook 服务的部署、可用性和安全性,增加了运维的复杂性。
2.4 适用场景
- 灵活扩展:需要使用不同的编程语言或技术栈实现准入控制的场景。
- 解耦需求:希望将准入控制逻辑与 Kubernetes API Server 解耦的场景。
- 动态扩展:需要根据需求动态扩展准入控制能力的场景。
3. 深入理解准入控制器的工作流程
- 请求到达 API Server:客户端发送一个 API 请求(如创建一个 Pod)到 Kubernetes API Server。
- 准入控制器拦截请求:API Server 在处理请求之前,会调用所有已注册的准入控制器(包括 Server-Side 和 Webhook)对请求进行处理。
- 准入控制器处理逻辑:
- Server-Side Admission:插件在 API Server 内部对请求进行处理,可以验证、修改或拒绝请求。
- Webhook Admission:API Server 将请求转发到 Webhook 服务,Webhook 服务根据业务逻辑返回允许、拒绝或修改后的请求。
- 处理结果:
- 允许:如果准入控制器允许请求,API Server 继续处理请求,将资源存储到 etcd。
- 拒绝:如果准入控制器拒绝请求,API Server 返回错误信息给客户端。
- 修改:准入控制器可以对请求进行修改,API Server 使用修改后的请求继续处理。
- 响应客户端:API Server 将处理结果返回给客户端。
4. 实现一个简单的准入控制器示例:限制 Pod 的资源使用
目标
在 Kubernetes 集群中,限制每个 Pod 的 CPU 和内存资源使用,确保不超过预设的配额。
使用 Server-Side Admission 实现
- 编写准入插件:
package main
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"k8s.io/api/admission/v1beta1"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/healthz"
"k8s.io/klog"
)
const (
DefaultCpuLimit = "2"
DefaultMemoryLimit = "4Gi"
)
type PodResourceAdmission struct {
// 可选的配置参数
}
func (p *PodResourceAdmission) Admit(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
// 获取请求的资源类型和动词
req := ar.Request
if req.Kind.Kind != "Pod" || req.Kind.Group != "" {
// 只处理核心的 Pod 资源
return &v1beta1.AdmissionResponse{
Allowed: true,
}
}
pod := &v1.Pod{}
if err := runtime.DecodeInto(ar.Request.Object.Raw, pod); err != nil {
return &v1beta1.AdmissionResponse{
Allowed: false,
Status: metav1.Status{
Message: err.Error(),
},
}
}
// 检查 CPU 请求和限制
if pod.Spec.Containers[0].Resources.Requests.Cpu().Value() > DefaultCpuLimit {
return &v1beta1.AdmissionResponse{
Allowed: false,
Status: metav1.Status{
Message: "CPU request exceeds allowed limit of " + DefaultCpuLimit,
},
}
}
// 检查内存请求和限制
if pod.Spec.Containers[0].Resources.Requests.Memory().Value() > DefaultMemoryLimit {
return &v1beta1.AdmissionResponse{
Allowed: false,
Status: metav1.Status{
Message: "Memory request exceeds allowed limit of " + DefaultMemoryLimit,
},
}
}
return &v1beta1.AdmissionResponse{
Allowed: true,
}
}
func main() {
// 初始化日志
klog.InitFlags(nil)
defer klog.Flush()
// 创建一个新的准入控制器
plugin := &PodResourceAdmission{}
// 注册准入控制器
pluginName := "pod-resource-admission"
handler := plugin.Admit
// 创建 HTTP 服务器
server := server.NewAPIServer(
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
)
// 注册准入控制器到 API Server
server.AddPlugin(pluginName, handler)
// 启动 HTTP 服务器
server.ServeHTTP()
// 阻塞主线程
select {}
}
- 编译插件:
将上述代码编译成共享库(shared library)。使用以下命令编译:
go build -buildmode=plugin -o pod-resource-admission.so main.go
- 配置 Kubernetes API Server:
将插件文件pod-resource-admission.so
放置在 API Server 的插件目录中。
修改 API Server 的配置文件,加载这个插件。
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
authentication-token-webhook: "true"
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
apiServer:
extraArgs:
admission-control-plugins: "NamespaceLifecycle,LimitRsrcAdmission,PodResourceAdmission"
pod-resource-admission-path: "/path/to/pod-resource-admission.so"
- 重启 Kubernetes API Server:
应用新的配置并重启 API Server,使插件生效。
使用 Webhook Admission 实现
- 编写 Webhook 服务:
from flask import Flask, request, jsonify
import json
app = Flask(__name__)
@app.route('/mutate', methods=['POST'])
def mutate():
request_json = json.loads(request.get_data())
pod = request_json['request']['object']
# 检查 CPU 请求
if pod['spec']['containers'][0]['resources']['requests']['cpu'] > '2':
return jsonify({
'status': {
'code': 403,
'message': 'CPU request exceeds allowed limit of 2'
},
'allowed': False
}), 403
# 检查内存请求
if pod['spec']['containers'][0]['resources']['requests']['memory'] > '4Gi':
return jsonify({
'status': {
'code': 403,
'message': 'Memory request exceeds allowed limit of 4Gi'
},
'allowed': False
}), 403
return jsonify({'allowed': True})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)
- 部署 Webhook 服务:
将上述代码打包成一个 Docker 镜像并部署到 Kubernetes 集群中。
创建一个 Kubernetes Service 和 Deployment 来暴露和管理这个 Webhook 服务。
apiVersion: apps/v1
kind: Deployment
metadata:
name: pod-resource-webhook
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
app: pod-resource-webhook
template:
metadata:
labels:
app: pod-resource-webhook
spec:
containers:
- name: pod-resource-webhook
image: your-dockerhub/pod-resource-webhook:1.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: pod-resource-webhook
namespace: kube-system
spec:
selector:
app: pod-resource-webhook
ports:
- name: http
port: 8080
targetPort: 8080
type: ClusterIP
- 配置 Kubernetes API Server:
修改 API Server 的配置文件,注册这个 Webhook 服务。
apiServer:
extraArgs:
admission-control-config-file: /etc/kubernetes/admission-webhook.yaml
创建 admission-webhook.yaml
文件,定义 Webhook 的配置:
admissionWebhook:
enabled: true
configuration:
apiVersion: admissionregistration.k8s.io/v1
kind: WebhookConfiguration
name: pod-resource-webhook
webhooks:
- name: pod-resource.mutating.k8s.io
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
clientConfig:
service:
name: pod-resource-webhook
namespace: kube-system
path: /mutate
- 应用配置并重启 API Server:
应用新的配置并重启 API Server,使 Webhook 生效。
5. 深入理解准入控制的优缺点
Server-Side Admission 的优缺点
- 优点:
- 性能:由于插件直接运行在 API Server 进程中,处理速度较快,延迟较低。
- 权限:插件可以访问 Kubernetes API Server 的所有内部功能和数据结构。
- 稳定:插件的运行状态直接影响 API Server 的性能,需要确保插件的稳定性和可靠性。
- 缺点:
- 耦合性:插件与 API Server 有较强的耦合性,升级或维护插件需要重新编译和重启 API Server。
- 复杂性:需要深入了解 Kubernetes 的内部实现细节,开发和维护成本较高。
Webhook Admission 的优缺点
- 优点:
- 解耦:Webhook 服务独立于 Kubernetes API Server,不会影响 API Server 的性能和稳定性。
- 灵活性:可以使用任何语言开发 Webhook 服务,不依赖于 Kubernetes 的内部实现。
- 易于扩展:可以根据需要动态扩展 Webhook 服务的处理能力。
- 缺点:
- 延迟:由于需要通过网络调用 Webhook 服务,可能会增加请求的处理时间。
- 复杂性:需要管理 Webhook 服务的部署、可用性和安全性。
6. 实际应用中的注意事项
性能考虑
- 对于高负载的集群,Server-Side Admission 通常比 Webhook Admission 更优,因为它减少了网络开销。
- 如果选择 Webhook Admission,确保 Webhook 服务有足够的处理能力,避免成为性能瓶颈。
安全性
- 确保 Webhook 服务的安全性,使用 TLS 加密通信,验证请求来源。
- 在 API Server 配置中,限制 Webhook 服务的访问权限,避免未授权的访问。
可靠性
- 确保准入控制器的稳定性和可靠性,避免因插件或 Webhook 服务的故障导致 API Server 无法正常工作。
- 实现重试机制和熔断机制,提高系统的容错能力。
监控与日志
- 配置监控工具(如 Prometheus 和 Grafana)监控准入控制器的性能和健康状态。
- 启用详细的日志记录,便于排查和调试问题。