@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
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AdotCollector composite — DaemonSet + RBAC + ConfigMap for AWS Distro for OpenTelemetry.
|
|
3
|
+
*
|
|
4
|
+
* @eks ADOT collector for CloudWatch and X-Ray. NodeAgent specialization
|
|
5
|
+
* with pre-configured pipelines for AWS observability.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface AdotCollectorProps {
|
|
9
|
+
/** AWS region. */
|
|
10
|
+
region: string;
|
|
11
|
+
/** EKS cluster name. */
|
|
12
|
+
clusterName: string;
|
|
13
|
+
/** Exporters to enable (default: ["cloudwatch", "xray"]). */
|
|
14
|
+
exporters?: ("cloudwatch" | "xray" | "prometheus")[];
|
|
15
|
+
/** Agent name (default: "adot-collector"). */
|
|
16
|
+
name?: string;
|
|
17
|
+
/** ADOT image (default: "public.ecr.aws/aws-observability/aws-otel-collector:latest"). */
|
|
18
|
+
image?: string;
|
|
19
|
+
/** Namespace (default: "amazon-metrics"). */
|
|
20
|
+
namespace?: string;
|
|
21
|
+
/** Additional labels. */
|
|
22
|
+
labels?: Record<string, string>;
|
|
23
|
+
/** CPU request (default: "100m"). */
|
|
24
|
+
cpuRequest?: string;
|
|
25
|
+
/** Memory request (default: "256Mi"). */
|
|
26
|
+
memoryRequest?: string;
|
|
27
|
+
/** CPU limit (default: "500m"). */
|
|
28
|
+
cpuLimit?: string;
|
|
29
|
+
/** Memory limit (default: "512Mi"). */
|
|
30
|
+
memoryLimit?: string;
|
|
31
|
+
/** IAM Role ARN for IRSA (adds eks.amazonaws.com/role-arn annotation to ServiceAccount). */
|
|
32
|
+
iamRoleArn?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface AdotCollectorResult {
|
|
36
|
+
daemonSet: Record<string, unknown>;
|
|
37
|
+
serviceAccount: Record<string, unknown>;
|
|
38
|
+
clusterRole: Record<string, unknown>;
|
|
39
|
+
clusterRoleBinding: Record<string, unknown>;
|
|
40
|
+
configMap: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Create an AdotCollector composite — returns prop objects for
|
|
45
|
+
* a DaemonSet, ServiceAccount, ClusterRole, ClusterRoleBinding, and ConfigMap.
|
|
46
|
+
*
|
|
47
|
+
* @eks
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* import { AdotCollector } from "@intentius/chant-lexicon-k8s";
|
|
51
|
+
*
|
|
52
|
+
* const { daemonSet, serviceAccount, clusterRole, clusterRoleBinding, configMap } = AdotCollector({
|
|
53
|
+
* region: "us-east-1",
|
|
54
|
+
* clusterName: "my-cluster",
|
|
55
|
+
* exporters: ["cloudwatch", "xray"],
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export function AdotCollector(props: AdotCollectorProps): AdotCollectorResult {
|
|
60
|
+
const {
|
|
61
|
+
region,
|
|
62
|
+
clusterName,
|
|
63
|
+
exporters = ["cloudwatch", "xray"],
|
|
64
|
+
name = "adot-collector",
|
|
65
|
+
image = "public.ecr.aws/aws-observability/aws-otel-collector:latest",
|
|
66
|
+
namespace = "amazon-metrics",
|
|
67
|
+
labels: extraLabels = {},
|
|
68
|
+
cpuRequest = "100m",
|
|
69
|
+
memoryRequest = "256Mi",
|
|
70
|
+
cpuLimit = "500m",
|
|
71
|
+
memoryLimit = "512Mi",
|
|
72
|
+
iamRoleArn,
|
|
73
|
+
} = props;
|
|
74
|
+
|
|
75
|
+
const saName = `${name}-sa`;
|
|
76
|
+
const clusterRoleName = `${name}-role`;
|
|
77
|
+
const bindingName = `${name}-binding`;
|
|
78
|
+
const configMapName = `${name}-config`;
|
|
79
|
+
|
|
80
|
+
const commonLabels: Record<string, string> = {
|
|
81
|
+
"app.kubernetes.io/name": name,
|
|
82
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
83
|
+
...extraLabels,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Build ADOT config YAML
|
|
87
|
+
const exporterConfigs: string[] = [];
|
|
88
|
+
const exporterNames: string[] = [];
|
|
89
|
+
|
|
90
|
+
if (exporters.includes("cloudwatch")) {
|
|
91
|
+
exporterConfigs.push(` awsemf:
|
|
92
|
+
region: ${region}
|
|
93
|
+
namespace: ContainerInsights
|
|
94
|
+
log_group_name: '/aws/containerinsights/${clusterName}/performance'
|
|
95
|
+
dimension_rollup_option: NoDimensionRollup`);
|
|
96
|
+
exporterNames.push("awsemf");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (exporters.includes("xray")) {
|
|
100
|
+
exporterConfigs.push(` awsxray:
|
|
101
|
+
region: ${region}`);
|
|
102
|
+
exporterNames.push("awsxray");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (exporters.includes("prometheus")) {
|
|
106
|
+
exporterConfigs.push(` prometheusremotewrite:
|
|
107
|
+
endpoint: http://prometheus:9090/api/v1/write`);
|
|
108
|
+
exporterNames.push("prometheusremotewrite");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const adotConfig = `receivers:
|
|
112
|
+
otlp:
|
|
113
|
+
protocols:
|
|
114
|
+
grpc:
|
|
115
|
+
endpoint: 0.0.0.0:4317
|
|
116
|
+
http:
|
|
117
|
+
endpoint: 0.0.0.0:4318
|
|
118
|
+
|
|
119
|
+
processors:
|
|
120
|
+
batch:
|
|
121
|
+
timeout: 30s
|
|
122
|
+
send_batch_size: 8192
|
|
123
|
+
|
|
124
|
+
exporters:
|
|
125
|
+
${exporterConfigs.join("\n")}
|
|
126
|
+
|
|
127
|
+
service:
|
|
128
|
+
pipelines:
|
|
129
|
+
metrics:
|
|
130
|
+
receivers: [otlp]
|
|
131
|
+
processors: [batch]
|
|
132
|
+
exporters: [${exporterNames.filter((e) => e !== "awsxray").join(", ") || "awsemf"}]
|
|
133
|
+
traces:
|
|
134
|
+
receivers: [otlp]
|
|
135
|
+
processors: [batch]
|
|
136
|
+
exporters: [${exporterNames.filter((e) => e !== "awsemf").join(", ") || "awsxray"}]
|
|
137
|
+
`;
|
|
138
|
+
|
|
139
|
+
const container: Record<string, unknown> = {
|
|
140
|
+
name,
|
|
141
|
+
image,
|
|
142
|
+
args: ["--config=/etc/adot/config.yaml"],
|
|
143
|
+
ports: [
|
|
144
|
+
{ containerPort: 4317, name: "otlp-grpc" },
|
|
145
|
+
{ containerPort: 4318, name: "otlp-http" },
|
|
146
|
+
],
|
|
147
|
+
resources: {
|
|
148
|
+
requests: { cpu: cpuRequest, memory: memoryRequest },
|
|
149
|
+
limits: { cpu: cpuLimit, memory: memoryLimit },
|
|
150
|
+
},
|
|
151
|
+
volumeMounts: [
|
|
152
|
+
{ name: "config", mountPath: "/etc/adot", readOnly: true },
|
|
153
|
+
],
|
|
154
|
+
securityContext: {
|
|
155
|
+
runAsNonRoot: true,
|
|
156
|
+
runAsUser: 10001,
|
|
157
|
+
readOnlyRootFilesystem: true,
|
|
158
|
+
allowPrivilegeEscalation: false,
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const daemonSetProps: Record<string, unknown> = {
|
|
163
|
+
metadata: {
|
|
164
|
+
name,
|
|
165
|
+
namespace,
|
|
166
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "agent" },
|
|
167
|
+
},
|
|
168
|
+
spec: {
|
|
169
|
+
selector: { matchLabels: { "app.kubernetes.io/name": name } },
|
|
170
|
+
template: {
|
|
171
|
+
metadata: { labels: { "app.kubernetes.io/name": name, ...extraLabels } },
|
|
172
|
+
spec: {
|
|
173
|
+
serviceAccountName: saName,
|
|
174
|
+
containers: [container],
|
|
175
|
+
volumes: [
|
|
176
|
+
{ name: "config", configMap: { name: configMapName } },
|
|
177
|
+
],
|
|
178
|
+
tolerations: [{ operator: "Exists" }],
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const serviceAccountProps: Record<string, unknown> = {
|
|
185
|
+
metadata: {
|
|
186
|
+
name: saName,
|
|
187
|
+
namespace,
|
|
188
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "agent" },
|
|
189
|
+
...(iamRoleArn ? { annotations: { "eks.amazonaws.com/role-arn": iamRoleArn } } : {}),
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const clusterRoleProps: Record<string, unknown> = {
|
|
194
|
+
metadata: {
|
|
195
|
+
name: clusterRoleName,
|
|
196
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
|
|
197
|
+
},
|
|
198
|
+
rules: [
|
|
199
|
+
{ apiGroups: [""], resources: ["pods", "nodes", "endpoints"], verbs: ["get", "list", "watch"] },
|
|
200
|
+
{ apiGroups: ["apps"], resources: ["replicasets"], verbs: ["get", "list", "watch"] },
|
|
201
|
+
{ apiGroups: ["batch"], resources: ["jobs"], verbs: ["get", "list", "watch"] },
|
|
202
|
+
{ apiGroups: [""], resources: ["nodes/proxy"], verbs: ["get"] },
|
|
203
|
+
{ apiGroups: [""], resources: ["nodes/stats", "configmaps", "events"], verbs: ["create", "get"] },
|
|
204
|
+
{ apiGroups: [""], resources: ["configmaps"], verbs: ["get", "update", "create"], resourceNames: ["otel-container-insight-clusterleader"] },
|
|
205
|
+
],
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const clusterRoleBindingProps: Record<string, unknown> = {
|
|
209
|
+
metadata: {
|
|
210
|
+
name: bindingName,
|
|
211
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "rbac" },
|
|
212
|
+
},
|
|
213
|
+
roleRef: {
|
|
214
|
+
apiGroup: "rbac.authorization.k8s.io",
|
|
215
|
+
kind: "ClusterRole",
|
|
216
|
+
name: clusterRoleName,
|
|
217
|
+
},
|
|
218
|
+
subjects: [
|
|
219
|
+
{
|
|
220
|
+
kind: "ServiceAccount",
|
|
221
|
+
name: saName,
|
|
222
|
+
namespace,
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const configMapProps: Record<string, unknown> = {
|
|
228
|
+
metadata: {
|
|
229
|
+
name: configMapName,
|
|
230
|
+
namespace,
|
|
231
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "config" },
|
|
232
|
+
},
|
|
233
|
+
data: {
|
|
234
|
+
"config.yaml": adotConfig,
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
daemonSet: daemonSetProps,
|
|
240
|
+
serviceAccount: serviceAccountProps,
|
|
241
|
+
clusterRole: clusterRoleProps,
|
|
242
|
+
clusterRoleBinding: clusterRoleBindingProps,
|
|
243
|
+
configMap: configMapProps,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgicIngress composite — Ingress with Azure Application Gateway Ingress Controller annotations.
|
|
3
|
+
*
|
|
4
|
+
* @aks Full `appgw.ingress.kubernetes.io/*` annotation set including SSL redirect,
|
|
5
|
+
* WAF policy, backend path prefix, and cookie-based affinity.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface AgicIngressHost {
|
|
9
|
+
/** Hostname (e.g., "api.example.com"). */
|
|
10
|
+
hostname: string;
|
|
11
|
+
/** Path rules for this host. */
|
|
12
|
+
paths: Array<{
|
|
13
|
+
path: string;
|
|
14
|
+
pathType?: string;
|
|
15
|
+
serviceName: string;
|
|
16
|
+
servicePort: number;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface AgicIngressProps {
|
|
21
|
+
/** Ingress name — used in metadata and labels. */
|
|
22
|
+
name: string;
|
|
23
|
+
/** Host definitions with paths. */
|
|
24
|
+
hosts: AgicIngressHost[];
|
|
25
|
+
/** Azure Key Vault certificate URI or secret name for TLS. */
|
|
26
|
+
certificateArn?: string;
|
|
27
|
+
/** WAF policy resource ID. */
|
|
28
|
+
wafPolicyId?: string;
|
|
29
|
+
/** Health check path for backend. */
|
|
30
|
+
healthCheckPath?: string;
|
|
31
|
+
/** Enable HTTP->HTTPS redirect (default: true when certificateArn set). */
|
|
32
|
+
sslRedirect?: boolean;
|
|
33
|
+
/** Backend path prefix override. */
|
|
34
|
+
backendPathPrefix?: string;
|
|
35
|
+
/** Enable cookie-based affinity (default: false). */
|
|
36
|
+
cookieAffinity?: boolean;
|
|
37
|
+
/** Additional annotations on the Ingress. */
|
|
38
|
+
annotations?: Record<string, string>;
|
|
39
|
+
/** Additional labels to apply to all resources. */
|
|
40
|
+
labels?: Record<string, string>;
|
|
41
|
+
/** Namespace for all resources. */
|
|
42
|
+
namespace?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface AgicIngressResult {
|
|
46
|
+
ingress: Record<string, unknown>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create an AgicIngress composite — returns prop objects for
|
|
51
|
+
* an Ingress with Azure Application Gateway Ingress Controller annotations.
|
|
52
|
+
*
|
|
53
|
+
* @aks
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* import { AgicIngress } from "@intentius/chant-lexicon-k8s";
|
|
57
|
+
*
|
|
58
|
+
* const { ingress } = AgicIngress({
|
|
59
|
+
* name: "api-ingress",
|
|
60
|
+
* hosts: [
|
|
61
|
+
* {
|
|
62
|
+
* hostname: "api.example.com",
|
|
63
|
+
* paths: [{ path: "/", serviceName: "api", servicePort: 80 }],
|
|
64
|
+
* },
|
|
65
|
+
* ],
|
|
66
|
+
* certificateArn: "keyvault-secret-name",
|
|
67
|
+
* wafPolicyId: "/subscriptions/.../Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies/my-waf",
|
|
68
|
+
* });
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export function AgicIngress(props: AgicIngressProps): AgicIngressResult {
|
|
72
|
+
const {
|
|
73
|
+
name,
|
|
74
|
+
hosts,
|
|
75
|
+
certificateArn,
|
|
76
|
+
wafPolicyId,
|
|
77
|
+
healthCheckPath,
|
|
78
|
+
sslRedirect,
|
|
79
|
+
backendPathPrefix,
|
|
80
|
+
cookieAffinity = false,
|
|
81
|
+
annotations: extraAnnotations = {},
|
|
82
|
+
labels: extraLabels = {},
|
|
83
|
+
namespace,
|
|
84
|
+
} = props;
|
|
85
|
+
|
|
86
|
+
const commonLabels: Record<string, string> = {
|
|
87
|
+
"app.kubernetes.io/name": name,
|
|
88
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
89
|
+
...extraLabels,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Build AGIC annotations
|
|
93
|
+
const annotations: Record<string, string> = {
|
|
94
|
+
"kubernetes.io/ingress.class": "azure/application-gateway",
|
|
95
|
+
...extraAnnotations,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (sslRedirect ?? (certificateArn !== undefined)) {
|
|
99
|
+
annotations["appgw.ingress.kubernetes.io/ssl-redirect"] = "true";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (certificateArn) {
|
|
103
|
+
annotations["appgw.ingress.kubernetes.io/appgw-ssl-certificate"] = certificateArn;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (wafPolicyId) {
|
|
107
|
+
annotations["appgw.ingress.kubernetes.io/waf-policy-for-path"] = wafPolicyId;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (healthCheckPath) {
|
|
111
|
+
annotations["appgw.ingress.kubernetes.io/health-probe-path"] = healthCheckPath;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (backendPathPrefix) {
|
|
115
|
+
annotations["appgw.ingress.kubernetes.io/backend-path-prefix"] = backendPathPrefix;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (cookieAffinity) {
|
|
119
|
+
annotations["appgw.ingress.kubernetes.io/cookie-based-affinity"] = "true";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const ingressRules = hosts.map((host) => ({
|
|
123
|
+
host: host.hostname,
|
|
124
|
+
http: {
|
|
125
|
+
paths: host.paths.map((p) => ({
|
|
126
|
+
path: p.path,
|
|
127
|
+
pathType: p.pathType ?? "Prefix",
|
|
128
|
+
backend: {
|
|
129
|
+
service: { name: p.serviceName, port: { number: p.servicePort } },
|
|
130
|
+
},
|
|
131
|
+
})),
|
|
132
|
+
},
|
|
133
|
+
}));
|
|
134
|
+
|
|
135
|
+
const ingressProps: Record<string, unknown> = {
|
|
136
|
+
metadata: {
|
|
137
|
+
name,
|
|
138
|
+
...(namespace && { namespace }),
|
|
139
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "ingress" },
|
|
140
|
+
annotations,
|
|
141
|
+
},
|
|
142
|
+
spec: {
|
|
143
|
+
ingressClassName: "azure/application-gateway",
|
|
144
|
+
rules: ingressRules,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
return { ingress: ingressProps };
|
|
149
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AlbIngress composite — Ingress with AWS Load Balancer Controller annotations.
|
|
3
|
+
*
|
|
4
|
+
* @eks Full `alb.ingress.kubernetes.io/*` annotation set including group name
|
|
5
|
+
* (shared ALB), SSL redirect, subnets, security groups.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface AlbIngressHost {
|
|
9
|
+
/** Hostname (e.g., "api.example.com"). */
|
|
10
|
+
hostname: string;
|
|
11
|
+
/** Path rules for this host. */
|
|
12
|
+
paths: Array<{
|
|
13
|
+
path: string;
|
|
14
|
+
pathType?: string;
|
|
15
|
+
serviceName: string;
|
|
16
|
+
/** Port on the Kubernetes Service (not the container port). */
|
|
17
|
+
servicePort: number;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface AlbIngressProps {
|
|
22
|
+
/** Ingress name — used in metadata and labels. */
|
|
23
|
+
name: string;
|
|
24
|
+
/** Host definitions with paths. */
|
|
25
|
+
hosts: AlbIngressHost[];
|
|
26
|
+
/** ALB scheme (default: "internet-facing"). */
|
|
27
|
+
scheme?: "internet-facing" | "internal";
|
|
28
|
+
/** Target type (default: "ip"). */
|
|
29
|
+
targetType?: "ip" | "instance";
|
|
30
|
+
/** ACM certificate ARN for TLS. */
|
|
31
|
+
certificateArn?: string;
|
|
32
|
+
/** WAF Web ACL ARN. */
|
|
33
|
+
wafAclArn?: string;
|
|
34
|
+
/** Health check path for target group. */
|
|
35
|
+
healthCheckPath?: string;
|
|
36
|
+
/** Ingress group name for shared ALB. */
|
|
37
|
+
groupName?: string;
|
|
38
|
+
/** Enable HTTP→HTTPS redirect (default: true when certificateArn set). */
|
|
39
|
+
sslRedirect?: boolean;
|
|
40
|
+
/** Additional annotations on the Ingress. */
|
|
41
|
+
annotations?: Record<string, string>;
|
|
42
|
+
/** Additional labels to apply to all resources. */
|
|
43
|
+
labels?: Record<string, string>;
|
|
44
|
+
/** Namespace for all resources. */
|
|
45
|
+
namespace?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface AlbIngressResult {
|
|
49
|
+
ingress: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create an AlbIngress composite — returns prop objects for
|
|
54
|
+
* an Ingress with AWS ALB Controller annotations.
|
|
55
|
+
*
|
|
56
|
+
* @eks
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* import { AlbIngress } from "@intentius/chant-lexicon-k8s";
|
|
60
|
+
*
|
|
61
|
+
* const { ingress } = AlbIngress({
|
|
62
|
+
* name: "api-ingress",
|
|
63
|
+
* hosts: [
|
|
64
|
+
* {
|
|
65
|
+
* hostname: "api.example.com",
|
|
66
|
+
* paths: [{ path: "/", serviceName: "api", servicePort: 80 }],
|
|
67
|
+
* },
|
|
68
|
+
* ],
|
|
69
|
+
* scheme: "internet-facing",
|
|
70
|
+
* certificateArn: "arn:aws:acm:us-east-1:123456789012:certificate/abc-123",
|
|
71
|
+
* groupName: "shared-alb",
|
|
72
|
+
* });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export function AlbIngress(props: AlbIngressProps): AlbIngressResult {
|
|
76
|
+
const {
|
|
77
|
+
name,
|
|
78
|
+
hosts,
|
|
79
|
+
scheme = "internet-facing",
|
|
80
|
+
targetType = "ip",
|
|
81
|
+
certificateArn,
|
|
82
|
+
wafAclArn,
|
|
83
|
+
healthCheckPath,
|
|
84
|
+
groupName,
|
|
85
|
+
sslRedirect,
|
|
86
|
+
annotations: extraAnnotations = {},
|
|
87
|
+
labels: extraLabels = {},
|
|
88
|
+
namespace,
|
|
89
|
+
} = props;
|
|
90
|
+
|
|
91
|
+
const commonLabels: Record<string, string> = {
|
|
92
|
+
"app.kubernetes.io/name": name,
|
|
93
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
94
|
+
...extraLabels,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Build ALB annotations
|
|
98
|
+
const annotations: Record<string, string> = {
|
|
99
|
+
"alb.ingress.kubernetes.io/scheme": scheme,
|
|
100
|
+
"alb.ingress.kubernetes.io/target-type": targetType,
|
|
101
|
+
...extraAnnotations,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
if (certificateArn) {
|
|
105
|
+
annotations["alb.ingress.kubernetes.io/certificate-arn"] = certificateArn;
|
|
106
|
+
annotations["alb.ingress.kubernetes.io/listen-ports"] = '[{"HTTPS":443}]';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (sslRedirect ?? !!certificateArn) {
|
|
110
|
+
annotations["alb.ingress.kubernetes.io/ssl-redirect"] = "443";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (wafAclArn) {
|
|
114
|
+
annotations["alb.ingress.kubernetes.io/wafv2-acl-arn"] = wafAclArn;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (healthCheckPath) {
|
|
118
|
+
annotations["alb.ingress.kubernetes.io/healthcheck-path"] = healthCheckPath;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (groupName) {
|
|
122
|
+
annotations["alb.ingress.kubernetes.io/group.name"] = groupName;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const ingressRules = hosts.map((host) => ({
|
|
126
|
+
host: host.hostname,
|
|
127
|
+
http: {
|
|
128
|
+
paths: host.paths.map((p) => ({
|
|
129
|
+
path: p.path,
|
|
130
|
+
pathType: p.pathType ?? "Prefix",
|
|
131
|
+
backend: {
|
|
132
|
+
service: { name: p.serviceName, port: { number: p.servicePort } },
|
|
133
|
+
},
|
|
134
|
+
})),
|
|
135
|
+
},
|
|
136
|
+
}));
|
|
137
|
+
|
|
138
|
+
const ingressProps: Record<string, unknown> = {
|
|
139
|
+
metadata: {
|
|
140
|
+
name,
|
|
141
|
+
...(namespace && { namespace }),
|
|
142
|
+
labels: { ...commonLabels, "app.kubernetes.io/component": "ingress" },
|
|
143
|
+
annotations,
|
|
144
|
+
},
|
|
145
|
+
spec: {
|
|
146
|
+
ingressClassName: "alb",
|
|
147
|
+
rules: ingressRules,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return { ingress: ingressProps };
|
|
152
|
+
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* PDB selectors match pod labels, and resource requests are set.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import type { ContainerSecurityContext } from "./security-context";
|
|
10
|
+
|
|
9
11
|
export interface AutoscaledServiceProps {
|
|
10
12
|
/** Application name — used in metadata and labels. */
|
|
11
13
|
name: string;
|
|
@@ -40,12 +42,33 @@ export interface AutoscaledServiceProps {
|
|
|
40
42
|
livenessPath?: string;
|
|
41
43
|
/** Readiness probe path (default: "/readyz"). */
|
|
42
44
|
readinessPath?: string;
|
|
45
|
+
/** Init containers (e.g., migrations, cert setup). */
|
|
46
|
+
initContainers?: Array<{
|
|
47
|
+
name: string;
|
|
48
|
+
image: string;
|
|
49
|
+
command?: string[];
|
|
50
|
+
args?: string[];
|
|
51
|
+
}>;
|
|
52
|
+
/** Container security context (supports PSS restricted fields). */
|
|
53
|
+
securityContext?: ContainerSecurityContext;
|
|
54
|
+
/** Termination grace period in seconds. */
|
|
55
|
+
terminationGracePeriodSeconds?: number;
|
|
56
|
+
/** Priority class name for pod scheduling. */
|
|
57
|
+
priorityClassName?: string;
|
|
43
58
|
/** Additional labels to apply to all resources. */
|
|
44
59
|
labels?: Record<string, string>;
|
|
45
60
|
/** Namespace for all resources. */
|
|
46
61
|
namespace?: string;
|
|
47
62
|
/** Environment variables for the container. */
|
|
48
63
|
env?: Array<{ name: string; value: string }>;
|
|
64
|
+
/** Service account name for the pod. */
|
|
65
|
+
serviceAccountName?: string;
|
|
66
|
+
/** Volumes to attach to the pod. */
|
|
67
|
+
volumes?: Array<Record<string, unknown>>;
|
|
68
|
+
/** Volume mounts for the primary container. */
|
|
69
|
+
volumeMounts?: Array<Record<string, unknown>>;
|
|
70
|
+
/** Convenience: auto-generate emptyDir volumes + mounts for writable temp dirs (e.g. ["/tmp", "/var/cache/nginx"]). */
|
|
71
|
+
tmpDirs?: string[];
|
|
49
72
|
}
|
|
50
73
|
|
|
51
74
|
export interface AutoscaledServiceResult {
|
|
@@ -89,9 +112,17 @@ export function AutoscaledService(props: AutoscaledServiceProps): AutoscaledServ
|
|
|
89
112
|
topologySpread = false,
|
|
90
113
|
livenessPath = "/healthz",
|
|
91
114
|
readinessPath = "/readyz",
|
|
115
|
+
initContainers,
|
|
116
|
+
securityContext,
|
|
117
|
+
terminationGracePeriodSeconds,
|
|
118
|
+
priorityClassName,
|
|
92
119
|
labels: extraLabels = {},
|
|
93
120
|
namespace,
|
|
94
121
|
env,
|
|
122
|
+
serviceAccountName,
|
|
123
|
+
volumes: explicitVolumes = [],
|
|
124
|
+
volumeMounts: explicitMounts = [],
|
|
125
|
+
tmpDirs = [],
|
|
95
126
|
} = props;
|
|
96
127
|
|
|
97
128
|
const commonLabels: Record<string, string> = {
|
|
@@ -115,6 +146,12 @@ export function AutoscaledService(props: AutoscaledServiceProps): AutoscaledServ
|
|
|
115
146
|
}];
|
|
116
147
|
})();
|
|
117
148
|
|
|
149
|
+
// Generate emptyDir volumes/mounts from tmpDirs, then merge with explicit
|
|
150
|
+
const tmpVolumes = tmpDirs.map((_, i) => ({ name: `tmp-${i}`, emptyDir: {} }));
|
|
151
|
+
const tmpMounts = tmpDirs.map((dir, i) => ({ name: `tmp-${i}`, mountPath: dir }));
|
|
152
|
+
const allVolumes = [...explicitVolumes, ...tmpVolumes];
|
|
153
|
+
const allMounts = [...explicitMounts, ...tmpMounts];
|
|
154
|
+
|
|
118
155
|
const resources: Record<string, unknown> = {
|
|
119
156
|
requests: { cpu: cpuRequest, memory: memoryRequest },
|
|
120
157
|
...(cpuLimit || memoryLimit
|
|
@@ -156,9 +193,23 @@ export function AutoscaledService(props: AutoscaledServiceProps): AutoscaledServ
|
|
|
156
193
|
periodSeconds: 5,
|
|
157
194
|
},
|
|
158
195
|
...(env && { env }),
|
|
196
|
+
...(securityContext && { securityContext }),
|
|
197
|
+
...(allMounts.length > 0 && { volumeMounts: allMounts }),
|
|
159
198
|
},
|
|
160
199
|
],
|
|
200
|
+
...(initContainers && {
|
|
201
|
+
initContainers: initContainers.map((ic) => ({
|
|
202
|
+
name: ic.name,
|
|
203
|
+
image: ic.image,
|
|
204
|
+
...(ic.command && { command: ic.command }),
|
|
205
|
+
...(ic.args && { args: ic.args }),
|
|
206
|
+
})),
|
|
207
|
+
}),
|
|
161
208
|
...(topologyConstraints && { topologySpreadConstraints: topologyConstraints }),
|
|
209
|
+
...(terminationGracePeriodSeconds !== undefined && { terminationGracePeriodSeconds }),
|
|
210
|
+
...(priorityClassName && { priorityClassName }),
|
|
211
|
+
...(serviceAccountName && { serviceAccountName }),
|
|
212
|
+
...(allVolumes.length > 0 && { volumes: allVolumes }),
|
|
162
213
|
},
|
|
163
214
|
},
|
|
164
215
|
},
|