@intentius/chant-lexicon-k8s 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/integrity.json +32 -0
- package/dist/manifest.json +8 -0
- package/dist/meta.json +1413 -0
- package/dist/rules/hardcoded-namespace.ts +56 -0
- package/dist/rules/k8s-helpers.ts +149 -0
- package/dist/rules/wk8005.ts +59 -0
- package/dist/rules/wk8006.ts +68 -0
- package/dist/rules/wk8041.ts +73 -0
- package/dist/rules/wk8042.ts +48 -0
- package/dist/rules/wk8101.ts +65 -0
- package/dist/rules/wk8102.ts +42 -0
- package/dist/rules/wk8103.ts +45 -0
- package/dist/rules/wk8104.ts +69 -0
- package/dist/rules/wk8105.ts +45 -0
- package/dist/rules/wk8201.ts +55 -0
- package/dist/rules/wk8202.ts +46 -0
- package/dist/rules/wk8203.ts +46 -0
- package/dist/rules/wk8204.ts +54 -0
- package/dist/rules/wk8205.ts +56 -0
- package/dist/rules/wk8207.ts +45 -0
- package/dist/rules/wk8208.ts +45 -0
- package/dist/rules/wk8209.ts +45 -0
- package/dist/rules/wk8301.ts +51 -0
- package/dist/rules/wk8302.ts +46 -0
- package/dist/rules/wk8303.ts +96 -0
- package/dist/skills/chant-k8s.md +433 -0
- package/dist/types/index.d.ts +2934 -0
- package/package.json +30 -0
- package/src/actions/actions.test.ts +83 -0
- package/src/actions/apps.ts +23 -0
- package/src/actions/batch.ts +9 -0
- package/src/actions/core.ts +62 -0
- package/src/actions/index.ts +50 -0
- package/src/actions/networking.ts +15 -0
- package/src/actions/rbac.ts +13 -0
- package/src/codegen/docs-cli.ts +3 -0
- package/src/codegen/docs.ts +1147 -0
- package/src/codegen/generate-cli.ts +41 -0
- package/src/codegen/generate-lexicon.ts +69 -0
- package/src/codegen/generate-typescript.ts +97 -0
- package/src/codegen/generate.ts +144 -0
- package/src/codegen/naming.test.ts +63 -0
- package/src/codegen/naming.ts +187 -0
- package/src/codegen/package.ts +56 -0
- package/src/codegen/patches.ts +108 -0
- package/src/codegen/snapshot.test.ts +95 -0
- package/src/codegen/typecheck.test.ts +24 -0
- package/src/codegen/typecheck.ts +4 -0
- package/src/codegen/versions.ts +43 -0
- package/src/composites/autoscaled-service.ts +236 -0
- package/src/composites/composites.test.ts +1109 -0
- package/src/composites/cron-workload.ts +167 -0
- package/src/composites/index.ts +14 -0
- package/src/composites/namespace-env.ts +163 -0
- package/src/composites/node-agent.ts +224 -0
- package/src/composites/stateful-app.ts +134 -0
- package/src/composites/web-app.ts +180 -0
- package/src/composites/worker-pool.ts +230 -0
- package/src/coverage.test.ts +27 -0
- package/src/coverage.ts +35 -0
- package/src/crd/loader.ts +112 -0
- package/src/crd/parser.test.ts +217 -0
- package/src/crd/parser.ts +279 -0
- package/src/crd/types.ts +54 -0
- package/src/default-labels.test.ts +111 -0
- package/src/default-labels.ts +122 -0
- package/src/generated/index.d.ts +2934 -0
- package/src/generated/index.ts +203 -0
- package/src/generated/lexicon-k8s.json +1413 -0
- package/src/generated/runtime.ts +4 -0
- package/src/import/generator.test.ts +121 -0
- package/src/import/generator.ts +285 -0
- package/src/import/parser.test.ts +156 -0
- package/src/import/parser.ts +204 -0
- package/src/import/roundtrip.test.ts +86 -0
- package/src/index.ts +38 -0
- package/src/lint/post-synth/k8s-helpers.test.ts +219 -0
- package/src/lint/post-synth/k8s-helpers.ts +149 -0
- package/src/lint/post-synth/post-synth.test.ts +969 -0
- package/src/lint/post-synth/wk8005.ts +59 -0
- package/src/lint/post-synth/wk8006.ts +68 -0
- package/src/lint/post-synth/wk8041.ts +73 -0
- package/src/lint/post-synth/wk8042.ts +48 -0
- package/src/lint/post-synth/wk8101.ts +65 -0
- package/src/lint/post-synth/wk8102.ts +42 -0
- package/src/lint/post-synth/wk8103.ts +45 -0
- package/src/lint/post-synth/wk8104.ts +69 -0
- package/src/lint/post-synth/wk8105.ts +45 -0
- package/src/lint/post-synth/wk8201.ts +55 -0
- package/src/lint/post-synth/wk8202.ts +46 -0
- package/src/lint/post-synth/wk8203.ts +46 -0
- package/src/lint/post-synth/wk8204.ts +54 -0
- package/src/lint/post-synth/wk8205.ts +56 -0
- package/src/lint/post-synth/wk8207.ts +45 -0
- package/src/lint/post-synth/wk8208.ts +45 -0
- package/src/lint/post-synth/wk8209.ts +45 -0
- package/src/lint/post-synth/wk8301.ts +51 -0
- package/src/lint/post-synth/wk8302.ts +46 -0
- package/src/lint/post-synth/wk8303.ts +96 -0
- package/src/lint/rules/hardcoded-namespace.ts +56 -0
- package/src/lint/rules/rules.test.ts +69 -0
- package/src/lsp/completions.test.ts +64 -0
- package/src/lsp/completions.ts +20 -0
- package/src/lsp/hover.test.ts +69 -0
- package/src/lsp/hover.ts +68 -0
- package/src/package-cli.ts +28 -0
- package/src/plugin.test.ts +209 -0
- package/src/plugin.ts +915 -0
- package/src/serializer.test.ts +275 -0
- package/src/serializer.ts +278 -0
- package/src/spec/fetch.test.ts +24 -0
- package/src/spec/fetch.ts +68 -0
- package/src/spec/parse.test.ts +102 -0
- package/src/spec/parse.ts +477 -0
- package/src/testdata/manifests/configmap.yaml +7 -0
- package/src/testdata/manifests/deployment.yaml +22 -0
- package/src/testdata/manifests/full-app.yaml +61 -0
- package/src/testdata/manifests/secret.yaml +7 -0
- package/src/testdata/manifests/service.yaml +15 -0
- package/src/validate-cli.ts +21 -0
- package/src/validate.test.ts +29 -0
- package/src/validate.ts +46 -0
- package/src/variables.ts +36 -0
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,915 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kubernetes lexicon plugin.
|
|
3
|
+
*
|
|
4
|
+
* Provides serializer, template detection, code generation,
|
|
5
|
+
* lint rules, and LSP/MCP integration for Kubernetes manifests.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createRequire } from "module";
|
|
9
|
+
import type { LexiconPlugin, SkillDefinition, InitTemplateSet } from "@intentius/chant/lexicon";
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
import type { LintRule } from "@intentius/chant/lint/rule";
|
|
12
|
+
import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
|
|
13
|
+
import { k8sSerializer } from "./serializer";
|
|
14
|
+
|
|
15
|
+
export const k8sPlugin: LexiconPlugin = {
|
|
16
|
+
name: "k8s",
|
|
17
|
+
serializer: k8sSerializer,
|
|
18
|
+
|
|
19
|
+
lintRules(): LintRule[] {
|
|
20
|
+
const { hardcodedNamespaceRule } = require("./lint/rules/hardcoded-namespace");
|
|
21
|
+
return [hardcodedNamespaceRule];
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
postSynthChecks(): PostSynthCheck[] {
|
|
25
|
+
const { wk8005 } = require("./lint/post-synth/wk8005");
|
|
26
|
+
const { wk8006 } = require("./lint/post-synth/wk8006");
|
|
27
|
+
const { wk8041 } = require("./lint/post-synth/wk8041");
|
|
28
|
+
const { wk8042 } = require("./lint/post-synth/wk8042");
|
|
29
|
+
const { wk8101 } = require("./lint/post-synth/wk8101");
|
|
30
|
+
const { wk8102 } = require("./lint/post-synth/wk8102");
|
|
31
|
+
const { wk8103 } = require("./lint/post-synth/wk8103");
|
|
32
|
+
const { wk8104 } = require("./lint/post-synth/wk8104");
|
|
33
|
+
const { wk8105 } = require("./lint/post-synth/wk8105");
|
|
34
|
+
const { wk8201 } = require("./lint/post-synth/wk8201");
|
|
35
|
+
const { wk8202 } = require("./lint/post-synth/wk8202");
|
|
36
|
+
const { wk8203 } = require("./lint/post-synth/wk8203");
|
|
37
|
+
const { wk8204 } = require("./lint/post-synth/wk8204");
|
|
38
|
+
const { wk8205 } = require("./lint/post-synth/wk8205");
|
|
39
|
+
const { wk8207 } = require("./lint/post-synth/wk8207");
|
|
40
|
+
const { wk8208 } = require("./lint/post-synth/wk8208");
|
|
41
|
+
const { wk8209 } = require("./lint/post-synth/wk8209");
|
|
42
|
+
const { wk8301 } = require("./lint/post-synth/wk8301");
|
|
43
|
+
const { wk8302 } = require("./lint/post-synth/wk8302");
|
|
44
|
+
const { wk8303 } = require("./lint/post-synth/wk8303");
|
|
45
|
+
return [
|
|
46
|
+
wk8005, wk8006, wk8041, wk8042,
|
|
47
|
+
wk8101, wk8102, wk8103, wk8104, wk8105,
|
|
48
|
+
wk8201, wk8202, wk8203, wk8204, wk8205, wk8207, wk8208, wk8209,
|
|
49
|
+
wk8301, wk8302, wk8303,
|
|
50
|
+
];
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
// K8s YAML has no template interpolation functions like CloudFormation's
|
|
54
|
+
// Fn::Sub or GitLab's !reference. Cross-resource references are handled by
|
|
55
|
+
// the AttrRef system in the serializer (resolves to metadata.name).
|
|
56
|
+
// ConfigMap/Secret valueFrom references are expressed as plain property
|
|
57
|
+
// values, not intrinsics.
|
|
58
|
+
intrinsics() {
|
|
59
|
+
return [];
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
initTemplates(template?: string): InitTemplateSet {
|
|
63
|
+
if (template === "microservice") {
|
|
64
|
+
return {
|
|
65
|
+
src: {
|
|
66
|
+
"infra.ts": `import { Deployment, Service, HorizontalPodAutoscaler, PodDisruptionBudget, Container, Probe, ResourceRequirements } from "@intentius/chant-lexicon-k8s";
|
|
67
|
+
|
|
68
|
+
export const deployment = new Deployment({
|
|
69
|
+
metadata: { name: "my-service", labels: { "app.kubernetes.io/name": "my-service" } },
|
|
70
|
+
spec: {
|
|
71
|
+
replicas: 2,
|
|
72
|
+
selector: { matchLabels: { "app.kubernetes.io/name": "my-service" } },
|
|
73
|
+
template: {
|
|
74
|
+
metadata: { labels: { "app.kubernetes.io/name": "my-service" } },
|
|
75
|
+
spec: {
|
|
76
|
+
containers: [
|
|
77
|
+
new Container({
|
|
78
|
+
name: "app",
|
|
79
|
+
image: "my-service:latest",
|
|
80
|
+
ports: [{ containerPort: 8080, name: "http" }],
|
|
81
|
+
resources: new ResourceRequirements({
|
|
82
|
+
limits: { cpu: "500m", memory: "256Mi" },
|
|
83
|
+
requests: { cpu: "100m", memory: "128Mi" },
|
|
84
|
+
}),
|
|
85
|
+
livenessProbe: new Probe({ httpGet: { path: "/healthz", port: 8080 }, initialDelaySeconds: 10 }),
|
|
86
|
+
readinessProbe: new Probe({ httpGet: { path: "/readyz", port: 8080 }, initialDelaySeconds: 5 }),
|
|
87
|
+
}),
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export const service = new Service({
|
|
95
|
+
metadata: { name: "my-service", labels: { "app.kubernetes.io/name": "my-service" } },
|
|
96
|
+
spec: {
|
|
97
|
+
selector: { "app.kubernetes.io/name": "my-service" },
|
|
98
|
+
ports: [{ port: 80, targetPort: 8080, protocol: "TCP", name: "http" }],
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
export const hpa = new HorizontalPodAutoscaler({
|
|
103
|
+
metadata: { name: "my-service" },
|
|
104
|
+
spec: {
|
|
105
|
+
scaleTargetRef: { apiVersion: "apps/v1", kind: "Deployment", name: "my-service" },
|
|
106
|
+
minReplicas: 2,
|
|
107
|
+
maxReplicas: 10,
|
|
108
|
+
metrics: [{ type: "Resource", resource: { name: "cpu", target: { type: "Utilization", averageUtilization: 70 } } }],
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
export const pdb = new PodDisruptionBudget({
|
|
113
|
+
metadata: { name: "my-service" },
|
|
114
|
+
spec: {
|
|
115
|
+
minAvailable: 1,
|
|
116
|
+
selector: { matchLabels: { "app.kubernetes.io/name": "my-service" } },
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
`,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (template === "stateful") {
|
|
125
|
+
return {
|
|
126
|
+
src: {
|
|
127
|
+
"infra.ts": `import { StatefulSet, Service } from "@intentius/chant-lexicon-k8s";
|
|
128
|
+
|
|
129
|
+
export const statefulSet = new StatefulSet({
|
|
130
|
+
metadata: { name: "my-db", labels: { "app.kubernetes.io/name": "my-db" } },
|
|
131
|
+
spec: {
|
|
132
|
+
serviceName: "my-db",
|
|
133
|
+
replicas: 3,
|
|
134
|
+
selector: { matchLabels: { "app.kubernetes.io/name": "my-db" } },
|
|
135
|
+
template: {
|
|
136
|
+
metadata: { labels: { "app.kubernetes.io/name": "my-db" } },
|
|
137
|
+
spec: {
|
|
138
|
+
containers: [
|
|
139
|
+
{
|
|
140
|
+
name: "db",
|
|
141
|
+
image: "postgres:16",
|
|
142
|
+
ports: [{ containerPort: 5432, name: "postgres" }],
|
|
143
|
+
volumeMounts: [{ name: "data", mountPath: "/var/lib/postgresql/data" }],
|
|
144
|
+
env: [
|
|
145
|
+
{ name: "POSTGRES_DB", value: "mydb" },
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
volumeClaimTemplates: [
|
|
152
|
+
{
|
|
153
|
+
metadata: { name: "data" },
|
|
154
|
+
spec: {
|
|
155
|
+
accessModes: ["ReadWriteOnce"],
|
|
156
|
+
resources: { requests: { storage: "10Gi" } },
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
export const service = new Service({
|
|
164
|
+
metadata: { name: "my-db", labels: { "app.kubernetes.io/name": "my-db" } },
|
|
165
|
+
spec: {
|
|
166
|
+
selector: { "app.kubernetes.io/name": "my-db" },
|
|
167
|
+
ports: [{ port: 5432, targetPort: 5432, name: "postgres" }],
|
|
168
|
+
clusterIP: "None",
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
`,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Default template — Deployment + Service
|
|
177
|
+
return {
|
|
178
|
+
src: {
|
|
179
|
+
"infra.ts": `import { Deployment, Service, Container, Probe } from "@intentius/chant-lexicon-k8s";
|
|
180
|
+
|
|
181
|
+
export const deployment = new Deployment({
|
|
182
|
+
metadata: { name: "my-app", labels: { "app.kubernetes.io/name": "my-app" } },
|
|
183
|
+
spec: {
|
|
184
|
+
replicas: 2,
|
|
185
|
+
selector: { matchLabels: { "app.kubernetes.io/name": "my-app" } },
|
|
186
|
+
template: {
|
|
187
|
+
metadata: { labels: { "app.kubernetes.io/name": "my-app" } },
|
|
188
|
+
spec: {
|
|
189
|
+
containers: [
|
|
190
|
+
new Container({
|
|
191
|
+
name: "app",
|
|
192
|
+
image: "my-app:latest",
|
|
193
|
+
ports: [{ containerPort: 8080, name: "http" }],
|
|
194
|
+
livenessProbe: new Probe({ httpGet: { path: "/healthz", port: 8080 } }),
|
|
195
|
+
readinessProbe: new Probe({ httpGet: { path: "/readyz", port: 8080 } }),
|
|
196
|
+
}),
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
export const service = new Service({
|
|
204
|
+
metadata: { name: "my-app", labels: { "app.kubernetes.io/name": "my-app" } },
|
|
205
|
+
spec: {
|
|
206
|
+
selector: { "app.kubernetes.io/name": "my-app" },
|
|
207
|
+
ports: [{ port: 80, targetPort: 8080, protocol: "TCP", name: "http" }],
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
`,
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
detectTemplate(data: unknown): boolean {
|
|
216
|
+
if (typeof data !== "object" || data === null) return false;
|
|
217
|
+
const obj = data as Record<string, unknown>;
|
|
218
|
+
|
|
219
|
+
// K8s manifests have apiVersion + kind
|
|
220
|
+
if (typeof obj.apiVersion === "string" && typeof obj.kind === "string") return true;
|
|
221
|
+
|
|
222
|
+
return false;
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
completionProvider(ctx: import("@intentius/chant/lsp/types").CompletionContext) {
|
|
226
|
+
const { k8sCompletions } = require("./lsp/completions");
|
|
227
|
+
return k8sCompletions(ctx);
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
hoverProvider(ctx: import("@intentius/chant/lsp/types").HoverContext) {
|
|
231
|
+
const { k8sHover } = require("./lsp/hover");
|
|
232
|
+
return k8sHover(ctx);
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
templateParser() {
|
|
236
|
+
const { K8sParser } = require("./import/parser");
|
|
237
|
+
return new K8sParser();
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
templateGenerator() {
|
|
241
|
+
const { K8sGenerator } = require("./import/generator");
|
|
242
|
+
return new K8sGenerator();
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
async docs(options?: { verbose?: boolean }): Promise<void> {
|
|
246
|
+
const { generateDocs } = await import("./codegen/docs");
|
|
247
|
+
await generateDocs(options);
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
async generate(options?: { verbose?: boolean }): Promise<void> {
|
|
251
|
+
const { generate, writeGeneratedFiles } = await import("./codegen/generate");
|
|
252
|
+
const { dirname } = await import("path");
|
|
253
|
+
const { fileURLToPath } = await import("url");
|
|
254
|
+
|
|
255
|
+
const result = await generate({ verbose: options?.verbose ?? true });
|
|
256
|
+
const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
257
|
+
writeGeneratedFiles(result, pkgDir);
|
|
258
|
+
|
|
259
|
+
console.error(
|
|
260
|
+
`Generated ${result.resources} resources, ${result.properties} property types, ${result.enums} enums`,
|
|
261
|
+
);
|
|
262
|
+
if (result.warnings.length > 0) {
|
|
263
|
+
console.error(`${result.warnings.length} warnings`);
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
async validate(options?: { verbose?: boolean }): Promise<void> {
|
|
268
|
+
const { validate } = await import("./validate");
|
|
269
|
+
const { printValidationResult } = await import("@intentius/chant/codegen/validate");
|
|
270
|
+
const result = await validate();
|
|
271
|
+
printValidationResult(result);
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
async coverage(options?: { verbose?: boolean; minOverall?: number }): Promise<void> {
|
|
275
|
+
const { analyzeK8sCoverage } = await import("./coverage");
|
|
276
|
+
await analyzeK8sCoverage({
|
|
277
|
+
verbose: options?.verbose,
|
|
278
|
+
minOverall: options?.minOverall,
|
|
279
|
+
});
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
async package(options?: { verbose?: boolean; force?: boolean }): Promise<void> {
|
|
283
|
+
const { packageLexicon } = await import("./codegen/package");
|
|
284
|
+
const { writeBundleSpec } = await import("@intentius/chant/codegen/package");
|
|
285
|
+
const { join, dirname } = await import("path");
|
|
286
|
+
const { fileURLToPath } = await import("url");
|
|
287
|
+
|
|
288
|
+
const { spec, stats } = await packageLexicon({ verbose: options?.verbose, force: options?.force });
|
|
289
|
+
|
|
290
|
+
const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
291
|
+
const distDir = join(pkgDir, "dist");
|
|
292
|
+
writeBundleSpec(spec, distDir);
|
|
293
|
+
|
|
294
|
+
console.error(`Packaged ${stats.resources} resources, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
mcpTools() {
|
|
298
|
+
return [
|
|
299
|
+
{
|
|
300
|
+
name: "diff",
|
|
301
|
+
description: "Compare current build output against previous output for Kubernetes manifests",
|
|
302
|
+
inputSchema: {
|
|
303
|
+
type: "object" as const,
|
|
304
|
+
properties: {
|
|
305
|
+
path: {
|
|
306
|
+
type: "string",
|
|
307
|
+
description: "Path to the infrastructure project directory",
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
async handler(params: Record<string, unknown>): Promise<unknown> {
|
|
312
|
+
const { diffCommand } = await import("@intentius/chant/cli/commands/diff");
|
|
313
|
+
const result = await diffCommand({
|
|
314
|
+
path: (params.path as string) ?? ".",
|
|
315
|
+
serializers: [k8sSerializer],
|
|
316
|
+
});
|
|
317
|
+
return result;
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
];
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
mcpResources() {
|
|
324
|
+
return [
|
|
325
|
+
{
|
|
326
|
+
uri: "resource-catalog",
|
|
327
|
+
name: "Kubernetes Resource Catalog",
|
|
328
|
+
description: "JSON list of all supported Kubernetes resource types",
|
|
329
|
+
mimeType: "application/json",
|
|
330
|
+
async handler(): Promise<string> {
|
|
331
|
+
const lexicon = require("./generated/lexicon-k8s.json") as Record<string, { resourceType: string; kind: string }>;
|
|
332
|
+
const entries = Object.entries(lexicon).map(([className, entry]) => ({
|
|
333
|
+
className,
|
|
334
|
+
resourceType: entry.resourceType,
|
|
335
|
+
kind: entry.kind,
|
|
336
|
+
}));
|
|
337
|
+
return JSON.stringify(entries);
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
uri: "examples/basic-deployment",
|
|
342
|
+
name: "Basic Deployment Example",
|
|
343
|
+
description: "A basic Kubernetes Deployment with Service",
|
|
344
|
+
mimeType: "text/typescript",
|
|
345
|
+
async handler(): Promise<string> {
|
|
346
|
+
return `import { Deployment, Service, Container, Probe } from "@intentius/chant-lexicon-k8s";
|
|
347
|
+
|
|
348
|
+
export const deployment = new Deployment({
|
|
349
|
+
metadata: { name: "my-app", labels: { "app.kubernetes.io/name": "my-app" } },
|
|
350
|
+
spec: {
|
|
351
|
+
replicas: 2,
|
|
352
|
+
selector: { matchLabels: { "app.kubernetes.io/name": "my-app" } },
|
|
353
|
+
template: {
|
|
354
|
+
metadata: { labels: { "app.kubernetes.io/name": "my-app" } },
|
|
355
|
+
spec: {
|
|
356
|
+
containers: [
|
|
357
|
+
new Container({
|
|
358
|
+
name: "app",
|
|
359
|
+
image: "my-app:1.0",
|
|
360
|
+
ports: [{ containerPort: 8080, name: "http" }],
|
|
361
|
+
livenessProbe: new Probe({ httpGet: { path: "/healthz", port: 8080 } }),
|
|
362
|
+
readinessProbe: new Probe({ httpGet: { path: "/readyz", port: 8080 } }),
|
|
363
|
+
}),
|
|
364
|
+
],
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
export const service = new Service({
|
|
371
|
+
metadata: { name: "my-app" },
|
|
372
|
+
spec: {
|
|
373
|
+
selector: { "app.kubernetes.io/name": "my-app" },
|
|
374
|
+
ports: [{ port: 80, targetPort: 8080, name: "http" }],
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
`;
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
];
|
|
381
|
+
},
|
|
382
|
+
|
|
383
|
+
skills(): SkillDefinition[] {
|
|
384
|
+
return [
|
|
385
|
+
{
|
|
386
|
+
name: "chant-k8s",
|
|
387
|
+
description: "Kubernetes manifest lifecycle — scaffold, generate, lint, build, apply, troubleshoot, rollback",
|
|
388
|
+
content: `---
|
|
389
|
+
skill: chant-k8s
|
|
390
|
+
description: Build, validate, and deploy Kubernetes manifests from a chant project
|
|
391
|
+
user-invocable: true
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
# Kubernetes Operational Playbook
|
|
395
|
+
|
|
396
|
+
## How chant and Kubernetes relate
|
|
397
|
+
|
|
398
|
+
chant is a **synthesis-only** tool — it compiles TypeScript source files into Kubernetes YAML manifests. chant does NOT call the Kubernetes API. Your job as an agent is to bridge the two:
|
|
399
|
+
|
|
400
|
+
- Use **chant** for: build, lint, diff (local YAML comparison)
|
|
401
|
+
- Use **kubectl / k8s API** for: apply, rollback, monitoring, troubleshooting
|
|
402
|
+
|
|
403
|
+
The source of truth for infrastructure is the TypeScript in \`src/\`. The generated YAML manifests are intermediate artifacts — never edit them by hand.
|
|
404
|
+
|
|
405
|
+
## Scaffolding a new project
|
|
406
|
+
|
|
407
|
+
### Initialize with a template
|
|
408
|
+
|
|
409
|
+
\`\`\`bash
|
|
410
|
+
chant init --lexicon k8s # default: Deployment + Service
|
|
411
|
+
chant init --lexicon k8s --template microservice # Deployment + Service + HPA + PDB
|
|
412
|
+
chant init --lexicon k8s --template stateful # StatefulSet + PVC + Service
|
|
413
|
+
\`\`\`
|
|
414
|
+
|
|
415
|
+
### Available templates
|
|
416
|
+
|
|
417
|
+
| Template | What it generates | Best for |
|
|
418
|
+
|----------|-------------------|----------|
|
|
419
|
+
| *(default)* | Deployment + Service | Simple stateless apps |
|
|
420
|
+
| \`microservice\` | Deployment + Service + HPA + PDB | Production microservices |
|
|
421
|
+
| \`stateful\` | StatefulSet + PVC + headless Service | Databases, caches |
|
|
422
|
+
|
|
423
|
+
## Build and validate
|
|
424
|
+
|
|
425
|
+
### Build manifests
|
|
426
|
+
|
|
427
|
+
\`\`\`bash
|
|
428
|
+
chant build src/ --output manifests.yaml
|
|
429
|
+
\`\`\`
|
|
430
|
+
|
|
431
|
+
Options:
|
|
432
|
+
- \`--format yaml\` — emit YAML (default for K8s)
|
|
433
|
+
- \`--watch\` — rebuild on source changes
|
|
434
|
+
- \`--output <path>\` — write to a specific file
|
|
435
|
+
|
|
436
|
+
### Lint the source
|
|
437
|
+
|
|
438
|
+
\`\`\`bash
|
|
439
|
+
chant lint src/
|
|
440
|
+
\`\`\`
|
|
441
|
+
|
|
442
|
+
### What each step catches
|
|
443
|
+
|
|
444
|
+
| Step | Catches | When to run |
|
|
445
|
+
|------|---------|-------------|
|
|
446
|
+
| \`chant lint\` | Hardcoded namespaces (WK8001) | Every edit |
|
|
447
|
+
| \`chant build\` | Post-synth: secrets in env (WK8005), latest tags (WK8006), API keys (WK8041), missing probes (WK8301), no resource limits (WK8201), privileged containers (WK8202), and more | Before apply |
|
|
448
|
+
| \`kubectl --dry-run=server\` | K8s API validation: schema errors, admission webhooks | Before production apply |
|
|
449
|
+
|
|
450
|
+
## Deploying to Kubernetes
|
|
451
|
+
|
|
452
|
+
### Apply manifests
|
|
453
|
+
|
|
454
|
+
\`\`\`bash
|
|
455
|
+
# Build
|
|
456
|
+
chant build src/ --output manifests.yaml
|
|
457
|
+
|
|
458
|
+
# Dry run first
|
|
459
|
+
kubectl apply -f manifests.yaml --dry-run=server
|
|
460
|
+
|
|
461
|
+
# Apply
|
|
462
|
+
kubectl apply -f manifests.yaml
|
|
463
|
+
\`\`\`
|
|
464
|
+
|
|
465
|
+
### Check rollout status
|
|
466
|
+
|
|
467
|
+
\`\`\`bash
|
|
468
|
+
kubectl rollout status deployment/my-app
|
|
469
|
+
\`\`\`
|
|
470
|
+
|
|
471
|
+
### Rollback
|
|
472
|
+
|
|
473
|
+
\`\`\`bash
|
|
474
|
+
kubectl rollout undo deployment/my-app
|
|
475
|
+
kubectl rollout undo deployment/my-app --to-revision=2
|
|
476
|
+
\`\`\`
|
|
477
|
+
|
|
478
|
+
## Debugging strategies
|
|
479
|
+
|
|
480
|
+
### Check pod status and events
|
|
481
|
+
|
|
482
|
+
\`\`\`bash
|
|
483
|
+
# Overview
|
|
484
|
+
kubectl get pods -l app.kubernetes.io/name=my-app
|
|
485
|
+
kubectl get events --sort-by=.lastTimestamp -n <namespace>
|
|
486
|
+
|
|
487
|
+
# Deep dive into a specific pod
|
|
488
|
+
kubectl describe pod <pod-name>
|
|
489
|
+
|
|
490
|
+
# Logs (current and previous crash)
|
|
491
|
+
kubectl logs <pod-name>
|
|
492
|
+
kubectl logs <pod-name> --previous
|
|
493
|
+
kubectl logs <pod-name> -c <container-name> # specific container
|
|
494
|
+
kubectl logs deployment/my-app --all-containers
|
|
495
|
+
|
|
496
|
+
# Debug containers (K8s 1.25+)
|
|
497
|
+
kubectl debug <pod-name> -it --image=busybox --target=<container>
|
|
498
|
+
|
|
499
|
+
# Port-forwarding for local testing
|
|
500
|
+
kubectl port-forward svc/my-app 8080:80
|
|
501
|
+
kubectl port-forward pod/<pod-name> 8080:8080
|
|
502
|
+
\`\`\`
|
|
503
|
+
|
|
504
|
+
### Common error patterns
|
|
505
|
+
|
|
506
|
+
| Status | Meaning | Diagnostic command | Typical fix |
|
|
507
|
+
|--------|---------|-------------------|-------------|
|
|
508
|
+
| Pending | Not scheduled | \`kubectl describe pod\` → Events | Check resource requests, node selectors, taints, PVC binding |
|
|
509
|
+
| CrashLoopBackOff | App crashing on start | \`kubectl logs --previous\` | Fix app startup, check probe config, increase initialDelaySeconds |
|
|
510
|
+
| ImagePullBackOff | Image not found | \`kubectl describe pod\` → Events | Verify image name/tag, check imagePullSecrets, registry auth |
|
|
511
|
+
| OOMKilled | Out of memory | \`kubectl describe pod\` → Last State | Increase memory limit, profile app memory usage |
|
|
512
|
+
| Evicted | Node disk/memory pressure | \`kubectl describe node\` | Increase limits, add node capacity, check for log/tmp bloat |
|
|
513
|
+
| CreateContainerError | Container config issue | \`kubectl describe pod\` → Events | Check volume mounts, configmap/secret refs, security context |
|
|
514
|
+
| Init:CrashLoopBackOff | Init container failing | \`kubectl logs -c <init-container>\` | Fix init container command, check dependencies |
|
|
515
|
+
|
|
516
|
+
### Resource inspection
|
|
517
|
+
|
|
518
|
+
\`\`\`bash
|
|
519
|
+
# Get all resources in namespace
|
|
520
|
+
kubectl get all -n <namespace>
|
|
521
|
+
|
|
522
|
+
# YAML output for debugging
|
|
523
|
+
kubectl get deployment/my-app -o yaml
|
|
524
|
+
|
|
525
|
+
# Check resource usage
|
|
526
|
+
kubectl top pods -l app.kubernetes.io/name=my-app
|
|
527
|
+
kubectl top nodes
|
|
528
|
+
\`\`\`
|
|
529
|
+
|
|
530
|
+
## Production safety
|
|
531
|
+
|
|
532
|
+
### Pre-apply validation
|
|
533
|
+
|
|
534
|
+
\`\`\`bash
|
|
535
|
+
# Always diff before applying
|
|
536
|
+
kubectl diff -f manifests.yaml
|
|
537
|
+
|
|
538
|
+
# Server-side dry run (validates with admission webhooks)
|
|
539
|
+
kubectl apply -f manifests.yaml --dry-run=server
|
|
540
|
+
|
|
541
|
+
# Client-side dry run (fast, but no webhook validation)
|
|
542
|
+
kubectl apply -f manifests.yaml --dry-run=client
|
|
543
|
+
\`\`\`
|
|
544
|
+
|
|
545
|
+
### Rollback
|
|
546
|
+
|
|
547
|
+
\`\`\`bash
|
|
548
|
+
# Check rollout history
|
|
549
|
+
kubectl rollout history deployment/my-app
|
|
550
|
+
|
|
551
|
+
# Undo last rollout
|
|
552
|
+
kubectl rollout undo deployment/my-app
|
|
553
|
+
|
|
554
|
+
# Roll back to a specific revision
|
|
555
|
+
kubectl rollout undo deployment/my-app --to-revision=2
|
|
556
|
+
|
|
557
|
+
# Watch rollout progress
|
|
558
|
+
kubectl rollout status deployment/my-app --timeout=300s
|
|
559
|
+
\`\`\`
|
|
560
|
+
|
|
561
|
+
### Deployment strategies
|
|
562
|
+
|
|
563
|
+
- **RollingUpdate** (default): Gradually replaces pods. Set \`maxSurge\` and \`maxUnavailable\`.
|
|
564
|
+
- **Recreate**: All pods terminated before new ones created. Use for stateful apps that cannot run multiple versions.
|
|
565
|
+
- **Canary**: Deploy a second Deployment with 1 replica + same selector labels. Route percentage via Ingress annotations or service mesh.
|
|
566
|
+
- **Blue/Green**: Two full Deployments (blue/green), switch Service selector between them.
|
|
567
|
+
|
|
568
|
+
## Composites
|
|
569
|
+
|
|
570
|
+
Composites are higher-level functions that produce multiple coordinated K8s resources from a single call. They return plain prop objects — not class instances — that you pass to generated constructors or serialize directly.
|
|
571
|
+
|
|
572
|
+
### WebApp — Deployment + Service + optional Ingress
|
|
573
|
+
|
|
574
|
+
\`\`\`typescript
|
|
575
|
+
import { WebApp } from "@intentius/chant-lexicon-k8s";
|
|
576
|
+
|
|
577
|
+
const { deployment, service, ingress } = WebApp({
|
|
578
|
+
name: "frontend",
|
|
579
|
+
image: "frontend:1.0",
|
|
580
|
+
port: 3000,
|
|
581
|
+
replicas: 3,
|
|
582
|
+
ingressHost: "frontend.example.com",
|
|
583
|
+
ingressTlsSecret: "frontend-tls",
|
|
584
|
+
});
|
|
585
|
+
\`\`\`
|
|
586
|
+
|
|
587
|
+
### StatefulApp — StatefulSet + headless Service + PVC
|
|
588
|
+
|
|
589
|
+
\`\`\`typescript
|
|
590
|
+
import { StatefulApp } from "@intentius/chant-lexicon-k8s";
|
|
591
|
+
|
|
592
|
+
const { statefulSet, service } = StatefulApp({
|
|
593
|
+
name: "postgres",
|
|
594
|
+
image: "postgres:16",
|
|
595
|
+
storageSize: "20Gi",
|
|
596
|
+
env: [{ name: "POSTGRES_DB", value: "mydb" }],
|
|
597
|
+
});
|
|
598
|
+
\`\`\`
|
|
599
|
+
|
|
600
|
+
### CronWorkload — CronJob + ServiceAccount + Role + RoleBinding
|
|
601
|
+
|
|
602
|
+
\`\`\`typescript
|
|
603
|
+
import { CronWorkload } from "@intentius/chant-lexicon-k8s";
|
|
604
|
+
|
|
605
|
+
const { cronJob, serviceAccount, role, roleBinding } = CronWorkload({
|
|
606
|
+
name: "db-backup",
|
|
607
|
+
image: "postgres:16",
|
|
608
|
+
schedule: "0 2 * * *",
|
|
609
|
+
command: ["pg_dump", "-h", "postgres", "mydb"],
|
|
610
|
+
rbacRules: [{ apiGroups: [""], resources: ["secrets"], verbs: ["get"] }],
|
|
611
|
+
});
|
|
612
|
+
\`\`\`
|
|
613
|
+
|
|
614
|
+
### AutoscaledService — Deployment + Service + HPA + PDB
|
|
615
|
+
|
|
616
|
+
Production HTTP service with autoscaling, health probes, and optional topology spreading.
|
|
617
|
+
|
|
618
|
+
\`\`\`typescript
|
|
619
|
+
import { AutoscaledService } from "@intentius/chant-lexicon-k8s";
|
|
620
|
+
|
|
621
|
+
const { deployment, service, hpa, pdb } = AutoscaledService({
|
|
622
|
+
name: "api",
|
|
623
|
+
image: "api:2.0",
|
|
624
|
+
port: 8080,
|
|
625
|
+
maxReplicas: 10,
|
|
626
|
+
minReplicas: 3,
|
|
627
|
+
cpuRequest: "200m",
|
|
628
|
+
memoryRequest: "256Mi",
|
|
629
|
+
cpuLimit: "1",
|
|
630
|
+
memoryLimit: "1Gi",
|
|
631
|
+
// Probe paths — defaults: /healthz and /readyz
|
|
632
|
+
livenessPath: "/healthz",
|
|
633
|
+
readinessPath: "/readyz",
|
|
634
|
+
// Zone-aware topology spreading (default: false)
|
|
635
|
+
topologySpread: true,
|
|
636
|
+
// Custom: topologySpread: { maxSkew: 2, topologyKey: "kubernetes.io/hostname" }
|
|
637
|
+
});
|
|
638
|
+
\`\`\`
|
|
639
|
+
|
|
640
|
+
Key features:
|
|
641
|
+
- **Probe paths**: \`livenessPath\` (default \`/healthz\`) and \`readinessPath\` (default \`/readyz\`) — override for apps that don't serve those routes
|
|
642
|
+
- **Topology spread**: \`topologySpread: true\` adds zone-aware spreading (maxSkew=1, DoNotSchedule); pass an object for custom key/skew
|
|
643
|
+
- **PDB minAvailable**: accepts number or string percentage (e.g., \`"50%"\`)
|
|
644
|
+
|
|
645
|
+
### WorkerPool — Deployment + RBAC + optional ConfigMap + optional HPA
|
|
646
|
+
|
|
647
|
+
Background queue workers with optional RBAC and autoscaling.
|
|
648
|
+
|
|
649
|
+
\`\`\`typescript
|
|
650
|
+
import { WorkerPool } from "@intentius/chant-lexicon-k8s";
|
|
651
|
+
|
|
652
|
+
const { deployment, serviceAccount, role, roleBinding, configMap, hpa } = WorkerPool({
|
|
653
|
+
name: "email-worker",
|
|
654
|
+
image: "worker:1.0",
|
|
655
|
+
command: ["bundle", "exec", "sidekiq"],
|
|
656
|
+
config: { REDIS_URL: "redis://redis:6379", QUEUE: "emails" },
|
|
657
|
+
autoscaling: { minReplicas: 2, maxReplicas: 20, targetCPUPercent: 60 },
|
|
658
|
+
});
|
|
659
|
+
\`\`\`
|
|
660
|
+
|
|
661
|
+
Key features:
|
|
662
|
+
- **RBAC opt-out**: Pass \`rbacRules: []\` to skip ServiceAccount/Role/RoleBinding creation entirely. Omitting \`rbacRules\` (undefined) creates default rules for secrets/configmaps.
|
|
663
|
+
- \`serviceAccount\`, \`role\`, \`roleBinding\` are optional in the result — check before use
|
|
664
|
+
|
|
665
|
+
### NamespaceEnv — Namespace + ResourceQuota + LimitRange + NetworkPolicy
|
|
666
|
+
|
|
667
|
+
Multi-tenant namespace provisioning with resource guardrails and network isolation.
|
|
668
|
+
|
|
669
|
+
\`\`\`typescript
|
|
670
|
+
import { NamespaceEnv } from "@intentius/chant-lexicon-k8s";
|
|
671
|
+
|
|
672
|
+
const { namespace, resourceQuota, limitRange, networkPolicy } = NamespaceEnv({
|
|
673
|
+
name: "team-alpha",
|
|
674
|
+
cpuQuota: "8",
|
|
675
|
+
memoryQuota: "16Gi",
|
|
676
|
+
maxPods: 50,
|
|
677
|
+
defaultCpuRequest: "100m",
|
|
678
|
+
defaultMemoryRequest: "128Mi",
|
|
679
|
+
defaultCpuLimit: "500m",
|
|
680
|
+
defaultMemoryLimit: "512Mi",
|
|
681
|
+
defaultDenyIngress: true,
|
|
682
|
+
defaultDenyEgress: true,
|
|
683
|
+
});
|
|
684
|
+
\`\`\`
|
|
685
|
+
|
|
686
|
+
Key features:
|
|
687
|
+
- **Quota-without-limits warning**: Setting ResourceQuota without LimitRange defaults emits a warning — pods without explicit resource requests will fail to schedule
|
|
688
|
+
- **Network policies**: \`defaultDenyIngress\` (default true), \`defaultDenyEgress\` (default false) — can be combined or used individually
|
|
689
|
+
|
|
690
|
+
### NodeAgent — DaemonSet + ServiceAccount + ClusterRole + ClusterRoleBinding + optional ConfigMap
|
|
691
|
+
|
|
692
|
+
Per-node agents (log collectors, security scanners, monitoring exporters).
|
|
693
|
+
|
|
694
|
+
\`\`\`typescript
|
|
695
|
+
import { NodeAgent } from "@intentius/chant-lexicon-k8s";
|
|
696
|
+
|
|
697
|
+
const { daemonSet, serviceAccount, clusterRole, clusterRoleBinding, configMap } = NodeAgent({
|
|
698
|
+
name: "log-collector",
|
|
699
|
+
image: "fluentd:v1.16",
|
|
700
|
+
port: 24224,
|
|
701
|
+
hostPaths: [
|
|
702
|
+
{ name: "varlog", hostPath: "/var/log", mountPath: "/var/log" },
|
|
703
|
+
],
|
|
704
|
+
config: { "fluent.conf": "..." },
|
|
705
|
+
rbacRules: [{ apiGroups: [""], resources: ["pods", "namespaces"], verbs: ["get", "list", "watch"] }],
|
|
706
|
+
cpuRequest: "50m", // default: 50m
|
|
707
|
+
memoryRequest: "64Mi", // default: 64Mi
|
|
708
|
+
cpuLimit: "200m", // default: 200m
|
|
709
|
+
memoryLimit: "128Mi", // default: 128Mi
|
|
710
|
+
namespace: "monitoring",
|
|
711
|
+
});
|
|
712
|
+
\`\`\`
|
|
713
|
+
|
|
714
|
+
Key features:
|
|
715
|
+
- **Container resources**: Always set with sensible defaults (50m/64Mi requests, 200m/128Mi limits) — override per-agent
|
|
716
|
+
- **Host paths**: Mounted read-only by default; set \`readOnly: false\` to enable writes
|
|
717
|
+
- **Tolerations**: \`tolerateAllTaints: true\` (default) ensures agent runs on every node
|
|
718
|
+
- Uses **ClusterRole/ClusterRoleBinding** (cluster-scoped) for node-level access
|
|
719
|
+
|
|
720
|
+
### Common patterns across all composites
|
|
721
|
+
|
|
722
|
+
- All resources carry \`app.kubernetes.io/name\`, \`app.kubernetes.io/managed-by: chant\`, and \`app.kubernetes.io/component\` labels
|
|
723
|
+
- Pass \`labels: { team: "platform" }\` to add extra labels to all resources
|
|
724
|
+
- Pass \`namespace: "prod"\` to set namespace on all namespaced resources
|
|
725
|
+
- Pass \`env: [{ name: "KEY", value: "val" }]\` for container environment variables
|
|
726
|
+
|
|
727
|
+
## Namespace management
|
|
728
|
+
|
|
729
|
+
Use the **NamespaceEnv** composite (above) to manage namespaces declaratively. For manual kubectl management:
|
|
730
|
+
|
|
731
|
+
\`\`\`bash
|
|
732
|
+
# Create namespace
|
|
733
|
+
kubectl create namespace my-project
|
|
734
|
+
|
|
735
|
+
# Set default resource quotas
|
|
736
|
+
kubectl apply -f - <<EOF
|
|
737
|
+
apiVersion: v1
|
|
738
|
+
kind: ResourceQuota
|
|
739
|
+
metadata:
|
|
740
|
+
name: default-quota
|
|
741
|
+
namespace: my-project
|
|
742
|
+
spec:
|
|
743
|
+
hard:
|
|
744
|
+
requests.cpu: "4"
|
|
745
|
+
requests.memory: 8Gi
|
|
746
|
+
limits.cpu: "8"
|
|
747
|
+
limits.memory: 16Gi
|
|
748
|
+
pods: "20"
|
|
749
|
+
EOF
|
|
750
|
+
|
|
751
|
+
# Set default container limits via LimitRange
|
|
752
|
+
kubectl apply -f - <<EOF
|
|
753
|
+
apiVersion: v1
|
|
754
|
+
kind: LimitRange
|
|
755
|
+
metadata:
|
|
756
|
+
name: default-limits
|
|
757
|
+
namespace: my-project
|
|
758
|
+
spec:
|
|
759
|
+
limits:
|
|
760
|
+
- default:
|
|
761
|
+
cpu: 500m
|
|
762
|
+
memory: 256Mi
|
|
763
|
+
defaultRequest:
|
|
764
|
+
cpu: 100m
|
|
765
|
+
memory: 128Mi
|
|
766
|
+
type: Container
|
|
767
|
+
EOF
|
|
768
|
+
\`\`\`
|
|
769
|
+
|
|
770
|
+
## Troubleshooting reference table
|
|
771
|
+
|
|
772
|
+
| Symptom | Likely cause | Resolution |
|
|
773
|
+
|---------|-------------|------------|
|
|
774
|
+
| Pod stuck in Pending | Insufficient CPU/memory on nodes | Scale up cluster or reduce resource requests |
|
|
775
|
+
| Pod stuck in Pending | PVC not bound | Check StorageClass exists, PV available |
|
|
776
|
+
| Pod stuck in Pending | Node selector/affinity mismatch | Verify node labels match selectors |
|
|
777
|
+
| Pod stuck in ContainerCreating | ConfigMap/Secret not found | Ensure referenced ConfigMaps/Secrets exist |
|
|
778
|
+
| Pod stuck in ContainerCreating | Volume mount failure | Check PVC status, CSI driver health |
|
|
779
|
+
| Service returns 503 | No ready endpoints | Check pod readiness probes, selector match |
|
|
780
|
+
| Service returns 503 | Wrong port configuration | Verify targetPort matches containerPort |
|
|
781
|
+
| Ingress returns 404 | Backend service not found | Check Ingress rules, service name/port |
|
|
782
|
+
| Ingress returns 404 | Wrong path matching | Check pathType (Prefix vs Exact) |
|
|
783
|
+
| HPA not scaling | Metrics server not installed | Install metrics-server |
|
|
784
|
+
| HPA not scaling | Resource requests not set | Add CPU/memory requests to containers |
|
|
785
|
+
| CronJob not running | Invalid cron expression | Validate cron syntax (5-field format) |
|
|
786
|
+
| NetworkPolicy blocking | Default deny applied | Add explicit allow rules for required traffic |
|
|
787
|
+
| RBAC permission denied | Missing Role/RoleBinding | Check ServiceAccount bindings and verb permissions |
|
|
788
|
+
|
|
789
|
+
## Quick reference
|
|
790
|
+
|
|
791
|
+
\`\`\`bash
|
|
792
|
+
# Build
|
|
793
|
+
chant build src/ --output manifests.yaml
|
|
794
|
+
|
|
795
|
+
# Lint
|
|
796
|
+
chant lint src/
|
|
797
|
+
|
|
798
|
+
# Validate
|
|
799
|
+
kubectl apply -f manifests.yaml --dry-run=server
|
|
800
|
+
|
|
801
|
+
# Diff
|
|
802
|
+
kubectl diff -f manifests.yaml
|
|
803
|
+
|
|
804
|
+
# Apply
|
|
805
|
+
kubectl apply -f manifests.yaml
|
|
806
|
+
|
|
807
|
+
# Status
|
|
808
|
+
kubectl get pods,svc,deploy
|
|
809
|
+
|
|
810
|
+
# Logs
|
|
811
|
+
kubectl logs deployment/my-app
|
|
812
|
+
|
|
813
|
+
# Rollback
|
|
814
|
+
kubectl rollout undo deployment/my-app
|
|
815
|
+
|
|
816
|
+
# Debug
|
|
817
|
+
kubectl describe pod <name>
|
|
818
|
+
kubectl logs <name> --previous
|
|
819
|
+
kubectl get events --sort-by=.lastTimestamp
|
|
820
|
+
\`\`\`
|
|
821
|
+
`,
|
|
822
|
+
triggers: [
|
|
823
|
+
{ type: "file-pattern", value: "**/*.k8s.ts" },
|
|
824
|
+
{ type: "file-pattern", value: "**/k8s/**/*.ts" },
|
|
825
|
+
{ type: "context", value: "kubernetes" },
|
|
826
|
+
{ type: "context", value: "k8s" },
|
|
827
|
+
{ type: "context", value: "kubectl" },
|
|
828
|
+
{ type: "context", value: "deployment" },
|
|
829
|
+
{ type: "context", value: "pod" },
|
|
830
|
+
{ type: "context", value: "composite" },
|
|
831
|
+
{ type: "context", value: "autoscaled" },
|
|
832
|
+
{ type: "context", value: "workerpool" },
|
|
833
|
+
{ type: "context", value: "namespace-env" },
|
|
834
|
+
{ type: "context", value: "node-agent" },
|
|
835
|
+
],
|
|
836
|
+
preConditions: [
|
|
837
|
+
"chant CLI is installed (chant --version succeeds)",
|
|
838
|
+
"kubectl is configured and can access the cluster",
|
|
839
|
+
"Project has chant source files in src/",
|
|
840
|
+
],
|
|
841
|
+
postConditions: [
|
|
842
|
+
"All pods are in Running state",
|
|
843
|
+
"No CrashLoopBackOff or Error pods",
|
|
844
|
+
],
|
|
845
|
+
parameters: [],
|
|
846
|
+
examples: [
|
|
847
|
+
{
|
|
848
|
+
title: "Basic deployment",
|
|
849
|
+
description: "Create a Deployment with Service",
|
|
850
|
+
input: "Create a web app deployment",
|
|
851
|
+
output: `new Deployment({
|
|
852
|
+
metadata: { name: "my-app", labels: { "app.kubernetes.io/name": "my-app" } },
|
|
853
|
+
spec: {
|
|
854
|
+
replicas: 2,
|
|
855
|
+
selector: { matchLabels: { "app.kubernetes.io/name": "my-app" } },
|
|
856
|
+
template: {
|
|
857
|
+
metadata: { labels: { "app.kubernetes.io/name": "my-app" } },
|
|
858
|
+
spec: {
|
|
859
|
+
containers: [{
|
|
860
|
+
name: "app",
|
|
861
|
+
image: "my-app:1.0",
|
|
862
|
+
ports: [{ containerPort: 8080 }],
|
|
863
|
+
}],
|
|
864
|
+
},
|
|
865
|
+
},
|
|
866
|
+
},
|
|
867
|
+
})`,
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
title: "Deploy to cluster",
|
|
871
|
+
description: "Build and apply manifests",
|
|
872
|
+
input: "Deploy my app to the cluster",
|
|
873
|
+
output: `chant build src/ --output manifests.yaml
|
|
874
|
+
kubectl apply -f manifests.yaml --dry-run=server
|
|
875
|
+
kubectl apply -f manifests.yaml
|
|
876
|
+
kubectl rollout status deployment/my-app`,
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
title: "AutoscaledService composite",
|
|
880
|
+
description: "Production HTTP service with HPA, PDB, and zone spreading",
|
|
881
|
+
input: "Create an autoscaled API service",
|
|
882
|
+
output: `import { AutoscaledService } from "@intentius/chant-lexicon-k8s";
|
|
883
|
+
|
|
884
|
+
const { deployment, service, hpa, pdb } = AutoscaledService({
|
|
885
|
+
name: "api",
|
|
886
|
+
image: "api:1.0",
|
|
887
|
+
port: 8080,
|
|
888
|
+
maxReplicas: 10,
|
|
889
|
+
cpuRequest: "100m",
|
|
890
|
+
memoryRequest: "128Mi",
|
|
891
|
+
topologySpread: true,
|
|
892
|
+
});`,
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
title: "NamespaceEnv composite",
|
|
896
|
+
description: "Multi-tenant namespace with guardrails",
|
|
897
|
+
input: "Set up a team namespace with quotas and network isolation",
|
|
898
|
+
output: `import { NamespaceEnv } from "@intentius/chant-lexicon-k8s";
|
|
899
|
+
|
|
900
|
+
const { namespace, resourceQuota, limitRange, networkPolicy } = NamespaceEnv({
|
|
901
|
+
name: "team-alpha",
|
|
902
|
+
cpuQuota: "8",
|
|
903
|
+
memoryQuota: "16Gi",
|
|
904
|
+
defaultCpuRequest: "100m",
|
|
905
|
+
defaultMemoryRequest: "128Mi",
|
|
906
|
+
defaultCpuLimit: "500m",
|
|
907
|
+
defaultMemoryLimit: "512Mi",
|
|
908
|
+
defaultDenyIngress: true,
|
|
909
|
+
});`,
|
|
910
|
+
},
|
|
911
|
+
],
|
|
912
|
+
},
|
|
913
|
+
];
|
|
914
|
+
},
|
|
915
|
+
};
|