feat: 初始化 ConfTemplate 云原生配置文件生成器
- Vue 3 + Element Plus + Monaco Editor 技术栈 - 支持 NGINX、Redis、MySQL、K8s Deployment、K8s Service 五个组件 - 侧边栏搜索过滤、动态表单联动、实时代码预览 - 一键复制/下载/重置,localStorage 状态持久化 - 纯前端静态页面,零后端依赖
This commit is contained in:
@@ -0,0 +1,416 @@
|
||||
export const k8sDeploymentSchema = {
|
||||
id: 'k8s-deployment',
|
||||
name: 'K8s Deployment',
|
||||
icon: 'Box',
|
||||
category: '云原生',
|
||||
description: 'Kubernetes 无状态应用部署控制器',
|
||||
format: 'yaml',
|
||||
fileName: 'deployment.yaml',
|
||||
groups: [
|
||||
{
|
||||
title: '基础信息',
|
||||
fields: [
|
||||
{
|
||||
key: 'appName',
|
||||
label: '应用名称',
|
||||
type: 'text',
|
||||
placeholder: 'my-app',
|
||||
default: 'my-app',
|
||||
required: true,
|
||||
tip: '将用作 Deployment 和 Label 的名称',
|
||||
},
|
||||
{
|
||||
key: 'namespace',
|
||||
label: '命名空间',
|
||||
type: 'text',
|
||||
placeholder: 'default',
|
||||
default: 'default',
|
||||
},
|
||||
{
|
||||
key: 'image',
|
||||
label: '容器镜像地址',
|
||||
type: 'text',
|
||||
placeholder: 'nginx:1.24',
|
||||
default: 'nginx:1.24',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'replicas',
|
||||
label: '副本数',
|
||||
type: 'number',
|
||||
min: 1,
|
||||
max: 100,
|
||||
default: 3,
|
||||
tip: '建议生产环境至少 2 副本',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '容器端口',
|
||||
fields: [
|
||||
{
|
||||
key: 'containerPort',
|
||||
label: '容器端口',
|
||||
type: 'number',
|
||||
min: 1,
|
||||
max: 65535,
|
||||
default: 80,
|
||||
},
|
||||
{
|
||||
key: 'protocol',
|
||||
label: '协议',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'TCP', value: 'TCP' },
|
||||
{ label: 'UDP', value: 'UDP' },
|
||||
],
|
||||
default: 'TCP',
|
||||
},
|
||||
{
|
||||
key: 'portName',
|
||||
label: '端口名称',
|
||||
type: 'text',
|
||||
placeholder: 'http',
|
||||
default: 'http',
|
||||
tip: 'Service 匹配时使用',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
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' },
|
||||
],
|
||||
default: '256Mi',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '健康检查',
|
||||
fields: [
|
||||
{
|
||||
key: 'enableLivenessProbe',
|
||||
label: '存活探针 (Liveness)',
|
||||
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',
|
||||
placeholder: '/healthz',
|
||||
default: '/healthz',
|
||||
dependsOn: { key: 'livenessProbeType', value: 'httpGet' },
|
||||
},
|
||||
{
|
||||
key: 'livenessPort',
|
||||
label: '探针端口',
|
||||
type: 'number',
|
||||
min: 1,
|
||||
max: 65535,
|
||||
default: 80,
|
||||
dependsOn: { key: 'enableLivenessProbe', value: true },
|
||||
},
|
||||
{
|
||||
key: 'enableReadinessProbe',
|
||||
label: '就绪探针 (Readiness)',
|
||||
type: 'switch',
|
||||
default: true,
|
||||
tip: '检测容器是否就绪接收流量',
|
||||
},
|
||||
{
|
||||
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',
|
||||
placeholder: '/ready',
|
||||
default: '/ready',
|
||||
dependsOn: { key: 'readinessProbeType', value: 'httpGet' },
|
||||
},
|
||||
{
|
||||
key: 'readinessPort',
|
||||
label: '探针端口',
|
||||
type: 'number',
|
||||
min: 1,
|
||||
max: 65535,
|
||||
default: 80,
|
||||
dependsOn: { key: 'enableReadinessProbe', value: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '部署策略',
|
||||
fields: [
|
||||
{
|
||||
key: 'strategy',
|
||||
label: '更新策略',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'RollingUpdate - 滚动更新 (推荐)', value: 'RollingUpdate' },
|
||||
{ label: 'Recreate - 先删后建', value: 'Recreate' },
|
||||
],
|
||||
default: 'RollingUpdate',
|
||||
},
|
||||
{
|
||||
key: 'maxSurge',
|
||||
label: '最大超出副本数',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '1', value: '1' },
|
||||
{ label: '25%', value: '25%' },
|
||||
{ label: '50%', value: '50%' },
|
||||
{ label: '100%', value: '100%' },
|
||||
],
|
||||
default: '25%',
|
||||
dependsOn: { key: 'strategy', value: 'RollingUpdate' },
|
||||
},
|
||||
{
|
||||
key: 'maxUnavailable',
|
||||
label: '最大不可用副本数',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: '0 (零停机)', value: '0' },
|
||||
{ label: '1', value: '1' },
|
||||
{ label: '25%', value: '25%' },
|
||||
],
|
||||
default: '0',
|
||||
dependsOn: { key: 'strategy', value: 'RollingUpdate' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '标签与注解',
|
||||
fields: [
|
||||
{
|
||||
key: 'appLabel',
|
||||
label: 'app 标签值',
|
||||
type: 'text',
|
||||
default: '',
|
||||
tip: '留空则使用应用名称',
|
||||
},
|
||||
{
|
||||
key: 'versionLabel',
|
||||
label: 'version 标签',
|
||||
type: 'text',
|
||||
placeholder: 'v1',
|
||||
default: 'v1',
|
||||
},
|
||||
{
|
||||
key: 'imagePullPolicy',
|
||||
label: '镜像拉取策略',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'IfNotPresent (推荐)', value: 'IfNotPresent' },
|
||||
{ label: 'Always', value: 'Always' },
|
||||
{ label: 'Never', value: 'Never' },
|
||||
],
|
||||
default: 'IfNotPresent',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
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]}: ${first[1]}`)
|
||||
for (let i = 1; i < entries.length; i++) {
|
||||
lines.push(`${prefix} ${entries[i][0]}: ${entries[i][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')
|
||||
}
|
||||
|
||||
export function generateK8sDeploymentYaml(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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const probeConfig = (type, path, port) => {
|
||||
if (type === 'httpGet') {
|
||||
return { httpGet: { path, port, scheme: 'HTTP' } }
|
||||
}
|
||||
if (type === 'tcpSocket') {
|
||||
return { tcpSocket: { port } }
|
||||
}
|
||||
return { exec: { command: ['cat', '/tmp/healthy'] } }
|
||||
}
|
||||
|
||||
if (config.enableLivenessProbe) {
|
||||
container.livenessProbe = {
|
||||
...probeConfig(config.livenessProbeType, config.livenessPath, config.livenessPort),
|
||||
initialDelaySeconds: 15,
|
||||
periodSeconds: 20,
|
||||
timeoutSeconds: 5,
|
||||
failureThreshold: 3,
|
||||
}
|
||||
}
|
||||
|
||||
if (config.enableReadinessProbe) {
|
||||
container.readinessProbe = {
|
||||
...probeConfig(config.readinessProbeType, config.readinessPath, config.readinessPort),
|
||||
initialDelaySeconds: 5,
|
||||
periodSeconds: 10,
|
||||
timeoutSeconds: 3,
|
||||
failureThreshold: 3,
|
||||
}
|
||||
}
|
||||
|
||||
const deployment = {
|
||||
apiVersion: 'apps/v1',
|
||||
kind: 'Deployment',
|
||||
metadata: {
|
||||
name: config.appName,
|
||||
namespace: config.namespace,
|
||||
labels,
|
||||
},
|
||||
spec: {
|
||||
replicas: config.replicas,
|
||||
selector: {
|
||||
matchLabels: { app: labels.app },
|
||||
},
|
||||
strategy: config.strategy === 'RollingUpdate'
|
||||
? {
|
||||
type: 'RollingUpdate',
|
||||
rollingUpdate: {
|
||||
maxSurge: config.maxSurge,
|
||||
maxUnavailable: config.maxUnavailable,
|
||||
},
|
||||
}
|
||||
: { type: 'Recreate' },
|
||||
template: {
|
||||
metadata: { labels },
|
||||
spec: {
|
||||
containers: [container],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const header = `# K8s Deployment - 由 ConfTemplate 生成
|
||||
# 生成时间: ${new Date().toLocaleString('zh-CN')}
|
||||
# 部署命令: kubectl apply -f ${config.fileName || 'deployment.yaml'}
|
||||
`
|
||||
|
||||
return header + '\n' + toYaml(deployment)
|
||||
}
|
||||
Reference in New Issue
Block a user