gcloud-lab/apps/base/osint-dashboard/templates/postgresql/cluster.yaml
Sirius Devops 5e477492e6 feat: add OSINT Dashboard Kubernetes infrastructure
- Helm chart scaffold (Chart.yaml, values.yaml, _helpers.tpl)
- Namespace + RBAC manifests
- PostgreSQL (CNPG, 3 replicas, PostGIS + TimescaleDB)
- NATS JetStream (3 replicas, persistent, custom subjects)
- Redis Sentinel (1 primary + 2 replicas, HA)
- MinIO distributed (4 replicas, bucket init job)
- Gateway API HTTPRoute + cert-manager TLS certificates
- Monitoring stack (Prometheus, Grafana, Alertmanager, exporters)
- NetworkPolicies (default deny + per-component policies)
- GitHub Actions CI/CD pipeline (lint, template, security scan)
- Flux CD staging overlay
2026-05-21 04:15:55 +00:00

152 lines
4.8 KiB
YAML

{{- if .Values.postgresql.enabled }}
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: {{ .Values.postgresql.clusterName }}
namespace: {{ .Values.namespace }}
labels:
{{- include "osint-dashboard.labels" . | nindent 4 }}
spec:
instances: {{ .Values.postgresql.instances }}
imageName: {{ .Values.postgresql.imageName }}
storage:
size: {{ .Values.postgresql.storage.size }}
storageClass: {{ .Values.postgresql.storage.storageClass }}
resources:
{{- toYaml .Values.postgresql.resources | nindent 4 }}
# PostGIS + TimescaleDB extensions via shared_preload_libraries
postgresql:
shared_preload_libraries:
- pggis
- timescaledb
parameters:
max_connections: "500"
shared_buffers: "2GB"
effective_cache_size: "6GB"
maintenance_work_mem: "512MB"
work_mem: "16MB"
wal_buffers: "64MB"
random_page_cost: "1.1"
effective_io_concurrency: "200"
default_statistics_target: "200"
max_parallel_workers_per_gather: "4"
bootstrap:
initdb:
database: osint
owner: osint_admin
secret:
name: {{ .Values.postgresql.credentialsSecret }}
postInitializationSQL:
# Install PostGIS extension
- >-
CREATE EXTENSION IF NOT EXISTS postgis;
- >-
CREATE EXTENSION IF NOT EXISTS postgis_raster;
- >-
CREATE EXTENSION IF NOT EXISTS postgis_topology;
# Install TimescaleDB extension
- >-
CREATE EXTENSION IF NOT EXISTS timescaledb;
# Create hypertable for events
- >-
CREATE TABLE IF NOT EXISTS events (
time TIMESTAMPTZ NOT NULL,
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
source TEXT NOT NULL,
event_type TEXT,
title TEXT,
description TEXT,
location GEOGRAPHY(POINT, 4326),
severity INT DEFAULT 0,
tags TEXT[],
raw_data JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
- >-
SELECT create_hypertable('events', 'time', if_not_exists => TRUE);
- >-
CREATE INDEX IF NOT EXISTS events_loc_idx ON events USING GIST (location);
- >-
CREATE INDEX IF NOT EXISTS events_time_idx ON events (time DESC);
# Create sources reference table
- >-
CREATE TABLE IF NOT EXISTS sources (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
type TEXT NOT NULL,
config JSONB,
active BOOLEAN DEFAULT TRUE
);
# Create video_feeds table
- >-
CREATE TABLE IF NOT EXISTS video_feeds (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
rtsp_url TEXT NOT NULL,
zlm_stream_key TEXT NOT NULL,
location GEOGRAPHY(POINT, 4326),
active BOOLEAN DEFAULT TRUE,
last_heartbeat TIMESTAMPTZ
);
managed:
roles:
- name: osint_admin
ensure: present
login: true
passwordSecret:
name: {{ .Values.postgresql.credentialsSecret }}
- name: osint_reader
ensure: present
login: true
passwordSecret:
name: osint-pg-reader-credentials
backup:
barmanObjectStore:
destinationPath: "{{ .Values.postgresql.backup.bucket }}"
googleCredentials:
gkeEnvironment: true
wal:
compression: gzip
data:
compression: gzip
jobs: 2
retentionPolicy: {{ .Values.postgresql.backup.retentionPolicy }}
target: primary
monitoring:
customQueries:
- query: >-
SELECT relname, schemaname, seq_scan, seq_tup_read, idx_scan, idx_tup_fetch
FROM pg_stat_user_tables WHERE schemaname = 'public';
metrics:
- relname:
usage: "LABEL"
description: "Table name"
- schemaname:
usage: "LABEL"
description: "Schema name"
- seq_scan:
usage: "GAUGE"
description: "Number of sequential scans"
- seq_tup_read:
usage: "GAUGE"
description: "Number of tuples read"
- idx_scan:
usage: "GAUGE"
description: "Number of index scans"
- idx_tup_fetch:
usage: "GAUGE"
description: "Number of tuples fetched via index"
affinity:
enablePodAntiAffinity: true
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
postgresql operator: {{ .Values.postgresql.clusterName }}
nodesAutoRemediationChecks:
livenessProbe:
initialDelaySeconds: 10
timeoutSeconds: 5
{{- end }}