Reference
Constructs API Reference¶
This document provides a complete reference for all TypeScript constructs used to generate the monitoring stack Kubernetes manifests.
Overview¶
The monitoring stack is built using 11 CDK8S constructs, each representing a component or group of related resources. All constructs extend the Construct base class from the constructs library and accept a configuration object conforming to the MonitoringConfig interface.
Common Patterns¶
Constructor Signature¶
All constructs follow this pattern:
export class ComponentConstruct extends Construct {
constructor(scope: Construct, id: string, props: ComponentProps) {
super(scope, id);
// Implementation...
}
}
Parameters:
scope: Parent construct (usually the main Chart)id: Unique identifier for this construct instanceprops: Component-specific properties
Props Interface¶
Each construct defines a Props interface:
export interface ComponentProps {
namespace: string; // Kubernetes namespace
config: MonitoringConfig; // Global configuration
}
Sync Waves¶
All constructs use ArgoCD sync waves for ordered deployment:
metadata: {
annotations: {
'argocd.argoproj.io/sync-wave': '0-3',
},
}
Wave Ordering:
Wave 0: Namespace, PriorityClass, S3 ProviderConfig
Wave 1: S3 Buckets, ExternalSecrets
Wave 2: Helm Charts (Prometheus, Loki)
Wave 3: Thanos components, Alloy
Constructs Reference¶
1. NamespaceConstruct¶
Purpose: Creates the monitoring namespace with labels and resource quotas.
File: charts/constructs/namespace.ts
Props:
export interface NamespaceProps {
name: string;
config: MonitoringConfig;
}
Resources Created:
Namespace: monitoring namespace
Example:
new NamespaceConstruct(this, 'namespace', {
name: 'monitoring',
config: config,
});
Sync Wave: 0 (first to be created)
Labels Applied:
labels:
name: monitoring
app.kubernetes.io/part-of: monitoring
pod-security.kubernetes.io/enforce: baseline
2. PriorityClassConstruct¶
Purpose: Creates high-priority class for critical monitoring components.
File: charts/constructs/priorityclass.ts
Props:
export interface PriorityClassProps {
namespace: string;
config: MonitoringConfig;
}
Resources Created:
PriorityClass: high-priority (value: 1000000)
Example:
new PriorityClassConstruct(this, 'priorityclass', {
namespace: 'monitoring',
config: config,
});
Sync Wave: 0
Priority Value: 1000000 (higher than default 0, lower than system-critical)
Description: “High priority for monitoring infrastructure components”
Global Default: false (must be explicitly assigned to pods)
3. S3ProviderConfigConstruct¶
Purpose: Configures Crossplane AWS provider for Hetzner S3.
File: charts/constructs/s3-providerconfig.ts
Props:
export interface S3ProviderConfigProps {
namespace: string;
config: MonitoringConfig;
}
Resources Created:
ProviderConfig: hetzner-s3
Example:
new S3ProviderConfigConstruct(this, 's3-providerconfig', {
namespace: 'monitoring',
config: config,
});
Sync Wave: 0
Configuration:
spec:
credentials:
source: Secret
secretRef:
name: monitoring-s3-credentials
namespace: monitoring
key: credentials
endpoint:
url:
static: https://fsn1.your-objectstorage.com
type: Static
hostnameImmutable: true
skip_region_validation: true
s3_use_path_style: true
Critical Fields:
hostnameImmutable: true: Required for S3-compatible storageskip_region_validation: true: Allows Hetzner regions (fsn1, nbg1, hel1)s3_use_path_style: true: Enables path-style S3 access
Credentials Secret: Referenced from crossplane-system namespace (replicated by ESO)
4. S3BucketsConstruct¶
Purpose: Creates S3 buckets for metrics and logs with lifecycle policies.
File: charts/constructs/s3-buckets.ts
Props:
export interface S3BucketsProps {
namespace: string;
config: MonitoringConfig;
}
Resources Created:
Bucket: metrics-thanos-kup6s (Prometheus metrics)Bucket: logs-loki-kup6s (Loki logs)BucketLifecycleConfiguration: thanos-bucket-lifecycle (730 days)BucketLifecycleConfiguration: loki-bucket-lifecycle (90 days)
Example:
new S3BucketsConstruct(this, 's3-buckets', {
namespace: 'monitoring',
config: config,
});
Sync Wave: 1 (after ProviderConfig exists)
Bucket Configuration:
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
name: metrics-thanos-kup6s
spec:
deletionPolicy: Delete
managementPolicies: [Observe, Create, Delete]
forProvider:
region: fsn1
providerConfigRef:
name: hetzner-s3
Important: managementPolicies excludes Update to avoid tagging operations (Hetzner S3 doesn’t support tags).
Lifecycle Policies:
Metrics: 730 days (2 years)
Logs: 90 days (safety margin, Loki internal retention: 31d)
5. S3ExternalSecretsConstruct¶
Purpose: Replicates S3 credentials from crossplane-system to monitoring namespace.
File: charts/constructs/s3-externalsecrets.ts
Props:
export interface S3ExternalSecretsProps {
namespace: string;
config: MonitoringConfig;
}
Resources Created:
ExternalSecret: monitoring-s3-credentialsExternalSecret: thanos-objstore-configExternalSecret: loki-s3-config
Example:
new S3ExternalSecretsConstruct(this, 's3-externalsecrets', {
namespace: 'monitoring',
config: config,
});
Sync Wave: 1 (after buckets, before Helm charts need secrets)
monitoring-s3-credentials (for Crossplane ProviderConfig):
spec:
secretStoreRef:
name: crossplane-credentials
kind: ClusterSecretStore
target:
name: monitoring-s3-credentials
template:
engineVersion: v2
data:
credentials: |
[default]
aws_access_key_id = {{ .access_key_id }}
aws_secret_access_key = {{ .secret_access_key }}
dataFrom:
- extract:
key: hetzner-s3-credentials
thanos-objstore-config (for Thanos components):
target:
template:
data:
objstore.yml: |
type: S3
config:
bucket: metrics-thanos-kup6s
endpoint: {{ .endpoint }}
access_key: {{ .access_key_id }}
secret_key: {{ .secret_access_key }}
insecure: false
loki-s3-config (for Loki components):
target:
template:
data:
s3-config.yaml: |
access_key_id: {{ .access_key_id }}
secret_access_key: {{ .secret_access_key }}
endpoint: {{ .endpoint }}
region: fsn1
bucket_name: logs-loki-kup6s
6. PrometheusHelmChartConstruct¶
Purpose: Deploys kube-prometheus-stack Helm chart with custom values.
File: charts/constructs/prometheus.ts
Props:
export interface PrometheusHelmChartProps {
namespace: string;
config: MonitoringConfig;
}
Resources Created:
HelmChart: kube-prometheus-stack (K3S Helm controller)
Example:
new PrometheusHelmChartConstruct(this, 'prometheus', {
namespace: 'monitoring',
config: config,
});
Sync Wave: 2 (after secrets exist)
Helm Configuration:
spec:
repo: https://prometheus-community.github.io/helm-charts
chart: kube-prometheus-stack
version: ${config.versions.prometheus}
targetNamespace: monitoring
valuesContent: |
prometheus:
prometheusSpec:
replicas: 2
retention: 3d
retentionSize: 2500MB
resources:
requests:
cpu: 100m
memory: 1500Mi
limits:
cpu: 1000m
memory: 3000Mi
thanos:
objectStorageConfig:
existingSecret:
name: thanos-objstore-config
key: objstore.yml
# ... (many more values)
Key Configurations:
Prometheus replicas: 2
Retention: 3 days local, unlimited via Thanos
Thanos sidecar: Enabled with S3 upload
Grafana: Included in chart
Alertmanager: 2 replicas with email config
7. LokiHelmChartConstruct¶
Purpose: Deploys Loki Helm chart in SimpleScalable mode.
File: charts/constructs/loki.ts
Props:
export interface LokiHelmChartProps {
namespace: string;
config: MonitoringConfig;
}
Resources Created:
HelmChart: loki (K3S Helm controller)
Example:
new LokiHelmChartConstruct(this, 'loki', {
namespace: 'monitoring',
config: config,
});
Sync Wave: 2 (after S3 secrets exist)
Helm Configuration:
spec:
repo: https://grafana.github.io/helm-charts
chart: loki
version: ${config.versions.loki}
targetNamespace: monitoring
valuesContent: |
deploymentMode: SimpleScalable
loki:
commonConfig:
replication_factor: 2
storage:
type: s3
bucketNames:
chunks: logs-loki-kup6s
ruler: logs-loki-kup6s
s3:
endpoint: ${config.s3.endpoint}
region: fsn1
secretAccessKey:
existingSecret: loki-s3-config
key: secret_access_key
write:
replicas: 2
resources:
requests:
cpu: 100m
memory: 256Mi
read:
replicas: 2
resources:
requests:
cpu: 100m
memory: 256Mi
backend:
replicas: 2
Key Configurations:
Deployment mode: SimpleScalable (write/read/backend separation)
Replication factor: 2 (for HA)
S3 storage: Chunks and indexes stored in logs-loki-kup6s
Retention: 744h (31 days)
8. ThanosQueryConstruct¶
Purpose: Deploys Thanos Query for global metrics querying.
File: charts/constructs/thanos-query.ts
Props:
export interface ThanosQueryProps {
namespace: string;
config: MonitoringConfig;
}
Resources Created:
Deployment: thanos-query (2 replicas)Service: thanos-query (9090/TCP for PromQL)ServiceMonitor: thanos-query-metrics
Example:
new ThanosQueryConstruct(this, 'thanos-query', {
namespace: 'monitoring',
config: config,
});
Sync Wave: 3 (after Prometheus with sidecar exists)
Deployment Spec:
spec:
replicas: 2
template:
spec:
containers:
- name: thanos-query
image: quay.io/thanos/thanos:v0.36.1
args:
- query
- --grpc-address=0.0.0.0:10901
- --http-address=0.0.0.0:9090
- --query.replica-label=replica
- --query.replica-label=prometheus_replica
- --endpoint=dnssrv+_grpc._tcp.prometheus-operated.monitoring.svc.cluster.local
- --endpoint=dnssrv+_grpc._tcp.thanos-store.monitoring.svc.cluster.local
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
Query Endpoints:
Prometheus sidecars (via DNS SRV):
dnssrv+_grpc._tcp.prometheus-operatedThanos Store (via DNS SRV):
dnssrv+_grpc._tcp.thanos-store
Deduplication: Uses replica and prometheus_replica labels
Anti-Affinity: Pods spread across nodes for HA
9. ThanosStoreConstruct¶
Purpose: Deploys Thanos Store gateways for historical S3 data.
File: charts/constructs/thanos-store.ts
Props:
export interface ThanosStoreProps {
namespace: string;
config: MonitoringConfig;
}
Resources Created:
StatefulSet: thanos-store (2 replicas)Service: thanos-store (10901/TCP gRPC)ServiceMonitor: thanos-store-metrics
Example:
new ThanosStoreConstruct(this, 'thanos-store', {
namespace: 'monitoring',
config: config,
});
Sync Wave: 3 (after S3 buckets and secrets exist)
StatefulSet Spec:
spec:
replicas: 2
serviceName: thanos-store
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ReadWriteOnce]
storageClassName: longhorn
resources:
requests:
storage: 10Gi
template:
spec:
containers:
- name: thanos-store
image: quay.io/thanos/thanos:v0.36.1
args:
- store
- --data-dir=/var/thanos/store
- --objstore.config-file=/etc/thanos/objstore.yml
- --index-cache.config=...
- --store.caching-bucket.config=...
volumeMounts:
- name: data
mountPath: /var/thanos/store
- name: objstore-config
mountPath: /etc/thanos
Caching Configuration:
Index cache: 500MB (postings, series metadata)
Chunk cache: 500MB (sample data)
Cache type: in-memory (future: memcached)
Storage: 10Gi Longhorn PVC per replica for cache
10. ThanosCompactorConstruct¶
Purpose: Deploys Thanos Compactor for downsampling and retention.
File: charts/constructs/thanos-compactor.ts
Props:
export interface ThanosCompactorProps {
namespace: string;
config: MonitoringConfig;
}
Resources Created:
StatefulSet: thanos-compactor (1 replica)Service: thanos-compactor (10902/TCP metrics)ServiceMonitor: thanos-compactor-metrics
Example:
new ThanosCompactorConstruct(this, 'thanos-compactor', {
namespace: 'monitoring',
config: config,
});
Sync Wave: 3 (after S3 buckets exist)
StatefulSet Spec:
spec:
replicas: 1 # Single instance (no HA needed for background job)
serviceName: thanos-compactor
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ReadWriteOnce]
storageClassName: longhorn
resources:
requests:
storage: 20Gi
template:
spec:
containers:
- name: thanos-compactor
image: quay.io/thanos/thanos:v0.36.1
args:
- compact
- --data-dir=/var/thanos/compact
- --objstore.config-file=/etc/thanos/objstore.yml
- --retention.resolution-raw=30d
- --retention.resolution-5m=180d
- --retention.resolution-1h=730d
- --wait
- --downsample.concurrency=2
Retention Policies:
Raw data (30s): 30 days
5-minute downsampled: 180 days (6 months)
1-hour downsampled: 730 days (2 years)
Compaction: Runs every few minutes, merges blocks, downsamples
Storage: 20Gi Longhorn PVC for compaction workspace
11. AlloyConstruct¶
Purpose: Deploys Alloy (Grafana Agent) DaemonSet for log collection.
File: charts/constructs/alloy.ts
Props:
export interface AlloyProps {
namespace: string;
config: MonitoringConfig;
}
Resources Created:
HelmChart: alloy (K3S Helm controller)
Example:
new AlloyConstruct(this, 'alloy', {
namespace: 'monitoring',
config: config,
});
Sync Wave: 3 (after Loki is ready)
Helm Configuration:
spec:
repo: https://grafana.github.io/helm-charts
chart: alloy
version: ${config.versions.alloy}
targetNamespace: monitoring
valuesContent: |
controller:
type: daemonset
alloy:
configMap:
create: true
content: |-
discovery.kubernetes "pods" {
role = "pod"
selectors {
role = "pod"
field = "spec.nodeName=$${HOSTNAME}"
}
}
loki.source.kubernetes "pods" {
targets = discovery.kubernetes.pods.targets
forward_to = [loki.relabel.logs.receiver]
}
loki.write "default" {
endpoint {
url = "http://loki-gateway.monitoring.svc.cluster.local/loki/api/v1/push"
}
external_labels = {
cluster = "kup6s",
}
}
Key Features:
DaemonSet: Runs on every node
Per-node filtering:
spec.nodeName=$HOSTNAMEJSON parsing: Extracts structured metadata
Labels: namespace, pod, container, app, component
Structured metadata: trace_id, request_id, user_id, connector_id
MonitoringConfig Interface¶
All constructs receive this configuration object:
export interface MonitoringConfig {
namespace: string;
versions: {
prometheus: string;
loki: string;
alloy: string;
};
s3: {
endpoint: string;
region: string;
buckets: {
metrics: string;
logs: string;
};
};
resources: {
prometheus: ResourceRequirements;
loki: {
write: ResourceRequirements;
read: ResourceRequirements;
backend: ResourceRequirements;
};
thanosQuery: ResourceRequirements;
thanosStore: ResourceRequirements;
thanosCompactor: ResourceRequirements;
alloy: ResourceRequirements;
grafana: ResourceRequirements;
alertmanager: ResourceRequirements;
};
retention: {
prometheus: string;
loki: string;
thanos: {
raw: string;
fiveMinute: string;
oneHour: string;
};
};
ingress: {
enabled: boolean;
className: string;
grafanaHost: string;
};
}
export interface ResourceRequirements {
requests: {
cpu: string;
memory: string;
};
limits: {
cpu: string;
memory: string;
};
}
Usage Example¶
Main Chart (monitoring-chart.ts):
import { Chart, ChartProps } from 'cdk8s';
import { Construct } from 'constructs';
import { MonitoringConfig } from './types';
import { NamespaceConstruct } from './constructs/namespace';
import { PrometheusHelmChartConstruct } from './constructs/prometheus';
// ... other imports
export class MonitoringChart extends Chart {
constructor(scope: Construct, id: string, props: ChartProps & { config: MonitoringConfig }) {
super(scope, id, props);
const { config } = props;
// Wave 0: Infrastructure
new NamespaceConstruct(this, 'namespace', {
name: config.namespace,
config: config,
});
new PriorityClassConstruct(this, 'priorityclass', {
namespace: config.namespace,
config: config,
});
new S3ProviderConfigConstruct(this, 's3-providerconfig', {
namespace: config.namespace,
config: config,
});
// Wave 1: S3 and Secrets
new S3BucketsConstruct(this, 's3-buckets', {
namespace: config.namespace,
config: config,
});
new S3ExternalSecretsConstruct(this, 's3-externalsecrets', {
namespace: config.namespace,
config: config,
});
// Wave 2: Core Components
new PrometheusHelmChartConstruct(this, 'prometheus', {
namespace: config.namespace,
config: config,
});
new LokiHelmChartConstruct(this, 'loki', {
namespace: config.namespace,
config: config,
});
// Wave 3: Thanos and Log Collection
new ThanosQueryConstruct(this, 'thanos-query', {
namespace: config.namespace,
config: config,
});
new ThanosStoreConstruct(this, 'thanos-store', {
namespace: config.namespace,
config: config,
});
new ThanosCompactorConstruct(this, 'thanos-compactor', {
namespace: config.namespace,
config: config,
});
new AlloyConstruct(this, 'alloy', {
namespace: config.namespace,
config: config,
});
}
}
Testing Constructs¶
Each construct can be tested independently:
import { Testing } from 'cdk8s';
import { PrometheusHelmChartConstruct } from '../constructs/prometheus';
import { MonitoringConfig } from '../types';
describe('PrometheusHelmChartConstruct', () => {
it('should create HelmChart with correct values', () => {
const app = Testing.app();
const chart = Testing.chart();
const config: MonitoringConfig = { /* ... */ };
new PrometheusHelmChartConstruct(chart, 'test', {
namespace: 'monitoring',
config: config,
});
const results = Testing.synth(chart);
expect(results).toMatchSnapshot();
});
});
See Also¶
Configuration Reference - config.yaml schema
CDK8S Approach - Why we use TypeScript
Architecture Overview - System design