一个 GitOps 工具,可将目录中所有 TypeScript 默认导出聚合为 Kubernetes 资源列表(JSON 格式),用于简化 GitOps 工作流程。
WARNING
由于 deno 动态导入的限制,本项目使用子进程来执行 .ts 文件。
.ts 和 .mts 文件与其他清单生成方法(如 Jsonnet、Kustomize 或普通 YAML 模板)相比,我们基于 TypeScript 的解决方案具有显著优势:
@kubernetes/client-node)获得完整的类型定义使用 TypeScript 和 Kubernetes 类型:
import { V1Deployment } from "@kubernetes/client-node";
// TypeScript 立即捕获错误!
const deployment: V1Deployment = {
apiVersion: "apps/v1",
kind: "Deployment",
metadata: {
name: "my-app",
// TypeScript 错误:'lables' 不存在。您是否想输入 'labels'?
lables: { app: "my-app" },
},
spec: {
// TypeScript 错误:不能将类型 'string' 分配给类型 'number'
replicas: "3",
selector: {
matchLabels: { app: "my-app" },
},
template: {
// 所有嵌套字段的完整自动补全!
metadata: { labels: { app: "my-app" } },
spec: {
containers: [
{
name: "app",
image: "my-app:latest",
// TypeScript 知道这应该是一个对象数组
ports: [{ containerPort: 8080 }],
},
],
},
},
},
};
相比之下,Jsonnet 或普通 YAML:
# 直接从源代码安装
deno install -A --name deno-manifest https://cnb.cool/yankeguo/deno-manifest/-/git/raw/main/main.ts
# 或者无需安装直接运行
deno run -A https://cnb.cool/yankeguo/deno-manifest/-/git/raw/main/main.ts
导航到包含 TypeScript 文件的目录并运行:
# 输出 JSON 到标准输出
deno-manifest
# 保存 JSON 输出到文件
deno-manifest > manifests.json
# 如需要转换为 YAML(需要 yq)
deno-manifest | yq eval -P > manifests.yaml
my-app/ ├── deployment.ts ├── service.ts ├── configmap.ts ├── components/ │ ├── redis.ts │ └── postgres.ts └── _test.ts # 被忽略(以 _ 开头)
deployment.ts:
export default {
apiVersion: "apps/v1",
kind: "Deployment",
metadata: {
name: "my-app",
namespace: "default",
},
spec: {
replicas: 3,
selector: {
matchLabels: { app: "my-app" },
},
template: {
metadata: {
labels: { app: "my-app" },
},
spec: {
containers: [
{
name: "app",
image: "my-app:latest",
ports: [{ containerPort: 8080 }],
},
],
},
},
},
};
service.ts:
export default {
apiVersion: "v1",
kind: "Service",
metadata: {
name: "my-app-service",
namespace: "default",
},
spec: {
selector: { app: "my-app" },
ports: [
{
port: 80,
targetPort: 8080,
},
],
type: "ClusterIP",
},
};
configmap.ts(动态生成):
export default function () {
return {
apiVersion: "v1",
kind: "ConfigMap",
metadata: {
name: "my-app-config",
namespace: "default",
},
data: {
"config.yaml": `
env: ${Deno.env.get("NODE_ENV") || "development"}
timestamp: ${new Date().toISOString()}
`.trim(),
},
};
}
components/redis.ts(多个资源):
const namespace = "default";
export default [
{
apiVersion: "apps/v1",
kind: "Deployment",
metadata: {
name: "redis",
namespace,
},
spec: {
replicas: 1,
selector: { matchLabels: { app: "redis" } },
template: {
metadata: { labels: { app: "redis" } },
spec: {
containers: [
{
name: "redis",
image: "redis:7-alpine",
ports: [{ containerPort: 6379 }],
},
],
},
},
},
},
{
apiVersion: "v1",
kind: "Service",
metadata: {
name: "redis",
namespace,
},
spec: {
selector: { app: "redis" },
ports: [{ port: 6379, targetPort: 6379 }],
},
},
];
在上述结构上运行 deno-manifest 会产生:
{
"apiVersion": "v1",
"kind": "List",
"items": [
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "my-app",
"namespace": "default"
},
"spec": {
"replicas": 3,
"selector": {
"matchLabels": { "app": "my-app" }
},
"template": {
"metadata": {
"labels": { "app": "my-app" }
},
"spec": {
"containers": [
{
"name": "app",
"image": "my-app:latest",
"ports": [{ "containerPort": 8080 }]
}
]
}
}
}
},
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"name": "my-app-service",
"namespace": "default"
},
"spec": {
"selector": { "app": "my-app" },
"ports": [
{
"port": 80,
"targetPort": 8080
}
],
"type": "ClusterIP"
}
},
{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": {
"name": "my-app-config",
"namespace": "default"
},
"data": {
"config.yaml": "env: development\ntimestamp: 2024-01-15T10:30:00.000Z"
}
},
{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "redis",
"namespace": "default"
},
"spec": {
"replicas": 1,
"selector": { "matchLabels": { "app": "redis" } },
"template": {
"metadata": { "labels": { "app": "redis" } },
"spec": {
"containers": [
{
"name": "redis",
"image": "redis:7-alpine",
"ports": [{ "containerPort": 6379 }]
}
]
}
}
}
},
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"name": "redis",
"namespace": "default"
},
"spec": {
"selector": { "app": "redis" },
"ports": [{ "port": 6379, "targetPort": 6379 }]
}
}
]
}
# 生成清单并应用到集群(JSON 格式)
deno-manifest | kubectl apply -f -
# 保存 JSON 到文件用于 GitOps 仓库
deno-manifest > k8s-manifests.json
git add k8s-manifests.json
git commit -m "feat: update kubernetes manifests"
# 如果您的 GitOps 工作流程偏好 YAML,可转换为 YAML
deno-manifest | yq eval -P > k8s-manifests.yaml
为不同环境创建不同的目录:
environments/ ├── development/ │ ├── deployment.ts │ └── service.ts ├── staging/ │ ├── deployment.ts │ └── service.ts └── production/ ├── deployment.ts └── service.ts
# 为特定环境生成清单
cd environments/production
deno-manifest > ../../manifests/production.json
使用 TypeScript 的强大功能进行动态配置:
// scaling.ts
export default function () {
const replicas = Deno.env.get("REPLICAS") || "3";
const environment = Deno.env.get("ENVIRONMENT") || "development";
return {
apiVersion: "apps/v1",
kind: "Deployment",
metadata: {
name: `app-${environment}`,
labels: {
environment,
"managed-by": "deno-manifest",
},
},
spec: {
replicas: parseInt(replicas),
// ... 部署规格的其余部分
},
};
}
*.ts - TypeScript 文件*.mts - TypeScript 模块文件. 或 _ 开头的文件(隐藏/私有文件)*.d.ts、*.d.mts - TypeScript 类型定义文件*_test.ts、*_test.mts - 测试文件.、_ 或 node_modules 开头的目录中的文件export default {
/* Kubernetes 资源 */
};
export default function () {
return {
/* Kubernetes 资源 */
};
}
export default [
{
/* 资源 1 */
},
{
/* 资源 2 */
},
];
deno-manifest 可以作为 ArgoCD 的配置管理插件(CMP)无缝集成,让您能够在 GitOps 工作流程中使用 TypeScript 生成 Kubernetes 清单。
ArgoCD 会自动检测仓库中的 TypeScript 文件,并使用 deno-manifest 生成 Kubernetes 资源。无需手动配置或指定插件名称 - 开箱即用!
# argocd-cm-plugin.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: deno-manifest-plugin
namespace: argocd
data:
plugin.yaml: |
apiVersion: argoproj.io/v1alpha1
kind: ConfigManagementPlugin
metadata:
name: deno-manifest
spec:
discover:
find:
glob: "**/*.ts"
generate:
command:
- deno
- run
- -A
- https://cnb.cool/yankeguo/deno-manifest/-/git/raw/main/main.ts
# argocd-repo-server-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: argocd-repo-server
namespace: argocd
spec:
template:
spec:
containers:
- name: deno-manifest-plugin
image: denoland/deno:latest
command: [/var/run/argocd/argocd-cmp-server]
securityContext:
runAsNonRoot: true
runAsUser: 999
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
seccompProfile:
type: RuntimeDefault
volumeMounts:
- mountPath: /var/run/argocd
name: var-files
- mountPath: /home/argocd/cmp-server/plugins
name: plugins
- mountPath: /home/argocd/cmp-server/config/plugin.yaml
subPath: plugin.yaml
name: deno-manifest-plugin
- mountPath: /tmp
name: tmp
volumes:
- configMap:
name: deno-manifest-plugin
name: deno-manifest-plugin
- emptyDir: {}
name: tmp
kubectl apply -f argocd-cm-plugin.yaml
kubectl apply -f argocd-repo-server-patch.yaml
# 重启 repo server 以加载插件
kubectl rollout restart deployment/argocd-repo-server -n argocd
插件安装后,ArgoCD 会自动为包含 TypeScript 文件的任何仓库使用 deno-manifest:
# example-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/yourorg/your-repo
targetRevision: main
path: manifests/ # 包含 .ts 文件的目录
# 无需指定插件 - 自动发现!
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
您可以通过 ArgoCD 向 TypeScript 清单传递环境变量:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
spec:
source:
plugin:
env:
- name: ENVIRONMENT
value: production
- name: REPLICAS
value: "5"
GUO YANKE,MIT 许可证