export const k8sDaemonSetSchema = { id: 'k8s-daemonset', name: 'K8s DaemonSet', icon: 'Box', category: 'K8S 工作负载', description: 'Kubernetes 守护进程集 — 在每个节点上运行一个 Pod 副本', format: 'yaml', fileName: 'daemonset.yaml', groups: [ { title: '基础信息', fields: [ { key: 'name', label: 'DaemonSet 名称', type: 'text', placeholder: 'my-daemonset', default: 'my-daemonset', required: true }, { key: 'namespace', label: '命名空间', type: 'text', placeholder: 'default', default: 'default' }, { key: 'appName', label: '应用名称', type: 'text', placeholder: 'my-app', default: 'my-app', required: true }, { key: 'image', label: '容器镜像', type: 'text', placeholder: 'fluentd:latest', default: 'fluentd:latest', required: true }, { key: 'imagePullPolicy', label: '镜像拉取策略', type: 'select', options: [ { label: 'IfNotPresent (推荐)', value: 'IfNotPresent' }, { label: 'Always (每次拉取)', value: 'Always' }, { label: 'Never (仅本地)', value: 'Never' }, ], default: 'IfNotPresent' }, ], }, { title: '容器端口', fields: [ { key: 'containerPort', label: '容器端口', type: 'number', min: 1, max: 65535, default: 8080 }, { key: 'portName', label: '端口名称', type: 'text', default: 'http' }, { key: 'protocol', label: '协议', type: 'select', options: [{ label: 'TCP', value: 'TCP' }, { label: 'UDP', value: 'UDP' }], default: 'TCP' }, ], }, { title: '资源限制', fields: [ { key: 'cpuRequest', label: 'CPU Requests', type: 'select', options: [ { label: '50m', value: '50m' },{ label: '100m', value: '100m' },{ label: '200m', value: '200m' }, { label: '500m', value: '500m' },{ label: '1', value: '1' }, ], default: '100m' }, { key: 'cpuLimit', label: 'CPU Limits', type: 'select', options: [ { label: '200m', value: '200m' },{ label: '500m', value: '500m' },{ label: '1', value: '1' }, { label: '2', value: '2' }, ], 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' }, ], 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' }, ], default: '256Mi' }, ], }, { title: '节点调度', fields: [ { key: 'hostNetwork', label: '使用主机网络', type: 'switch', default: false, tip: 'Pod 将使用宿主机网络命名空间' }, { key: 'tolerationsEnabled', label: '添加 Toleration', type: 'switch', default: false, tip: '允许在有污点的节点上运行' }, { key: 'tolerationKey', label: 'Toleration Key', type: 'text', placeholder: 'node-role.kubernetes.io/master', default: '', dependsOn: { key: 'tolerationsEnabled', value: true } }, { key: 'tolerationValue', label: 'Toleration Value', type: 'text', placeholder: '', default: '', dependsOn: { key: 'tolerationsEnabled', value: true } }, ], }, { title: '更新策略', fields: [ { key: 'updateStrategy', label: '更新策略', type: 'select', options: [ { label: 'RollingUpdate - 滚动更新 (推荐)', value: 'RollingUpdate' }, { label: 'OnDelete - 手动删除触发', value: 'OnDelete' }, ], default: 'RollingUpdate' }, ], }, { title: '启动探针 (Startup Probe)', fields: [ { key: 'enableStartupProbe', label: '启用启动探针', type: 'switch', default: false, tip: '慢启动容器必备,通过前 liveness/readiness 不会生效' }, { key: 'startupProbeType', label: '探针方式', type: 'select', options: [ { label: 'HTTP GET', value: 'httpGet' },{ label: 'TCP Socket', value: 'tcpSocket' },{ label: 'Exec Command', value: 'exec' }, ], default: 'httpGet', dependsOn: { key: 'enableStartupProbe', value: true } }, { key: 'startupPath', label: '检查路径', type: 'text', default: '/healthz', dependsOn: { key: 'startupProbeType', value: 'httpGet' } }, { key: 'startupPort', label: '检查端口', type: 'number', min: 1, max: 65535, default: 8080, 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: 'startupPeriod', label: '检查周期 (秒)', type: 'number', min: 1, max: 300, default: 10, dependsOn: { key: 'enableStartupProbe', value: true } }, { key: 'startupFailureThreshold', label: '失败阈值', type: 'number', min: 1, max: 100, default: 30, dependsOn: { key: 'enableStartupProbe', value: true }, tip: '30次×10秒=最多等300秒启动' }, { key: 'startupTimeout', label: '超时时间 (秒)', type: 'number', min: 1, max: 60, default: 5, dependsOn: { key: 'enableStartupProbe', value: true } }, ], }, { title: '存活探针 (Liveness Probe)', fields: [ { key: 'enableLivenessProbe', label: '启用存活探针', type: 'switch', default: true, tip: '失败则重启容器' }, { key: 'livenessProbeType', label: '探针方式', type: 'select', 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', label: '检查路径', type: 'text', default: '/healthz', dependsOn: { key: 'livenessProbeType', value: 'httpGet' } }, { key: 'livenessPort', label: '检查端口', type: 'number', min: 1, max: 65535, default: 8080, dependsOn: { key: 'enableLivenessProbe', value: true } }, { key: 'livenessCommand', label: 'Exec 命令', type: 'text', placeholder: 'cat /tmp/healthy', default: 'cat /tmp/healthy', dependsOn: { key: 'livenessProbeType', value: 'exec' } }, { key: 'livenessHttpHeaders', label: 'HTTP 请求头', type: 'text', placeholder: 'X-Custom-Header: value', default: '', dependsOn: { key: 'livenessProbeType', value: 'httpGet' }, tip: '格式: Header-Name: value' }, { key: 'livenessInitialDelay', label: '初始延迟 (秒)', type: 'number', min: 0, max: 600, default: 15, dependsOn: { key: 'enableLivenessProbe', value: true } }, { key: 'livenessPeriod', label: '检查周期 (秒)', type: 'number', min: 1, max: 300, default: 20, dependsOn: { key: 'enableLivenessProbe', value: true } }, { key: 'livenessTimeout', label: '超时时间 (秒)', type: 'number', min: 1, max: 60, default: 5, dependsOn: { key: 'enableLivenessProbe', value: true } }, { key: 'livenessFailureThreshold', label: '失败阈值', type: 'number', min: 1, max: 30, default: 3, dependsOn: { key: 'enableLivenessProbe', value: true } }, { key: 'livenessSuccessThreshold', label: '成功阈值', type: 'number', min: 1, max: 10, default: 1, dependsOn: { key: 'enableLivenessProbe', value: true }, tip: 'liveness 只能为 1' }, ], }, { title: '就绪探针 (Readiness Probe)', fields: [ { key: 'enableReadinessProbe', label: '启用就绪探针', type: 'switch', default: true, tip: '失败则从 Service 端点摘除' }, { 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', label: '检查路径', type: 'text', default: '/ready', dependsOn: { key: 'readinessProbeType', value: 'httpGet' } }, { key: 'readinessPort', label: '检查端口', type: 'number', min: 1, max: 65535, default: 8080, 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 } }, ], }, { title: '环境变量', fields: [ { key: 'enableEnv', label: '定义环境变量', type: 'switch', default: false }, { 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' }, { key: 'enableEnvFromConfigMap', label: '从 ConfigMap 导入', type: 'switch', default: false }, { key: 'envFromConfigMapName', label: 'ConfigMap 名称', type: 'text', default: 'my-config', dependsOn: { key: 'enableEnvFromConfigMap', 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: 'hostPath - 主机路径', value: 'hostPath' }, { label: 'ConfigMap', value: 'configMap' }, { label: 'Secret', value: 'secret' }, ], 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: '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: '节点调度 — 节点亲和性 (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: '安全上下文 (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: '标签与注解', fields: [ { key: 'appLabel', label: 'app 标签值', type: 'text', default: '', tip: '留空则使用应用名称' }, { key: 'versionLabel', label: 'version 标签', type: 'text', placeholder: 'v1', default: 'v1' }, { key: 'serviceAccountName', label: 'ServiceAccount 名称', type: 'text', placeholder: '留空使用 default', default: '', tip: 'RBAC 权限控制' }, { key: 'terminationGracePeriod', label: '优雅终止时间 (秒)', type: 'number', min: 0, max: 300, default: 30, tip: 'SIGTERM 后等待多久发送 SIGKILL' }, ], }, ], } // ======================== 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 buildNodeAffinity(config) { if (!config.enableNodeAffinity) return undefined 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 const affinity = {} if (config.nodeAffinityType === 'required') { affinity.nodeAffinity = { requiredDuringSchedulingIgnoredDuringExecution: { nodeSelectorTerms: [term] } } } else { affinity.nodeAffinity = { preferredDuringSchedulingIgnoredDuringExecution: [{ weight: config.nodeAffinityWeight || 80, preference: term, }], } } return affinity } export function generateK8sDaemonSetYaml(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 } }) } // Security Context (container-level) if (config.enableSecurityContext) { container.securityContext = { runAsUser: config.runAsUser, runAsGroup: config.runAsGroup, runAsNonRoot: config.runAsNonRoot, readOnlyRootFilesystem: config.readOnlyRootFilesystem || undefined, allowPrivilegeEscalation: config.allowPrivilegeEscalation, } } // 卷挂载 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 === '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) } const podSpec = { containers: [container] } if (config.hostNetwork) podSpec.hostNetwork = true // ServiceAccount if (config.serviceAccountName) podSpec.serviceAccountName = config.serviceAccountName // 优雅终止 if (config.terminationGracePeriod) podSpec.terminationGracePeriodSeconds = config.terminationGracePeriod // Tolerations (existing simple + new advanced) if (config.tolerationsEnabled && config.tolerationKey) { const toleration = { key: config.tolerationKey, operator: 'Exists', effect: 'NoSchedule' } if (config.tolerationValue) { toleration.operator = 'Equal' toleration.value = config.tolerationValue } podSpec.tolerations = [toleration] } // Node Affinity const affinity = buildNodeAffinity(config) if (affinity) podSpec.affinity = affinity // Volumes if (volumes.length) podSpec.volumes = volumes // fsGroup if (config.enableSecurityContext && config.fsGroup) { podSpec.securityContext = { fsGroup: config.fsGroup } } const daemonset = { apiVersion: 'apps/v1', kind: 'DaemonSet', metadata: { name: config.name, namespace: config.namespace, labels }, spec: { selector: { matchLabels: { app: config.appLabel || config.appName } }, updateStrategy: config.updateStrategy === 'RollingUpdate' ? { type: 'RollingUpdate', rollingUpdate: { maxUnavailable: 1 } } : { type: 'OnDelete' }, template: { metadata: { labels }, spec: podSpec }, }, } const header = `# K8s DaemonSet - 由 ConfTemplate 生成\n# 生成时间: ${new Date().toLocaleString('zh-CN')}\n# 部署命令: kubectl apply -f ${config.fileName || 'daemonset.yaml'}\n` return header + '\n' + toYaml(daemonset) } function toYaml(obj, indent = 0) { const lines = [] const prefix = ' '.repeat(indent) for (const [key, value] of Object.entries(obj)) { if (value === null || value === undefined || value === '') continue if (Array.isArray(value)) { lines.push(`${prefix}${key}:`) for (const item of value) { if (typeof item === 'object' && item !== null) { const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') if (entries.length === 0) continue const first = entries[0] 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++) { 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 { lines.push(`${prefix} - ${item}`) } } } else if (typeof value === 'object') { lines.push(`${prefix}${key}:`) lines.push(toYaml(value, indent + 1)) } else { lines.push(`${prefix}${key}: ${value}`) } } return lines.join('\n') }