feat: 8 个核心 schema 生产级增强
K8S 工作负载 (5个): - StatefulSet: +探针(Startup/Liveness/Readiness) +环境变量 +节点亲和性 +Pod反亲和性 +容忍度 +安全上下文 +ServiceAccount - DaemonSet: +容器端口 +探针 +环境变量 +卷挂载 +节点亲和性 +安全上下文 +ServiceAccount - Pod: +探针 +卷挂载(5种类型) +NodeSelector +节点亲和性 +Pod反亲和性 +容忍度 +安全上下文 +HostNetwork +DNSPolicy - CronJob: +容器端口 +资源限制 +环境变量 +卷挂载 +安全上下文 +ServiceAccount - Job: +容器端口 +资源限制 +环境变量 +卷挂载 +安全上下文 +ServiceAccount 中间件 (3个): - Redis: +ACL配置 +慢日志 +延迟监控 +客户端管理(ioThreads/lazyfree) - PostgreSQL: +连接池 +流复制(GTID/WAL) +Autovacuum调优 - NGINX: +限流(limit_req) +Upstream负载均衡(least_conn/ip_hash) +缓存(proxy_cache)
This commit is contained in:
+197
-10
@@ -1,9 +1,14 @@
|
||||
/**
|
||||
* K8s CronJob 生产级 Schema — 由 ConfTemplate 生成
|
||||
* 增强: 容器端口、资源限制、环境变量、卷挂载、安全上下文、ServiceAccount
|
||||
*/
|
||||
|
||||
export const k8sCronJobSchema = {
|
||||
id: 'k8s-cronjob',
|
||||
name: 'K8s CronJob',
|
||||
icon: 'Box',
|
||||
category: 'K8S 工作负载',
|
||||
description: 'Kubernetes CronJob — 定时调度批处理任务',
|
||||
description: 'Kubernetes CronJob — 定时调度批处理任务(生产级)',
|
||||
format: 'yaml',
|
||||
fileName: 'cronjob.yaml',
|
||||
groups: [
|
||||
@@ -33,6 +38,17 @@ export const k8sCronJobSchema = {
|
||||
default: 'busybox:latest',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'imagePullPolicy',
|
||||
label: '镜像拉取策略',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'IfNotPresent (推荐)', value: 'IfNotPresent' },
|
||||
{ label: 'Always (每次拉取)', value: 'Always' },
|
||||
{ label: 'Never (仅本地)', value: 'Never' },
|
||||
],
|
||||
default: 'IfNotPresent',
|
||||
},
|
||||
{
|
||||
key: 'command',
|
||||
label: 'Command',
|
||||
@@ -118,6 +134,102 @@ export const k8sCronJobSchema = {
|
||||
max: 100,
|
||||
default: 6,
|
||||
},
|
||||
{
|
||||
key: 'activeDeadlineSeconds',
|
||||
label: '超时时间 (秒)',
|
||||
type: 'number',
|
||||
min: 0,
|
||||
max: 86400,
|
||||
default: 0,
|
||||
tip: '0 表示不限制超时',
|
||||
},
|
||||
{
|
||||
key: 'ttlSecondsAfterFinished',
|
||||
label: '自动清理 (秒)',
|
||||
type: 'number',
|
||||
min: 0,
|
||||
max: 86400,
|
||||
default: 0,
|
||||
tip: '完成后自动删除 Job,0 表示保留',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '容器端口',
|
||||
fields: [
|
||||
{ key: 'containerPort', label: '容器端口', type: 'number', min: 1, max: 65535, default: 80 },
|
||||
{ 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' },{ label: '2', value: '2' },
|
||||
], 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' },{ 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' },{ label: '4Gi', value: '4Gi' },
|
||||
], default: '256Mi' },
|
||||
],
|
||||
},
|
||||
{
|
||||
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: '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: '安全上下文 (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: 'ServiceAccount',
|
||||
fields: [
|
||||
{ key: 'serviceAccountName', label: 'ServiceAccount 名称', type: 'text', placeholder: '留空使用 default', default: '', tip: 'RBAC 权限控制' },
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -126,10 +238,8 @@ export const k8sCronJobSchema = {
|
||||
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) {
|
||||
@@ -137,9 +247,18 @@ function toYaml(obj, indent = 0) {
|
||||
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]}: ${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++) {
|
||||
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 {
|
||||
lines.push(`${prefix} - ${item}`)
|
||||
@@ -152,7 +271,6 @@ function toYaml(obj, indent = 0) {
|
||||
lines.push(`${prefix}${key}: ${value}`)
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
@@ -160,9 +278,79 @@ export function generateK8sCronJobYaml(config) {
|
||||
const container = {
|
||||
name: config.name,
|
||||
image: config.image,
|
||||
imagePullPolicy: config.imagePullPolicy,
|
||||
command: config.command.split(' '),
|
||||
}
|
||||
|
||||
// 容器端口
|
||||
if (config.containerPort) {
|
||||
container.ports = [{
|
||||
name: config.portName || 'http',
|
||||
containerPort: config.containerPort,
|
||||
protocol: config.protocol || 'TCP',
|
||||
}]
|
||||
}
|
||||
|
||||
// 资源限制
|
||||
container.resources = {
|
||||
requests: { cpu: config.cpuRequest, memory: config.memoryRequest },
|
||||
limits: { cpu: config.cpuLimit, memory: config.memoryLimit },
|
||||
}
|
||||
|
||||
// 环境变量
|
||||
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],
|
||||
restartPolicy: config.restartPolicy,
|
||||
}
|
||||
|
||||
// ServiceAccount
|
||||
if (config.serviceAccountName) podSpec.serviceAccountName = config.serviceAccountName
|
||||
|
||||
// Volumes
|
||||
if (volumes.length) podSpec.volumes = volumes
|
||||
|
||||
// fsGroup
|
||||
if (config.enableSecurityContext && config.fsGroup) {
|
||||
podSpec.securityContext = { fsGroup: config.fsGroup }
|
||||
}
|
||||
|
||||
const cronjob = {
|
||||
apiVersion: 'batch/v1',
|
||||
kind: 'CronJob',
|
||||
@@ -179,11 +367,10 @@ export function generateK8sCronJobYaml(config) {
|
||||
jobTemplate: {
|
||||
spec: {
|
||||
backoffLimit: config.backoffLimit,
|
||||
activeDeadlineSeconds: config.activeDeadlineSeconds > 0 ? config.activeDeadlineSeconds : undefined,
|
||||
ttlSecondsAfterFinished: config.ttlSecondsAfterFinished > 0 ? config.ttlSecondsAfterFinished : undefined,
|
||||
template: {
|
||||
spec: {
|
||||
containers: [container],
|
||||
restartPolicy: config.restartPolicy,
|
||||
},
|
||||
spec: podSpec,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user