--- # Declarative ClickHouse schema bootstrap. Runs as an ArgoCD PostSync hook so it # executes after the ClickHouseInstallation is reconciled, and re-runs on every # sync (idempotent CREATE ... IF NOT EXISTS). Edit the DDL here to evolve the # schema; the Vector aggregator writes to logs.raw with skip_unknown_fields, so # adding columns is backward-compatible. apiVersion: batch/v1 kind: Job metadata: name: clickhouse-schema namespace: logging annotations: argocd.argoproj.io/hook: PostSync argocd.argoproj.io/hook-delete-policy: BeforeHookCreation labels: app.kubernetes.io/name: clickhouse-schema app.kubernetes.io/component: bootstrap spec: backoffLimit: 20 activeDeadlineSeconds: 1800 ttlSecondsAfterFinished: 3600 template: metadata: labels: app.kubernetes.io/name: clickhouse-schema vector.dev/exclude: "true" spec: restartPolicy: OnFailure securityContext: runAsNonRoot: true runAsUser: 101 runAsGroup: 101 containers: - name: clickhouse-schema image: docker.io/clickhouse/clickhouse-server:24.8 securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: - ALL env: - name: HOME value: /tmp - name: CLICKHOUSE_USER valueFrom: secretKeyRef: name: clickhouse-credentials key: username - name: CLICKHOUSE_PASSWORD valueFrom: secretKeyRef: name: clickhouse-credentials key: password command: - /bin/bash - -ec - | host=clickhouse-logs.logging.svc.cluster.local echo "Waiting for ClickHouse at ${host}:9000 ..." until clickhouse-client --host "$host" --port 9000 \ --user "$CLICKHOUSE_USER" --password "$CLICKHOUSE_PASSWORD" \ --query "SELECT 1" >/dev/null 2>&1; do echo " not ready, retrying in 5s"; sleep 5 done echo "Applying schema ..." clickhouse-client --host "$host" --port 9000 \ --user "$CLICKHOUSE_USER" --password "$CLICKHOUSE_PASSWORD" \ --multiquery <<'EOSQL' CREATE DATABASE IF NOT EXISTS logs; CREATE TABLE IF NOT EXISTS logs.raw ( timestamp DateTime64(3) DEFAULT now64(3), host LowCardinality(String) DEFAULT '', source LowCardinality(String) DEFAULT '', namespace LowCardinality(String) DEFAULT '', pod String DEFAULT '', container LowCardinality(String) DEFAULT '', stream LowCardinality(String) DEFAULT '', severity LowCardinality(String) DEFAULT '', message String DEFAULT '', labels Map(LowCardinality(String), String), fields Map(LowCardinality(String), String) ) ENGINE = MergeTree PARTITION BY toDate(timestamp) ORDER BY (source, namespace, host, timestamp) TTL toDateTime(timestamp) + INTERVAL 3 DAY SETTINGS index_granularity = 8192; -- One row per archived S3 object (written by logarchiver). No TTL: -- the index must outlive logs.raw so the long-term S3 archive stays -- searchable. Keep in sync with logarchiver internal/index/ddl.go. CREATE TABLE IF NOT EXISTS logs.archive_index ( object_key String, bucket LowCardinality(String), subject LowCardinality(String), hosts Array(LowCardinality(String)), min_ts DateTime64(3), max_ts DateTime64(3), event_count UInt64, raw_bytes UInt64, stored_bytes UInt64, compression LowCardinality(String), cipher LowCardinality(String), container_format LowCardinality(String), key_name LowCardinality(String), key_fingerprint String, created_at DateTime64(3) DEFAULT now64(3), INDEX idx_hosts hosts TYPE bloom_filter GRANULARITY 1 ) ENGINE = MergeTree PARTITION BY toYYYYMM(min_ts) ORDER BY (subject, min_ts, object_key); EOSQL echo "Schema applied." resources: requests: cpu: 50m memory: 128Mi limits: cpu: 500m memory: 512Mi volumeMounts: - name: tmp mountPath: /tmp volumes: - name: tmp emptyDir: {}