Kubernetes Webhook实现GPU资源自动转换与优化调度
1. 项目背景与核心需求
在Kubernetes集群中管理GPU资源一直是个颇具挑战性的任务。传统方式直接使用nvidia.com/gpu资源分配存在诸多限制:无法实现细粒度共享、缺乏灵活的调度策略、资源利用率低下。而vGPU技术能够将物理GPU切分为多个虚拟GPU,显著提升资源利用率。
本项目通过开发一个Kubernetes Webhook,实现了以下核心功能:
- 自动将Pod声明的
nvidia.com/gpu资源转换为Volcano调度器支持的vGPU资源格式 - 动态设置vGPU内存大小(支持按模型大小预设配置)
- 强制使用Volcano调度器以获得更好的GPU调度能力
- 保持资源请求与限制的完整性,避免Kubernetes校验失败
2. 技术架构解析
2.1 整体工作流程
整个系统的工作流程可分为四个关键阶段:
- 证书准备阶段:使用cert-manager创建自签名CA,为Webhook服务签发TLS证书
- Webhook部署阶段:包含Python处理逻辑的Deployment、对外服务的Service、配置存储的ConfigMap
- 注册阶段:通过MutatingWebhookConfiguration注册我们的Webhook到Kubernetes API
- 运行时阶段:当用户创建Pod时,API Server会调用我们的Webhook进行资源转换
2.2 核心组件交互
+----------------+ +---------------+ +-----------------+ | kubectl | | API Server | | Webhook Server | | apply pod +-------> (k8s) +-------> (我们的服务) | +----------------+ +-------+-------+ +--------+--------+ ^ | | | | v | +--------+--------+ +---------------+ cert-manager | | (证书管理) | +-----------------+3. 证书管理实现
3.1 cert-manager部署
首先需要部署cert-manager来处理证书生命周期:
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml3.2 创建自签名ClusterIssuer
创建自签名证书颁发机构(CA):
apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: selfsigned spec: selfSigned: {}3.3 签发Webhook TLS证书
为Webhook服务创建证书资源:
apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: volcano-injector-cert namespace: gpustack spec: secretName: volcano-injector-tls dnsNames: - volcano-injector.gpustack.svc issuerRef: name: selfsigned kind: ClusterIssuer关键点:证书必须包含Webhook Service的完整DNS名称
<service>.<namespace>.svc
4. Webhook服务实现
4.1 核心处理逻辑
Webhook的核心Python代码实现了以下关键功能:
def mutate(): # 1. 解析请求 req = request.get_json() request_obj = req.get("request", {}) pod = request_obj.get("object", {}) # 2. 初始化patch列表 patch = [] # 3. 强制使用Volcano调度器 if spec.get("schedulerName") != "volcano": add_op(patch, "/spec/schedulerName", "volcano") # 4. 处理每个容器 for i, container in enumerate(containers): base_path = f"/spec/containers/{i}/resources" # 5. GPU→vGPU转换 if "nvidia.com/gpu" in limits: # 先删除原GPU请求 remove_op(patch, f"{base_path}/limits/nvidia.com~1gpu") remove_op(patch, f"{base_path}/requests/nvidia.com~1gpu") # 添加vGPU配置 add_op(patch, f"{base_path}/limits/volcano.sh~1vgpu-number", gpu) add_op(patch, f"{base_path}/limits/volcano.sh~1vgpu-memory", vgpu_memory) # ...同样处理requests... # 确保有CPU和内存配置 add_op(patch, f"{base_path}/limits/cpu", DEFAULT_CPU) add_op(patch, f"{base_path}/requests/cpu", DEFAULT_CPU) # ...内存处理类似... # 6. 返回patch return jsonify({ "response": { "allowed": True, "patchType": "JSONPatch", "patch": base64.b64encode(json.dumps(patch).encode()).decode() } })4.2 关键设计决策
操作顺序至关重要:
- 必须先删除原GPU配置
- 然后添加vGPU配置
- 最后补充CPU/内存等必填字段
默认值设计:
- 预设了不同模型大小的vGPU内存配置
- 为CPU和内存提供了合理的默认值
错误处理:
- 捕获所有异常并返回允许创建
- 详细日志记录便于排查问题
5. 部署Webhook服务
5.1 将代码打包为ConfigMap
kubectl create configmap volcano-injector \ --from-file=webhook.py=webhook.py \ --namespace=gpustack \ --dry-run=client -o yaml > configmap-py.yaml5.2 构建自定义镜像
Dockerfile关键内容:
FROM python:3.9-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txtrequirements.txt包含的依赖:
Flask==2.3.3 Werkzeug==2.3.7 jsonpatch==1.335.3 部署Webhook Deployment
apiVersion: apps/v1 kind: Deployment metadata: name: volcano-injector namespace: gpustack spec: replicas: 1 template: spec: containers: - name: injector image: your-registry/python:v4 command: ["python", "/app/webhook.py"] volumeMounts: - name: config mountPath: /app - name: tls mountPath: /tls volumes: - name: config configMap: name: volcano-injector - name: tls secret: secretName: volcano-injector-tls5.4 创建Service
apiVersion: v1 kind: Service metadata: name: volcano-injector namespace: gpustack spec: selector: app: injector ports: - port: 443 targetPort: 443 type: ClusterIP6. 注册Webhook配置
6.1 MutatingWebhookConfiguration
apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: name: volcano-injector webhooks: - name: volcano.injector.k8s.io admissionReviewVersions: ["v1"] sideEffects: None failurePolicy: Fail namespaceSelector: matchExpressions: - key: kubernetes.io/metadata.name operator: NotIn values: [kube-system, cert-manager, gpustack] clientConfig: service: name: volcano-injector namespace: gpustack path: "/mutate" port: 443 caBundle: <base64编码的CA证书> rules: - operations: ["CREATE"] apiGroups: [""] apiVersions: ["v1"] resources: ["pods"]6.2 关键配置说明
namespaceSelector:
- 排除了系统命名空间
- 避免对Webhook自身Pod的递归调用
failurePolicy:
- 设置为Fail确保转换失败时Pod创建会被拒绝
- 生产环境建议从Ignore开始
caBundle:
- 必须包含验证Webhook服务器证书的CA证书
- 可通过
kubectl get secret volcano-injector-tls -n gpustack -o jsonpath='{.data.ca\.crt}'获取
7. 测试与验证
7.1 测试Pod定义
apiVersion: v1 kind: Pod metadata: name: gpustack-test spec: containers: - name: test image: nvidia/cuda:12.2.0-base-ubuntu22.04 command: ["bash", "-c", "nvidia-smi && sleep 3600"] resources: limits: nvidia.com/gpu: 17.2 验证转换结果
查看最终Pod配置:
kubectl get pod gpustack-test -o yaml预期看到:
schedulerName设置为volcanonvidia.com/gpu被移除- 替换为
volcano.sh/vgpu-number和volcano.sh/vgpu-memory - 自动添加了CPU和内存资源
7.3 查看Webhook日志
kubectl logs -n gpustack deployment/volcano-injector -f正常日志应显示:
- 接收到的Pod名称
- 执行的转换操作
- 生成的完整patch内容
8. 常见问题与解决方案
8.1 证书相关问题
问题现象:
Error from server (InternalError): failed calling webhook: Post "https://...": x509: certificate signed by unknown authority解决方案:
- 确保证书Secret已正确创建
- 检查MutatingWebhookConfiguration中的caBundle是否正确
- 验证证书的DNS名称匹配Service地址
8.2 Webhook调用失败
问题现象:
failed calling webhook: service "volcano-injector" not found排查步骤:
- 检查Service是否存在且selector匹配Pod标签
- 确认Service和Webhook配置在同一个namespace
- 验证网络策略是否允许API Server访问Webhook Pod
8.3 资源校验失败
问题现象:
The Pod is invalid: spec.containers[0].resources.limits: Required value原因分析:
- Webhook没有正确补充CPU/内存资源
- 操作顺序不当导致校验失败
修复方法:
- 确保在移除GPU后立即添加vGPU配置
- 最后才补充CPU/内存等必填字段
- 检查Python代码中的操作顺序
9. 性能优化建议
增加就绪探针:
readinessProbe: httpGet: path: /healthz port: 443 scheme: HTTPS实现缓存机制:
- 缓存模型大小到vGPU内存的映射
- 减少每次请求的解析时间
优化日志级别:
- 生产环境降低默认日志级别
- 关键操作保留INFO级别
横向扩展:
- 根据负载增加Deployment副本数
- 考虑使用HPA自动扩缩容
10. 安全加固措施
细粒度访问控制:
- 通过namespaceSelector和objectSelector限制Webhook作用范围
- 避免处理系统关键Pod
证书轮换:
- 设置cert-manager自动轮换证书
- 监控证书过期时间
请求验证:
- 验证请求来源IP范围
- 检查User-Agent是否为Kubernetes API Server
资源限制:
resources: limits: cpu: 500m memory: 256Mi
11. 项目扩展方向
支持动态vGPU配置:
- 根据Pod注解动态设置vGPU内存
- 支持百分比形式的显存分配
多GPU卡支持:
- 识别不同型号GPU卡
- 提供异构GPU分配能力
监控集成:
- 暴露Prometheus指标
- 记录转换统计信息
策略引擎:
- 基于资源使用情况自动调整vGPU大小
- 实现超额订阅策略
这个Webhook实现已经过生产环境验证,能够稳定处理每秒数十个Pod创建请求。关键在于确保证书配置正确和操作顺序合理。实际部署时建议先从少量命名空间开始试用,逐步扩大范围。