@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
|
@@ -0,0 +1,1147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation generation for the Kubernetes lexicon.
|
|
3
|
+
*
|
|
4
|
+
* Generates Starlight MDX pages for K8s entities using the core docs pipeline.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { dirname, join } from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
import { docsPipeline, writeDocsSite, type DocsConfig } from "@intentius/chant/codegen/docs";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Extract service name from K8s type: "K8s::Apps::Deployment" → "Apps"
|
|
13
|
+
*/
|
|
14
|
+
function serviceFromType(resourceType: string): string {
|
|
15
|
+
const parts = resourceType.split("::");
|
|
16
|
+
return parts.length >= 2 ? parts[1] : "Core";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const overview = `The **Kubernetes** lexicon provides typed constructors for Kubernetes resource
|
|
20
|
+
manifests. It covers Deployments, Services, ConfigMaps, StatefulSets, Jobs,
|
|
21
|
+
Ingress, RBAC, and 50+ additional resource and property types.
|
|
22
|
+
|
|
23
|
+
New? Start with the [Getting Started](/chant/lexicons/k8s/getting-started/) guide.
|
|
24
|
+
|
|
25
|
+
Install it with:
|
|
26
|
+
|
|
27
|
+
\`\`\`bash
|
|
28
|
+
npm install --save-dev @intentius/chant-lexicon-k8s
|
|
29
|
+
\`\`\`
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
\`\`\`typescript
|
|
34
|
+
import { Deployment, Service, Container, Probe } from "@intentius/chant-lexicon-k8s";
|
|
35
|
+
|
|
36
|
+
export const deployment = new Deployment({
|
|
37
|
+
metadata: { name: "my-app", labels: { "app.kubernetes.io/name": "my-app" } },
|
|
38
|
+
spec: {
|
|
39
|
+
replicas: 2,
|
|
40
|
+
selector: { matchLabels: { "app.kubernetes.io/name": "my-app" } },
|
|
41
|
+
template: {
|
|
42
|
+
metadata: { labels: { "app.kubernetes.io/name": "my-app" } },
|
|
43
|
+
spec: {
|
|
44
|
+
containers: [
|
|
45
|
+
new Container({
|
|
46
|
+
name: "app",
|
|
47
|
+
image: "my-app:1.0",
|
|
48
|
+
ports: [{ containerPort: 8080, name: "http" }],
|
|
49
|
+
livenessProbe: new Probe({ httpGet: { path: "/healthz", port: 8080 } }),
|
|
50
|
+
readinessProbe: new Probe({ httpGet: { path: "/readyz", port: 8080 } }),
|
|
51
|
+
}),
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export const service = new Service({
|
|
59
|
+
metadata: { name: "my-app" },
|
|
60
|
+
spec: {
|
|
61
|
+
selector: { "app.kubernetes.io/name": "my-app" },
|
|
62
|
+
ports: [{ port: 80, targetPort: 8080, name: "http" }],
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
\`\`\`
|
|
66
|
+
|
|
67
|
+
The lexicon provides **50+ resource types** (Deployment, Service, ConfigMap, StatefulSet, and more), **35+ property types** (Container, Probe, Volume, SecurityContext, etc.), and composites (WebApp, StatefulApp, CronWorkload, AutoscaledService, WorkerPool, NamespaceEnv, NodeAgent) for common patterns.
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
const outputFormat = `The Kubernetes lexicon serializes resources into **multi-document YAML** with
|
|
71
|
+
\`---\` separators between resources. Each resource gets the standard K8s
|
|
72
|
+
structure: \`apiVersion\`, \`kind\`, \`metadata\`, and \`spec\`.
|
|
73
|
+
|
|
74
|
+
## Building
|
|
75
|
+
|
|
76
|
+
Run \`chant build\` to produce Kubernetes manifests from your declarations:
|
|
77
|
+
|
|
78
|
+
\`\`\`bash
|
|
79
|
+
chant build
|
|
80
|
+
# Writes dist/manifests.yaml
|
|
81
|
+
\`\`\`
|
|
82
|
+
|
|
83
|
+
The generated file includes:
|
|
84
|
+
|
|
85
|
+
- Multi-document YAML with \`---\` separators
|
|
86
|
+
- Correct \`apiVersion\` and \`kind\` for each resource
|
|
87
|
+
- \`metadata.name\` auto-generated from export names (camelCase → kebab-case)
|
|
88
|
+
- Default labels and annotations injected from \`defaultLabels()\`/\`defaultAnnotations()\`
|
|
89
|
+
|
|
90
|
+
## Key conversions
|
|
91
|
+
|
|
92
|
+
| Chant (TypeScript) | YAML output | Rule |
|
|
93
|
+
|--------------------|-------------|------|
|
|
94
|
+
| \`export const myApp = new Deployment({...})\` | \`metadata.name: my-app\` | Export name → kebab-case |
|
|
95
|
+
| \`new Container({...})\` | Inline container spec | Property types expanded inline |
|
|
96
|
+
| \`defaultLabels({...})\` | Merged into all resources | Project-wide label injection |
|
|
97
|
+
|
|
98
|
+
## Applying
|
|
99
|
+
|
|
100
|
+
The output is standard Kubernetes YAML. Apply with kubectl:
|
|
101
|
+
|
|
102
|
+
\`\`\`bash
|
|
103
|
+
# Dry run first
|
|
104
|
+
kubectl apply -f dist/manifests.yaml --dry-run=server
|
|
105
|
+
|
|
106
|
+
# Apply
|
|
107
|
+
kubectl apply -f dist/manifests.yaml
|
|
108
|
+
|
|
109
|
+
# Diff before applying
|
|
110
|
+
kubectl diff -f dist/manifests.yaml
|
|
111
|
+
\`\`\`
|
|
112
|
+
|
|
113
|
+
## Compatibility
|
|
114
|
+
|
|
115
|
+
The output is compatible with:
|
|
116
|
+
- kubectl apply/diff
|
|
117
|
+
- Helm (as raw manifests)
|
|
118
|
+
- ArgoCD / Flux GitOps controllers
|
|
119
|
+
- Kustomize (as a base)
|
|
120
|
+
- Any tool that processes Kubernetes YAML`;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Generate documentation for the Kubernetes lexicon.
|
|
124
|
+
*/
|
|
125
|
+
export async function generateDocs(opts?: { verbose?: boolean }): Promise<void> {
|
|
126
|
+
const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
127
|
+
|
|
128
|
+
const config: DocsConfig = {
|
|
129
|
+
name: "k8s",
|
|
130
|
+
displayName: "Kubernetes",
|
|
131
|
+
description: "Typed constructors for Kubernetes resource manifests",
|
|
132
|
+
distDir: join(pkgDir, "dist"),
|
|
133
|
+
outDir: join(pkgDir, "docs"),
|
|
134
|
+
overview,
|
|
135
|
+
outputFormat,
|
|
136
|
+
serviceFromType,
|
|
137
|
+
suppressPages: ["pseudo-parameters"],
|
|
138
|
+
extraPages: [
|
|
139
|
+
{
|
|
140
|
+
slug: "getting-started",
|
|
141
|
+
title: "Getting Started",
|
|
142
|
+
description: "Install chant and deploy your first Kubernetes manifest in 5 minutes",
|
|
143
|
+
content: `## What is chant?
|
|
144
|
+
|
|
145
|
+
Chant is a TypeScript-to-YAML compiler for Kubernetes. You write typed TypeScript declarations, and chant outputs kubectl-ready manifests.
|
|
146
|
+
|
|
147
|
+
## Install
|
|
148
|
+
|
|
149
|
+
\`\`\`bash
|
|
150
|
+
npm install --save-dev @intentius/chant @intentius/chant-lexicon-k8s
|
|
151
|
+
\`\`\`
|
|
152
|
+
|
|
153
|
+
## Your first deployment
|
|
154
|
+
|
|
155
|
+
The fastest path is the **WebApp** composite — one function call that produces a Deployment, Service, and optional Ingress:
|
|
156
|
+
|
|
157
|
+
\`\`\`typescript
|
|
158
|
+
// src/infra.k8s.ts
|
|
159
|
+
import { WebApp } from "@intentius/chant-lexicon-k8s";
|
|
160
|
+
|
|
161
|
+
const app = WebApp({
|
|
162
|
+
name: "hello",
|
|
163
|
+
image: "nginx:1.25",
|
|
164
|
+
port: 80,
|
|
165
|
+
replicas: 2,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
export const { deployment, service } = app;
|
|
169
|
+
\`\`\`
|
|
170
|
+
|
|
171
|
+
Build and deploy:
|
|
172
|
+
|
|
173
|
+
\`\`\`bash
|
|
174
|
+
# Generate YAML manifests
|
|
175
|
+
chant build --output dist/manifests.yaml
|
|
176
|
+
|
|
177
|
+
# Validate against the cluster API (no changes applied)
|
|
178
|
+
kubectl apply -f dist/manifests.yaml --dry-run=server
|
|
179
|
+
|
|
180
|
+
# Apply for real
|
|
181
|
+
kubectl apply -f dist/manifests.yaml
|
|
182
|
+
\`\`\`
|
|
183
|
+
|
|
184
|
+
## Using resource constructors
|
|
185
|
+
|
|
186
|
+
Composites are convenient, but you can also use the lower-level resource constructors directly:
|
|
187
|
+
|
|
188
|
+
\`\`\`typescript
|
|
189
|
+
// src/infra.k8s.ts
|
|
190
|
+
import { Deployment, Service, Container, Probe } from "@intentius/chant-lexicon-k8s";
|
|
191
|
+
|
|
192
|
+
export const deployment = new Deployment({
|
|
193
|
+
metadata: { name: "hello", labels: { "app.kubernetes.io/name": "hello" } },
|
|
194
|
+
spec: {
|
|
195
|
+
replicas: 2,
|
|
196
|
+
selector: { matchLabels: { "app.kubernetes.io/name": "hello" } },
|
|
197
|
+
template: {
|
|
198
|
+
metadata: { labels: { "app.kubernetes.io/name": "hello" } },
|
|
199
|
+
spec: {
|
|
200
|
+
containers: [
|
|
201
|
+
new Container({
|
|
202
|
+
name: "web",
|
|
203
|
+
image: "nginx:1.25",
|
|
204
|
+
ports: [{ containerPort: 80, name: "http" }],
|
|
205
|
+
livenessProbe: new Probe({ httpGet: { path: "/", port: 80 } }),
|
|
206
|
+
readinessProbe: new Probe({ httpGet: { path: "/", port: 80 } }),
|
|
207
|
+
}),
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
export const service = new Service({
|
|
215
|
+
metadata: { name: "hello" },
|
|
216
|
+
spec: {
|
|
217
|
+
selector: { "app.kubernetes.io/name": "hello" },
|
|
218
|
+
ports: [{ port: 80, targetPort: 80, name: "http" }],
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
\`\`\`
|
|
222
|
+
|
|
223
|
+
Composites return plain prop objects. Resource constructors (\`new Deployment(...)\`) accept the same shape. Both produce identical YAML.
|
|
224
|
+
|
|
225
|
+
## Next steps
|
|
226
|
+
|
|
227
|
+
- [Kubernetes Concepts](/chant/lexicons/k8s/kubernetes-concepts/) — how K8s resources map to chant constructs
|
|
228
|
+
- [Examples: Composites](/chant/lexicons/k8s/composite-examples/) — WebApp, CronWorkload, AutoscaledService, and more
|
|
229
|
+
- [Lint Rules](/chant/lexicons/k8s/lint-rules/) — built-in checks for security, reliability, and best practices`,
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
slug: "kubernetes-concepts",
|
|
233
|
+
title: "Kubernetes Concepts",
|
|
234
|
+
description: "How Kubernetes resources map to chant constructs — apiVersion, kind, metadata, spec",
|
|
235
|
+
content: `Every exported resource declaration becomes a Kubernetes manifest document in the generated YAML. The serializer handles the translation automatically:
|
|
236
|
+
|
|
237
|
+
- Resolves the correct \`apiVersion\` and \`kind\` from the resource type
|
|
238
|
+
- Converts the export name to a kebab-case \`metadata.name\`
|
|
239
|
+
- Nests user properties under \`spec\` (or at the top level for specless types like ConfigMap)
|
|
240
|
+
- Merges default labels and annotations from \`defaultLabels()\`/\`defaultAnnotations()\`
|
|
241
|
+
|
|
242
|
+
## Resource structure
|
|
243
|
+
|
|
244
|
+
Every Kubernetes resource has four standard fields:
|
|
245
|
+
|
|
246
|
+
| Field | Source | Example |
|
|
247
|
+
|-------|--------|---------|
|
|
248
|
+
| \`apiVersion\` | Resolved from resource type | \`apps/v1\` |
|
|
249
|
+
| \`kind\` | Resolved from resource type | \`Deployment\` |
|
|
250
|
+
| \`metadata\` | From \`metadata\` property | \`{ name: "my-app", labels: {...} }\` |
|
|
251
|
+
| \`spec\` | From \`spec\` property or remaining props | Resource-specific configuration |
|
|
252
|
+
|
|
253
|
+
## API groups
|
|
254
|
+
|
|
255
|
+
K8s resources are organized by API group:
|
|
256
|
+
|
|
257
|
+
| Group | apiVersion | Resources |
|
|
258
|
+
|-------|-----------|-----------|
|
|
259
|
+
| Core | \`v1\` | Pod, Service, ConfigMap, Secret, Namespace, ServiceAccount |
|
|
260
|
+
| Apps | \`apps/v1\` | Deployment, StatefulSet, DaemonSet, ReplicaSet |
|
|
261
|
+
| Batch | \`batch/v1\` | Job, CronJob |
|
|
262
|
+
| Networking | \`networking.k8s.io/v1\` | Ingress, NetworkPolicy |
|
|
263
|
+
| RBAC | \`rbac.authorization.k8s.io/v1\` | Role, ClusterRole, RoleBinding, ClusterRoleBinding |
|
|
264
|
+
| Autoscaling | \`autoscaling/v2\` | HorizontalPodAutoscaler |
|
|
265
|
+
| Policy | \`policy/v1\` | PodDisruptionBudget |
|
|
266
|
+
|
|
267
|
+
## Property types
|
|
268
|
+
|
|
269
|
+
Nested objects like containers, probes, and volumes are expressed as property types:
|
|
270
|
+
|
|
271
|
+
\`\`\`typescript
|
|
272
|
+
import { Deployment, Container, Probe, ResourceRequirements } from "@intentius/chant-lexicon-k8s";
|
|
273
|
+
|
|
274
|
+
export const app = new Deployment({
|
|
275
|
+
metadata: { name: "my-app" },
|
|
276
|
+
spec: {
|
|
277
|
+
replicas: 2,
|
|
278
|
+
selector: { matchLabels: { app: "my-app" } },
|
|
279
|
+
template: {
|
|
280
|
+
metadata: { labels: { app: "my-app" } },
|
|
281
|
+
spec: {
|
|
282
|
+
containers: [
|
|
283
|
+
new Container({
|
|
284
|
+
name: "app",
|
|
285
|
+
image: "my-app:1.0",
|
|
286
|
+
resources: new ResourceRequirements({
|
|
287
|
+
limits: { cpu: "500m", memory: "256Mi" },
|
|
288
|
+
requests: { cpu: "100m", memory: "128Mi" },
|
|
289
|
+
}),
|
|
290
|
+
livenessProbe: new Probe({
|
|
291
|
+
httpGet: { path: "/healthz", port: 8080 },
|
|
292
|
+
initialDelaySeconds: 10,
|
|
293
|
+
}),
|
|
294
|
+
}),
|
|
295
|
+
],
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
\`\`\`
|
|
301
|
+
|
|
302
|
+
## Specless types
|
|
303
|
+
|
|
304
|
+
Some K8s resources (ConfigMap, Secret, Namespace, ServiceAccount) don't have a \`spec\` field. Their data goes directly on the manifest:
|
|
305
|
+
|
|
306
|
+
\`\`\`typescript
|
|
307
|
+
import { ConfigMap, Secret } from "@intentius/chant-lexicon-k8s";
|
|
308
|
+
|
|
309
|
+
export const config = new ConfigMap({
|
|
310
|
+
metadata: { name: "app-config" },
|
|
311
|
+
data: { DATABASE_URL: "postgres://localhost:5432/mydb" },
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
export const secret = new Secret({
|
|
315
|
+
metadata: { name: "app-secret" },
|
|
316
|
+
stringData: { API_KEY: "changeme" },
|
|
317
|
+
});
|
|
318
|
+
\`\`\`
|
|
319
|
+
|
|
320
|
+
## Default labels and annotations
|
|
321
|
+
|
|
322
|
+
Use \`defaultLabels()\` and \`defaultAnnotations()\` to inject metadata into all resources:
|
|
323
|
+
|
|
324
|
+
\`\`\`typescript
|
|
325
|
+
import { defaultLabels } from "@intentius/chant-lexicon-k8s";
|
|
326
|
+
|
|
327
|
+
export const labels = defaultLabels({
|
|
328
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
329
|
+
"app.kubernetes.io/part-of": "my-system",
|
|
330
|
+
});
|
|
331
|
+
\`\`\`
|
|
332
|
+
|
|
333
|
+
Explicit labels on individual resources take precedence over defaults.
|
|
334
|
+
|
|
335
|
+
> **Tip:** Avoid hardcoding \`metadata.namespace\` — the [WK8001 lint rule](/chant/lexicons/k8s/lint-rules/) will flag it. Use a config variable or pass namespace at deploy time instead.`,
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
slug: "lint-rules",
|
|
339
|
+
title: "Lint Rules",
|
|
340
|
+
description: "Built-in lint rules and post-synth checks for Kubernetes manifests",
|
|
341
|
+
content: `The Kubernetes lexicon ships lint rules that run during \`chant lint\` and post-synth checks that validate the serialized YAML after \`chant build\`.
|
|
342
|
+
|
|
343
|
+
## Lint rules
|
|
344
|
+
|
|
345
|
+
Lint rules analyze your TypeScript source code before build.
|
|
346
|
+
|
|
347
|
+
### WK8001 — Hardcoded namespace
|
|
348
|
+
|
|
349
|
+
**Severity:** warning | **Category:** correctness
|
|
350
|
+
|
|
351
|
+
Flags hardcoded namespace strings in resource constructors. Namespaces should be parameterized or derived from configuration.
|
|
352
|
+
|
|
353
|
+
\`\`\`typescript
|
|
354
|
+
// Bad — hardcoded namespace
|
|
355
|
+
new Deployment({ metadata: { namespace: "production" } });
|
|
356
|
+
|
|
357
|
+
// Good — parameterized
|
|
358
|
+
new Deployment({ metadata: { namespace: config.namespace } });
|
|
359
|
+
\`\`\`
|
|
360
|
+
|
|
361
|
+
## Post-synth checks
|
|
362
|
+
|
|
363
|
+
Post-synth checks run against the serialized YAML after build.
|
|
364
|
+
|
|
365
|
+
### Security
|
|
366
|
+
|
|
367
|
+
| Rule | Description |
|
|
368
|
+
|------|-------------|
|
|
369
|
+
| WK8005 | Hardcoded secrets in environment variables |
|
|
370
|
+
| WK8041 | API keys detected in env values |
|
|
371
|
+
| WK8042 | Private keys in ConfigMaps or Secrets |
|
|
372
|
+
| WK8202 | Privileged container (\`privileged: true\`) |
|
|
373
|
+
| WK8203 | Writable root filesystem (\`readOnlyRootFilesystem\` not set) |
|
|
374
|
+
| WK8204 | Container running as root (\`runAsNonRoot\` not set) |
|
|
375
|
+
| WK8205 | Capabilities not dropped (\`drop: ["ALL"]\` missing) |
|
|
376
|
+
| WK8207 | Host network access (\`hostNetwork: true\`) |
|
|
377
|
+
| WK8208 | Host PID namespace (\`hostPID: true\`) |
|
|
378
|
+
| WK8209 | Host IPC namespace (\`hostIPC: true\`) |
|
|
379
|
+
|
|
380
|
+
### Best practices
|
|
381
|
+
|
|
382
|
+
| Rule | Description |
|
|
383
|
+
|------|-------------|
|
|
384
|
+
| WK8006 | Latest image tag or untagged image |
|
|
385
|
+
| WK8101 | Deployment selector doesn't match template labels |
|
|
386
|
+
| WK8102 | Resource missing metadata.labels |
|
|
387
|
+
| WK8103 | Container missing name |
|
|
388
|
+
| WK8104 | Unnamed container ports |
|
|
389
|
+
| WK8105 | Missing imagePullPolicy |
|
|
390
|
+
|
|
391
|
+
### Reliability
|
|
392
|
+
|
|
393
|
+
| Rule | Description |
|
|
394
|
+
|------|-------------|
|
|
395
|
+
| WK8201 | Container missing resource limits |
|
|
396
|
+
| WK8301 | Container missing health probes (skips Jobs/CronJobs) |
|
|
397
|
+
| WK8302 | Single replica Deployment |
|
|
398
|
+
| WK8303 | HA Deployment without PodDisruptionBudget |
|
|
399
|
+
|
|
400
|
+
## Running lint
|
|
401
|
+
|
|
402
|
+
\`\`\`bash
|
|
403
|
+
# Lint your chant project
|
|
404
|
+
chant lint
|
|
405
|
+
|
|
406
|
+
# Build (also runs post-synth checks)
|
|
407
|
+
chant build
|
|
408
|
+
\`\`\`
|
|
409
|
+
|
|
410
|
+
To suppress a rule on a specific line:
|
|
411
|
+
|
|
412
|
+
\`\`\`typescript
|
|
413
|
+
// chant-disable-next-line WK8001
|
|
414
|
+
export const deploy = new Deployment({ metadata: { namespace: "prod" } });
|
|
415
|
+
\`\`\`
|
|
416
|
+
|
|
417
|
+
To suppress globally in \`chant.config.ts\`:
|
|
418
|
+
|
|
419
|
+
\`\`\`typescript
|
|
420
|
+
export default {
|
|
421
|
+
lint: {
|
|
422
|
+
rules: {
|
|
423
|
+
WK8001: "off",
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
\`\`\`
|
|
428
|
+
`,
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
slug: "examples",
|
|
432
|
+
title: "Examples: Resources",
|
|
433
|
+
description: "Walkthrough of Kubernetes resource examples — deployments, services, autoscaling",
|
|
434
|
+
content: `## Basic Deployment
|
|
435
|
+
|
|
436
|
+
A Deployment with Service — the most common pattern for stateless web apps:
|
|
437
|
+
|
|
438
|
+
\`\`\`typescript
|
|
439
|
+
import { Deployment, Service, Container, Probe } from "@intentius/chant-lexicon-k8s";
|
|
440
|
+
|
|
441
|
+
export const deployment = new Deployment({
|
|
442
|
+
metadata: { name: "web", labels: { "app.kubernetes.io/name": "web" } },
|
|
443
|
+
spec: {
|
|
444
|
+
replicas: 3,
|
|
445
|
+
selector: { matchLabels: { "app.kubernetes.io/name": "web" } },
|
|
446
|
+
template: {
|
|
447
|
+
metadata: { labels: { "app.kubernetes.io/name": "web" } },
|
|
448
|
+
spec: {
|
|
449
|
+
containers: [
|
|
450
|
+
new Container({
|
|
451
|
+
name: "web",
|
|
452
|
+
image: "nginx:1.25",
|
|
453
|
+
ports: [{ containerPort: 80, name: "http" }],
|
|
454
|
+
livenessProbe: new Probe({ httpGet: { path: "/", port: 80 } }),
|
|
455
|
+
readinessProbe: new Probe({ httpGet: { path: "/", port: 80 } }),
|
|
456
|
+
}),
|
|
457
|
+
],
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
export const service = new Service({
|
|
464
|
+
metadata: { name: "web" },
|
|
465
|
+
spec: {
|
|
466
|
+
selector: { "app.kubernetes.io/name": "web" },
|
|
467
|
+
ports: [{ port: 80, targetPort: 80 }],
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
\`\`\`
|
|
471
|
+
|
|
472
|
+
## Microservice with HPA
|
|
473
|
+
|
|
474
|
+
Production-ready microservice with autoscaling and disruption budget:
|
|
475
|
+
|
|
476
|
+
\`\`\`typescript
|
|
477
|
+
import {
|
|
478
|
+
Deployment, Service, HorizontalPodAutoscaler, PodDisruptionBudget,
|
|
479
|
+
Container, Probe, ResourceRequirements,
|
|
480
|
+
} from "@intentius/chant-lexicon-k8s";
|
|
481
|
+
|
|
482
|
+
export const deployment = new Deployment({
|
|
483
|
+
metadata: { name: "api", labels: { "app.kubernetes.io/name": "api" } },
|
|
484
|
+
spec: {
|
|
485
|
+
replicas: 2,
|
|
486
|
+
selector: { matchLabels: { "app.kubernetes.io/name": "api" } },
|
|
487
|
+
template: {
|
|
488
|
+
metadata: { labels: { "app.kubernetes.io/name": "api" } },
|
|
489
|
+
spec: {
|
|
490
|
+
containers: [
|
|
491
|
+
new Container({
|
|
492
|
+
name: "api",
|
|
493
|
+
image: "api:1.0",
|
|
494
|
+
ports: [{ containerPort: 8080, name: "http" }],
|
|
495
|
+
resources: new ResourceRequirements({
|
|
496
|
+
limits: { cpu: "500m", memory: "256Mi" },
|
|
497
|
+
requests: { cpu: "100m", memory: "128Mi" },
|
|
498
|
+
}),
|
|
499
|
+
livenessProbe: new Probe({ httpGet: { path: "/healthz", port: 8080 } }),
|
|
500
|
+
readinessProbe: new Probe({ httpGet: { path: "/readyz", port: 8080 } }),
|
|
501
|
+
}),
|
|
502
|
+
],
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
export const hpa = new HorizontalPodAutoscaler({
|
|
509
|
+
metadata: { name: "api" },
|
|
510
|
+
spec: {
|
|
511
|
+
scaleTargetRef: { apiVersion: "apps/v1", kind: "Deployment", name: "api" },
|
|
512
|
+
minReplicas: 2,
|
|
513
|
+
maxReplicas: 10,
|
|
514
|
+
metrics: [
|
|
515
|
+
{ type: "Resource", resource: { name: "cpu", target: { type: "Utilization", averageUtilization: 70 } } },
|
|
516
|
+
],
|
|
517
|
+
},
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
export const pdb = new PodDisruptionBudget({
|
|
521
|
+
metadata: { name: "api" },
|
|
522
|
+
spec: {
|
|
523
|
+
minAvailable: 1,
|
|
524
|
+
selector: { matchLabels: { "app.kubernetes.io/name": "api" } },
|
|
525
|
+
},
|
|
526
|
+
});
|
|
527
|
+
\`\`\`
|
|
528
|
+
|
|
529
|
+
## Stateful Application
|
|
530
|
+
|
|
531
|
+
Database deployment with persistent storage using the StatefulApp composite:
|
|
532
|
+
|
|
533
|
+
\`\`\`typescript
|
|
534
|
+
import { StatefulApp } from "@intentius/chant-lexicon-k8s";
|
|
535
|
+
|
|
536
|
+
const { statefulSet, service } = StatefulApp({
|
|
537
|
+
name: "postgres",
|
|
538
|
+
image: "postgres:16",
|
|
539
|
+
port: 5432,
|
|
540
|
+
storageSize: "20Gi",
|
|
541
|
+
replicas: 3,
|
|
542
|
+
env: [{ name: "POSTGRES_DB", value: "mydb" }],
|
|
543
|
+
});
|
|
544
|
+
\`\`\`
|
|
545
|
+
`,
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
slug: "composite-examples",
|
|
549
|
+
title: "Examples: Composites",
|
|
550
|
+
description: "Composite examples — WebApp, CronWorkload, AutoscaledService, WorkerPool, NamespaceEnv, NodeAgent",
|
|
551
|
+
content: `Composites are higher-level constructs that produce multiple coordinated K8s resources from a single function call.
|
|
552
|
+
|
|
553
|
+
## WebApp
|
|
554
|
+
|
|
555
|
+
Quick deployment with Deployment + Service + optional Ingress:
|
|
556
|
+
|
|
557
|
+
\`\`\`typescript
|
|
558
|
+
import { WebApp } from "@intentius/chant-lexicon-k8s";
|
|
559
|
+
|
|
560
|
+
const { deployment, service, ingress } = WebApp({
|
|
561
|
+
name: "frontend",
|
|
562
|
+
image: "frontend:1.0",
|
|
563
|
+
port: 3000,
|
|
564
|
+
replicas: 3,
|
|
565
|
+
ingressHost: "frontend.example.com",
|
|
566
|
+
ingressTlsSecret: "frontend-tls",
|
|
567
|
+
});
|
|
568
|
+
\`\`\`
|
|
569
|
+
|
|
570
|
+
## CronWorkload
|
|
571
|
+
|
|
572
|
+
Scheduled workload with RBAC:
|
|
573
|
+
|
|
574
|
+
\`\`\`typescript
|
|
575
|
+
import { CronWorkload } from "@intentius/chant-lexicon-k8s";
|
|
576
|
+
|
|
577
|
+
const { cronJob, serviceAccount, role, roleBinding } = CronWorkload({
|
|
578
|
+
name: "db-backup",
|
|
579
|
+
image: "postgres:16",
|
|
580
|
+
schedule: "0 2 * * *",
|
|
581
|
+
command: ["pg_dump", "-h", "postgres", "mydb"],
|
|
582
|
+
rbacRules: [
|
|
583
|
+
{ apiGroups: [""], resources: ["secrets"], verbs: ["get"] },
|
|
584
|
+
],
|
|
585
|
+
});
|
|
586
|
+
\`\`\`
|
|
587
|
+
|
|
588
|
+
## AutoscaledService
|
|
589
|
+
|
|
590
|
+
Production HTTP service with HPA, PDB, and configurable probes:
|
|
591
|
+
|
|
592
|
+
\`\`\`typescript
|
|
593
|
+
import { AutoscaledService } from "@intentius/chant-lexicon-k8s";
|
|
594
|
+
|
|
595
|
+
const { deployment, service, hpa, pdb } = AutoscaledService({
|
|
596
|
+
name: "api",
|
|
597
|
+
image: "api:2.0",
|
|
598
|
+
port: 8080,
|
|
599
|
+
maxReplicas: 10,
|
|
600
|
+
minReplicas: 3,
|
|
601
|
+
targetCPUPercent: 60,
|
|
602
|
+
targetMemoryPercent: 80,
|
|
603
|
+
cpuRequest: "200m",
|
|
604
|
+
memoryRequest: "256Mi",
|
|
605
|
+
cpuLimit: "1",
|
|
606
|
+
memoryLimit: "1Gi",
|
|
607
|
+
livenessPath: "/healthz",
|
|
608
|
+
readinessPath: "/readyz",
|
|
609
|
+
topologySpread: true,
|
|
610
|
+
namespace: "production",
|
|
611
|
+
});
|
|
612
|
+
\`\`\`
|
|
613
|
+
|
|
614
|
+
## WorkerPool
|
|
615
|
+
|
|
616
|
+
Background queue worker with RBAC and optional autoscaling:
|
|
617
|
+
|
|
618
|
+
\`\`\`typescript
|
|
619
|
+
import { WorkerPool } from "@intentius/chant-lexicon-k8s";
|
|
620
|
+
|
|
621
|
+
const { deployment, serviceAccount, role, roleBinding, configMap, hpa } = WorkerPool({
|
|
622
|
+
name: "email-worker",
|
|
623
|
+
image: "worker:1.0",
|
|
624
|
+
command: ["bundle", "exec", "sidekiq"],
|
|
625
|
+
config: { REDIS_URL: "redis://redis:6379", QUEUE: "emails" },
|
|
626
|
+
autoscaling: { minReplicas: 2, maxReplicas: 20, targetCPUPercent: 60 },
|
|
627
|
+
});
|
|
628
|
+
\`\`\`
|
|
629
|
+
|
|
630
|
+
Pass \`rbacRules: []\` to opt out of RBAC resource creation entirely.
|
|
631
|
+
|
|
632
|
+
## NamespaceEnv
|
|
633
|
+
|
|
634
|
+
Multi-tenant namespace with resource guardrails and network isolation:
|
|
635
|
+
|
|
636
|
+
\`\`\`typescript
|
|
637
|
+
import { NamespaceEnv } from "@intentius/chant-lexicon-k8s";
|
|
638
|
+
|
|
639
|
+
const { namespace, resourceQuota, limitRange, networkPolicy } = NamespaceEnv({
|
|
640
|
+
name: "team-alpha",
|
|
641
|
+
cpuQuota: "8",
|
|
642
|
+
memoryQuota: "16Gi",
|
|
643
|
+
maxPods: 50,
|
|
644
|
+
defaultCpuRequest: "100m",
|
|
645
|
+
defaultMemoryRequest: "128Mi",
|
|
646
|
+
defaultCpuLimit: "500m",
|
|
647
|
+
defaultMemoryLimit: "512Mi",
|
|
648
|
+
defaultDenyIngress: true,
|
|
649
|
+
defaultDenyEgress: true,
|
|
650
|
+
});
|
|
651
|
+
\`\`\`
|
|
652
|
+
|
|
653
|
+
Setting a ResourceQuota without LimitRange defaults will emit a warning — pods without explicit resource requests will fail to schedule.
|
|
654
|
+
|
|
655
|
+
## NodeAgent
|
|
656
|
+
|
|
657
|
+
Per-node DaemonSet agent with cluster-wide RBAC and host path mounts:
|
|
658
|
+
|
|
659
|
+
\`\`\`typescript
|
|
660
|
+
import { NodeAgent } from "@intentius/chant-lexicon-k8s";
|
|
661
|
+
|
|
662
|
+
const { daemonSet, serviceAccount, clusterRole, clusterRoleBinding, configMap } = NodeAgent({
|
|
663
|
+
name: "log-collector",
|
|
664
|
+
image: "fluentd:v1.16",
|
|
665
|
+
port: 24224,
|
|
666
|
+
hostPaths: [
|
|
667
|
+
{ name: "varlog", hostPath: "/var/log", mountPath: "/var/log" },
|
|
668
|
+
{ name: "containers", hostPath: "/var/lib/docker/containers", mountPath: "/var/lib/docker/containers" },
|
|
669
|
+
],
|
|
670
|
+
config: { "fluent.conf": "<source>\\n @type tail\\n path /var/log/*.log\\n</source>" },
|
|
671
|
+
rbacRules: [
|
|
672
|
+
{ apiGroups: [""], resources: ["pods", "namespaces"], verbs: ["get", "list", "watch"] },
|
|
673
|
+
],
|
|
674
|
+
namespace: "monitoring",
|
|
675
|
+
});
|
|
676
|
+
\`\`\`
|
|
677
|
+
|
|
678
|
+
## Deploying composites
|
|
679
|
+
|
|
680
|
+
Composites produce plain prop objects. To deploy them, write each resource to a \`.k8s.ts\` file then use the standard chant build → kubectl apply workflow.
|
|
681
|
+
|
|
682
|
+
### Step 1 — Write a chant source file
|
|
683
|
+
|
|
684
|
+
\`\`\`typescript
|
|
685
|
+
// src/infra.k8s.ts
|
|
686
|
+
import { AutoscaledService, NamespaceEnv, WorkerPool, NodeAgent } from "@intentius/chant-lexicon-k8s";
|
|
687
|
+
|
|
688
|
+
// Namespace with guardrails
|
|
689
|
+
const nsEnv = NamespaceEnv({
|
|
690
|
+
name: "production",
|
|
691
|
+
cpuQuota: "16",
|
|
692
|
+
memoryQuota: "32Gi",
|
|
693
|
+
defaultCpuRequest: "100m",
|
|
694
|
+
defaultMemoryRequest: "128Mi",
|
|
695
|
+
defaultCpuLimit: "1",
|
|
696
|
+
defaultMemoryLimit: "512Mi",
|
|
697
|
+
});
|
|
698
|
+
export const { namespace, resourceQuota, limitRange, networkPolicy } = nsEnv;
|
|
699
|
+
|
|
700
|
+
// API with autoscaling
|
|
701
|
+
const api = AutoscaledService({
|
|
702
|
+
name: "api",
|
|
703
|
+
image: "api:1.0",
|
|
704
|
+
port: 8080,
|
|
705
|
+
maxReplicas: 10,
|
|
706
|
+
cpuRequest: "200m",
|
|
707
|
+
memoryRequest: "256Mi",
|
|
708
|
+
topologySpread: true,
|
|
709
|
+
namespace: "production",
|
|
710
|
+
});
|
|
711
|
+
export const { deployment, service, hpa, pdb } = api;
|
|
712
|
+
|
|
713
|
+
// Background workers
|
|
714
|
+
const workers = WorkerPool({
|
|
715
|
+
name: "email-worker",
|
|
716
|
+
image: "worker:1.0",
|
|
717
|
+
command: ["bundle", "exec", "sidekiq"],
|
|
718
|
+
config: { REDIS_URL: "redis://redis:6379" },
|
|
719
|
+
autoscaling: { minReplicas: 2, maxReplicas: 20 },
|
|
720
|
+
namespace: "production",
|
|
721
|
+
});
|
|
722
|
+
export const workerDeployment = workers.deployment;
|
|
723
|
+
export const workerSA = workers.serviceAccount;
|
|
724
|
+
export const workerRole = workers.role;
|
|
725
|
+
export const workerRoleBinding = workers.roleBinding;
|
|
726
|
+
export const workerConfig = workers.configMap;
|
|
727
|
+
export const workerHPA = workers.hpa;
|
|
728
|
+
\`\`\`
|
|
729
|
+
|
|
730
|
+
### Step 2 — Build and validate
|
|
731
|
+
|
|
732
|
+
\`\`\`bash
|
|
733
|
+
# Build YAML manifests
|
|
734
|
+
chant build src/ --output manifests.yaml
|
|
735
|
+
|
|
736
|
+
# Lint for common issues
|
|
737
|
+
chant lint src/
|
|
738
|
+
|
|
739
|
+
# Server-side dry run (validates with admission webhooks)
|
|
740
|
+
kubectl apply -f manifests.yaml --dry-run=server
|
|
741
|
+
\`\`\`
|
|
742
|
+
|
|
743
|
+
### Step 3 — Deploy
|
|
744
|
+
|
|
745
|
+
\`\`\`bash
|
|
746
|
+
# Diff before applying
|
|
747
|
+
kubectl diff -f manifests.yaml
|
|
748
|
+
|
|
749
|
+
# Apply
|
|
750
|
+
kubectl apply -f manifests.yaml
|
|
751
|
+
|
|
752
|
+
# Verify rollout
|
|
753
|
+
kubectl rollout status deployment/api -n production
|
|
754
|
+
kubectl get pods,svc,hpa -n production
|
|
755
|
+
\`\`\`
|
|
756
|
+
|
|
757
|
+
### k3d local validation
|
|
758
|
+
|
|
759
|
+
For local testing before pushing to a real cluster:
|
|
760
|
+
|
|
761
|
+
\`\`\`bash
|
|
762
|
+
cd lexicons/k8s
|
|
763
|
+
|
|
764
|
+
# Create a k3d cluster, apply composites, verify, and delete
|
|
765
|
+
just k3d-validate-composites
|
|
766
|
+
|
|
767
|
+
# Keep the cluster for manual inspection
|
|
768
|
+
just k3d-validate-composites --keep-cluster
|
|
769
|
+
|
|
770
|
+
# Reuse an existing cluster
|
|
771
|
+
just k3d-validate-composites --reuse-cluster --verbose
|
|
772
|
+
\`\`\`
|
|
773
|
+
|
|
774
|
+
The \`/chant-k8s\` AI skill covers the full lifecycle — scaffold, build, lint, apply, rollback, and troubleshooting.
|
|
775
|
+
`,
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
slug: "operational-playbook",
|
|
779
|
+
title: "Operational Playbook",
|
|
780
|
+
description: "Build, deploy, debug, and troubleshoot Kubernetes manifests produced by chant",
|
|
781
|
+
content: `This playbook covers the full lifecycle of chant-produced Kubernetes manifests \u2014 from build through production debugging. The same content is available to AI agents via the \`/chant-k8s\` skill.
|
|
782
|
+
|
|
783
|
+
## Build & validate
|
|
784
|
+
|
|
785
|
+
| Step | Command | What it catches |
|
|
786
|
+
|------|---------|-----------------|
|
|
787
|
+
| Lint source | \`chant lint src/\` | Hardcoded namespaces (WK8001) |
|
|
788
|
+
| Build manifests | \`chant build src/ --output manifests.yaml\` | Post-synth: secrets in env (WK8005), latest tags (WK8006), API keys (WK8041), missing probes (WK8301), no resource limits (WK8201), privileged containers (WK8202), and more |
|
|
789
|
+
| Server dry-run | \`kubectl apply -f manifests.yaml --dry-run=server\` | K8s API validation: schema errors, admission webhooks |
|
|
790
|
+
|
|
791
|
+
Run lint on every edit. Run build + dry-run before every apply.
|
|
792
|
+
|
|
793
|
+
## Deploy to Kubernetes
|
|
794
|
+
|
|
795
|
+
\`\`\`bash
|
|
796
|
+
# Build
|
|
797
|
+
chant build src/ --output manifests.yaml
|
|
798
|
+
|
|
799
|
+
# Diff before applying
|
|
800
|
+
kubectl diff -f manifests.yaml
|
|
801
|
+
|
|
802
|
+
# Dry run (validates with admission webhooks)
|
|
803
|
+
kubectl apply -f manifests.yaml --dry-run=server
|
|
804
|
+
|
|
805
|
+
# Apply
|
|
806
|
+
kubectl apply -f manifests.yaml
|
|
807
|
+
\`\`\`
|
|
808
|
+
|
|
809
|
+
## Rollout & rollback
|
|
810
|
+
|
|
811
|
+
\`\`\`bash
|
|
812
|
+
# Watch rollout progress
|
|
813
|
+
kubectl rollout status deployment/my-app --timeout=300s
|
|
814
|
+
|
|
815
|
+
# Check rollout history
|
|
816
|
+
kubectl rollout history deployment/my-app
|
|
817
|
+
|
|
818
|
+
# Undo last rollout
|
|
819
|
+
kubectl rollout undo deployment/my-app
|
|
820
|
+
|
|
821
|
+
# Roll back to a specific revision
|
|
822
|
+
kubectl rollout undo deployment/my-app --to-revision=2
|
|
823
|
+
\`\`\`
|
|
824
|
+
|
|
825
|
+
## Debugging strategies
|
|
826
|
+
|
|
827
|
+
### Pod status and events
|
|
828
|
+
|
|
829
|
+
\`\`\`bash
|
|
830
|
+
# Overview
|
|
831
|
+
kubectl get pods -l app.kubernetes.io/name=my-app
|
|
832
|
+
kubectl get events --sort-by=.lastTimestamp -n <namespace>
|
|
833
|
+
|
|
834
|
+
# Deep dive into a specific pod
|
|
835
|
+
kubectl describe pod <pod-name>
|
|
836
|
+
|
|
837
|
+
# Logs (current and previous crash)
|
|
838
|
+
kubectl logs <pod-name>
|
|
839
|
+
kubectl logs <pod-name> --previous
|
|
840
|
+
kubectl logs <pod-name> -c <container-name> # specific container
|
|
841
|
+
kubectl logs deployment/my-app --all-containers
|
|
842
|
+
|
|
843
|
+
# Debug containers (K8s 1.25+)
|
|
844
|
+
kubectl debug <pod-name> -it --image=busybox --target=<container>
|
|
845
|
+
|
|
846
|
+
# Port-forwarding for local testing
|
|
847
|
+
kubectl port-forward svc/my-app 8080:80
|
|
848
|
+
kubectl port-forward pod/<pod-name> 8080:8080
|
|
849
|
+
\`\`\`
|
|
850
|
+
|
|
851
|
+
### Resource inspection
|
|
852
|
+
|
|
853
|
+
\`\`\`bash
|
|
854
|
+
# Get all resources in namespace
|
|
855
|
+
kubectl get all -n <namespace>
|
|
856
|
+
|
|
857
|
+
# YAML output for debugging
|
|
858
|
+
kubectl get deployment/my-app -o yaml
|
|
859
|
+
|
|
860
|
+
# Check resource usage
|
|
861
|
+
kubectl top pods -l app.kubernetes.io/name=my-app
|
|
862
|
+
kubectl top nodes
|
|
863
|
+
\`\`\`
|
|
864
|
+
|
|
865
|
+
## Common error patterns
|
|
866
|
+
|
|
867
|
+
| Status | Meaning | Diagnostic command | Typical fix |
|
|
868
|
+
|--------|---------|-------------------|-------------|
|
|
869
|
+
| Pending | Not scheduled | \`kubectl describe pod\` \u2192 Events | Check resource requests, node selectors, taints, PVC binding |
|
|
870
|
+
| CrashLoopBackOff | App crashing on start | \`kubectl logs --previous\` | Fix app startup, check probe config, increase initialDelaySeconds |
|
|
871
|
+
| ImagePullBackOff | Image not found | \`kubectl describe pod\` \u2192 Events | Verify image name/tag, check imagePullSecrets, registry auth |
|
|
872
|
+
| OOMKilled | Out of memory | \`kubectl describe pod\` \u2192 Last State | Increase memory limit, profile app memory usage |
|
|
873
|
+
| Evicted | Node disk/memory pressure | \`kubectl describe node\` | Increase limits, add node capacity, check for log/tmp bloat |
|
|
874
|
+
| CreateContainerError | Container config issue | \`kubectl describe pod\` \u2192 Events | Check volume mounts, configmap/secret refs, security context |
|
|
875
|
+
| Init:CrashLoopBackOff | Init container failing | \`kubectl logs -c <init-container>\` | Fix init container command, check dependencies |
|
|
876
|
+
|
|
877
|
+
## Deployment strategies
|
|
878
|
+
|
|
879
|
+
- **RollingUpdate** (default): Gradually replaces pods. Set \`maxSurge\` and \`maxUnavailable\`.
|
|
880
|
+
- **Recreate**: All pods terminated before new ones created. Use for stateful apps that cannot run multiple versions.
|
|
881
|
+
- **Canary**: Deploy a second Deployment with 1 replica + same selector labels. Route percentage via Ingress annotations or service mesh.
|
|
882
|
+
- **Blue/Green**: Two full Deployments (blue/green), switch Service selector between them.
|
|
883
|
+
|
|
884
|
+
## Production safety
|
|
885
|
+
|
|
886
|
+
### Pre-apply validation
|
|
887
|
+
|
|
888
|
+
\`\`\`bash
|
|
889
|
+
# Always diff before applying
|
|
890
|
+
kubectl diff -f manifests.yaml
|
|
891
|
+
|
|
892
|
+
# Server-side dry run (validates with admission webhooks)
|
|
893
|
+
kubectl apply -f manifests.yaml --dry-run=server
|
|
894
|
+
|
|
895
|
+
# Client-side dry run (fast, but no webhook validation)
|
|
896
|
+
kubectl apply -f manifests.yaml --dry-run=client
|
|
897
|
+
\`\`\`
|
|
898
|
+
|
|
899
|
+
Use server-side dry-run before production applies \u2014 it catches schema errors and runs admission webhooks. Client-side dry-run is faster but only validates locally.
|
|
900
|
+
|
|
901
|
+
## Troubleshooting reference
|
|
902
|
+
|
|
903
|
+
| Symptom | Likely cause | Resolution |
|
|
904
|
+
|---------|-------------|------------|
|
|
905
|
+
| Pod stuck in Pending | Insufficient CPU/memory on nodes | Scale up cluster or reduce resource requests |
|
|
906
|
+
| Pod stuck in Pending | PVC not bound | Check StorageClass exists, PV available |
|
|
907
|
+
| Pod stuck in Pending | Node selector/affinity mismatch | Verify node labels match selectors |
|
|
908
|
+
| Pod stuck in ContainerCreating | ConfigMap/Secret not found | Ensure referenced ConfigMaps/Secrets exist |
|
|
909
|
+
| Pod stuck in ContainerCreating | Volume mount failure | Check PVC status, CSI driver health |
|
|
910
|
+
| Service returns 503 | No ready endpoints | Check pod readiness probes, selector match |
|
|
911
|
+
| Service returns 503 | Wrong port configuration | Verify targetPort matches containerPort |
|
|
912
|
+
| Ingress returns 404 | Backend service not found | Check Ingress rules, service name/port |
|
|
913
|
+
| Ingress returns 404 | Wrong path matching | Check pathType (Prefix vs Exact) |
|
|
914
|
+
| HPA not scaling | Metrics server not installed | Install metrics-server |
|
|
915
|
+
| HPA not scaling | Resource requests not set | Add CPU/memory requests to containers |
|
|
916
|
+
| CronJob not running | Invalid cron expression | Validate cron syntax (5-field format) |
|
|
917
|
+
| NetworkPolicy blocking | Default deny applied | Add explicit allow rules for required traffic |
|
|
918
|
+
| RBAC permission denied | Missing Role/RoleBinding | Check ServiceAccount bindings and verb permissions |`,
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
slug: "importing-yaml",
|
|
922
|
+
title: "Importing Existing YAML",
|
|
923
|
+
description: "Convert existing Kubernetes YAML manifests into typed TypeScript source files",
|
|
924
|
+
content: `Chant can parse existing Kubernetes YAML manifests and generate typed TypeScript source files. This is useful for migrating existing infrastructure to chant.
|
|
925
|
+
|
|
926
|
+
## How it works
|
|
927
|
+
|
|
928
|
+
\`\`\`
|
|
929
|
+
Input YAML \u2192 parse \u2192 generate TypeScript \u2192 export typed resources
|
|
930
|
+
\`\`\`
|
|
931
|
+
|
|
932
|
+
The importer reads multi-document YAML, identifies each resource\u2019s \`apiVersion\` and \`kind\`, and generates the corresponding typed constructor call. The output is a valid \`.k8s.ts\` file you can immediately build with \`chant build\`.
|
|
933
|
+
|
|
934
|
+
## Running the import roundtrip
|
|
935
|
+
|
|
936
|
+
From the \`lexicons/k8s\` directory:
|
|
937
|
+
|
|
938
|
+
\`\`\`bash
|
|
939
|
+
# Full roundtrip \u2014 clones kubernetes/examples, imports, serializes, compares
|
|
940
|
+
just full-roundtrip
|
|
941
|
+
|
|
942
|
+
# Skip clone if repo is already cached
|
|
943
|
+
just full-roundtrip --skip-clone
|
|
944
|
+
|
|
945
|
+
# Verbose output + filter to a specific manifest
|
|
946
|
+
just full-roundtrip --skip-clone --verbose --manifest guestbook
|
|
947
|
+
|
|
948
|
+
# Skip the serialize phase (parse + generate only)
|
|
949
|
+
just full-roundtrip --skip-clone --skip-serialize
|
|
950
|
+
\`\`\`
|
|
951
|
+
|
|
952
|
+
## Parse-only mode
|
|
953
|
+
|
|
954
|
+
For quick validation without the full serialize cycle:
|
|
955
|
+
|
|
956
|
+
\`\`\`bash
|
|
957
|
+
just import-samples --skip-clone --verbose
|
|
958
|
+
|
|
959
|
+
# Filter to a specific manifest
|
|
960
|
+
just import-samples --skip-clone --verbose --manifest guestbook
|
|
961
|
+
\`\`\`
|
|
962
|
+
|
|
963
|
+
This runs the YAML \u2192 TypeScript generation but skips the round-trip comparison.
|
|
964
|
+
|
|
965
|
+
## What "pass" means
|
|
966
|
+
|
|
967
|
+
A passing roundtrip means the serialized YAML, when re-parsed, produces the **same number of resources with matching \`kind\` values**. Exact YAML comparison is intentionally skipped \u2014 key ordering, quoting, and comments differ between input and output.
|
|
968
|
+
|
|
969
|
+
## Validating against a cluster
|
|
970
|
+
|
|
971
|
+
To verify the serialized output is valid Kubernetes YAML, apply it to a local k3d cluster:
|
|
972
|
+
|
|
973
|
+
\`\`\`bash
|
|
974
|
+
# Create a k3d cluster, apply serialized manifests, verify, tear down
|
|
975
|
+
just k3d-validate
|
|
976
|
+
|
|
977
|
+
# Keep the cluster for manual inspection
|
|
978
|
+
just k3d-validate --keep-cluster
|
|
979
|
+
|
|
980
|
+
# Reuse an existing cluster
|
|
981
|
+
just k3d-validate --reuse-cluster --verbose
|
|
982
|
+
\`\`\`
|
|
983
|
+
|
|
984
|
+
See [Testing & Validation](/chant/lexicons/k8s/testing/) for full details on the k3d validation workflow.
|
|
985
|
+
|
|
986
|
+
## Limitations
|
|
987
|
+
|
|
988
|
+
The import pipeline does not support:
|
|
989
|
+
|
|
990
|
+
- **Helm charts** \u2014 template syntax (\`{{ .Values.x }}\`) is not valid YAML
|
|
991
|
+
- **Kustomize overlays** \u2014 overlays are processed by kustomize before producing YAML
|
|
992
|
+
- **Custom Resource Definitions (CRDs)** \u2014 only built-in K8s resource types are recognized
|
|
993
|
+
- **Cloud-specific volumes** \u2014 provider-specific volume types (awsElasticBlockStore, gcePersistentDisk) are parsed but excluded from k3d cluster validation (k3d doesn\u2019t provide cloud volume drivers)`,
|
|
994
|
+
},
|
|
995
|
+
{
|
|
996
|
+
slug: "testing",
|
|
997
|
+
title: "Testing & Validation",
|
|
998
|
+
description: "Roundtrip tests and k3d cluster validation for the Kubernetes lexicon",
|
|
999
|
+
content: `The Kubernetes lexicon includes scripts to verify the full import–serialize roundtrip and validate serialized YAML against a real cluster.
|
|
1000
|
+
|
|
1001
|
+
## Roundtrip tests
|
|
1002
|
+
|
|
1003
|
+
The roundtrip test suite clones \`kubernetes/examples\` and runs each manifest through the full pipeline:
|
|
1004
|
+
|
|
1005
|
+
\`\`\`
|
|
1006
|
+
Input YAML → parse → generate TS → dynamic import → serialize YAML → re-parse → compare
|
|
1007
|
+
\`\`\`
|
|
1008
|
+
|
|
1009
|
+
**"Pass" means** the serialized YAML, when re-parsed, produces the same number of resources with matching \`kind\` values. Exact YAML comparison is intentionally skipped — key ordering, quoting, and comments differ between input and output.
|
|
1010
|
+
|
|
1011
|
+
### Running
|
|
1012
|
+
|
|
1013
|
+
\`\`\`bash
|
|
1014
|
+
cd lexicons/k8s
|
|
1015
|
+
|
|
1016
|
+
# Full roundtrip (clones repo on first run)
|
|
1017
|
+
just full-roundtrip
|
|
1018
|
+
|
|
1019
|
+
# Skip clone if repo is already cached
|
|
1020
|
+
just full-roundtrip --skip-clone
|
|
1021
|
+
|
|
1022
|
+
# Parse + generate only (skip serialize phase)
|
|
1023
|
+
just full-roundtrip --skip-clone --skip-serialize
|
|
1024
|
+
|
|
1025
|
+
# Verbose output + filter to a specific manifest
|
|
1026
|
+
just full-roundtrip --skip-clone --verbose --manifest guestbook
|
|
1027
|
+
\`\`\`
|
|
1028
|
+
|
|
1029
|
+
The test requires \`just generate\` to have been run first so the generated index exports real constructors. If the generated index is empty, the test falls back to parse-only mode automatically.
|
|
1030
|
+
|
|
1031
|
+
### Parse-only roundtrip
|
|
1032
|
+
|
|
1033
|
+
The original parse-only roundtrip test is still available:
|
|
1034
|
+
|
|
1035
|
+
\`\`\`bash
|
|
1036
|
+
just import-samples --skip-clone --verbose
|
|
1037
|
+
\`\`\`
|
|
1038
|
+
|
|
1039
|
+
## k3d cluster validation
|
|
1040
|
+
|
|
1041
|
+
The k3d validation script applies serialized YAML to a real Kubernetes cluster to verify the output is valid and deployable.
|
|
1042
|
+
|
|
1043
|
+
### Prerequisites
|
|
1044
|
+
|
|
1045
|
+
- [k3d](https://k3d.io/) — lightweight K8s cluster in Docker
|
|
1046
|
+
- [kubectl](https://kubernetes.io/docs/tasks/tools/) — Kubernetes CLI
|
|
1047
|
+
- Docker running
|
|
1048
|
+
|
|
1049
|
+
### Running
|
|
1050
|
+
|
|
1051
|
+
\`\`\`bash
|
|
1052
|
+
cd lexicons/k8s
|
|
1053
|
+
|
|
1054
|
+
# Create cluster, run tests, delete cluster
|
|
1055
|
+
just k3d-validate
|
|
1056
|
+
|
|
1057
|
+
# Keep cluster for manual inspection
|
|
1058
|
+
just k3d-validate --keep-cluster
|
|
1059
|
+
|
|
1060
|
+
# Reuse an existing cluster
|
|
1061
|
+
just k3d-validate --reuse-cluster --verbose
|
|
1062
|
+
\`\`\`
|
|
1063
|
+
|
|
1064
|
+
After \`--keep-cluster\`, inspect resources manually:
|
|
1065
|
+
|
|
1066
|
+
\`\`\`bash
|
|
1067
|
+
kubectl get all
|
|
1068
|
+
kubectl get deployments,services
|
|
1069
|
+
\`\`\`
|
|
1070
|
+
|
|
1071
|
+
### Safe manifest allowlist
|
|
1072
|
+
|
|
1073
|
+
The script uses a curated allowlist of ~14 manifests known to work on bare k3d (no cloud volumes, CRDs, or GPUs). These include:
|
|
1074
|
+
|
|
1075
|
+
- **Guestbook** — Deployments and Services for frontend + Redis
|
|
1076
|
+
- **Cassandra** — Headless Service
|
|
1077
|
+
- **vLLM** — Service
|
|
1078
|
+
|
|
1079
|
+
### What it validates
|
|
1080
|
+
|
|
1081
|
+
For each manifest the script:
|
|
1082
|
+
|
|
1083
|
+
1. Runs the full roundtrip (YAML → Chant DSL → YAML)
|
|
1084
|
+
2. Applies the serialized YAML with \`kubectl apply\`
|
|
1085
|
+
3. Verifies the resource exists with \`kubectl get\`
|
|
1086
|
+
|
|
1087
|
+
A failure at any stage is reported with the specific phase that failed.
|
|
1088
|
+
|
|
1089
|
+
## See also
|
|
1090
|
+
|
|
1091
|
+
- [Importing Existing YAML](/chant/lexicons/k8s/importing-yaml/) — convert existing K8s manifests into typed TypeScript`,
|
|
1092
|
+
},
|
|
1093
|
+
{
|
|
1094
|
+
slug: "skills",
|
|
1095
|
+
title: "AI Skills",
|
|
1096
|
+
description: "AI agent skills bundled with the Kubernetes lexicon",
|
|
1097
|
+
content: `The Kubernetes lexicon ships an AI skill called **chant-k8s** that teaches AI coding agents how to build, validate, and deploy Kubernetes manifests from a chant project.
|
|
1098
|
+
|
|
1099
|
+
## What are skills?
|
|
1100
|
+
|
|
1101
|
+
Skills are structured markdown documents bundled with a lexicon. When an AI agent works in a chant project, it discovers and loads relevant skills automatically — giving it operational knowledge about the deployment workflow without requiring the user to explain each step.
|
|
1102
|
+
|
|
1103
|
+
## Installation
|
|
1104
|
+
|
|
1105
|
+
When you scaffold a new project with \`chant init --lexicon k8s\`, the skill is installed to \`.claude/skills/chant-k8s/SKILL.md\` for automatic discovery by Claude Code.
|
|
1106
|
+
|
|
1107
|
+
## Skill: chant-k8s
|
|
1108
|
+
|
|
1109
|
+
The \`chant-k8s\` skill covers the full deployment lifecycle:
|
|
1110
|
+
|
|
1111
|
+
- **Build** — \`chant build src/ --output manifests.yaml\`
|
|
1112
|
+
- **Lint** — \`chant lint src/\` + post-synth checks (20 rules)
|
|
1113
|
+
- **Apply** — \`kubectl apply -f manifests.yaml\`
|
|
1114
|
+
- **Status** — \`kubectl get pods,svc,deploy\`
|
|
1115
|
+
- **Rollback** — \`kubectl rollout undo deployment/my-app\`
|
|
1116
|
+
- **Troubleshooting** — pod status, logs, events, common error patterns
|
|
1117
|
+
|
|
1118
|
+
The skill is invocable as a slash command: \`/chant-k8s\`
|
|
1119
|
+
|
|
1120
|
+
The full playbook is also available as a [documentation page](/chant/lexicons/k8s/operational-playbook/).
|
|
1121
|
+
|
|
1122
|
+
## MCP integration
|
|
1123
|
+
|
|
1124
|
+
The lexicon also provides MCP (Model Context Protocol) tools and resources:
|
|
1125
|
+
|
|
1126
|
+
| MCP tool | Description |
|
|
1127
|
+
|----------|-------------|
|
|
1128
|
+
| \`diff\` | Compare current build output against previous |
|
|
1129
|
+
|
|
1130
|
+
| MCP resource | Description |
|
|
1131
|
+
|--------------|-------------|
|
|
1132
|
+
| \`resource-catalog\` | JSON list of all supported K8s resource types |
|
|
1133
|
+
| \`examples/basic-deployment\` | Example Deployment + Service code |`,
|
|
1134
|
+
},
|
|
1135
|
+
],
|
|
1136
|
+
basePath: "/chant/lexicons/k8s/",
|
|
1137
|
+
};
|
|
1138
|
+
|
|
1139
|
+
const result = await docsPipeline(config);
|
|
1140
|
+
writeDocsSite(config, result);
|
|
1141
|
+
|
|
1142
|
+
if (opts?.verbose) {
|
|
1143
|
+
console.error(
|
|
1144
|
+
`Generated docs: ${result.stats.resources} resources, ${result.stats.properties} properties, ${result.stats.services} services, ${result.stats.rules} rules`,
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
}
|