Files
ConfTemplate/src/schemas/k8s-cronjob.js
T
cnbugs 6426eedab5 feat: 新增 Dockerfile 配置模板,总计 60 个组件
Dockerfile 支持:
- 基础镜像 (FROM) + 多阶段构建 + 平台架构
- 元数据 LABEL / 构建参数 ARG
- 环境变量 ENV / 工作目录 WORKDIR
- 文件复制 COPY + chown/chmod + ADD (自动解压/URL)
- 依赖安装: apk/apt/yum/dnf 包管理器 + 应用依赖 + 构建命令
- 端口 EXPOSE / 数据卷 VOLUME
- 用户 USER (非 root 运行)
- 健康检查 HEALTHCHECK (interval/timeout/retries/start-period)
- 入口 ENTRYPOINT / CMD (exec/shell 格式)
- 停止信号 STOPSIGNAL / Shell 指令
2026-07-24 15:12:46 +08:00

424 lines
16 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 权限控制' },
],
},
],
}
// ======================== YAML 生成器 ========================
function toYaml(obj, indent) {
indent = indent || 0
var lines = []
var prefix = ''
for (var p = 0; p < indent; p++) prefix += ' '
var keys = Object.keys(obj)
for (var ki = 0; ki < keys.length; ki++) {
var key = keys[ki]
var value = obj[key]
if (value === null || value === undefined || value === '') continue
if (Array.isArray(value)) {
lines.push(prefix + key + ':')
for (var ai = 0; ai < value.length; ai++) {
var item = value[ai]
if (typeof item === 'object' && item !== null) {
lines.push(renderArrayObject(item, indent + 1))
} 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')
}
function renderArrayObject(item, indent) {
var prefix = ''
for (var p = 0; p < indent; p++) prefix += ' '
var entries = Object.entries(item).filter(function(pair) { return pair[1] !== null && pair[1] !== undefined && pair[1] !== '' })
if (entries.length === 0) return ''
var lines = []
var first = entries[0]
var fk = first[0], fv = first[1]
if (Array.isArray(fv)) {
lines.push(prefix + '- ' + fk + ':')
for (var ai = 0; ai < fv.length; ai++) {
var arrItem = fv[ai]
if (typeof arrItem === 'object' && arrItem !== null) {
lines.push(renderArrayObject(arrItem, indent + 2))
} else {
lines.push(prefix + ' - ' + arrItem)
}
}
} else if (typeof fv === 'object' && fv !== null) {
lines.push(prefix + '- ' + fk + ':')
lines.push(toYaml(fv, indent + 2))
} else {
lines.push(prefix + '- ' + fk + ': ' + fv)
}
for (var i = 1; i < entries.length; i++) {
var k = entries[i][0], v = entries[i][1]
if (v === null || v === undefined || v === '') continue
if (Array.isArray(v)) {
lines.push(prefix + ' ' + k + ':')
for (var ai = 0; ai < v.length; ai++) {
var arrItem = v[ai]
if (typeof arrItem === 'object' && arrItem !== null) {
lines.push(renderArrayObject(arrItem, indent + 2))
} else {
lines.push(prefix + ' - ' + arrItem)
}
}
} else if (typeof v === 'object') {
lines.push(prefix + ' ' + k + ':')
lines.push(toYaml(v, indent + 2))
} else {
lines.push(prefix + ' ' + k + ': ' + v)
}
}
return lines.join('\n')
}
export function generateK8sCronJobYaml(config) {
var 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(function(line) {
var idx = line.indexOf('=')
return { name: line.substring(0, idx).trim(), value: line.substring(idx + 1).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 } })
}
// 卷挂载
var volumes = []
if (config.enableVolumeMount) {
container.volumeMounts = [{ name: config.volumeName, mountPath: config.volumeMountPath, readOnly: config.volumeReadOnly || undefined }]
var vol = { name: config.volumeName }
var 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,
}
}
var 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 }
}
var 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,
},
},
},
},
}
var header = '# K8s CronJob - 由 ConfTemplate 生成\n# 生成时间: ' + new Date().toLocaleString('zh-CN') + '\n# 部署命令: kubectl apply -f ' + (config.fileName || 'cronjob.yaml') + '\n'
return header + '\n' + toYaml(cronjob)
}