feat: K8s Deployment 全面增强,补充生产级配置
新增 11 个配置分组: - 启动探针 (Startup Probe): 类型、路径/端口/命令、初始延迟、周期、失败阈值、超时 - 存活探针 (Liveness Probe): 完整参数 + HTTP Headers + Exec 命令 - 就绪探针 (Readiness Probe): 完整参数 + HTTP Headers + Exec 命令 - 环境变量: KEY=VALUE 直接输入、从 ConfigMap/Secret 导入 - 卷挂载: emptyDir / PVC / ConfigMap / Secret / HostPath - NodeSelector: 简单节点标签匹配 - 节点亲和性 (Node Affinity): required / preferred + In/NotIn/Exists/Gt/Lt - Pod 反亲和性: 跨节点/可用区分散副本 - 容忍度 (Tolerations): NoSchedule / PreferNoSchedule / NoExecute - 安全上下文: runAsUser、readOnlyRootFilesystem、fsGroup - 部署策略增强: revisionHistoryLimit、progressDeadlineSeconds、minReadySeconds - 标签增强: ServiceAccount、优雅终止时间
This commit is contained in:
+395
-338
@@ -2,7 +2,7 @@ export const k8sDeploymentSchema = {
|
|||||||
id: 'k8s-deployment',
|
id: 'k8s-deployment',
|
||||||
name: 'K8s Deployment',
|
name: 'K8s Deployment',
|
||||||
icon: 'Box',
|
icon: 'Box',
|
||||||
category: '云原生',
|
category: 'K8S 工作负载',
|
||||||
description: 'Kubernetes 无状态应用部署控制器',
|
description: 'Kubernetes 无状态应用部署控制器',
|
||||||
format: 'yaml',
|
format: 'yaml',
|
||||||
fileName: 'deployment.yaml',
|
fileName: 'deployment.yaml',
|
||||||
@@ -10,287 +10,434 @@ export const k8sDeploymentSchema = {
|
|||||||
{
|
{
|
||||||
title: '基础信息',
|
title: '基础信息',
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{ key: 'appName', label: '应用名称', type: 'text', default: 'my-app', required: true, tip: 'Deployment 和 Label 名称' },
|
||||||
key: 'appName',
|
{ key: 'namespace', label: '命名空间', type: 'text', default: 'default' },
|
||||||
label: '应用名称',
|
{ key: 'image', label: '容器镜像', type: 'text', placeholder: 'nginx:1.24', default: 'nginx:1.24', required: true },
|
||||||
type: 'text',
|
{ key: 'replicas', label: '副本数', type: 'number', min: 1, max: 100, default: 3 },
|
||||||
placeholder: 'my-app',
|
{ key: 'imagePullPolicy', label: '镜像拉取策略', type: 'select', options: [
|
||||||
default: 'my-app',
|
{ label: 'IfNotPresent (推荐)', value: 'IfNotPresent' },
|
||||||
required: true,
|
{ label: 'Always (每次拉取)', value: 'Always' },
|
||||||
tip: '将用作 Deployment 和 Label 的名称',
|
{ label: 'Never (仅本地)', value: 'Never' },
|
||||||
},
|
], default: 'IfNotPresent' },
|
||||||
{
|
|
||||||
key: 'namespace',
|
|
||||||
label: '命名空间',
|
|
||||||
type: 'text',
|
|
||||||
placeholder: 'default',
|
|
||||||
default: 'default',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'image',
|
|
||||||
label: '容器镜像地址',
|
|
||||||
type: 'text',
|
|
||||||
placeholder: 'nginx:1.24',
|
|
||||||
default: 'nginx:1.24',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'replicas',
|
|
||||||
label: '副本数',
|
|
||||||
type: 'number',
|
|
||||||
min: 1,
|
|
||||||
max: 100,
|
|
||||||
default: 3,
|
|
||||||
tip: '建议生产环境至少 2 副本',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '容器端口',
|
title: '容器端口',
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{ key: 'containerPort', label: '容器端口', type: 'number', min: 1, max: 65535, default: 80 },
|
||||||
key: 'containerPort',
|
{ key: 'portName', label: '端口名称', type: 'text', default: 'http' },
|
||||||
label: '容器端口',
|
{ key: 'protocol', label: '协议', type: 'select', options: [{ label: 'TCP', value: 'TCP' }, { label: 'UDP', value: 'UDP' }], default: 'TCP' },
|
||||||
type: 'number',
|
|
||||||
min: 1,
|
|
||||||
max: 65535,
|
|
||||||
default: 80,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'protocol',
|
|
||||||
label: '协议',
|
|
||||||
type: 'select',
|
|
||||||
options: [
|
|
||||||
{ label: 'TCP', value: 'TCP' },
|
|
||||||
{ label: 'UDP', value: 'UDP' },
|
|
||||||
],
|
|
||||||
default: 'TCP',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'portName',
|
|
||||||
label: '端口名称',
|
|
||||||
type: 'text',
|
|
||||||
placeholder: 'http',
|
|
||||||
default: 'http',
|
|
||||||
tip: 'Service 匹配时使用',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '资源限制',
|
title: '资源限制',
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{ key: 'cpuRequest', label: 'CPU Requests', type: 'select', options: [
|
||||||
key: 'cpuRequest',
|
{ label: '50m', value: '50m' },{ label: '100m', value: '100m' },{ label: '200m', value: '200m' },
|
||||||
label: 'CPU Requests',
|
{ label: '500m', value: '500m' },{ label: '1', value: '1' },{ label: '2', value: '2' },
|
||||||
type: 'select',
|
], default: '100m' },
|
||||||
options: [
|
{ key: 'cpuLimit', label: 'CPU Limits', type: 'select', options: [
|
||||||
{ label: '50m', value: '50m' },
|
{ label: '200m', value: '200m' },{ label: '500m', value: '500m' },{ label: '1', value: '1' },
|
||||||
{ label: '100m', value: '100m' },
|
{ label: '2', value: '2' },{ label: '4', value: '4' },
|
||||||
{ label: '200m', value: '200m' },
|
], default: '500m' },
|
||||||
{ label: '500m', value: '500m' },
|
{ key: 'memoryRequest', label: 'Memory Requests', type: 'select', options: [
|
||||||
{ label: '1', value: '1' },
|
{ label: '64Mi', value: '64Mi' },{ label: '128Mi', value: '128Mi' },{ label: '256Mi', value: '256Mi' },
|
||||||
{ label: '2', value: '2' },
|
{ label: '512Mi', value: '512Mi' },{ label: '1Gi', value: '1Gi' },
|
||||||
],
|
], default: '128Mi' },
|
||||||
default: '100m',
|
{ key: 'memoryLimit', label: 'Memory Limits', type: 'select', options: [
|
||||||
},
|
{ label: '128Mi', value: '128Mi' },{ label: '256Mi', value: '256Mi' },{ label: '512Mi', value: '512Mi' },
|
||||||
{
|
{ label: '1Gi', value: '1Gi' },{ label: '2Gi', value: '2Gi' },{ label: '4Gi', value: '4Gi' },
|
||||||
key: 'cpuLimit',
|
], default: '256Mi' },
|
||||||
label: 'CPU Limits',
|
|
||||||
type: 'select',
|
|
||||||
options: [
|
|
||||||
{ label: '200m', value: '200m' },
|
|
||||||
{ label: '500m', value: '500m' },
|
|
||||||
{ label: '1', value: '1' },
|
|
||||||
{ label: '2', value: '2' },
|
|
||||||
{ label: '4', value: '4' },
|
|
||||||
],
|
|
||||||
default: '500m',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'memoryRequest',
|
|
||||||
label: 'Memory Requests',
|
|
||||||
type: 'select',
|
|
||||||
options: [
|
|
||||||
{ label: '64Mi', value: '64Mi' },
|
|
||||||
{ label: '128Mi', value: '128Mi' },
|
|
||||||
{ label: '256Mi', value: '256Mi' },
|
|
||||||
{ label: '512Mi', value: '512Mi' },
|
|
||||||
{ label: '1Gi', value: '1Gi' },
|
|
||||||
],
|
|
||||||
default: '128Mi',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'memoryLimit',
|
|
||||||
label: 'Memory Limits',
|
|
||||||
type: 'select',
|
|
||||||
options: [
|
|
||||||
{ label: '128Mi', value: '128Mi' },
|
|
||||||
{ label: '256Mi', value: '256Mi' },
|
|
||||||
{ label: '512Mi', value: '512Mi' },
|
|
||||||
{ label: '1Gi', value: '1Gi' },
|
|
||||||
{ label: '2Gi', value: '2Gi' },
|
|
||||||
],
|
|
||||||
default: '256Mi',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '健康检查',
|
title: '启动探针 (Startup Probe)',
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{ key: 'enableStartupProbe', label: '启用启动探针', type: 'switch', default: false, tip: '慢启动容器必备,通过前 liveness/readiness 不会生效' },
|
||||||
key: 'enableLivenessProbe',
|
{ key: 'startupProbeType', label: '探针方式', type: 'select', options: [
|
||||||
label: '存活探针 (Liveness)',
|
{ label: 'HTTP GET', value: 'httpGet' },{ label: 'TCP Socket', value: 'tcpSocket' },{ label: 'Exec Command', value: 'exec' },
|
||||||
type: 'switch',
|
], default: 'httpGet', dependsOn: { key: 'enableStartupProbe', value: true } },
|
||||||
default: true,
|
{ key: 'startupPath', label: '检查路径', type: 'text', default: '/healthz', dependsOn: { key: 'startupProbeType', value: 'httpGet' } },
|
||||||
tip: '检测容器是否存活,失败则重启',
|
{ key: 'startupPort', label: '检查端口', type: 'number', min: 1, max: 65535, default: 80, dependsOn: { key: 'enableStartupProbe', value: true } },
|
||||||
},
|
{ key: 'startupCommand', label: 'Exec 命令', type: 'text', placeholder: 'cat /tmp/healthy', default: 'cat /tmp/healthy', dependsOn: { key: 'startupProbeType', value: 'exec' } },
|
||||||
{
|
{ key: 'startupInitialDelay', label: '初始延迟 (秒)', type: 'number', min: 0, max: 600, default: 0, dependsOn: { key: 'enableStartupProbe', value: true } },
|
||||||
key: 'livenessProbeType',
|
{ key: 'startupPeriod', label: '检查周期 (秒)', type: 'number', min: 1, max: 300, default: 10, dependsOn: { key: 'enableStartupProbe', value: true } },
|
||||||
label: '探针方式',
|
{ key: 'startupFailureThreshold', label: '失败阈值', type: 'number', min: 1, max: 100, default: 30, dependsOn: { key: 'enableStartupProbe', value: true }, tip: '30次×10秒=最多等300秒启动' },
|
||||||
type: 'select',
|
{ key: 'startupTimeout', label: '超时时间 (秒)', type: 'number', min: 1, max: 60, default: 5, dependsOn: { key: 'enableStartupProbe', value: true } },
|
||||||
options: [
|
|
||||||
{ label: 'HTTP GET', value: 'httpGet' },
|
|
||||||
{ label: 'TCP Socket', value: 'tcpSocket' },
|
|
||||||
{ label: 'Exec Command', value: 'exec' },
|
|
||||||
],
|
],
|
||||||
default: 'httpGet',
|
|
||||||
dependsOn: { key: 'enableLivenessProbe', value: true },
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'livenessPath',
|
title: '存活探针 (Liveness Probe)',
|
||||||
label: '健康检查路径',
|
fields: [
|
||||||
type: 'text',
|
{ key: 'enableLivenessProbe', label: '启用存活探针', type: 'switch', default: true, tip: '失败则重启容器' },
|
||||||
placeholder: '/healthz',
|
{ key: 'livenessProbeType', label: '探针方式', type: 'select', options: [
|
||||||
default: '/healthz',
|
{ label: 'HTTP GET', value: 'httpGet' },{ label: 'TCP Socket', value: 'tcpSocket' },{ label: 'Exec Command', value: 'exec' },
|
||||||
dependsOn: { key: 'livenessProbeType', value: 'httpGet' },
|
], default: 'httpGet', dependsOn: { key: 'enableLivenessProbe', value: true } },
|
||||||
},
|
{ key: 'livenessPath', label: '检查路径', type: 'text', default: '/healthz', dependsOn: { key: 'livenessProbeType', value: 'httpGet' } },
|
||||||
{
|
{ key: 'livenessPort', label: '检查端口', type: 'number', min: 1, max: 65535, default: 80, dependsOn: { key: 'enableLivenessProbe', value: true } },
|
||||||
key: 'livenessPort',
|
{ key: 'livenessCommand', label: 'Exec 命令', type: 'text', placeholder: 'cat /tmp/healthy', default: 'cat /tmp/healthy', dependsOn: { key: 'livenessProbeType', value: 'exec' } },
|
||||||
label: '探针端口',
|
{ key: 'livenessHttpHeaders', label: 'HTTP 请求头', type: 'text', placeholder: 'X-Custom-Header: value', default: '', dependsOn: { key: 'livenessProbeType', value: 'httpGet' }, tip: '格式: Header-Name: value' },
|
||||||
type: 'number',
|
{ key: 'livenessInitialDelay', label: '初始延迟 (秒)', type: 'number', min: 0, max: 600, default: 15, dependsOn: { key: 'enableLivenessProbe', value: true } },
|
||||||
min: 1,
|
{ key: 'livenessPeriod', label: '检查周期 (秒)', type: 'number', min: 1, max: 300, default: 20, dependsOn: { key: 'enableLivenessProbe', value: true } },
|
||||||
max: 65535,
|
{ key: 'livenessTimeout', label: '超时时间 (秒)', type: 'number', min: 1, max: 60, default: 5, dependsOn: { key: 'enableLivenessProbe', value: true } },
|
||||||
default: 80,
|
{ key: 'livenessFailureThreshold', label: '失败阈值', type: 'number', min: 1, max: 30, default: 3, dependsOn: { key: 'enableLivenessProbe', value: true } },
|
||||||
dependsOn: { key: 'enableLivenessProbe', value: true },
|
{ key: 'livenessSuccessThreshold', label: '成功阈值', type: 'number', min: 1, max: 10, default: 1, dependsOn: { key: 'enableLivenessProbe', value: true }, tip: 'liveness 只能为 1' },
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'enableReadinessProbe',
|
|
||||||
label: '就绪探针 (Readiness)',
|
|
||||||
type: 'switch',
|
|
||||||
default: true,
|
|
||||||
tip: '检测容器是否就绪接收流量',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'readinessProbeType',
|
|
||||||
label: '探针方式',
|
|
||||||
type: 'select',
|
|
||||||
options: [
|
|
||||||
{ label: 'HTTP GET', value: 'httpGet' },
|
|
||||||
{ label: 'TCP Socket', value: 'tcpSocket' },
|
|
||||||
{ label: 'Exec Command', value: 'exec' },
|
|
||||||
],
|
],
|
||||||
default: 'httpGet',
|
|
||||||
dependsOn: { key: 'enableReadinessProbe', value: true },
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'readinessPath',
|
title: '就绪探针 (Readiness Probe)',
|
||||||
label: '就绪检查路径',
|
fields: [
|
||||||
type: 'text',
|
{ key: 'enableReadinessProbe', label: '启用就绪探针', type: 'switch', default: true, tip: '失败则从 Service 端点摘除' },
|
||||||
placeholder: '/ready',
|
{ key: 'readinessProbeType', label: '探针方式', type: 'select', options: [
|
||||||
default: '/ready',
|
{ label: 'HTTP GET', value: 'httpGet' },{ label: 'TCP Socket', value: 'tcpSocket' },{ label: 'Exec Command', value: 'exec' },
|
||||||
dependsOn: { key: 'readinessProbeType', value: 'httpGet' },
|
], default: 'httpGet', dependsOn: { key: 'enableReadinessProbe', value: true } },
|
||||||
|
{ key: 'readinessPath', label: '检查路径', type: 'text', default: '/ready', dependsOn: { key: 'readinessProbeType', value: 'httpGet' } },
|
||||||
|
{ key: 'readinessPort', label: '检查端口', type: 'number', min: 1, max: 65535, default: 80, dependsOn: { key: 'enableReadinessProbe', value: true } },
|
||||||
|
{ key: 'readinessCommand', label: 'Exec 命令', type: 'text', placeholder: 'cat /tmp/ready', default: 'cat /tmp/ready', dependsOn: { key: 'readinessProbeType', value: 'exec' } },
|
||||||
|
{ key: 'readinessHttpHeaders', label: 'HTTP 请求头', type: 'text', placeholder: 'X-Custom-Header: value', default: '', dependsOn: { key: 'readinessProbeType', value: 'httpGet' } },
|
||||||
|
{ key: 'readinessInitialDelay', label: '初始延迟 (秒)', type: 'number', min: 0, max: 600, default: 5, dependsOn: { key: 'enableReadinessProbe', value: true } },
|
||||||
|
{ key: 'readinessPeriod', label: '检查周期 (秒)', type: 'number', min: 1, max: 300, default: 10, dependsOn: { key: 'enableReadinessProbe', value: true } },
|
||||||
|
{ key: 'readinessTimeout', label: '超时时间 (秒)', type: 'number', min: 1, max: 60, default: 3, dependsOn: { key: 'enableReadinessProbe', value: true } },
|
||||||
|
{ key: 'readinessFailureThreshold', label: '失败阈值', type: 'number', min: 1, max: 30, default: 3, dependsOn: { key: 'enableReadinessProbe', value: true } },
|
||||||
|
{ key: 'readinessSuccessThreshold', label: '成功阈值', type: 'number', min: 1, max: 10, default: 1, dependsOn: { key: 'enableReadinessProbe', value: true } },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'readinessPort',
|
title: '环境变量',
|
||||||
label: '探针端口',
|
fields: [
|
||||||
type: 'number',
|
{ key: 'enableEnv', label: '定义环境变量', type: 'switch', default: false },
|
||||||
min: 1,
|
{ key: 'envVars', label: '环境变量 (KEY=VALUE)', type: 'text', placeholder: 'APP_ENV=production\nLOG_LEVEL=info', default: 'APP_ENV=production\nLOG_LEVEL=info', dependsOn: { key: 'enableEnv', value: true }, tip: '每行一个,格式 KEY=VALUE' },
|
||||||
max: 65535,
|
{ key: 'enableEnvFromConfigMap', label: '从 ConfigMap 导入', type: 'switch', default: false },
|
||||||
default: 80,
|
{ key: 'envFromConfigMapName', label: 'ConfigMap 名称', type: 'text', default: 'my-config', dependsOn: { key: 'enableEnvFromConfigMap', value: true } },
|
||||||
dependsOn: { key: 'enableReadinessProbe', value: true },
|
{ key: 'enableEnvFromSecret', label: '从 Secret 导入', type: 'switch', default: false },
|
||||||
|
{ key: 'envFromSecretName', label: 'Secret 名称', type: 'text', default: 'my-secret', dependsOn: { key: 'enableEnvFromSecret', value: true } },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '卷挂载',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableVolumeMount', label: '挂载卷', type: 'switch', default: false },
|
||||||
|
{ key: 'volumeType', label: '卷类型', type: 'select', options: [
|
||||||
|
{ label: 'emptyDir - 临时目录', value: 'emptyDir' },
|
||||||
|
{ label: 'PersistentVolumeClaim', value: 'pvc' },
|
||||||
|
{ label: 'ConfigMap', value: 'configMap' },
|
||||||
|
{ label: 'Secret', value: 'secret' },
|
||||||
|
{ label: 'HostPath - 主机路径', value: 'hostPath' },
|
||||||
|
], default: 'emptyDir', dependsOn: { key: 'enableVolumeMount', value: true } },
|
||||||
|
{ key: 'volumeName', label: '卷名称', type: 'text', default: 'data', dependsOn: { key: 'enableVolumeMount', value: true } },
|
||||||
|
{ key: 'volumeMountPath', label: '挂载路径', type: 'text', default: '/data', dependsOn: { key: 'enableVolumeMount', value: true } },
|
||||||
|
{ key: 'volumeReadOnly', label: '只读挂载', type: 'switch', default: false, dependsOn: { key: 'enableVolumeMount', value: true } },
|
||||||
|
{ key: 'pvcClaimName', label: 'PVC 名称', type: 'text', default: 'my-pvc', dependsOn: { key: 'volumeType', value: 'pvc' } },
|
||||||
|
{ key: 'configMapName', label: 'ConfigMap 名称', type: 'text', default: 'my-config', dependsOn: { key: 'volumeType', value: 'configMap' } },
|
||||||
|
{ key: 'secretVolumeName', label: 'Secret 名称', type: 'text', default: 'my-secret', dependsOn: { key: 'volumeType', value: 'secret' } },
|
||||||
|
{ key: 'hostPath', label: '主机路径', type: 'text', default: '/tmp/data', dependsOn: { key: 'volumeType', value: 'hostPath' } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '节点调度 — NodeSelector',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableNodeSelector', label: '启用 NodeSelector', type: 'switch', default: false, tip: '简单的节点标签匹配' },
|
||||||
|
{ key: 'nodeSelectorKey', label: '标签 Key', type: 'text', placeholder: 'kubernetes.io/os', default: 'kubernetes.io/os', dependsOn: { key: 'enableNodeSelector', value: true } },
|
||||||
|
{ key: 'nodeSelectorValue', label: '标签 Value', type: 'text', placeholder: 'linux', default: 'linux', dependsOn: { key: 'enableNodeSelector', value: true } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '节点调度 — 节点亲和性 (Node Affinity)',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableNodeAffinity', label: '启用节点亲和性', type: 'switch', default: false, tip: '比 NodeSelector 更灵活的调度规则' },
|
||||||
|
{ key: 'nodeAffinityType', label: '亲和类型', type: 'select', options: [
|
||||||
|
{ label: 'required - 必须满足 (硬性)', value: 'required' },
|
||||||
|
{ label: 'preferred - 尽量满足 (软性)', value: 'preferred' },
|
||||||
|
], default: 'required', dependsOn: { key: 'enableNodeAffinity', value: true } },
|
||||||
|
{ key: 'nodeAffinityKey1', label: '匹配标签 Key', type: 'text', placeholder: 'topology.kubernetes.io/zone', default: 'topology.kubernetes.io/zone', dependsOn: { key: 'enableNodeAffinity', value: true } },
|
||||||
|
{ key: 'nodeAffinityOperator1', label: '操作符', type: 'select', options: [
|
||||||
|
{ label: 'In - 值在列表中', value: 'In' },
|
||||||
|
{ label: 'NotIn - 值不在列表中', value: 'NotIn' },
|
||||||
|
{ label: 'Exists - 标签存在', value: 'Exists' },
|
||||||
|
{ label: 'DoesNotExist - 标签不存在', value: 'DoesNotExist' },
|
||||||
|
{ label: 'Gt - 大于', value: 'Gt' },
|
||||||
|
{ label: 'Lt - 小于', value: 'Lt' },
|
||||||
|
], default: 'In', dependsOn: { key: 'enableNodeAffinity', value: true } },
|
||||||
|
{ key: 'nodeAffinityValues1', label: '匹配值 (逗号分隔)', type: 'text', placeholder: 'cn-east-1a,cn-east-1b', default: 'cn-east-1a,cn-east-1b', dependsOn: { key: 'nodeAffinityOperator1', valueNotIn: ['Exists', 'DoesNotExist'] } },
|
||||||
|
{ key: 'nodeAffinityWeight', label: '权重 (preferred)', type: 'number', min: 1, max: 100, default: 80, dependsOn: { key: 'nodeAffinityType', value: 'preferred' } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Pod 反亲和性 (Pod Anti-Affinity)',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enablePodAntiAffinity', label: '启用 Pod 反亲和性', type: 'switch', default: false, tip: '让副本分散到不同节点/区域,提高可用性' },
|
||||||
|
{ key: 'podAntiAffinityType', label: '亲和类型', type: 'select', options: [
|
||||||
|
{ label: 'required - 必须分散 (硬性)', value: 'required' },
|
||||||
|
{ label: 'preferred - 尽量分散 (软性)', value: 'preferred' },
|
||||||
|
], default: 'preferred', dependsOn: { key: 'enablePodAntiAffinity', value: true } },
|
||||||
|
{ key: 'podAntiAffinityKey', label: '匹配标签 Key', type: 'text', default: 'app', dependsOn: { key: 'enablePodAntiAffinity', value: true } },
|
||||||
|
{ key: 'podAntiAffinityTopology', label: '拓扑域', type: 'select', options: [
|
||||||
|
{ label: 'kubernetes.io/hostname (节点级)', value: 'kubernetes.io/hostname' },
|
||||||
|
{ label: 'topology.kubernetes.io/zone (可用区级)', value: 'topology.kubernetes.io/zone' },
|
||||||
|
{ label: 'topology.kubernetes.io/region (地域级)', value: 'topology.kubernetes.io/region' },
|
||||||
|
], default: 'kubernetes.io/hostname', dependsOn: { key: 'enablePodAntiAffinity', value: true } },
|
||||||
|
{ key: 'podAntiAffinityWeight', label: '权重 (preferred)', type: 'number', min: 1, max: 100, default: 100, dependsOn: { key: 'podAntiAffinityType', value: 'preferred' } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '容忍度 (Tolerations)',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableToleration', label: '启用容忍度', type: 'switch', default: false, tip: '允许调度到有 taint 的节点' },
|
||||||
|
{ key: 'tolerationKey', label: 'Taint Key', type: 'text', placeholder: 'node-role.kubernetes.io/master', default: 'node-role.kubernetes.io/master', dependsOn: { key: 'enableToleration', value: true } },
|
||||||
|
{ key: 'tolerationOperator', label: '操作符', type: 'select', options: [
|
||||||
|
{ label: 'Equal - 值匹配', value: 'Equal' },
|
||||||
|
{ label: 'Exists - Key 存在即可', value: 'Exists' },
|
||||||
|
], default: 'Exists', dependsOn: { key: 'enableToleration', value: true } },
|
||||||
|
{ key: 'tolerationValue', label: 'Taint Value', type: 'text', default: '', dependsOn: { key: 'tolerationOperator', value: 'Equal' } },
|
||||||
|
{ key: 'tolerationEffect', label: 'Taint Effect', type: 'select', options: [
|
||||||
|
{ label: 'NoSchedule - 不调度新 Pod', value: 'NoSchedule' },
|
||||||
|
{ label: 'PreferNoSchedule - 尽量不调度', value: 'PreferNoSchedule' },
|
||||||
|
{ label: 'NoExecute - 驱逐已有 Pod', value: 'NoExecute' },
|
||||||
|
], default: 'NoSchedule', dependsOn: { key: 'enableToleration', value: true } },
|
||||||
|
{ key: 'tolerationSeconds', label: '容忍时长 (秒)', type: 'number', min: 0, max: 86400, default: 300, dependsOn: { key: 'tolerationEffect', value: 'NoExecute' }, tip: '仅 NoExecute 有效' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '安全上下文 (Security Context)',
|
||||||
|
fields: [
|
||||||
|
{ key: 'enableSecurityContext', label: '启用安全上下文', type: 'switch', default: false },
|
||||||
|
{ key: 'runAsUser', label: '运行用户 UID', type: 'number', min: 0, max: 65535, default: 1000, dependsOn: { key: 'enableSecurityContext', value: true } },
|
||||||
|
{ key: 'runAsGroup', label: '运行用户组 GID', type: 'number', min: 0, max: 65535, default: 1000, dependsOn: { key: 'enableSecurityContext', value: true } },
|
||||||
|
{ key: 'runAsNonRoot', label: '禁止 Root 运行', type: 'switch', default: true, dependsOn: { key: 'enableSecurityContext', value: true } },
|
||||||
|
{ key: 'readOnlyRootFilesystem', label: '只读根文件系统', type: 'switch', default: false, dependsOn: { key: 'enableSecurityContext', value: true }, tip: '增强安全性,需要写入的目录用 emptyDir 挂载' },
|
||||||
|
{ key: 'allowPrivilegeEscalation', label: '允许提权', type: 'switch', default: false, dependsOn: { key: 'enableSecurityContext', value: true } },
|
||||||
|
{ key: 'fsGroup', label: '文件系统 GID', type: 'number', min: 0, max: 65535, default: 2000, dependsOn: { key: 'enableSecurityContext', value: true }, tip: '卷文件的所有组' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '部署策略',
|
title: '部署策略',
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{ key: 'strategy', label: '更新策略', type: 'select', options: [
|
||||||
key: 'strategy',
|
|
||||||
label: '更新策略',
|
|
||||||
type: 'select',
|
|
||||||
options: [
|
|
||||||
{ label: 'RollingUpdate - 滚动更新 (推荐)', value: 'RollingUpdate' },
|
{ label: 'RollingUpdate - 滚动更新 (推荐)', value: 'RollingUpdate' },
|
||||||
{ label: 'Recreate - 先删后建', value: 'Recreate' },
|
{ label: 'Recreate - 先删后建', value: 'Recreate' },
|
||||||
],
|
], default: 'RollingUpdate' },
|
||||||
default: 'RollingUpdate',
|
{ key: 'maxSurge', label: '最大超出副本数', type: 'select', options: [
|
||||||
},
|
{ label: '0', value: '0' },{ label: '1', value: '1' },{ label: '25%', value: '25%' },
|
||||||
{
|
{ label: '50%', value: '50%' },{ label: '100%', value: '100%' },
|
||||||
key: 'maxSurge',
|
], default: '25%', dependsOn: { key: 'strategy', value: 'RollingUpdate' } },
|
||||||
label: '最大超出副本数',
|
{ key: 'maxUnavailable', label: '最大不可用副本数', type: 'select', options: [
|
||||||
type: 'select',
|
{ label: '0 (零停机)', value: '0' },{ label: '1', value: '1' },{ label: '25%', value: '25%' },
|
||||||
options: [
|
], default: '0', dependsOn: { key: 'strategy', value: 'RollingUpdate' } },
|
||||||
{ label: '1', value: '1' },
|
{ key: 'revisionHistoryLimit', label: '历史版本保留数', type: 'number', min: 0, max: 50, default: 10, tip: '用于回滚' },
|
||||||
{ label: '25%', value: '25%' },
|
{ key: 'progressDeadlineSeconds', label: '部署超时 (秒)', type: 'number', min: 60, max: 3600, default: 600, tip: '超时则标记为失败' },
|
||||||
{ label: '50%', value: '50%' },
|
{ key: 'minReadySeconds', label: '最小就绪等待 (秒)', type: 'number', min: 0, max: 300, default: 0, tip: '容器就绪后多久视为可用' },
|
||||||
{ label: '100%', value: '100%' },
|
|
||||||
],
|
|
||||||
default: '25%',
|
|
||||||
dependsOn: { key: 'strategy', value: 'RollingUpdate' },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'maxUnavailable',
|
|
||||||
label: '最大不可用副本数',
|
|
||||||
type: 'select',
|
|
||||||
options: [
|
|
||||||
{ label: '0 (零停机)', value: '0' },
|
|
||||||
{ label: '1', value: '1' },
|
|
||||||
{ label: '25%', value: '25%' },
|
|
||||||
],
|
|
||||||
default: '0',
|
|
||||||
dependsOn: { key: 'strategy', value: 'RollingUpdate' },
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '标签与注解',
|
title: '标签与注解',
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{ key: 'appLabel', label: 'app 标签值', type: 'text', default: '', tip: '留空则使用应用名称' },
|
||||||
key: 'appLabel',
|
{ key: 'versionLabel', label: 'version 标签', type: 'text', placeholder: 'v1', default: 'v1' },
|
||||||
label: 'app 标签值',
|
{ key: 'serviceAccountName', label: 'ServiceAccount 名称', type: 'text', placeholder: '留空使用 default', default: '', tip: 'RBAC 权限控制' },
|
||||||
type: 'text',
|
{ key: 'terminationGracePeriod', label: '优雅终止时间 (秒)', type: 'number', min: 0, max: 300, default: 30, tip: 'SIGTERM 后等待多久发送 SIGKILL' },
|
||||||
default: '',
|
|
||||||
tip: '留空则使用应用名称',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'versionLabel',
|
|
||||||
label: 'version 标签',
|
|
||||||
type: 'text',
|
|
||||||
placeholder: 'v1',
|
|
||||||
default: 'v1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'imagePullPolicy',
|
|
||||||
label: '镜像拉取策略',
|
|
||||||
type: 'select',
|
|
||||||
options: [
|
|
||||||
{ label: 'IfNotPresent (推荐)', value: 'IfNotPresent' },
|
|
||||||
{ label: 'Always', value: 'Always' },
|
|
||||||
{ label: 'Never', value: 'Never' },
|
|
||||||
],
|
|
||||||
default: 'IfNotPresent',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ======================== YAML 生成器 ========================
|
||||||
|
|
||||||
|
function buildProbe(config, prefix, type, path, port, cmd, headers, initDelay, period, timeout, failure, success) {
|
||||||
|
if (!config[prefix]) return null
|
||||||
|
const probeType = config[type] || 'httpGet'
|
||||||
|
const probe = {}
|
||||||
|
|
||||||
|
if (probeType === 'httpGet') {
|
||||||
|
probe.httpGet = { path: config[path], port: config[port], scheme: 'HTTP' }
|
||||||
|
const h = config[headers]
|
||||||
|
if (h) {
|
||||||
|
const hdrs = h.split('\n').filter(Boolean).map(line => {
|
||||||
|
const [k, ...v] = line.split(':')
|
||||||
|
return { name: k.trim(), value: v.join(':').trim() }
|
||||||
|
})
|
||||||
|
if (hdrs.length) probe.httpGet.httpHeaders = hdrs
|
||||||
|
}
|
||||||
|
} else if (probeType === 'tcpSocket') {
|
||||||
|
probe.tcpSocket = { port: config[port] }
|
||||||
|
} else {
|
||||||
|
const c = config[cmd] || 'cat /tmp/healthy'
|
||||||
|
probe.exec = { command: c.split(' ') }
|
||||||
|
}
|
||||||
|
|
||||||
|
probe.initialDelaySeconds = config[initDelay]
|
||||||
|
probe.periodSeconds = config[period]
|
||||||
|
probe.timeoutSeconds = config[timeout]
|
||||||
|
probe.failureThreshold = config[failure]
|
||||||
|
if (success !== null) probe.successThreshold = config[success]
|
||||||
|
return probe
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAffinity(config) {
|
||||||
|
const affinity = {}
|
||||||
|
|
||||||
|
// Node Affinity
|
||||||
|
if (config.enableNodeAffinity) {
|
||||||
|
const key = config.nodeAffinityKey1
|
||||||
|
const op = config.nodeAffinityOperator1
|
||||||
|
const vals = (config.nodeAffinityValues1 || '').split(',').map(s => s.trim()).filter(Boolean)
|
||||||
|
const term = { matchExpressions: [{ key, operator: op }] }
|
||||||
|
if (['In', 'NotIn'].includes(op)) term.matchExpressions[0].values = vals
|
||||||
|
|
||||||
|
if (config.nodeAffinityType === 'required') {
|
||||||
|
affinity.nodeAffinity = { requiredDuringSchedulingIgnoredDuringExecution: { nodeSelectorTerms: [term] } }
|
||||||
|
} else {
|
||||||
|
affinity.nodeAffinity = {
|
||||||
|
preferredDuringSchedulingIgnoredDuringExecution: [{
|
||||||
|
weight: config.nodeAffinityWeight || 80,
|
||||||
|
preference: term,
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pod Anti-Affinity
|
||||||
|
if (config.enablePodAntiAffinity) {
|
||||||
|
const labelKey = config.podAntiAffinityKey || 'app'
|
||||||
|
const topology = config.podAntiAffinityTopology || 'kubernetes.io/hostname'
|
||||||
|
const term = {
|
||||||
|
labelSelector: { matchExpressions: [{ key: labelKey, operator: 'In', values: [config.appLabel || config.appName] }] },
|
||||||
|
topologyKey: topology,
|
||||||
|
}
|
||||||
|
if (config.podAntiAffinityType === 'required') {
|
||||||
|
affinity.podAntiAffinity = { requiredDuringSchedulingIgnoredDuringExecution: [term] }
|
||||||
|
} else {
|
||||||
|
affinity.podAntiAffinity = {
|
||||||
|
preferredDuringSchedulingIgnoredDuringExecution: [{
|
||||||
|
weight: config.podAntiAffinityWeight || 100,
|
||||||
|
podAffinityTerm: term,
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(affinity).length ? affinity : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateK8sDeploymentYaml(config) {
|
||||||
|
const labels = { app: config.appLabel || config.appName }
|
||||||
|
if (config.versionLabel) labels.version = config.versionLabel
|
||||||
|
|
||||||
|
const container = {
|
||||||
|
name: config.appName,
|
||||||
|
image: config.image,
|
||||||
|
imagePullPolicy: config.imagePullPolicy,
|
||||||
|
ports: [{ name: config.portName, containerPort: config.containerPort, protocol: config.protocol }],
|
||||||
|
resources: {
|
||||||
|
requests: { cpu: config.cpuRequest, memory: config.memoryRequest },
|
||||||
|
limits: { cpu: config.cpuLimit, memory: config.memoryLimit },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 探针
|
||||||
|
container.startupProbe = buildProbe(config, 'enableStartupProbe', 'startupProbeType', 'startupPath', 'startupPort', 'startupCommand', null, 'startupInitialDelay', 'startupPeriod', 'startupTimeout', 'startupFailureThreshold', null)
|
||||||
|
container.livenessProbe = buildProbe(config, 'enableLivenessProbe', 'livenessProbeType', 'livenessPath', 'livenessPort', 'livenessCommand', 'livenessHttpHeaders', 'livenessInitialDelay', 'livenessPeriod', 'livenessTimeout', 'livenessFailureThreshold', 'livenessSuccessThreshold')
|
||||||
|
container.readinessProbe = buildProbe(config, 'enableReadinessProbe', 'readinessProbeType', 'readinessPath', 'readinessPort', 'readinessCommand', 'readinessHttpHeaders', 'readinessInitialDelay', 'readinessPeriod', 'readinessTimeout', 'readinessFailureThreshold', 'readinessSuccessThreshold')
|
||||||
|
if (!container.startupProbe) delete container.startupProbe
|
||||||
|
if (!container.livenessProbe) delete container.livenessProbe
|
||||||
|
if (!container.readinessProbe) delete container.readinessProbe
|
||||||
|
|
||||||
|
// 环境变量
|
||||||
|
if (config.enableEnv && config.envVars) {
|
||||||
|
container.env = config.envVars.split('\n').filter(Boolean).map(line => {
|
||||||
|
const [k, ...v] = line.split('=')
|
||||||
|
return { name: k.trim(), value: v.join('=').trim() }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (config.enableEnvFromConfigMap || config.enableEnvFromSecret) {
|
||||||
|
container.envFrom = []
|
||||||
|
if (config.enableEnvFromConfigMap) container.envFrom.push({ configMapRef: { name: config.envFromConfigMapName } })
|
||||||
|
if (config.enableEnvFromSecret) container.envFrom.push({ secretRef: { name: config.envFromSecretName } })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 卷挂载
|
||||||
|
const volumes = []
|
||||||
|
if (config.enableVolumeMount) {
|
||||||
|
container.volumeMounts = [{ name: config.volumeName, mountPath: config.volumeMountPath, readOnly: config.volumeReadOnly || undefined }]
|
||||||
|
const vol = { name: config.volumeName }
|
||||||
|
const vt = config.volumeType
|
||||||
|
if (vt === 'emptyDir') vol.emptyDir = {}
|
||||||
|
else if (vt === 'pvc') vol.persistentVolumeClaim = { claimName: config.pvcClaimName }
|
||||||
|
else if (vt === 'configMap') vol.configMap = { name: config.configMapName }
|
||||||
|
else if (vt === 'secret') vol.secret = { secretName: config.secretVolumeName }
|
||||||
|
else if (vt === 'hostPath') vol.hostPath = { path: config.hostPath, type: 'DirectoryOrCreate' }
|
||||||
|
volumes.push(vol)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Security Context
|
||||||
|
if (config.enableSecurityContext) {
|
||||||
|
container.securityContext = {
|
||||||
|
runAsUser: config.runAsUser,
|
||||||
|
runAsGroup: config.runAsGroup,
|
||||||
|
runAsNonRoot: config.runAsNonRoot,
|
||||||
|
readOnlyRootFilesystem: config.readOnlyRootFilesystem || undefined,
|
||||||
|
allowPrivilegeEscalation: config.allowPrivilegeEscalation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const podSpec = { containers: [container] }
|
||||||
|
|
||||||
|
// ServiceAccount
|
||||||
|
if (config.serviceAccountName) podSpec.serviceAccountName = config.serviceAccountName
|
||||||
|
|
||||||
|
// 优雅终止
|
||||||
|
if (config.terminationGracePeriod) podSpec.terminationGracePeriodSeconds = config.terminationGracePeriod
|
||||||
|
|
||||||
|
// NodeSelector
|
||||||
|
if (config.enableNodeSelector && config.nodeSelectorKey) {
|
||||||
|
podSpec.nodeSelector = { [config.nodeSelectorKey]: config.nodeSelectorValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Affinity
|
||||||
|
const affinity = buildAffinity(config)
|
||||||
|
if (affinity) podSpec.affinity = affinity
|
||||||
|
|
||||||
|
// Tolerations
|
||||||
|
if (config.enableToleration) {
|
||||||
|
const t = { key: config.tolerationKey, operator: config.tolerationOperator, effect: config.tolerationEffect }
|
||||||
|
if (config.tolerationOperator === 'Equal') t.value = config.tolerationValue
|
||||||
|
if (config.tolerationEffect === 'NoExecute' && config.tolerationSeconds) t.tolerationSeconds = config.tolerationSeconds
|
||||||
|
podSpec.tolerations = [t]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Volumes
|
||||||
|
if (volumes.length) podSpec.volumes = volumes
|
||||||
|
|
||||||
|
// fsGroup
|
||||||
|
if (config.enableSecurityContext && config.fsGroup) {
|
||||||
|
podSpec.securityContext = { fsGroup: config.fsGroup }
|
||||||
|
}
|
||||||
|
|
||||||
|
const deployment = {
|
||||||
|
apiVersion: 'apps/v1',
|
||||||
|
kind: 'Deployment',
|
||||||
|
metadata: { name: config.appName, namespace: config.namespace, labels },
|
||||||
|
spec: {
|
||||||
|
replicas: config.replicas,
|
||||||
|
revisionHistoryLimit: config.revisionHistoryLimit,
|
||||||
|
progressDeadlineSeconds: config.progressDeadlineSeconds,
|
||||||
|
minReadySeconds: config.minReadySeconds,
|
||||||
|
selector: { matchLabels: { app: labels.app } },
|
||||||
|
strategy: config.strategy === 'RollingUpdate'
|
||||||
|
? { type: 'RollingUpdate', rollingUpdate: { maxSurge: config.maxSurge, maxUnavailable: config.maxUnavailable } }
|
||||||
|
: { type: 'Recreate' },
|
||||||
|
template: { metadata: { labels }, spec: podSpec },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const header = `# K8s Deployment - 由 ConfTemplate 生成\n# 生成时间: ${new Date().toLocaleString('zh-CN')}\n# 部署命令: kubectl apply -f ${config.fileName || 'deployment.yaml'}\n`
|
||||||
|
return header + '\n' + toYaml(deployment)
|
||||||
|
}
|
||||||
|
|
||||||
function toYaml(obj, indent = 0) {
|
function toYaml(obj, indent = 0) {
|
||||||
const lines = []
|
const lines = []
|
||||||
const prefix = ' '.repeat(indent)
|
const prefix = ' '.repeat(indent)
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined || value === '') continue
|
if (value === null || value === undefined || value === '') continue
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
lines.push(`${prefix}${key}:`)
|
lines.push(`${prefix}${key}:`)
|
||||||
for (const item of value) {
|
for (const item of value) {
|
||||||
@@ -298,9 +445,18 @@ function toYaml(obj, indent = 0) {
|
|||||||
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '')
|
||||||
if (entries.length === 0) continue
|
if (entries.length === 0) continue
|
||||||
const first = entries[0]
|
const first = entries[0]
|
||||||
lines.push(`${prefix} - ${first[0]}: ${first[1]}`)
|
lines.push(`${prefix} - ${first[0]}: ${typeof first[1] === 'object' ? '' : first[1]}`)
|
||||||
|
if (typeof first[1] === 'object') {
|
||||||
|
lines.push(toYaml(first[1], indent + 3))
|
||||||
|
}
|
||||||
for (let i = 1; i < entries.length; i++) {
|
for (let i = 1; i < entries.length; i++) {
|
||||||
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`)
|
const [k, v] = entries[i]
|
||||||
|
if (typeof v === 'object') {
|
||||||
|
lines.push(`${prefix} ${k}:`)
|
||||||
|
lines.push(toYaml(v, indent + 4))
|
||||||
|
} else {
|
||||||
|
lines.push(`${prefix} ${k}: ${v}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lines.push(`${prefix} - ${item}`)
|
lines.push(`${prefix} - ${item}`)
|
||||||
@@ -313,104 +469,5 @@ function toYaml(obj, indent = 0) {
|
|||||||
lines.push(`${prefix}${key}: ${value}`)
|
lines.push(`${prefix}${key}: ${value}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateK8sDeploymentYaml(config) {
|
|
||||||
const labels = {
|
|
||||||
app: config.appLabel || config.appName,
|
|
||||||
}
|
|
||||||
if (config.versionLabel) {
|
|
||||||
labels.version = config.versionLabel
|
|
||||||
}
|
|
||||||
|
|
||||||
const container = {
|
|
||||||
name: config.appName,
|
|
||||||
image: config.image,
|
|
||||||
imagePullPolicy: config.imagePullPolicy,
|
|
||||||
ports: [{
|
|
||||||
name: config.portName,
|
|
||||||
containerPort: config.containerPort,
|
|
||||||
protocol: config.protocol,
|
|
||||||
}],
|
|
||||||
resources: {
|
|
||||||
requests: {
|
|
||||||
cpu: config.cpuRequest,
|
|
||||||
memory: config.memoryRequest,
|
|
||||||
},
|
|
||||||
limits: {
|
|
||||||
cpu: config.cpuLimit,
|
|
||||||
memory: config.memoryLimit,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const probeConfig = (type, path, port) => {
|
|
||||||
if (type === 'httpGet') {
|
|
||||||
return { httpGet: { path, port, scheme: 'HTTP' } }
|
|
||||||
}
|
|
||||||
if (type === 'tcpSocket') {
|
|
||||||
return { tcpSocket: { port } }
|
|
||||||
}
|
|
||||||
return { exec: { command: ['cat', '/tmp/healthy'] } }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.enableLivenessProbe) {
|
|
||||||
container.livenessProbe = {
|
|
||||||
...probeConfig(config.livenessProbeType, config.livenessPath, config.livenessPort),
|
|
||||||
initialDelaySeconds: 15,
|
|
||||||
periodSeconds: 20,
|
|
||||||
timeoutSeconds: 5,
|
|
||||||
failureThreshold: 3,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.enableReadinessProbe) {
|
|
||||||
container.readinessProbe = {
|
|
||||||
...probeConfig(config.readinessProbeType, config.readinessPath, config.readinessPort),
|
|
||||||
initialDelaySeconds: 5,
|
|
||||||
periodSeconds: 10,
|
|
||||||
timeoutSeconds: 3,
|
|
||||||
failureThreshold: 3,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const deployment = {
|
|
||||||
apiVersion: 'apps/v1',
|
|
||||||
kind: 'Deployment',
|
|
||||||
metadata: {
|
|
||||||
name: config.appName,
|
|
||||||
namespace: config.namespace,
|
|
||||||
labels,
|
|
||||||
},
|
|
||||||
spec: {
|
|
||||||
replicas: config.replicas,
|
|
||||||
selector: {
|
|
||||||
matchLabels: { app: labels.app },
|
|
||||||
},
|
|
||||||
strategy: config.strategy === 'RollingUpdate'
|
|
||||||
? {
|
|
||||||
type: 'RollingUpdate',
|
|
||||||
rollingUpdate: {
|
|
||||||
maxSurge: config.maxSurge,
|
|
||||||
maxUnavailable: config.maxUnavailable,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: { type: 'Recreate' },
|
|
||||||
template: {
|
|
||||||
metadata: { labels },
|
|
||||||
spec: {
|
|
||||||
containers: [container],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const header = `# K8s Deployment - 由 ConfTemplate 生成
|
|
||||||
# 生成时间: ${new Date().toLocaleString('zh-CN')}
|
|
||||||
# 部署命令: kubectl apply -f ${config.fileName || 'deployment.yaml'}
|
|
||||||
`
|
|
||||||
|
|
||||||
return header + '\n' + toYaml(deployment)
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user