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 instance

  • props: 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 storage

  • skip_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-credentials

  • ExternalSecret: thanos-objstore-config

  • ExternalSecret: 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-operated

  • Thanos 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=$HOSTNAME

  • JSON 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