4e98eb8c4a
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)
384 lines
15 KiB
JavaScript
384 lines
15 KiB
JavaScript
/**
|
||
* 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: '完成后自动删除 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 权限控制' },
|
||
],
|
||
},
|
||
],
|
||
}
|
||
|
||
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)
|
||
}
|