@intentius/chant-lexicon-k8s 0.0.13 → 0.0.15
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 +20 -15
- package/dist/manifest.json +1 -1
- package/dist/rules/wk8204.ts +33 -1
- package/dist/rules/wk8304.ts +70 -0
- package/dist/rules/wk8305.ts +115 -0
- package/dist/rules/wk8306.ts +50 -0
- package/dist/skills/chant-k8s-eks.md +156 -0
- package/dist/skills/chant-k8s-patterns.md +245 -0
- package/dist/skills/chant-k8s.md +36 -227
- package/package.json +27 -24
- package/src/codegen/docs.ts +5 -5
- package/src/composites/adot-collector.ts +245 -0
- package/src/composites/agic-ingress.ts +149 -0
- package/src/composites/alb-ingress.ts +152 -0
- package/src/composites/autoscaled-service.ts +51 -0
- package/src/composites/azure-disk-storage-class.ts +82 -0
- package/src/composites/azure-file-storage-class.ts +77 -0
- package/src/composites/azure-monitor-collector.ts +232 -0
- package/src/composites/batch-job.ts +221 -0
- package/src/composites/composites.test.ts +1584 -0
- package/src/composites/config-connector-context.ts +62 -0
- package/src/composites/configured-app.ts +224 -0
- package/src/composites/cron-workload.ts +6 -0
- package/src/composites/ebs-storage-class.ts +96 -0
- package/src/composites/efs-storage-class.ts +77 -0
- package/src/composites/external-dns-agent.ts +174 -0
- package/src/composites/filestore-storage-class.ts +79 -0
- package/src/composites/fluent-bit-agent.ts +220 -0
- package/src/composites/gce-pd-storage-class.ts +85 -0
- package/src/composites/gke-gateway.ts +143 -0
- package/src/composites/index.ts +47 -0
- package/src/composites/irsa-service-account.ts +114 -0
- package/src/composites/metrics-server.ts +224 -0
- package/src/composites/monitored-service.ts +221 -0
- package/src/composites/network-isolated-app.ts +202 -0
- package/src/composites/node-agent.ts +6 -0
- package/src/composites/secure-ingress.ts +149 -0
- package/src/composites/security-context.ts +10 -0
- package/src/composites/sidecar-app.ts +207 -0
- package/src/composites/stateful-app.ts +67 -15
- package/src/composites/web-app.ts +104 -35
- package/src/composites/worker-pool.ts +38 -4
- package/src/composites/workload-identity-sa.ts +118 -0
- package/src/composites/workload-identity-service-account.ts +116 -0
- package/src/index.ts +24 -2
- package/src/lint/post-synth/post-synth.test.ts +362 -1
- package/src/lint/post-synth/wk8204.ts +33 -1
- package/src/lint/post-synth/wk8304.ts +70 -0
- package/src/lint/post-synth/wk8305.ts +115 -0
- package/src/lint/post-synth/wk8306.ts +50 -0
- package/src/plugin.test.ts +2 -2
- package/src/plugin.ts +556 -242
- package/src/serializer.test.ts +120 -0
- package/src/serializer.ts +16 -4
package/dist/integrity.json
CHANGED
|
@@ -1,32 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "xxhash64",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "490e9ca9417db298",
|
|
5
5
|
"meta.json": "1ce194f36f9b5f90",
|
|
6
6
|
"types/index.d.ts": "beec4cc869064186",
|
|
7
7
|
"rules/hardcoded-namespace.ts": "54b216c71018e101",
|
|
8
|
-
"rules/wk8006.ts": "6e04f754f79f076e",
|
|
9
8
|
"rules/wk8201.ts": "4dbbd20e21b5fa04",
|
|
10
|
-
"rules/
|
|
11
|
-
"rules/k8s-helpers.ts": "53a6d3bfbedb2852",
|
|
9
|
+
"rules/wk8204.ts": "9244d6fdd6d2f7d",
|
|
12
10
|
"rules/wk8301.ts": "283265ab0c5b8511",
|
|
13
|
-
"rules/
|
|
11
|
+
"rules/wk8306.ts": "5338575bfb0f9251",
|
|
12
|
+
"rules/wk8102.ts": "78d4aac387107b56",
|
|
13
|
+
"rules/wk8305.ts": "5c4b9482a0f3b91d",
|
|
14
14
|
"rules/wk8101.ts": "f8ffcf6e5c89076b",
|
|
15
15
|
"rules/wk8302.ts": "a80d1eab37c0dbe4",
|
|
16
|
+
"rules/wk8005.ts": "a9a1b93b80f3aa51",
|
|
17
|
+
"rules/wk8209.ts": "820df53f304e0a59",
|
|
16
18
|
"rules/wk8203.ts": "f5bf17539c0428c5",
|
|
17
|
-
"rules/wk8042.ts": "6064c84481ae8551",
|
|
18
|
-
"rules/wk8202.ts": "6bd950ae2128256c",
|
|
19
|
-
"rules/wk8207.ts": "6f2bc621d530afa2",
|
|
20
19
|
"rules/wk8104.ts": "94bd75c16b8d50b7",
|
|
20
|
+
"rules/wk8303.ts": "c545464e2af8fbb9",
|
|
21
|
+
"rules/wk8103.ts": "e5d6a506d966e312",
|
|
21
22
|
"rules/wk8205.ts": "d000527c6371a05",
|
|
23
|
+
"rules/wk8042.ts": "6064c84481ae8551",
|
|
24
|
+
"rules/wk8006.ts": "6e04f754f79f076e",
|
|
22
25
|
"rules/wk8041.ts": "4df512c93caaef50",
|
|
23
|
-
"rules/
|
|
24
|
-
"rules/
|
|
25
|
-
"rules/wk8303.ts": "c545464e2af8fbb9",
|
|
26
|
-
"rules/wk8204.ts": "31c3f8eac8455795",
|
|
26
|
+
"rules/wk8304.ts": "f51bf894c5a08dbe",
|
|
27
|
+
"rules/wk8202.ts": "6bd950ae2128256c",
|
|
27
28
|
"rules/wk8208.ts": "1133f9e53c174ae9",
|
|
28
|
-
"rules/
|
|
29
|
-
"
|
|
29
|
+
"rules/wk8105.ts": "8dbcfe399f23656a",
|
|
30
|
+
"rules/k8s-helpers.ts": "53a6d3bfbedb2852",
|
|
31
|
+
"rules/wk8207.ts": "6f2bc621d530afa2",
|
|
32
|
+
"skills/chant-k8s.md": "c7db82c3ba37c78",
|
|
33
|
+
"skills/chant-k8s-eks.md": "f79f31f058c7f2ed",
|
|
34
|
+
"skills/chant-k8s-patterns.md": "c5151ed799145c4b"
|
|
30
35
|
},
|
|
31
|
-
"composite": "
|
|
36
|
+
"composite": "2a6c7f09f87d9a38"
|
|
32
37
|
}
|
package/dist/manifest.json
CHANGED
package/dist/rules/wk8204.ts
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
* Containers should set securityContext.runAsNonRoot to true at either
|
|
5
5
|
* the container level or pod level. Running as root inside a container
|
|
6
6
|
* increases the blast radius of a container breakout.
|
|
7
|
+
*
|
|
8
|
+
* Additionally warns when runAsNonRoot: true is set but no explicit
|
|
9
|
+
* runAsUser is provided — without a numeric UID, K8s relies on the
|
|
10
|
+
* image's USER directive, which may be root (UID 0).
|
|
7
11
|
*/
|
|
8
12
|
|
|
9
13
|
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
@@ -30,13 +34,17 @@ export const wk8204: PostSynthCheck = {
|
|
|
30
34
|
// Check pod-level securityContext
|
|
31
35
|
const podSecCtx = podSpec.securityContext as Record<string, unknown> | undefined;
|
|
32
36
|
const podRunAsNonRoot = podSecCtx?.runAsNonRoot === true;
|
|
37
|
+
const podRunAsUser = podSecCtx?.runAsUser;
|
|
33
38
|
|
|
34
39
|
const containers = extractContainers(manifest);
|
|
35
40
|
for (const container of containers) {
|
|
36
41
|
const secCtx = container.securityContext;
|
|
37
42
|
const containerRunAsNonRoot = secCtx?.runAsNonRoot === true;
|
|
43
|
+
const containerRunAsUser = secCtx?.runAsUser;
|
|
44
|
+
|
|
45
|
+
const hasRunAsNonRoot = podRunAsNonRoot || containerRunAsNonRoot;
|
|
38
46
|
|
|
39
|
-
if (!
|
|
47
|
+
if (!hasRunAsNonRoot) {
|
|
40
48
|
diagnostics.push({
|
|
41
49
|
checkId: "WK8204",
|
|
42
50
|
severity: "warning",
|
|
@@ -44,6 +52,30 @@ export const wk8204: PostSynthCheck = {
|
|
|
44
52
|
entity: resourceName,
|
|
45
53
|
lexicon: "k8s",
|
|
46
54
|
});
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// runAsNonRoot is true — check for explicit runAsUser
|
|
59
|
+
const effectiveRunAsUser = containerRunAsUser ?? podRunAsUser;
|
|
60
|
+
|
|
61
|
+
if (effectiveRunAsUser === 0) {
|
|
62
|
+
// Contradictory: runAsNonRoot: true + runAsUser: 0
|
|
63
|
+
diagnostics.push({
|
|
64
|
+
checkId: "WK8204",
|
|
65
|
+
severity: "warning",
|
|
66
|
+
message: `Container "${container.name ?? "(unnamed)"}" in ${manifest.kind} "${resourceName}" has runAsNonRoot: true but runAsUser: 0 — these settings are contradictory and the container will fail to start`,
|
|
67
|
+
entity: resourceName,
|
|
68
|
+
lexicon: "k8s",
|
|
69
|
+
});
|
|
70
|
+
} else if (effectiveRunAsUser === undefined || effectiveRunAsUser === null) {
|
|
71
|
+
// runAsNonRoot: true but no explicit UID
|
|
72
|
+
diagnostics.push({
|
|
73
|
+
checkId: "WK8204",
|
|
74
|
+
severity: "warning",
|
|
75
|
+
message: `Container "${container.name ?? "(unnamed)"}" in ${manifest.kind} "${resourceName}" has runAsNonRoot: true but no explicit runAsUser — set a numeric UID to ensure the container doesn't run as root`,
|
|
76
|
+
entity: resourceName,
|
|
77
|
+
lexicon: "k8s",
|
|
78
|
+
});
|
|
47
79
|
}
|
|
48
80
|
}
|
|
49
81
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WK8304: SSL Redirect Without Certificate
|
|
3
|
+
*
|
|
4
|
+
* Flags Ingress resources that have `alb.ingress.kubernetes.io/ssl-redirect`
|
|
5
|
+
* annotation set but are missing `alb.ingress.kubernetes.io/certificate-arn`
|
|
6
|
+
* or don't have HTTPS in their listen-ports.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
10
|
+
import { getPrimaryOutput, parseK8sManifests } from "./k8s-helpers";
|
|
11
|
+
|
|
12
|
+
export const wk8304: PostSynthCheck = {
|
|
13
|
+
id: "WK8304",
|
|
14
|
+
description: "SSL redirect without certificate — ssl-redirect annotation requires a valid certificate-arn and HTTPS listen-ports",
|
|
15
|
+
|
|
16
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
17
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
18
|
+
|
|
19
|
+
for (const [, output] of ctx.outputs) {
|
|
20
|
+
const yaml = getPrimaryOutput(output);
|
|
21
|
+
const manifests = parseK8sManifests(yaml);
|
|
22
|
+
|
|
23
|
+
for (const manifest of manifests) {
|
|
24
|
+
if (manifest.kind !== "Ingress") continue;
|
|
25
|
+
|
|
26
|
+
const annotations = manifest.metadata?.annotations as Record<string, string> | undefined;
|
|
27
|
+
if (!annotations) continue;
|
|
28
|
+
|
|
29
|
+
const sslRedirect = annotations["alb.ingress.kubernetes.io/ssl-redirect"];
|
|
30
|
+
if (!sslRedirect) continue;
|
|
31
|
+
|
|
32
|
+
const resourceName = manifest.metadata?.name ?? "Ingress";
|
|
33
|
+
const certArn = annotations["alb.ingress.kubernetes.io/certificate-arn"];
|
|
34
|
+
|
|
35
|
+
if (!certArn) {
|
|
36
|
+
diagnostics.push({
|
|
37
|
+
checkId: "WK8304",
|
|
38
|
+
severity: "warning",
|
|
39
|
+
message: `Ingress "${resourceName}" has ssl-redirect annotation but no certificate-arn — HTTPS redirect will fail without a TLS certificate`,
|
|
40
|
+
entity: resourceName,
|
|
41
|
+
lexicon: "k8s",
|
|
42
|
+
});
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check listen-ports includes HTTPS
|
|
47
|
+
const listenPorts = annotations["alb.ingress.kubernetes.io/listen-ports"];
|
|
48
|
+
if (listenPorts) {
|
|
49
|
+
try {
|
|
50
|
+
const ports = JSON.parse(listenPorts) as Array<Record<string, number>>;
|
|
51
|
+
const hasHttps = ports.some((p) => "HTTPS" in p);
|
|
52
|
+
if (!hasHttps) {
|
|
53
|
+
diagnostics.push({
|
|
54
|
+
checkId: "WK8304",
|
|
55
|
+
severity: "warning",
|
|
56
|
+
message: `Ingress "${resourceName}" has ssl-redirect but listen-ports does not include HTTPS`,
|
|
57
|
+
entity: resourceName,
|
|
58
|
+
lexicon: "k8s",
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
// Can't parse listen-ports — skip this check
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return diagnostics;
|
|
69
|
+
},
|
|
70
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WK8305: Ingress Port Not Matching Service
|
|
3
|
+
*
|
|
4
|
+
* Flags Ingress backends whose `service.port.number` does not match
|
|
5
|
+
* any declared port on the referenced Service in the manifest set.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
9
|
+
import { getPrimaryOutput, parseK8sManifests } from "./k8s-helpers";
|
|
10
|
+
import type { K8sManifest } from "./k8s-helpers";
|
|
11
|
+
|
|
12
|
+
export const wk8305: PostSynthCheck = {
|
|
13
|
+
id: "WK8305",
|
|
14
|
+
description: "Ingress port not matching Service — backend port must match a declared Service port",
|
|
15
|
+
|
|
16
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
17
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
18
|
+
|
|
19
|
+
for (const [, output] of ctx.outputs) {
|
|
20
|
+
const yaml = getPrimaryOutput(output);
|
|
21
|
+
const manifests = parseK8sManifests(yaml);
|
|
22
|
+
|
|
23
|
+
// Build a map of Service name+namespace → set of port numbers
|
|
24
|
+
const servicePorts = collectServicePorts(manifests);
|
|
25
|
+
|
|
26
|
+
for (const manifest of manifests) {
|
|
27
|
+
if (manifest.kind !== "Ingress") continue;
|
|
28
|
+
|
|
29
|
+
const ingressName = manifest.metadata?.name ?? "Ingress";
|
|
30
|
+
const ingressNamespace = manifest.metadata?.namespace ?? "default";
|
|
31
|
+
const spec = manifest.spec;
|
|
32
|
+
if (!spec) continue;
|
|
33
|
+
|
|
34
|
+
const rules = spec.rules as Array<Record<string, unknown>> | undefined;
|
|
35
|
+
if (!rules) continue;
|
|
36
|
+
|
|
37
|
+
for (const rule of rules) {
|
|
38
|
+
const http = rule.http as Record<string, unknown> | undefined;
|
|
39
|
+
if (!http) continue;
|
|
40
|
+
|
|
41
|
+
const paths = http.paths as Array<Record<string, unknown>> | undefined;
|
|
42
|
+
if (!paths) continue;
|
|
43
|
+
|
|
44
|
+
for (const pathEntry of paths) {
|
|
45
|
+
const backend = pathEntry.backend as Record<string, unknown> | undefined;
|
|
46
|
+
if (!backend) continue;
|
|
47
|
+
|
|
48
|
+
const service = backend.service as Record<string, unknown> | undefined;
|
|
49
|
+
if (!service) continue;
|
|
50
|
+
|
|
51
|
+
const svcName = service.name as string | undefined;
|
|
52
|
+
const port = service.port as Record<string, unknown> | undefined;
|
|
53
|
+
const portNumber = port?.number as number | undefined;
|
|
54
|
+
|
|
55
|
+
if (!svcName || portNumber === undefined) continue;
|
|
56
|
+
|
|
57
|
+
// Look up the Service in the manifest set
|
|
58
|
+
const key = `${ingressNamespace}/${svcName}`;
|
|
59
|
+
const knownPorts = servicePorts.get(key);
|
|
60
|
+
|
|
61
|
+
// Skip if the Service is not in the manifest set (external service)
|
|
62
|
+
if (!knownPorts) continue;
|
|
63
|
+
|
|
64
|
+
if (!knownPorts.has(portNumber)) {
|
|
65
|
+
diagnostics.push({
|
|
66
|
+
checkId: "WK8305",
|
|
67
|
+
severity: "warning",
|
|
68
|
+
message: `Ingress "${ingressName}" references Service "${svcName}" port ${portNumber}, but the Service only declares ports [${[...knownPorts].join(", ")}]`,
|
|
69
|
+
entity: ingressName,
|
|
70
|
+
lexicon: "k8s",
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return diagnostics;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Collect port numbers from all Service manifests, keyed by namespace/name.
|
|
84
|
+
*/
|
|
85
|
+
function collectServicePorts(manifests: K8sManifest[]): Map<string, Set<number>> {
|
|
86
|
+
const result = new Map<string, Set<number>>();
|
|
87
|
+
|
|
88
|
+
for (const manifest of manifests) {
|
|
89
|
+
if (manifest.kind !== "Service") continue;
|
|
90
|
+
|
|
91
|
+
const name = manifest.metadata?.name;
|
|
92
|
+
const namespace = manifest.metadata?.namespace ?? "default";
|
|
93
|
+
if (!name) continue;
|
|
94
|
+
|
|
95
|
+
const key = `${namespace}/${name}`;
|
|
96
|
+
const ports = new Set<number>();
|
|
97
|
+
|
|
98
|
+
const spec = manifest.spec;
|
|
99
|
+
if (spec) {
|
|
100
|
+
const specPorts = spec.ports as Array<Record<string, unknown>> | undefined;
|
|
101
|
+
if (specPorts) {
|
|
102
|
+
for (const p of specPorts) {
|
|
103
|
+
const port = p.port as number | undefined;
|
|
104
|
+
if (port !== undefined) {
|
|
105
|
+
ports.add(port);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
result.set(key, ports);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WK8306: Container Command Starts With Flag
|
|
3
|
+
*
|
|
4
|
+
* If `command[0]` starts with `-` or `--`, it's almost certainly a mistake —
|
|
5
|
+
* the first element should be the binary/entrypoint, flags belong in `args`.
|
|
6
|
+
* This causes OCI runtime errors because the container runtime tries to
|
|
7
|
+
* execute the flag as a binary.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
11
|
+
import { getPrimaryOutput, parseK8sManifests, extractContainers, WORKLOAD_KINDS } from "./k8s-helpers";
|
|
12
|
+
|
|
13
|
+
export const wk8306: PostSynthCheck = {
|
|
14
|
+
id: "WK8306",
|
|
15
|
+
description: "Container command starts with flag — first element should be a binary, not a flag",
|
|
16
|
+
|
|
17
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
18
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
19
|
+
|
|
20
|
+
for (const [, output] of ctx.outputs) {
|
|
21
|
+
const yaml = getPrimaryOutput(output);
|
|
22
|
+
const manifests = parseK8sManifests(yaml);
|
|
23
|
+
|
|
24
|
+
for (const manifest of manifests) {
|
|
25
|
+
if (!manifest.kind || !WORKLOAD_KINDS.has(manifest.kind)) continue;
|
|
26
|
+
|
|
27
|
+
const resourceName = manifest.metadata?.name ?? manifest.kind;
|
|
28
|
+
const containers = extractContainers(manifest);
|
|
29
|
+
|
|
30
|
+
for (const container of containers) {
|
|
31
|
+
const command = (container as Record<string, unknown>).command as unknown[] | undefined;
|
|
32
|
+
if (!Array.isArray(command) || command.length === 0) continue;
|
|
33
|
+
|
|
34
|
+
const firstArg = String(command[0]);
|
|
35
|
+
if (firstArg.startsWith("-")) {
|
|
36
|
+
diagnostics.push({
|
|
37
|
+
checkId: "WK8306",
|
|
38
|
+
severity: "error",
|
|
39
|
+
message: `Container "${container.name ?? "(unnamed)"}" in ${manifest.kind} "${resourceName}" has command[0]="${firstArg}" which starts with a flag — the first element should be the binary, flags belong in args`,
|
|
40
|
+
entity: resourceName,
|
|
41
|
+
lexicon: "k8s",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return diagnostics;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: chant-k8s-eks
|
|
3
|
+
description: EKS-specific Kubernetes patterns and composites
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# EKS Kubernetes Patterns
|
|
8
|
+
|
|
9
|
+
## EKS Composites Overview
|
|
10
|
+
|
|
11
|
+
These composites produce K8s YAML with EKS-specific annotations and configurations.
|
|
12
|
+
|
|
13
|
+
### IrsaServiceAccount — ServiceAccount with IAM Role annotation
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { IrsaServiceAccount } from "@intentius/chant-lexicon-k8s";
|
|
17
|
+
|
|
18
|
+
const { serviceAccount, role, roleBinding } = IrsaServiceAccount({
|
|
19
|
+
name: "app-sa",
|
|
20
|
+
iamRoleArn: "arn:aws:iam::123456789012:role/my-app-role",
|
|
21
|
+
rbacRules: [
|
|
22
|
+
{ apiGroups: [""], resources: ["secrets"], verbs: ["get"] },
|
|
23
|
+
],
|
|
24
|
+
namespace: "prod",
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### AlbIngress — Ingress with AWS ALB Controller annotations
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { AlbIngress } from "@intentius/chant-lexicon-k8s";
|
|
32
|
+
|
|
33
|
+
const { ingress } = AlbIngress({
|
|
34
|
+
name: "api-ingress",
|
|
35
|
+
hosts: [
|
|
36
|
+
{
|
|
37
|
+
hostname: "api.example.com",
|
|
38
|
+
paths: [{ path: "/", serviceName: "api", servicePort: 80 }],
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
scheme: "internet-facing",
|
|
42
|
+
certificateArn: "arn:aws:acm:us-east-1:123456789012:certificate/abc-123",
|
|
43
|
+
groupName: "shared-alb",
|
|
44
|
+
healthCheckPath: "/healthz",
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Features:
|
|
49
|
+
- Auto-sets `alb.ingress.kubernetes.io/*` annotations
|
|
50
|
+
- SSL redirect enabled by default when `certificateArn` set
|
|
51
|
+
- `groupName` for shared ALB across multiple Ingresses
|
|
52
|
+
- `wafAclArn` for WAFv2 integration
|
|
53
|
+
|
|
54
|
+
### EbsStorageClass — StorageClass for EBS CSI
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { EbsStorageClass } from "@intentius/chant-lexicon-k8s";
|
|
58
|
+
|
|
59
|
+
const { storageClass } = EbsStorageClass({
|
|
60
|
+
name: "gp3-encrypted",
|
|
61
|
+
type: "gp3",
|
|
62
|
+
encrypted: true,
|
|
63
|
+
iops: "3000",
|
|
64
|
+
throughput: "125",
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### EfsStorageClass — StorageClass for EFS CSI (ReadWriteMany)
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { EfsStorageClass } from "@intentius/chant-lexicon-k8s";
|
|
72
|
+
|
|
73
|
+
const { storageClass } = EfsStorageClass({
|
|
74
|
+
name: "efs-shared",
|
|
75
|
+
fileSystemId: "fs-12345678",
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Use EFS when you need ReadWriteMany (shared across pods/nodes). Use EBS for ReadWriteOnce (single pod).
|
|
80
|
+
|
|
81
|
+
### FluentBitAgent — DaemonSet for CloudWatch logging
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { FluentBitAgent } from "@intentius/chant-lexicon-k8s";
|
|
85
|
+
|
|
86
|
+
const result = FluentBitAgent({
|
|
87
|
+
logGroup: "/aws/eks/my-cluster/containers",
|
|
88
|
+
region: "us-east-1",
|
|
89
|
+
clusterName: "my-cluster",
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### ExternalDnsAgent — ExternalDNS for Route53
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { ExternalDnsAgent } from "@intentius/chant-lexicon-k8s";
|
|
97
|
+
|
|
98
|
+
const result = ExternalDnsAgent({
|
|
99
|
+
iamRoleArn: "arn:aws:iam::123456789012:role/external-dns-role",
|
|
100
|
+
domainFilters: ["example.com"],
|
|
101
|
+
txtOwnerId: "my-cluster",
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### AdotCollector — ADOT for CloudWatch/X-Ray
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { AdotCollector } from "@intentius/chant-lexicon-k8s";
|
|
109
|
+
|
|
110
|
+
const result = AdotCollector({
|
|
111
|
+
region: "us-east-1",
|
|
112
|
+
clusterName: "my-cluster",
|
|
113
|
+
exporters: ["cloudwatch", "xray"],
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Pod Identity vs IRSA
|
|
118
|
+
|
|
119
|
+
| Feature | IRSA | Pod Identity |
|
|
120
|
+
|---------|------|-------------|
|
|
121
|
+
| K8s annotation needed | Yes (`eks.amazonaws.com/role-arn`) | No |
|
|
122
|
+
| Composite available | **IrsaServiceAccount** | None needed |
|
|
123
|
+
| Setup | OIDC provider + IAM role trust policy | EKS Pod Identity Agent add-on + association |
|
|
124
|
+
| When to use | Existing clusters, broad compatibility | New clusters (EKS 1.28+), simpler management |
|
|
125
|
+
|
|
126
|
+
For Pod Identity, no K8s-side composite is needed — configure the association via AWS API/CloudFormation and use a plain ServiceAccount.
|
|
127
|
+
|
|
128
|
+
## Karpenter
|
|
129
|
+
|
|
130
|
+
Karpenter replaces Cluster Autoscaler for node provisioning. Karpenter NodePool and EC2NodeClass are simple CRDs — use CRD import rather than composites:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# Import Karpenter CRDs into your chant project
|
|
134
|
+
chant import --url https://raw.githubusercontent.com/aws/karpenter/main/pkg/apis/crds/karpenter.sh_nodepools.yaml
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Fargate Considerations
|
|
138
|
+
|
|
139
|
+
When running on EKS Fargate:
|
|
140
|
+
- **No DaemonSets** — FluentBitAgent and AdotCollector cannot run on Fargate nodes
|
|
141
|
+
- **No hostPath volumes** — use EFS for shared storage
|
|
142
|
+
- **No privileged containers** — security context restrictions apply
|
|
143
|
+
- For Fargate logging, use the built-in Fluent Bit log router (Fargate logging configuration)
|
|
144
|
+
|
|
145
|
+
## EKS Add-ons
|
|
146
|
+
|
|
147
|
+
Common add-ons managed via AWS (not K8s manifests):
|
|
148
|
+
- **vpc-cni** — Amazon VPC CNI plugin
|
|
149
|
+
- **coredns** — Cluster DNS
|
|
150
|
+
- **kube-proxy** — Network proxy
|
|
151
|
+
- **aws-ebs-csi-driver** — EBS CSI driver (required for EbsStorageClass)
|
|
152
|
+
- **aws-efs-csi-driver** — EFS CSI driver (required for EfsStorageClass)
|
|
153
|
+
- **adot** — AWS Distro for OpenTelemetry (alternative to AdotCollector composite)
|
|
154
|
+
- **aws-guardduty-agent** — Runtime threat detection
|
|
155
|
+
|
|
156
|
+
Configure add-ons via the AWS lexicon (`@intentius/chant-lexicon-aws`) CloudFormation resources.
|