@bluedynamics/cdk8s-monitoring 0.3.2 → 1.0.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/CLAUDE.md CHANGED
@@ -46,6 +46,7 @@ documentation/ # Sphinx/MyST docs (Diataxis, plone-doc-style)
46
46
  ## Design Rules
47
47
 
48
48
  - **App-agnostic**: never add cluster- or application-specific resources (dashboards, alert rules, domains) to this library. Integration charts attach those beside `MonitoringChart`.
49
+ - *Exception — generic infra monitors*: monitors for infrastructure components that are generic across clusters using this stack (Traefik ingress, Longhorn storage) may live here as **opt-in, off-by-default** constructs (`config.traefik`, `config.longhorn`), including their scrape config and — gated by their own sub-toggle — the matching generic dashboard/alert. They must stay disabled unless a consumer explicitly enables them. This is *not* a license for anything cluster- or app-specific.
49
50
  - **Defaults in the package, overrides in the chart**: universal values live in `DEFAULT_CONFIG`; cluster-specific values are required input. `mergeConfig` deep-merges.
50
51
  - **Generated `imports/` are not hand-edited** and are excluded from lint/style.
51
52
 
package/README.md CHANGED
@@ -71,6 +71,23 @@ new MyAppDashboardConstruct(chart, 'my-app-dashboard', { namespace: config.names
71
71
 
72
72
  A dashboard construct is a `ConfigMap` (use the re-exported `KubeConfigMap`) labeled `grafana_dashboard: "1"` so the Grafana sidecar discovers it. Alert rules are plain `PrometheusRule` resources you manage in your integration repo.
73
73
 
74
+ ### Generic infrastructure monitors (opt-in)
75
+
76
+ Monitors for infrastructure components that are generic across clusters running this stack are built in but **disabled by default** — enable them per cluster. They are the exception to the "no dashboards/alerts in the library" rule because they are not app- or cluster-specific.
77
+
78
+ ```typescript
79
+ new MonitoringChart(app, 'monitoring', mergeConfig({
80
+ // ...required config...
81
+ traefik: { enabled: true, namespace: 'traefik' }, // PodMonitor + Grafana dashboard
82
+ longhorn: { enabled: true }, // ServiceMonitor + volume-health alerts
83
+ }));
84
+ ```
85
+
86
+ - **`traefik`** (`TraefikConfig`): a `PodMonitor` (the k3s-bundled Traefik exposes the metrics port on the Pod, not the Service) plus, unless `dashboard: false`, a Grafana dashboard `ConfigMap`. `namespace` is where Traefik runs (default `traefik`).
87
+ - **`longhorn`** (`LonghornConfig`): a `ServiceMonitor` for `longhorn-manager` (port `manager`) plus, unless `alerts: false`, a `PrometheusRule` with `LonghornVolumeFaulted` (critical) / `LonghornVolumeUnhealthy` (warning). `namespace` defaults to `longhorn-system`.
88
+
89
+ Both stay inert unless `enabled: true`, so the base stack and existing consumers are unaffected.
90
+
74
91
  ## Prerequisites
75
92
 
76
93
  The stack expects these to exist in the cluster:
@@ -0,0 +1,24 @@
1
+ import { Construct } from 'constructs';
2
+ import { LonghornConfig } from '../types';
3
+ export interface LonghornMonitorProps {
4
+ /** Namespace the monitoring stack lives in (where the ServiceMonitor/PrometheusRule go). */
5
+ readonly monitoringNamespace: string;
6
+ /** Longhorn config (namespace it runs in + alerts toggle). */
7
+ readonly longhorn: LonghornConfig;
8
+ }
9
+ /**
10
+ * Generic, opt-in monitor for Longhorn distributed storage.
11
+ *
12
+ * ServiceMonitor scrapes the longhorn-manager metrics (port `manager`, 9500):
13
+ * volume/node/storage-pool health and capacity.
14
+ *
15
+ * Optionally ships volume-health alerts (config.longhorn.alerts). Motivated by a
16
+ * real incident (2026-05-21): a CNPG replica PVC went detached/unknown with no
17
+ * alert; noticed only 2h later via a downstream CNPG error. These rules surface
18
+ * storage health within minutes.
19
+ *
20
+ * longhorn_volume_robustness: 0=unknown, 1=healthy, 2=degraded, 3=faulted.
21
+ */
22
+ export declare class LonghornMonitorConstruct extends Construct {
23
+ constructor(scope: Construct, id: string, props: LonghornMonitorProps);
24
+ }
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LonghornMonitorConstruct = void 0;
4
+ const constructs_1 = require("constructs");
5
+ const monitoring_coreos_com_1 = require("../imports/monitoring.coreos.com");
6
+ /**
7
+ * Generic, opt-in monitor for Longhorn distributed storage.
8
+ *
9
+ * ServiceMonitor scrapes the longhorn-manager metrics (port `manager`, 9500):
10
+ * volume/node/storage-pool health and capacity.
11
+ *
12
+ * Optionally ships volume-health alerts (config.longhorn.alerts). Motivated by a
13
+ * real incident (2026-05-21): a CNPG replica PVC went detached/unknown with no
14
+ * alert; noticed only 2h later via a downstream CNPG error. These rules surface
15
+ * storage health within minutes.
16
+ *
17
+ * longhorn_volume_robustness: 0=unknown, 1=healthy, 2=degraded, 3=faulted.
18
+ */
19
+ class LonghornMonitorConstruct extends constructs_1.Construct {
20
+ constructor(scope, id, props) {
21
+ super(scope, id);
22
+ const { monitoringNamespace, longhorn } = props;
23
+ const labels = {
24
+ 'app.kubernetes.io/name': 'longhorn',
25
+ 'app.kubernetes.io/component': 'storage',
26
+ 'app.kubernetes.io/managed-by': 'cdk8s',
27
+ 'app.kubernetes.io/part-of': 'monitoring',
28
+ 'release': 'kube-prometheus-stack',
29
+ };
30
+ new monitoring_coreos_com_1.ServiceMonitor(this, 'longhorn-metrics', {
31
+ metadata: { name: 'longhorn-metrics', namespace: monitoringNamespace, labels },
32
+ spec: {
33
+ selector: { matchLabels: { app: 'longhorn-manager' } },
34
+ namespaceSelector: { matchNames: [longhorn.namespace] },
35
+ endpoints: [
36
+ { port: 'manager', path: '/metrics', interval: '30s', scrapeTimeout: '10s' },
37
+ ],
38
+ },
39
+ });
40
+ if (longhorn.alerts) {
41
+ new monitoring_coreos_com_1.PrometheusRule(this, 'longhorn-volume-health-rules', {
42
+ metadata: { name: 'longhorn-volume-health', namespace: monitoringNamespace, labels },
43
+ spec: {
44
+ groups: [
45
+ {
46
+ name: 'longhorn.volume-health',
47
+ rules: [
48
+ {
49
+ alert: 'LonghornVolumeFaulted',
50
+ expr: monitoring_coreos_com_1.PrometheusRuleSpecGroupsRulesExpr.fromString('longhorn_volume_robustness == 3'),
51
+ for: '1m',
52
+ labels: { severity: 'critical' },
53
+ annotations: {
54
+ summary: 'Longhorn volume {{ $labels.volume }} is faulted',
55
+ description: 'Volume {{ $labels.volume }} (node={{ $labels.node }}, pvc={{ $labels.pvc }}) is in faulted state. ' +
56
+ 'Data may be lost. Recovery: scale workload down, delete PVC, scale up so the StatefulSet/operator re-creates and re-syncs from a healthy source.',
57
+ },
58
+ },
59
+ {
60
+ alert: 'LonghornVolumeUnhealthy',
61
+ expr: monitoring_coreos_com_1.PrometheusRuleSpecGroupsRulesExpr.fromString('longhorn_volume_robustness == 0 or longhorn_volume_robustness == 2'),
62
+ for: '10m',
63
+ labels: { severity: 'warning' },
64
+ annotations: {
65
+ summary: 'Longhorn volume {{ $labels.volume }} not healthy (robustness={{ $value }})',
66
+ description: 'Volume {{ $labels.volume }} (node={{ $labels.node }}, pvc={{ $labels.pvc }}) has been non-healthy for >10m. ' +
67
+ 'robustness values: 0=unknown, 2=degraded. A short degraded window during a replica rebuild is normal; 10m+ means manual investigation.',
68
+ },
69
+ },
70
+ ],
71
+ },
72
+ ],
73
+ },
74
+ });
75
+ }
76
+ }
77
+ }
78
+ exports.LonghornMonitorConstruct = LonghornMonitorConstruct;
79
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG9uZ2hvcm4tbW9uaXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jb25zdHJ1Y3RzL2xvbmdob3JuLW1vbml0b3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsMkNBQXVDO0FBQ3ZDLDRFQUkwQztBQVUxQzs7Ozs7Ozs7Ozs7O0dBWUc7QUFDSCxNQUFhLHdCQUF5QixTQUFRLHNCQUFTO0lBQ3JELFlBQVksS0FBZ0IsRUFBRSxFQUFVLEVBQUUsS0FBMkI7UUFDbkUsS0FBSyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztRQUNqQixNQUFNLEVBQUUsbUJBQW1CLEVBQUUsUUFBUSxFQUFFLEdBQUcsS0FBSyxDQUFDO1FBRWhELE1BQU0sTUFBTSxHQUFHO1lBQ2Isd0JBQXdCLEVBQUUsVUFBVTtZQUNwQyw2QkFBNkIsRUFBRSxTQUFTO1lBQ3hDLDhCQUE4QixFQUFFLE9BQU87WUFDdkMsMkJBQTJCLEVBQUUsWUFBWTtZQUN6QyxTQUFTLEVBQUUsdUJBQXVCO1NBQ25DLENBQUM7UUFFRixJQUFJLHNDQUFjLENBQUMsSUFBSSxFQUFFLGtCQUFrQixFQUFFO1lBQzNDLFFBQVEsRUFBRSxFQUFFLElBQUksRUFBRSxrQkFBa0IsRUFBRSxTQUFTLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxFQUFFO1lBQzlFLElBQUksRUFBRTtnQkFDSixRQUFRLEVBQUUsRUFBRSxXQUFXLEVBQUUsRUFBRSxHQUFHLEVBQUUsa0JBQWtCLEVBQUUsRUFBRTtnQkFDdEQsaUJBQWlCLEVBQUUsRUFBRSxVQUFVLEVBQUUsQ0FBQyxRQUFRLENBQUMsU0FBUyxDQUFDLEVBQUU7Z0JBQ3ZELFNBQVMsRUFBRTtvQkFDVCxFQUFFLElBQUksRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFLGFBQWEsRUFBRSxLQUFLLEVBQUU7aUJBQzdFO2FBQ0Y7U0FDRixDQUFDLENBQUM7UUFFSCxJQUFJLFFBQVEsQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNwQixJQUFJLHNDQUFjLENBQUMsSUFBSSxFQUFFLDhCQUE4QixFQUFFO2dCQUN2RCxRQUFRLEVBQUUsRUFBRSxJQUFJLEVBQUUsd0JBQXdCLEVBQUUsU0FBUyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sRUFBRTtnQkFDcEYsSUFBSSxFQUFFO29CQUNKLE1BQU0sRUFBRTt3QkFDTjs0QkFDRSxJQUFJLEVBQUUsd0JBQXdCOzRCQUM5QixLQUFLLEVBQUU7Z0NBQ0w7b0NBQ0UsS0FBSyxFQUFFLHVCQUF1QjtvQ0FDOUIsSUFBSSxFQUFFLHlEQUFpQyxDQUFDLFVBQVUsQ0FBQyxpQ0FBaUMsQ0FBQztvQ0FDckYsR0FBRyxFQUFFLElBQUk7b0NBQ1QsTUFBTSxFQUFFLEVBQUUsUUFBUSxFQUFFLFVBQVUsRUFBRTtvQ0FDaEMsV0FBVyxFQUFFO3dDQUNYLE9BQU8sRUFBRSxpREFBaUQ7d0NBQzFELFdBQVcsRUFDVCxvR0FBb0c7NENBQ3BHLGtKQUFrSjtxQ0FDcko7aUNBQ0Y7Z0NBQ0Q7b0NBQ0UsS0FBSyxFQUFFLHlCQUF5QjtvQ0FDaEMsSUFBSSxFQUFFLHlEQUFpQyxDQUFDLFVBQVUsQ0FDaEQsb0VBQW9FLENBQ3JFO29DQUNELEdBQUcsRUFBRSxLQUFLO29DQUNWLE1BQU0sRUFBRSxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUU7b0NBQy9CLFdBQVcsRUFBRTt3Q0FDWCxPQUFPLEVBQUUsNEVBQTRFO3dDQUNyRixXQUFXLEVBQ1QsOEdBQThHOzRDQUM5Ryx3SUFBd0k7cUNBQzNJO2lDQUNGOzZCQUNGO3lCQUNGO3FCQUNGO2lCQUNGO2FBQ0YsQ0FBQyxDQUFDO1FBQ0wsQ0FBQztJQUNILENBQUM7Q0FDRjtBQWpFRCw0REFpRUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb25zdHJ1Y3QgfSBmcm9tICdjb25zdHJ1Y3RzJztcbmltcG9ydCB7XG4gIFByb21ldGhldXNSdWxlLFxuICBQcm9tZXRoZXVzUnVsZVNwZWNHcm91cHNSdWxlc0V4cHIsXG4gIFNlcnZpY2VNb25pdG9yLFxufSBmcm9tICcuLi9pbXBvcnRzL21vbml0b3JpbmcuY29yZW9zLmNvbSc7XG5pbXBvcnQgeyBMb25naG9ybkNvbmZpZyB9IGZyb20gJy4uL3R5cGVzJztcblxuZXhwb3J0IGludGVyZmFjZSBMb25naG9ybk1vbml0b3JQcm9wcyB7XG4gIC8qKiBOYW1lc3BhY2UgdGhlIG1vbml0b3Jpbmcgc3RhY2sgbGl2ZXMgaW4gKHdoZXJlIHRoZSBTZXJ2aWNlTW9uaXRvci9Qcm9tZXRoZXVzUnVsZSBnbykuICovXG4gIHJlYWRvbmx5IG1vbml0b3JpbmdOYW1lc3BhY2U6IHN0cmluZztcbiAgLyoqIExvbmdob3JuIGNvbmZpZyAobmFtZXNwYWNlIGl0IHJ1bnMgaW4gKyBhbGVydHMgdG9nZ2xlKS4gKi9cbiAgcmVhZG9ubHkgbG9uZ2hvcm46IExvbmdob3JuQ29uZmlnO1xufVxuXG4vKipcbiAqIEdlbmVyaWMsIG9wdC1pbiBtb25pdG9yIGZvciBMb25naG9ybiBkaXN0cmlidXRlZCBzdG9yYWdlLlxuICpcbiAqIFNlcnZpY2VNb25pdG9yIHNjcmFwZXMgdGhlIGxvbmdob3JuLW1hbmFnZXIgbWV0cmljcyAocG9ydCBgbWFuYWdlcmAsIDk1MDApOlxuICogdm9sdW1lL25vZGUvc3RvcmFnZS1wb29sIGhlYWx0aCBhbmQgY2FwYWNpdHkuXG4gKlxuICogT3B0aW9uYWxseSBzaGlwcyB2b2x1bWUtaGVhbHRoIGFsZXJ0cyAoY29uZmlnLmxvbmdob3JuLmFsZXJ0cykuIE1vdGl2YXRlZCBieSBhXG4gKiByZWFsIGluY2lkZW50ICgyMDI2LTA1LTIxKTogYSBDTlBHIHJlcGxpY2EgUFZDIHdlbnQgZGV0YWNoZWQvdW5rbm93biB3aXRoIG5vXG4gKiBhbGVydDsgbm90aWNlZCBvbmx5IDJoIGxhdGVyIHZpYSBhIGRvd25zdHJlYW0gQ05QRyBlcnJvci4gVGhlc2UgcnVsZXMgc3VyZmFjZVxuICogc3RvcmFnZSBoZWFsdGggd2l0aGluIG1pbnV0ZXMuXG4gKlxuICogbG9uZ2hvcm5fdm9sdW1lX3JvYnVzdG5lc3M6IDA9dW5rbm93biwgMT1oZWFsdGh5LCAyPWRlZ3JhZGVkLCAzPWZhdWx0ZWQuXG4gKi9cbmV4cG9ydCBjbGFzcyBMb25naG9ybk1vbml0b3JDb25zdHJ1Y3QgZXh0ZW5kcyBDb25zdHJ1Y3Qge1xuICBjb25zdHJ1Y3RvcihzY29wZTogQ29uc3RydWN0LCBpZDogc3RyaW5nLCBwcm9wczogTG9uZ2hvcm5Nb25pdG9yUHJvcHMpIHtcbiAgICBzdXBlcihzY29wZSwgaWQpO1xuICAgIGNvbnN0IHsgbW9uaXRvcmluZ05hbWVzcGFjZSwgbG9uZ2hvcm4gfSA9IHByb3BzO1xuXG4gICAgY29uc3QgbGFiZWxzID0ge1xuICAgICAgJ2FwcC5rdWJlcm5ldGVzLmlvL25hbWUnOiAnbG9uZ2hvcm4nLFxuICAgICAgJ2FwcC5rdWJlcm5ldGVzLmlvL2NvbXBvbmVudCc6ICdzdG9yYWdlJyxcbiAgICAgICdhcHAua3ViZXJuZXRlcy5pby9tYW5hZ2VkLWJ5JzogJ2NkazhzJyxcbiAgICAgICdhcHAua3ViZXJuZXRlcy5pby9wYXJ0LW9mJzogJ21vbml0b3JpbmcnLFxuICAgICAgJ3JlbGVhc2UnOiAna3ViZS1wcm9tZXRoZXVzLXN0YWNrJyxcbiAgICB9O1xuXG4gICAgbmV3IFNlcnZpY2VNb25pdG9yKHRoaXMsICdsb25naG9ybi1tZXRyaWNzJywge1xuICAgICAgbWV0YWRhdGE6IHsgbmFtZTogJ2xvbmdob3JuLW1ldHJpY3MnLCBuYW1lc3BhY2U6IG1vbml0b3JpbmdOYW1lc3BhY2UsIGxhYmVscyB9LFxuICAgICAgc3BlYzoge1xuICAgICAgICBzZWxlY3RvcjogeyBtYXRjaExhYmVsczogeyBhcHA6ICdsb25naG9ybi1tYW5hZ2VyJyB9IH0sXG4gICAgICAgIG5hbWVzcGFjZVNlbGVjdG9yOiB7IG1hdGNoTmFtZXM6IFtsb25naG9ybi5uYW1lc3BhY2VdIH0sXG4gICAgICAgIGVuZHBvaW50czogW1xuICAgICAgICAgIHsgcG9ydDogJ21hbmFnZXInLCBwYXRoOiAnL21ldHJpY3MnLCBpbnRlcnZhbDogJzMwcycsIHNjcmFwZVRpbWVvdXQ6ICcxMHMnIH0sXG4gICAgICAgIF0sXG4gICAgICB9LFxuICAgIH0pO1xuXG4gICAgaWYgKGxvbmdob3JuLmFsZXJ0cykge1xuICAgICAgbmV3IFByb21ldGhldXNSdWxlKHRoaXMsICdsb25naG9ybi12b2x1bWUtaGVhbHRoLXJ1bGVzJywge1xuICAgICAgICBtZXRhZGF0YTogeyBuYW1lOiAnbG9uZ2hvcm4tdm9sdW1lLWhlYWx0aCcsIG5hbWVzcGFjZTogbW9uaXRvcmluZ05hbWVzcGFjZSwgbGFiZWxzIH0sXG4gICAgICAgIHNwZWM6IHtcbiAgICAgICAgICBncm91cHM6IFtcbiAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgbmFtZTogJ2xvbmdob3JuLnZvbHVtZS1oZWFsdGgnLFxuICAgICAgICAgICAgICBydWxlczogW1xuICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgIGFsZXJ0OiAnTG9uZ2hvcm5Wb2x1bWVGYXVsdGVkJyxcbiAgICAgICAgICAgICAgICAgIGV4cHI6IFByb21ldGhldXNSdWxlU3BlY0dyb3Vwc1J1bGVzRXhwci5mcm9tU3RyaW5nKCdsb25naG9ybl92b2x1bWVfcm9idXN0bmVzcyA9PSAzJyksXG4gICAgICAgICAgICAgICAgICBmb3I6ICcxbScsXG4gICAgICAgICAgICAgICAgICBsYWJlbHM6IHsgc2V2ZXJpdHk6ICdjcml0aWNhbCcgfSxcbiAgICAgICAgICAgICAgICAgIGFubm90YXRpb25zOiB7XG4gICAgICAgICAgICAgICAgICAgIHN1bW1hcnk6ICdMb25naG9ybiB2b2x1bWUge3sgJGxhYmVscy52b2x1bWUgfX0gaXMgZmF1bHRlZCcsXG4gICAgICAgICAgICAgICAgICAgIGRlc2NyaXB0aW9uOlxuICAgICAgICAgICAgICAgICAgICAgICdWb2x1bWUge3sgJGxhYmVscy52b2x1bWUgfX0gKG5vZGU9e3sgJGxhYmVscy5ub2RlIH19LCBwdmM9e3sgJGxhYmVscy5wdmMgfX0pIGlzIGluIGZhdWx0ZWQgc3RhdGUuICcgK1xuICAgICAgICAgICAgICAgICAgICAgICdEYXRhIG1heSBiZSBsb3N0LiBSZWNvdmVyeTogc2NhbGUgd29ya2xvYWQgZG93biwgZGVsZXRlIFBWQywgc2NhbGUgdXAgc28gdGhlIFN0YXRlZnVsU2V0L29wZXJhdG9yIHJlLWNyZWF0ZXMgYW5kIHJlLXN5bmNzIGZyb20gYSBoZWFsdGh5IHNvdXJjZS4nLFxuICAgICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgIGFsZXJ0OiAnTG9uZ2hvcm5Wb2x1bWVVbmhlYWx0aHknLFxuICAgICAgICAgICAgICAgICAgZXhwcjogUHJvbWV0aGV1c1J1bGVTcGVjR3JvdXBzUnVsZXNFeHByLmZyb21TdHJpbmcoXG4gICAgICAgICAgICAgICAgICAgICdsb25naG9ybl92b2x1bWVfcm9idXN0bmVzcyA9PSAwIG9yIGxvbmdob3JuX3ZvbHVtZV9yb2J1c3RuZXNzID09IDInLFxuICAgICAgICAgICAgICAgICAgKSxcbiAgICAgICAgICAgICAgICAgIGZvcjogJzEwbScsXG4gICAgICAgICAgICAgICAgICBsYWJlbHM6IHsgc2V2ZXJpdHk6ICd3YXJuaW5nJyB9LFxuICAgICAgICAgICAgICAgICAgYW5ub3RhdGlvbnM6IHtcbiAgICAgICAgICAgICAgICAgICAgc3VtbWFyeTogJ0xvbmdob3JuIHZvbHVtZSB7eyAkbGFiZWxzLnZvbHVtZSB9fSBub3QgaGVhbHRoeSAocm9idXN0bmVzcz17eyAkdmFsdWUgfX0pJyxcbiAgICAgICAgICAgICAgICAgICAgZGVzY3JpcHRpb246XG4gICAgICAgICAgICAgICAgICAgICAgJ1ZvbHVtZSB7eyAkbGFiZWxzLnZvbHVtZSB9fSAobm9kZT17eyAkbGFiZWxzLm5vZGUgfX0sIHB2Yz17eyAkbGFiZWxzLnB2YyB9fSkgaGFzIGJlZW4gbm9uLWhlYWx0aHkgZm9yID4xMG0uICcgK1xuICAgICAgICAgICAgICAgICAgICAgICdyb2J1c3RuZXNzIHZhbHVlczogMD11bmtub3duLCAyPWRlZ3JhZGVkLiBBIHNob3J0IGRlZ3JhZGVkIHdpbmRvdyBkdXJpbmcgYSByZXBsaWNhIHJlYnVpbGQgaXMgbm9ybWFsOyAxMG0rIG1lYW5zIG1hbnVhbCBpbnZlc3RpZ2F0aW9uLicsXG4gICAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgIF0sXG4gICAgICAgICAgICB9LFxuICAgICAgICAgIF0sXG4gICAgICAgIH0sXG4gICAgICB9KTtcbiAgICB9XG4gIH1cbn1cbiJdfQ==
@@ -0,0 +1,20 @@
1
+ import { Construct } from 'constructs';
2
+ import { TraefikConfig } from '../types';
3
+ export interface TraefikMonitorProps {
4
+ /** Namespace the monitoring stack lives in (where the PodMonitor/ConfigMap go). */
5
+ readonly monitoringNamespace: string;
6
+ /** Traefik config (namespace it runs in + dashboard toggle). */
7
+ readonly traefik: TraefikConfig;
8
+ }
9
+ /**
10
+ * Generic, opt-in monitor for a Traefik ingress controller.
11
+ *
12
+ * Uses a PodMonitor (not a ServiceMonitor): the k3s-bundled Traefik Service only
13
+ * exposes web/websecure; the `metrics` port is on the Pod, not the Service.
14
+ *
15
+ * Optionally ships the Traefik Grafana dashboard as a ConfigMap (grafana_dashboard
16
+ * label → sidecar auto-discovery). Enabled via config.traefik in MonitoringChart.
17
+ */
18
+ export declare class TraefikMonitorConstruct extends Construct {
19
+ constructor(scope: Construct, id: string, props: TraefikMonitorProps);
20
+ }
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TraefikMonitorConstruct = void 0;
4
+ const constructs_1 = require("constructs");
5
+ const traefik_dashboard_1 = require("../dashboards/traefik-dashboard");
6
+ const k8s_1 = require("../imports/k8s");
7
+ const monitoring_coreos_com_1 = require("../imports/monitoring.coreos.com");
8
+ /**
9
+ * Generic, opt-in monitor for a Traefik ingress controller.
10
+ *
11
+ * Uses a PodMonitor (not a ServiceMonitor): the k3s-bundled Traefik Service only
12
+ * exposes web/websecure; the `metrics` port is on the Pod, not the Service.
13
+ *
14
+ * Optionally ships the Traefik Grafana dashboard as a ConfigMap (grafana_dashboard
15
+ * label → sidecar auto-discovery). Enabled via config.traefik in MonitoringChart.
16
+ */
17
+ class TraefikMonitorConstruct extends constructs_1.Construct {
18
+ constructor(scope, id, props) {
19
+ super(scope, id);
20
+ const { monitoringNamespace, traefik } = props;
21
+ new monitoring_coreos_com_1.PodMonitor(this, 'traefik-metrics', {
22
+ metadata: {
23
+ name: 'traefik-metrics',
24
+ namespace: monitoringNamespace,
25
+ labels: {
26
+ 'app.kubernetes.io/name': 'traefik',
27
+ 'app.kubernetes.io/component': 'ingress-controller',
28
+ 'app.kubernetes.io/managed-by': 'cdk8s',
29
+ 'app.kubernetes.io/part-of': 'monitoring',
30
+ 'release': 'kube-prometheus-stack',
31
+ },
32
+ },
33
+ spec: {
34
+ selector: { matchLabels: { 'app.kubernetes.io/name': 'traefik' } },
35
+ namespaceSelector: { matchNames: [traefik.namespace] },
36
+ podMetricsEndpoints: [
37
+ { port: 'metrics', path: '/metrics', interval: '30s', scrapeTimeout: '10s' },
38
+ ],
39
+ },
40
+ });
41
+ if (traefik.dashboard) {
42
+ new k8s_1.KubeConfigMap(this, 'traefik-dashboard', {
43
+ metadata: {
44
+ name: 'traefik-dashboard',
45
+ namespace: monitoringNamespace,
46
+ labels: {
47
+ 'grafana_dashboard': '1',
48
+ 'app.kubernetes.io/name': 'traefik',
49
+ 'app.kubernetes.io/component': 'dashboard',
50
+ 'app.kubernetes.io/managed-by': 'cdk8s',
51
+ 'app.kubernetes.io/part-of': 'monitoring',
52
+ },
53
+ },
54
+ data: { 'traefik.json': JSON.stringify(traefik_dashboard_1.TRAEFIK_DASHBOARD) },
55
+ });
56
+ }
57
+ }
58
+ }
59
+ exports.TraefikMonitorConstruct = TraefikMonitorConstruct;
60
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHJhZWZpay1tb25pdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvbnN0cnVjdHMvdHJhZWZpay1tb25pdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDJDQUF1QztBQUN2Qyx1RUFBb0U7QUFDcEUsd0NBQStDO0FBQy9DLDRFQUE4RDtBQVU5RDs7Ozs7Ozs7R0FRRztBQUNILE1BQWEsdUJBQXdCLFNBQVEsc0JBQVM7SUFDcEQsWUFBWSxLQUFnQixFQUFFLEVBQVUsRUFBRSxLQUEwQjtRQUNsRSxLQUFLLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBQ2pCLE1BQU0sRUFBRSxtQkFBbUIsRUFBRSxPQUFPLEVBQUUsR0FBRyxLQUFLLENBQUM7UUFFL0MsSUFBSSxrQ0FBVSxDQUFDLElBQUksRUFBRSxpQkFBaUIsRUFBRTtZQUN0QyxRQUFRLEVBQUU7Z0JBQ1IsSUFBSSxFQUFFLGlCQUFpQjtnQkFDdkIsU0FBUyxFQUFFLG1CQUFtQjtnQkFDOUIsTUFBTSxFQUFFO29CQUNOLHdCQUF3QixFQUFFLFNBQVM7b0JBQ25DLDZCQUE2QixFQUFFLG9CQUFvQjtvQkFDbkQsOEJBQThCLEVBQUUsT0FBTztvQkFDdkMsMkJBQTJCLEVBQUUsWUFBWTtvQkFDekMsU0FBUyxFQUFFLHVCQUF1QjtpQkFDbkM7YUFDRjtZQUNELElBQUksRUFBRTtnQkFDSixRQUFRLEVBQUUsRUFBRSxXQUFXLEVBQUUsRUFBRSx3QkFBd0IsRUFBRSxTQUFTLEVBQUUsRUFBRTtnQkFDbEUsaUJBQWlCLEVBQUUsRUFBRSxVQUFVLEVBQUUsQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLEVBQUU7Z0JBQ3RELG1CQUFtQixFQUFFO29CQUNuQixFQUFFLElBQUksRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFLGFBQWEsRUFBRSxLQUFLLEVBQUU7aUJBQzdFO2FBQ0Y7U0FDRixDQUFDLENBQUM7UUFFSCxJQUFJLE9BQU8sQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUN0QixJQUFJLG1CQUFhLENBQUMsSUFBSSxFQUFFLG1CQUFtQixFQUFFO2dCQUMzQyxRQUFRLEVBQUU7b0JBQ1IsSUFBSSxFQUFFLG1CQUFtQjtvQkFDekIsU0FBUyxFQUFFLG1CQUFtQjtvQkFDOUIsTUFBTSxFQUFFO3dCQUNOLG1CQUFtQixFQUFFLEdBQUc7d0JBQ3hCLHdCQUF3QixFQUFFLFNBQVM7d0JBQ25DLDZCQUE2QixFQUFFLFdBQVc7d0JBQzFDLDhCQUE4QixFQUFFLE9BQU87d0JBQ3ZDLDJCQUEyQixFQUFFLFlBQVk7cUJBQzFDO2lCQUNGO2dCQUNELElBQUksRUFBRSxFQUFFLGNBQWMsRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLHFDQUFpQixDQUFDLEVBQUU7YUFDNUQsQ0FBQyxDQUFDO1FBQ0wsQ0FBQztJQUNILENBQUM7Q0FDRjtBQTNDRCwwREEyQ0MiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb25zdHJ1Y3QgfSBmcm9tICdjb25zdHJ1Y3RzJztcbmltcG9ydCB7IFRSQUVGSUtfREFTSEJPQVJEIH0gZnJvbSAnLi4vZGFzaGJvYXJkcy90cmFlZmlrLWRhc2hib2FyZCc7XG5pbXBvcnQgeyBLdWJlQ29uZmlnTWFwIH0gZnJvbSAnLi4vaW1wb3J0cy9rOHMnO1xuaW1wb3J0IHsgUG9kTW9uaXRvciB9IGZyb20gJy4uL2ltcG9ydHMvbW9uaXRvcmluZy5jb3Jlb3MuY29tJztcbmltcG9ydCB7IFRyYWVmaWtDb25maWcgfSBmcm9tICcuLi90eXBlcyc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgVHJhZWZpa01vbml0b3JQcm9wcyB7XG4gIC8qKiBOYW1lc3BhY2UgdGhlIG1vbml0b3Jpbmcgc3RhY2sgbGl2ZXMgaW4gKHdoZXJlIHRoZSBQb2RNb25pdG9yL0NvbmZpZ01hcCBnbykuICovXG4gIHJlYWRvbmx5IG1vbml0b3JpbmdOYW1lc3BhY2U6IHN0cmluZztcbiAgLyoqIFRyYWVmaWsgY29uZmlnIChuYW1lc3BhY2UgaXQgcnVucyBpbiArIGRhc2hib2FyZCB0b2dnbGUpLiAqL1xuICByZWFkb25seSB0cmFlZmlrOiBUcmFlZmlrQ29uZmlnO1xufVxuXG4vKipcbiAqIEdlbmVyaWMsIG9wdC1pbiBtb25pdG9yIGZvciBhIFRyYWVmaWsgaW5ncmVzcyBjb250cm9sbGVyLlxuICpcbiAqIFVzZXMgYSBQb2RNb25pdG9yIChub3QgYSBTZXJ2aWNlTW9uaXRvcik6IHRoZSBrM3MtYnVuZGxlZCBUcmFlZmlrIFNlcnZpY2Ugb25seVxuICogZXhwb3NlcyB3ZWIvd2Vic2VjdXJlOyB0aGUgYG1ldHJpY3NgIHBvcnQgaXMgb24gdGhlIFBvZCwgbm90IHRoZSBTZXJ2aWNlLlxuICpcbiAqIE9wdGlvbmFsbHkgc2hpcHMgdGhlIFRyYWVmaWsgR3JhZmFuYSBkYXNoYm9hcmQgYXMgYSBDb25maWdNYXAgKGdyYWZhbmFfZGFzaGJvYXJkXG4gKiBsYWJlbCDihpIgc2lkZWNhciBhdXRvLWRpc2NvdmVyeSkuIEVuYWJsZWQgdmlhIGNvbmZpZy50cmFlZmlrIGluIE1vbml0b3JpbmdDaGFydC5cbiAqL1xuZXhwb3J0IGNsYXNzIFRyYWVmaWtNb25pdG9yQ29uc3RydWN0IGV4dGVuZHMgQ29uc3RydWN0IHtcbiAgY29uc3RydWN0b3Ioc2NvcGU6IENvbnN0cnVjdCwgaWQ6IHN0cmluZywgcHJvcHM6IFRyYWVmaWtNb25pdG9yUHJvcHMpIHtcbiAgICBzdXBlcihzY29wZSwgaWQpO1xuICAgIGNvbnN0IHsgbW9uaXRvcmluZ05hbWVzcGFjZSwgdHJhZWZpayB9ID0gcHJvcHM7XG5cbiAgICBuZXcgUG9kTW9uaXRvcih0aGlzLCAndHJhZWZpay1tZXRyaWNzJywge1xuICAgICAgbWV0YWRhdGE6IHtcbiAgICAgICAgbmFtZTogJ3RyYWVmaWstbWV0cmljcycsXG4gICAgICAgIG5hbWVzcGFjZTogbW9uaXRvcmluZ05hbWVzcGFjZSxcbiAgICAgICAgbGFiZWxzOiB7XG4gICAgICAgICAgJ2FwcC5rdWJlcm5ldGVzLmlvL25hbWUnOiAndHJhZWZpaycsXG4gICAgICAgICAgJ2FwcC5rdWJlcm5ldGVzLmlvL2NvbXBvbmVudCc6ICdpbmdyZXNzLWNvbnRyb2xsZXInLFxuICAgICAgICAgICdhcHAua3ViZXJuZXRlcy5pby9tYW5hZ2VkLWJ5JzogJ2NkazhzJyxcbiAgICAgICAgICAnYXBwLmt1YmVybmV0ZXMuaW8vcGFydC1vZic6ICdtb25pdG9yaW5nJyxcbiAgICAgICAgICAncmVsZWFzZSc6ICdrdWJlLXByb21ldGhldXMtc3RhY2snLFxuICAgICAgICB9LFxuICAgICAgfSxcbiAgICAgIHNwZWM6IHtcbiAgICAgICAgc2VsZWN0b3I6IHsgbWF0Y2hMYWJlbHM6IHsgJ2FwcC5rdWJlcm5ldGVzLmlvL25hbWUnOiAndHJhZWZpaycgfSB9LFxuICAgICAgICBuYW1lc3BhY2VTZWxlY3RvcjogeyBtYXRjaE5hbWVzOiBbdHJhZWZpay5uYW1lc3BhY2VdIH0sXG4gICAgICAgIHBvZE1ldHJpY3NFbmRwb2ludHM6IFtcbiAgICAgICAgICB7IHBvcnQ6ICdtZXRyaWNzJywgcGF0aDogJy9tZXRyaWNzJywgaW50ZXJ2YWw6ICczMHMnLCBzY3JhcGVUaW1lb3V0OiAnMTBzJyB9LFxuICAgICAgICBdLFxuICAgICAgfSxcbiAgICB9KTtcblxuICAgIGlmICh0cmFlZmlrLmRhc2hib2FyZCkge1xuICAgICAgbmV3IEt1YmVDb25maWdNYXAodGhpcywgJ3RyYWVmaWstZGFzaGJvYXJkJywge1xuICAgICAgICBtZXRhZGF0YToge1xuICAgICAgICAgIG5hbWU6ICd0cmFlZmlrLWRhc2hib2FyZCcsXG4gICAgICAgICAgbmFtZXNwYWNlOiBtb25pdG9yaW5nTmFtZXNwYWNlLFxuICAgICAgICAgIGxhYmVsczoge1xuICAgICAgICAgICAgJ2dyYWZhbmFfZGFzaGJvYXJkJzogJzEnLFxuICAgICAgICAgICAgJ2FwcC5rdWJlcm5ldGVzLmlvL25hbWUnOiAndHJhZWZpaycsXG4gICAgICAgICAgICAnYXBwLmt1YmVybmV0ZXMuaW8vY29tcG9uZW50JzogJ2Rhc2hib2FyZCcsXG4gICAgICAgICAgICAnYXBwLmt1YmVybmV0ZXMuaW8vbWFuYWdlZC1ieSc6ICdjZGs4cycsXG4gICAgICAgICAgICAnYXBwLmt1YmVybmV0ZXMuaW8vcGFydC1vZic6ICdtb25pdG9yaW5nJyxcbiAgICAgICAgICB9LFxuICAgICAgICB9LFxuICAgICAgICBkYXRhOiB7ICd0cmFlZmlrLmpzb24nOiBKU09OLnN0cmluZ2lmeShUUkFFRklLX0RBU0hCT0FSRCkgfSxcbiAgICAgIH0pO1xuICAgIH1cbiAgfVxufVxuIl19
@@ -0,0 +1 @@
1
+ export declare const TRAEFIK_DASHBOARD: Record<string, unknown>;