Files
ConfTemplate/src/schemas/k8s-cronjob.js
T
cnbugs 4e98eb8c4a 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)
2026-07-22 15:00:34 +08:00

384 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* K8s CronJob 生产级 Schema — 由 ConfTemplate 生成
* 增强: 容器端口、资源限制、环境变量、卷挂载、安全上下文、ServiceAccount
*/
export const k8sCronJobSchema = {
id: 'k8s-cronjob',
name: 'K8s CronJob',
icon: 'Box',
category: 'K8S 工作负载',
description: 'Kubernetes CronJob — 定时调度批处理任务(生产级)',
format: 'yaml',
fileName: 'cronjob.yaml',
groups: [
{
title: '基础信息',
fields: [
{
key: 'name',
label: 'CronJob 名称',
type: 'text',
placeholder: 'my-cronjob',
default: 'my-cronjob',
required: true,
},
{
key: 'namespace',
label: '命名空间',
type: 'text',
placeholder: 'default',
default: 'default',
},
{
key: 'image',
label: '容器镜像',
type: 'text',
placeholder: 'busybox:latest',
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',
type: 'text',
placeholder: 'echo hello',
default: 'echo hello',
required: true,
tip: '容器启动命令',
},
],
},
{
title: '调度配置',
fields: [
{
key: 'schedule',
label: 'Cron 表达式',
type: 'text',
placeholder: '0 * * * *',
default: '0 * * * *',
required: true,
tip: '标准 cron 格式: 分 时 日 月 周',
},
{
key: 'concurrencyPolicy',
label: '并发策略',
type: 'select',
options: [
{ label: 'Allow - 允许并发', value: 'Allow' },
{ label: 'Replace - 替换上一个', value: 'Replace' },
{ label: 'Forbid - 禁止并发', value: 'Forbid' },
],
default: 'Allow',
},
{
key: 'suspend',
label: '暂停调度',
type: 'switch',
default: false,
tip: '暂停后不再创建新的 Job',
},
],
},
{
title: '历史与清理',
fields: [
{
key: 'successfulJobsHistoryLimit',
label: '成功 Job 保留数',
type: 'number',
min: 0,
max: 10,
default: 3,
},
{
key: 'failedJobsHistoryLimit',
label: '失败 Job 保留数',
type: 'number',
min: 0,
max: 10,
default: 1,
},
],
},
{
title: 'Job 配置',
fields: [
{
key: 'restartPolicy',
label: '重启策略',
type: 'select',
options: [
{ label: 'OnFailure (推荐)', value: 'OnFailure' },
{ label: 'Never', value: 'Never' },
],
default: 'OnFailure',
},
{
key: 'backoffLimit',
label: '重试次数上限',
type: 'number',
min: 0,
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: '完成后自动删除 Job0 表示保留',
},
],
},
{
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 权限控制' },
],
},
],
}
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')
}
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',
metadata: {
name: config.name,
namespace: config.namespace,
},
spec: {
schedule: config.schedule,
concurrencyPolicy: config.concurrencyPolicy,
successfulJobsHistoryLimit: config.successfulJobsHistoryLimit,
failedJobsHistoryLimit: config.failedJobsHistoryLimit,
suspend: config.suspend || undefined,
jobTemplate: {
spec: {
backoffLimit: config.backoffLimit,
activeDeadlineSeconds: config.activeDeadlineSeconds > 0 ? config.activeDeadlineSeconds : undefined,
ttlSecondsAfterFinished: config.ttlSecondsAfterFinished > 0 ? config.ttlSecondsAfterFinished : undefined,
template: {
spec: podSpec,
},
},
},
},
}
const header = `# K8s CronJob - 由 ConfTemplate 生成\n# 生成时间: ${new Date().toLocaleString('zh-CN')}\n# 部署命令: kubectl apply -f ${config.fileName || 'cronjob.yaml'}\n`
return header + '\n' + toYaml(cronjob)
}