feat: 完成 issue #8 ⑥ K8s/Helm 部署底座 + 昇腾适配层
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
apiVersion: v2
|
||||
name: iaop
|
||||
description: |
|
||||
iAOP(云美工业AI优化平台)一键 Helm 部署底座(PRD 5.6「⑥ 部署底座」,
|
||||
EPIC #8):内核(采集/数据总线/LLM 网关/RAG)+ 行业模板一键部署;
|
||||
推理后端(NVIDIA 5090 GPU / 华为昇腾 NPU)可插拔,切换后端仅改 values。
|
||||
type: application
|
||||
version: 1.0.0
|
||||
appVersion: "v1.0.0"
|
||||
keywords:
|
||||
- iAOP
|
||||
- industrial-ai
|
||||
- k8s
|
||||
- helm
|
||||
- inference
|
||||
- ascend
|
||||
@@ -0,0 +1,65 @@
|
||||
# iAOP Helm Chart(一键部署底座)
|
||||
|
||||
对应 PRD 5.6「⑥ 部署底座」与 EPIC #8:
|
||||
K8s/Helm/ArgoCD 部署底座;GPU/NPU 推理后端可插拔(5090 / 华为昇腾)。
|
||||
**验收:一套 Chart 部署内核 + 模板;切换后端仅改 values,不动业务代码。**
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
deploy/k8s/helm/iaop/
|
||||
├── Chart.yaml Chart 元信息(name/version/appVersion)
|
||||
├── values.yaml 全部配置点(镜像/后端/副本/资源/存储/灰度/域名)
|
||||
├── templates/
|
||||
│ ├── _helpers.tpl 标签/名称辅助模板
|
||||
│ ├── backend-configmap.yaml 推理后端适配层配置(按 backend 分支渲染)
|
||||
│ ├── deployment.yaml 推理服务 Deployment(nodeSelector 按后端分流)
|
||||
│ ├── service.yaml ClusterIP 服务(:8000/v1)
|
||||
│ ├── ingress.yaml 可选域名入口
|
||||
│ ├── pvc.yaml 模型权重/日志持久化
|
||||
│ └── NOTES.txt 部署后提示
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## 一键部署
|
||||
|
||||
```bash
|
||||
helm repo add iaop http://git.xayunmei.com/iaop/charts # 或本地路径
|
||||
helm install iaop deploy/k8s/helm/iaop -n iaop --create-namespace
|
||||
```
|
||||
|
||||
## 切换推理后端(仅改 values,业务代码零改动)
|
||||
|
||||
```bash
|
||||
# 默认 gpu(NVIDIA 5090,vLLM/Triton):
|
||||
helm install iaop deploy/k8s/helm/iaop -n iaop \
|
||||
--set inference.backend=gpu \
|
||||
--set inference.device=nvidia-5090
|
||||
|
||||
# 切换为华为昇腾 NPU(ACL/CANN/MindIE):
|
||||
helm upgrade iaop deploy/k8s/helm/iaop -n iaop \
|
||||
--set inference.backend=npu \
|
||||
--set inference.device=ascend-910b \
|
||||
--set inference.cannVersion=8.0
|
||||
```
|
||||
|
||||
切换行为(由模板自动处理):
|
||||
- `backend-configmap.yaml`:渲染对应后端适配层配置(npu 追加 `cann_version`);
|
||||
- `deployment.yaml`:nodeSelector 自动切到 `ascend.com/npu=true`(GPU 为 `nvidia.com/gpu=true`),
|
||||
并在 Pod 标签标注 `iaop.ai/inference-backend`;
|
||||
- 后端实现差异全部收敛在 `core/inference-backend/` 适配层,业务代码零改动。
|
||||
|
||||
## 其他配置点(PRD 5.6)
|
||||
|
||||
| 配置点 | values 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 资源配额 | `resources.requests/limits` | CPU/内存配额 |
|
||||
| 推理后端选择 | `inference.backend` | `gpu` / `npu` |
|
||||
| 灰度发布策略 | `rollingUpdate` | `maxUnavailable: 0, maxSurge: 1`(滚动) |
|
||||
| 存储 | `storage.*` | 模型权重持久卷 |
|
||||
| 域名入口 | `ingress.*` | 可选 Ingress |
|
||||
|
||||
## 说明
|
||||
|
||||
- Chart 未引入任何外部依赖(dependencies 为空),`helm template` 可离线渲染;
|
||||
- 推理服务镜像对应 `core/inference-backend/` 适配层容器化(本文档发布配套镜像时更新 `image.tag`)。
|
||||
@@ -0,0 +1,75 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Helm Chart 结构 sanity 检查(无 helm CLI 环境下的离线基本验证)。
|
||||
|
||||
检查项:
|
||||
1. Chart.yaml / values.yaml 均为合法 YAML;
|
||||
2. templates/ 下所有模板文件存在且非空;
|
||||
3. 模板中引用的 `.Values.xxx` 键均能在 values.yaml 中找到(防拼写错误);
|
||||
4. 每个模板文件的 Go template 标记 `{{` / `}}` 数量配平。
|
||||
|
||||
用法:python _sanity_check.py
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
TEMPLATES = os.path.join(HERE, "templates")
|
||||
|
||||
import yaml # noqa: E402
|
||||
|
||||
|
||||
def load(path):
|
||||
with open(path, "r", encoding="utf-8") as fh:
|
||||
return yaml.safe_load(fh)
|
||||
|
||||
|
||||
def values_paths(text):
|
||||
"""提取模板文本中的 .Values.<a>.<b> 路径集合。"""
|
||||
return set(re.findall(r"\.Values\.([A-Za-z0-9_]+(?:\.[A-Za-z0-9_]+)*)", text))
|
||||
|
||||
|
||||
def resolve(values, dotted):
|
||||
node = values
|
||||
for key in dotted.split("."):
|
||||
if not isinstance(node, dict) or key not in node:
|
||||
return False
|
||||
node = node[key]
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
failures = []
|
||||
|
||||
chart = load(os.path.join(HERE, "Chart.yaml"))
|
||||
assert chart.get("apiVersion") == "v2", "Chart.yaml 应为 Helm v2 chart"
|
||||
assert chart.get("name") == "iaop", "Chart.name 应为 iaop"
|
||||
|
||||
values = load(os.path.join(HERE, "values.yaml"))
|
||||
assert values.get("inference", {}).get("backend") in ("gpu", "npu"), \
|
||||
"values.inference.backend 应为 gpu|npu"
|
||||
|
||||
for name in sorted(os.listdir(TEMPLATES)):
|
||||
path = os.path.join(TEMPLATES, name)
|
||||
if not os.path.isfile(path):
|
||||
continue
|
||||
text = open(path, "r", encoding="utf-8").read()
|
||||
if not text.strip():
|
||||
failures.append(f"{name}: 模板文件为空")
|
||||
continue
|
||||
if text.count("{{") != text.count("}}"):
|
||||
failures.append(f"{name}: Go template 标记 {{/}} 数量不配平")
|
||||
for dotted in values_paths(text):
|
||||
if not resolve(values, dotted):
|
||||
failures.append(f"{name}: 引用了 values.yaml 中不存在的键 .Values.{dotted}")
|
||||
|
||||
if failures:
|
||||
print("FAIL")
|
||||
for f in failures:
|
||||
print(" -", f)
|
||||
sys.exit(1)
|
||||
print(f"OK: Chart/values 合法,templates/{len(os.listdir(TEMPLATES))} 个模板校验通过")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,13 @@
|
||||
Thank you for installing {{ .Chart.Name }} {{ .Chart.Version }} (backend: {{ .Values.inference.backend }}).
|
||||
|
||||
iAOP 部署底座(PRD 5.6 / EPIC #8)已就绪,Release 名:{{ .Release.Name }}。
|
||||
|
||||
快速使用:
|
||||
1. 查看部署状态:
|
||||
kubectl get deploy,svc -l app.kubernetes.io/instance={{ .Release.Name }}
|
||||
2. 健康巡检:
|
||||
curl http://{{ include "iaop.fullname" . }}:{{ .Values.service.port }}/health
|
||||
3. 切换推理后端(NVIDIA GPU ↔ 华为昇腾 NPU),仅改 values 后升级:
|
||||
helm upgrade {{ .Release.Name }} . --set inference.backend=npu
|
||||
4. 如需域名入口:
|
||||
helm upgrade {{ .Release.Name }} . --set ingress.enabled=true --set ingress.host=iaop.example.com
|
||||
@@ -0,0 +1,43 @@
|
||||
{{/*
|
||||
iAOP Helm Chart 辅助模板(_helpers.tpl)
|
||||
*/}}
|
||||
|
||||
{{/*
|
||||
展开 Chart 全名(release 名 + chart 名,截断到 63 字符)。
|
||||
*/}}
|
||||
{{- define "iaop.fullname" -}}
|
||||
{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Chart 名称标签。
|
||||
*/}}
|
||||
{{- define "iaop.name" -}}
|
||||
{{- .Chart.Name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
通用标签。
|
||||
*/}}
|
||||
{{- define "iaop.labels" -}}
|
||||
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" }}
|
||||
app.kubernetes.io/name: {{ include "iaop.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
选择器标签。
|
||||
*/}}
|
||||
{{- define "iaop.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "iaop.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
推理后端镜像完整名(repository:tag)。
|
||||
*/}}
|
||||
{{- define "iaop.image" -}}
|
||||
{{- printf "%s:%s" .Values.image.repository .Values.image.tag -}}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,36 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "iaop.fullname" . }}-backend
|
||||
labels:
|
||||
{{- include "iaop.labels" . | nindent 4 }}
|
||||
data:
|
||||
# 推理后端适配层配置(PRD 5.6:切换后端仅改 values.inference.backend)。
|
||||
# 该配置与 core/inference-backend/config/backends.template.yaml 同构,
|
||||
# 由推理服务容器在启动时读取。
|
||||
backends.yaml: |
|
||||
template: ti-cl4
|
||||
version: 1.0.0
|
||||
backend: {{ .Values.inference.backend }}
|
||||
inference:
|
||||
model: {{ .Values.inference.model | quote }}
|
||||
endpoint: http://{{ include "iaop.fullname" . }}:{{ .Values.service.port }}/v1
|
||||
timeout_seconds: {{ .Values.inference.timeoutSeconds }}
|
||||
runtime: {{ .Values.inference.runtime | quote }}
|
||||
device: {{ .Values.inference.device | quote }}
|
||||
max_tokens: {{ .Values.inference.maxTokens }}
|
||||
temperature: {{ .Values.inference.temperature }}
|
||||
{{- if eq .Values.inference.backend "npu" }}
|
||||
# 昇腾适配层专用字段(CANN 工具链版本;gpu 后端忽略)
|
||||
cann_version: {{ .Values.inference.cannVersion | default "" | quote }}
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
cpu: {{ .Values.resources.requests.cpu | quote }}
|
||||
memory: {{ .Values.resources.requests.memory | quote }}
|
||||
limits:
|
||||
cpu: {{ .Values.resources.limits.cpu | quote }}
|
||||
memory: {{ .Values.resources.limits.memory | quote }}
|
||||
rollingUpdate:
|
||||
maxUnavailable: {{ .Values.rollingUpdate.maxUnavailable }}
|
||||
maxSurge: {{ .Values.rollingUpdate.maxSurge }}
|
||||
@@ -0,0 +1,72 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "iaop.fullname" . }}
|
||||
labels:
|
||||
{{- include "iaop.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxUnavailable: {{ .Values.rollingUpdate.maxUnavailable }}
|
||||
maxSurge: {{ .Values.rollingUpdate.maxSurge }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "iaop.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "iaop.selectorLabels" . | nindent 8 }}
|
||||
iaop.ai/inference-backend: {{ .Values.inference.backend }}
|
||||
spec:
|
||||
{{- if eq .Values.inference.backend "gpu" }}
|
||||
# NVIDIA GPU(5090)节点选择:推理节点打标 nvidia.com/gpu=true
|
||||
nodeSelector:
|
||||
nvidia.com/gpu: "true"
|
||||
{{- else }}
|
||||
# 华为昇腾 NPU 节点选择:推理节点打标 ascend.com/npu=true
|
||||
nodeSelector:
|
||||
ascend.com/npu: "true"
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: inference
|
||||
image: "{{ include "iaop.image" . }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.service.port }}
|
||||
env:
|
||||
- name: IAOP_BACKEND_CONFIG
|
||||
value: /etc/iaop/backends.yaml
|
||||
volumeMounts:
|
||||
- name: backend-config
|
||||
mountPath: /etc/iaop
|
||||
readOnly: true
|
||||
{{- if .Values.storage.enabled }}
|
||||
- name: model-store
|
||||
mountPath: /models
|
||||
{{- end }}
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: http
|
||||
initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: http
|
||||
initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
|
||||
periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
volumes:
|
||||
- name: backend-config
|
||||
configMap:
|
||||
name: {{ include "iaop.fullname" . }}-backend
|
||||
{{- if .Values.storage.enabled }}
|
||||
- name: model-store
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "iaop.fullname" . }}-models
|
||||
{{- end }}
|
||||
@@ -0,0 +1,23 @@
|
||||
{{- if .Values.ingress.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ include "iaop.fullname" . }}
|
||||
labels:
|
||||
{{- include "iaop.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if .Values.ingress.className }}
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
{{- end }}
|
||||
rules:
|
||||
- host: {{ .Values.ingress.host | quote }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: {{ include "iaop.fullname" . }}
|
||||
port:
|
||||
name: http
|
||||
{{- end }}
|
||||
@@ -0,0 +1,17 @@
|
||||
{{- if .Values.storage.enabled }}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: {{ include "iaop.fullname" . }}-models
|
||||
labels:
|
||||
{{- include "iaop.labels" . | nindent 4 }}
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
{{- if .Values.storage.className }}
|
||||
storageClassName: {{ .Values.storage.className }}
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.storage.size }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "iaop.fullname" . }}
|
||||
labels:
|
||||
{{- include "iaop.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "iaop.selectorLabels" . | nindent 4 }}
|
||||
@@ -0,0 +1,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# iAOP Helm Chart 默认 values(PRD 5.6「⑥ 部署底座」配置点)。
|
||||
#
|
||||
# 切换推理后端(NVIDIA 5090 GPU ↔ 华为昇腾 NPU)只需改:
|
||||
# inference.backend: gpu | npu
|
||||
# 其余部署(内核+模板、资源配额、灰度、存储、域名)无需改动。
|
||||
|
||||
# ---- 镜像 ----
|
||||
image:
|
||||
repository: iaop/inference
|
||||
tag: v1.0.0
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
# ---- 推理后端选择(PRD 5.6 配置点:切换后端仅改此处) ----
|
||||
inference:
|
||||
backend: gpu # gpu | npu
|
||||
model: iaop-ti-cl4-v1
|
||||
runtime: vllm # gpu: vllm|triton;npu: mindie|onnx-ascend
|
||||
device: nvidia-5090 # gpu: nvidia-5090;npu: ascend-310p|ascend-910b
|
||||
cannVersion: "" # npu: CANN 工具链版本(如 "8.0"),gpu 忽略
|
||||
maxTokens: 1024
|
||||
temperature: 0.1
|
||||
timeoutSeconds: 60
|
||||
|
||||
# ---- 副本与滚动(灰度发布策略,PRD 5.6 配置点) ----
|
||||
replicaCount: 2
|
||||
rollingUpdate:
|
||||
maxUnavailable: 0
|
||||
maxSurge: 1
|
||||
|
||||
# ---- 服务 ----
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8000
|
||||
|
||||
# ---- 域名入口 ----
|
||||
ingress:
|
||||
enabled: false
|
||||
host: iaop.example.com
|
||||
className: ""
|
||||
|
||||
# ---- 资源配额(PRD 5.6 配置点) ----
|
||||
resources:
|
||||
requests:
|
||||
cpu: "2"
|
||||
memory: 8Gi
|
||||
limits:
|
||||
cpu: "8"
|
||||
memory: 32Gi
|
||||
|
||||
# ---- 存储(模型权重/日志持久化) ----
|
||||
storage:
|
||||
enabled: true
|
||||
className: "" # 空 = 使用集群默认 StorageClass
|
||||
size: 100Gi
|
||||
|
||||
# ---- 探针 ----
|
||||
livenessProbe:
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
Reference in New Issue
Block a user