@intentius/chant-lexicon-k8s 0.95.1 → 0.97.0
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/composites/catalog.d.ts.map +1 -1
- package/dist/composites/index.d.ts +4 -0
- package/dist/composites/index.d.ts.map +1 -1
- package/dist/composites/monitored-service.d.ts +19 -0
- package/dist/composites/monitored-service.d.ts.map +1 -1
- package/dist/composites/otel-collector-agent.d.ts +22 -0
- package/dist/composites/otel-collector-agent.d.ts.map +1 -1
- package/dist/composites/otel-collector-gateway.d.ts +173 -0
- package/dist/composites/otel-collector-gateway.d.ts.map +1 -0
- package/dist/composites/otel-collector-shape.d.ts +73 -0
- package/dist/composites/otel-collector-shape.d.ts.map +1 -0
- package/dist/composites/otel-collector.d.ts +19 -1
- package/dist/composites/otel-collector.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +7 -3
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/otel-placement-helpers.d.ts +103 -0
- package/dist/lint/post-synth/otel-placement-helpers.d.ts.map +1 -0
- package/dist/lint/post-synth/wk8601.d.ts +17 -0
- package/dist/lint/post-synth/wk8601.d.ts.map +1 -0
- package/dist/lint/post-synth/wk8602.d.ts +23 -0
- package/dist/lint/post-synth/wk8602.d.ts.map +1 -0
- package/dist/lint/post-synth/wk8603.d.ts +19 -0
- package/dist/lint/post-synth/wk8603.d.ts.map +1 -0
- package/dist/manifest.json +1 -1
- package/dist/okf/index.md +3 -0
- package/dist/okf/rules/WK8601.md +15 -0
- package/dist/okf/rules/WK8602.md +16 -0
- package/dist/okf/rules/WK8603.md +15 -0
- package/dist/okf/types/DaemonSet.md +3 -0
- package/dist/okf/types/Service.md +1 -0
- package/dist/rules/otel-placement-helpers.ts +277 -0
- package/dist/rules/wk8601.ts +44 -0
- package/dist/rules/wk8602.ts +133 -0
- package/dist/rules/wk8603.ts +66 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/skills/chant-k8s.md +2 -0
- package/package.json +4 -3
- package/src/composites/__snapshots__/gke-otel-collector.snapshot.test.ts.snap +401 -0
- package/src/composites/catalog.ts +109 -0
- package/src/composites/gke-otel-collector.snapshot.test.ts +51 -0
- package/src/composites/index.ts +9 -0
- package/src/composites/monitored-service.ts +40 -16
- package/src/composites/otel-collector-agent.ts +62 -26
- package/src/composites/otel-collector-gateway.test.ts +260 -0
- package/src/composites/otel-collector-gateway.ts +325 -0
- package/src/composites/otel-collector-shape.ts +141 -0
- package/src/composites/otel-collector.ts +74 -31
- package/src/index.ts +3 -0
- package/src/lint/audit-catalog.ts +3 -0
- package/src/lint/post-synth/index.ts +6 -0
- package/src/lint/post-synth/otel-placement-helpers.ts +277 -0
- package/src/lint/post-synth/otel-placement.test.ts +401 -0
- package/src/lint/post-synth/wk8601.ts +44 -0
- package/src/lint/post-synth/wk8602.ts +133 -0
- package/src/lint/post-synth/wk8603.ts +66 -0
- package/src/prometheus-rule-groups.test.ts +135 -0
- package/src/serializer.ts +9 -1
- package/src/skills/chant-k8s.md +2 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The prometheus lexicon's RuleGroup inside the k8s lexicon (#2901): the same
|
|
3
|
+
* group renders as a rule file and inside a PrometheusRule, whether it goes
|
|
4
|
+
* in directly or through MonitoredService, and MonitoredService's older
|
|
5
|
+
* alertRules input renders exactly as before.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, expect, test } from "vitest";
|
|
8
|
+
import { load, loadAll } from "js-yaml";
|
|
9
|
+
import type { Declarable } from "@intentius/chant/declarable";
|
|
10
|
+
import {
|
|
11
|
+
RuleGroup,
|
|
12
|
+
hasTool,
|
|
13
|
+
prometheusSerializer,
|
|
14
|
+
promtoolCheckRules,
|
|
15
|
+
emitYaml,
|
|
16
|
+
type RuleFileConfig,
|
|
17
|
+
} from "@intentius/chant-lexicon-prometheus";
|
|
18
|
+
import { k8sSerializer } from "./serializer";
|
|
19
|
+
import { PrometheusRule } from "./generated";
|
|
20
|
+
import { MonitoredService } from "./composites/monitored-service";
|
|
21
|
+
|
|
22
|
+
function entities(record: Record<string, unknown>): Map<string, Declarable> {
|
|
23
|
+
return new Map(Object.entries(record) as Array<[string, Declarable]>);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function k8sDocs(record: Record<string, unknown>): Array<Record<string, any>> {
|
|
27
|
+
const out = k8sSerializer.serialize(entities(record));
|
|
28
|
+
return loadAll(typeof out === "string" ? out : out.primary) as Array<Record<string, any>>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function ruleFile(record: Record<string, unknown>): RuleFileConfig {
|
|
32
|
+
const out = prometheusSerializer.serialize(entities(record));
|
|
33
|
+
return load(typeof out === "string" ? out : out.primary) as RuleFileConfig;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const apiGroup = () =>
|
|
37
|
+
new RuleGroup({
|
|
38
|
+
name: "api",
|
|
39
|
+
interval: "30s",
|
|
40
|
+
rules: [
|
|
41
|
+
{ record: "job:http_errors:ratio5m", expr: 'sum by (job) (rate(http_requests_total{code=~"5.."}[5m])) / sum by (job) (rate(http_requests_total[5m]))' },
|
|
42
|
+
{ alert: "ApiErrors", expr: "job:http_errors:ratio5m > 0.05", for: "10m", labels: { severity: "page" }, annotations: { summary: "5xx above 5%" } },
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("PrometheusRule takes RuleGroups", () => {
|
|
47
|
+
test("an exported RuleGroup renders as the group the rule file holds, not as a reference", () => {
|
|
48
|
+
const api = apiGroup();
|
|
49
|
+
const rule = new PrometheusRule({ metadata: { name: "api-rules" }, spec: { groups: [api] } });
|
|
50
|
+
const [doc] = k8sDocs({ api, rule });
|
|
51
|
+
expect(doc.kind).toBe("PrometheusRule");
|
|
52
|
+
expect(doc.spec.groups).toEqual(ruleFile({ api }).groups);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("an inline RuleGroup renders the same way", () => {
|
|
56
|
+
const rule = new PrometheusRule({ metadata: { name: "api-rules" }, spec: { groups: [apiGroup()] } });
|
|
57
|
+
const [doc] = k8sDocs({ rule });
|
|
58
|
+
expect(doc.spec.groups).toEqual(ruleFile({ api: apiGroup() }).groups);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("plain group objects still pass through untouched", () => {
|
|
62
|
+
const groups = [{ name: "raw", rules: [{ alert: "A", expr: "vector(1)" }] }];
|
|
63
|
+
const [doc] = k8sDocs({ rule: new PrometheusRule({ metadata: { name: "raw" }, spec: { groups } }) });
|
|
64
|
+
expect(doc.spec.groups).toEqual(groups);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test.skipIf(!hasTool(process.env.PROMTOOL ?? "promtool"))("the CRD's groups pass promtool as a rule file", () => {
|
|
68
|
+
const [doc] = k8sDocs({ rule: new PrometheusRule({ metadata: { name: "api-rules" }, spec: { groups: [apiGroup()] } }) });
|
|
69
|
+
const r = promtoolCheckRules(emitYaml({ groups: doc.spec.groups }));
|
|
70
|
+
expect(r.ok, r.output).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("MonitoredService ruleGroups", () => {
|
|
75
|
+
const minProps = { name: "api", image: "api:1.0" };
|
|
76
|
+
|
|
77
|
+
test("creates a PrometheusRule holding the groups", () => {
|
|
78
|
+
const result = MonitoredService({ ...minProps, ruleGroups: [apiGroup()] });
|
|
79
|
+
const docs = k8sDocs({ prometheusRule: result.prometheusRule });
|
|
80
|
+
expect(docs[0].metadata.name).toBe("api-alerts");
|
|
81
|
+
expect(docs[0].spec.groups).toEqual(ruleFile({ api: apiGroup() }).groups);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("takes plain group props too", () => {
|
|
85
|
+
const result = MonitoredService({ ...minProps, ruleGroups: [{ name: "plain", rules: [{ record: "a:b", expr: "sum(up)" }] }] });
|
|
86
|
+
expect(k8sDocs({ r: result.prometheusRule })[0].spec.groups).toEqual([{ name: "plain", rules: [{ record: "a:b", expr: "sum(up)" }] }]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("with alertRules, the alertRules group comes first", () => {
|
|
90
|
+
const result = MonitoredService({
|
|
91
|
+
...minProps,
|
|
92
|
+
alertRules: [{ name: "HighError", expr: "rate(errors[5m]) > 0.1", severity: "critical" }],
|
|
93
|
+
ruleGroups: [apiGroup()],
|
|
94
|
+
});
|
|
95
|
+
expect(k8sDocs({ r: result.prometheusRule })[0].spec.groups.map((g: { name: string }) => g.name)).toEqual(["api.rules", "api"]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("alertRules alone renders exactly as it did before ruleGroups existed", () => {
|
|
99
|
+
const result = MonitoredService({
|
|
100
|
+
...minProps,
|
|
101
|
+
alertRules: [
|
|
102
|
+
{ name: "HighError", expr: "rate(errors[5m]) > 0.1", for: "5m", severity: "critical", annotations: { summary: "s" } },
|
|
103
|
+
{ name: "NoSeverity", expr: "up == 0" },
|
|
104
|
+
],
|
|
105
|
+
});
|
|
106
|
+
const out = k8sSerializer.serialize(entities({ r: result.prometheusRule }));
|
|
107
|
+
expect(typeof out === "string" ? out : out.primary).toBe(`apiVersion: monitoring.coreos.com/v1
|
|
108
|
+
kind: PrometheusRule
|
|
109
|
+
metadata:
|
|
110
|
+
name: api-alerts
|
|
111
|
+
labels:
|
|
112
|
+
app.kubernetes.io/name: api
|
|
113
|
+
app.kubernetes.io/managed-by: chant
|
|
114
|
+
app.kubernetes.io/component: monitoring
|
|
115
|
+
spec:
|
|
116
|
+
groups:
|
|
117
|
+
- name: api.rules
|
|
118
|
+
rules:
|
|
119
|
+
- alert: HighError
|
|
120
|
+
expr: rate(errors[5m]) > 0.1
|
|
121
|
+
for: '5m'
|
|
122
|
+
labels:
|
|
123
|
+
severity: critical
|
|
124
|
+
annotations:
|
|
125
|
+
summary: s
|
|
126
|
+
- alert: NoSeverity
|
|
127
|
+
expr: up == 0
|
|
128
|
+
labels: {}
|
|
129
|
+
`);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("no rules, no PrometheusRule", () => {
|
|
133
|
+
expect(MonitoredService({ ...minProps, ruleGroups: [] }).prometheusRule).toBeUndefined();
|
|
134
|
+
});
|
|
135
|
+
});
|
package/src/serializer.ts
CHANGED
|
@@ -31,6 +31,7 @@ import { emitYAML } from "@intentius/chant/yaml";
|
|
|
31
31
|
import { isDefaultLabels, isDefaultAnnotations, type DefaultLabels, type DefaultAnnotations } from "./default-labels";
|
|
32
32
|
import { isRenderedManifestEntity } from "./manifest-entity";
|
|
33
33
|
import { isEncryptedSecretFileEntity } from "./sops/entity";
|
|
34
|
+
import { isRuleGroup, ruleGroupConfig } from "@intentius/chant-lexicon-prometheus";
|
|
34
35
|
|
|
35
36
|
const require = createRequire(import.meta.url);
|
|
36
37
|
|
|
@@ -265,8 +266,15 @@ function k8sVisitor(entityNames: Map<Declarable, string>): SerializerVisitor {
|
|
|
265
266
|
|
|
266
267
|
return {
|
|
267
268
|
attrRef: (name, attr) => resolveK8sAttr(byName.get(name), name, attr),
|
|
268
|
-
|
|
269
|
+
// A prometheus-lexicon RuleGroup (in a PrometheusRule's spec.groups) is
|
|
270
|
+
// data, not a reference: it renders as the same group the rule file
|
|
271
|
+
// holds, whether it was exported as its own entity or written inline.
|
|
272
|
+
resourceRef: (name) => {
|
|
273
|
+
const entity = byName.get(name);
|
|
274
|
+
return isRuleGroup(entity) ? ruleGroupConfig(entity) : name;
|
|
275
|
+
},
|
|
269
276
|
propertyDeclarable: (entity, walk) => {
|
|
277
|
+
if (isRuleGroup(entity)) return ruleGroupConfig(entity);
|
|
270
278
|
if (!isResourceDeclarable(entity) || typeof entity.props !== "object" || entity.props === null) {
|
|
271
279
|
return undefined;
|
|
272
280
|
}
|
package/src/skills/chant-k8s.md
CHANGED
|
@@ -171,6 +171,8 @@ Composites are higher-level functions that produce multiple coordinated K8s reso
|
|
|
171
171
|
| Multi-host TLS Ingress (cert-manager) | **SecureIngress** | Ingress + optional Certificate |
|
|
172
172
|
| HPA metrics provider | **MetricsServer** | Deployment + Service + RBAC + APIService |
|
|
173
173
|
| CockroachDB cluster | **CockroachDbCluster** | StatefulSet + Services + PVCs + RBAC + optional CertificateRequests |
|
|
174
|
+
| OpenTelemetry agent on every node | **OtelCollector** | DaemonSet + node-local Service + RBAC + ConfigMap |
|
|
175
|
+
| OpenTelemetry gateway tier | **OtelCollectorGateway** | Deployment + ClusterIP and headless Services + PDB + ConfigMap; point agents at it with `gatewayExporter(gateway, { loadBalance: true })` |
|
|
174
176
|
|
|
175
177
|
### Decision Tree — AWS (EKS) Composites
|
|
176
178
|
|