Files
eSlider ea062af86a
CI & Release / Verify simulator (push) Successful in 11s
CI & Release / Trivy scan (push) Successful in 9s
CI & Release / Semantic Release (push) Successful in 5s
perf: bound lake scans and add platform Grafana dashboard
Explorer and exporter were scanning the full Parquet lake every 1–5s
(~2000 files, 200MB+), driving ~2.2 CPU cores. Limit metrics to the
last N flights, cache DuckDB views and the partition tree, slow live
polls to 2s, and keep drones alive after seal to stop restart churn.

Add node-exporter, scan-duration metrics, and a Swarm Platform Grafana
dashboard for node CPU/memory and scan health.
2026-07-08 21:19:53 +01:00

570 lines
13 KiB
Terraform

# Simulated fleet (T4 dev environment, docs/06) on the k3d cluster created
# by infra/ansible/sim-cluster.yml. Drones are StatefulSet replicas writing
# to a shared hostPath lake — the same /data contract as the on-board NVMe.
locals {
labels = { "app.kubernetes.io/part-of" = "swarm-sim" }
}
resource "kubernetes_namespace" "swarm" {
metadata {
name = var.namespace
labels = local.labels
}
}
# --- Fleet ------------------------------------------------------------------
resource "kubernetes_stateful_set" "drone" {
metadata {
name = "drone"
namespace = kubernetes_namespace.swarm.metadata[0].name
labels = local.labels
}
spec {
service_name = "drone"
replicas = var.drone_count
# A landed drone powers off; the next start is the next flight
pod_management_policy = "Parallel"
selector {
match_labels = { app = "drone" }
}
template {
metadata {
labels = merge(local.labels, { app = "drone" })
}
spec {
container {
name = "drone"
image = var.simulator_image
image_pull_policy = "Never" # imported via k3d image import
env {
name = "DRONE_ID"
value_from {
field_ref {
field_path = "metadata.name"
}
}
}
env {
name = "DURATION_S"
value = tostring(var.flight_duration_s)
}
env {
name = "SPEEDUP"
value = tostring(var.speedup)
}
env {
name = "DATA_DIR"
value = "/data"
}
env {
name = "KEEP_ALIVE"
value = "1"
}
env {
name = "IMU_HZ"
value = "20"
}
volume_mount {
name = "lake"
mount_path = "/data"
}
resources {
requests = { cpu = "50m", memory = "128Mi" }
limits = { cpu = "500m", memory = "512Mi" }
}
}
volume {
name = "lake"
host_path {
path = var.data_host_path
}
}
}
}
}
}
# --- Exporter + Prometheus + Grafana (observability, docs/07) ----------------
resource "kubernetes_deployment" "exporter" {
metadata {
name = "exporter"
namespace = kubernetes_namespace.swarm.metadata[0].name
labels = local.labels
}
spec {
replicas = 1
selector {
match_labels = { app = "exporter" }
}
template {
metadata {
labels = merge(local.labels, { app = "exporter" })
}
spec {
container {
name = "exporter"
image = var.simulator_image
image_pull_policy = "Never"
command = ["python", "monitoring/exporter.py"]
env {
name = "DATA_DIR"
value = "/data"
}
env {
name = "EXPORTER_PORT"
value = "9105"
}
env {
name = "SCAN_INTERVAL_S"
value = "30"
}
env {
name = "METRIC_FLIGHT_WINDOW"
value = "5"
}
port {
container_port = 9105
}
resources {
requests = { cpu = "50m", memory = "64Mi" }
limits = { cpu = "500m", memory = "256Mi" }
}
volume_mount {
name = "lake"
mount_path = "/data"
read_only = true
}
}
volume {
name = "lake"
host_path {
path = var.data_host_path
}
}
}
}
}
}
resource "kubernetes_service" "exporter" {
metadata {
name = "exporter"
namespace = kubernetes_namespace.swarm.metadata[0].name
}
spec {
selector = { app = "exporter" }
port {
port = 9105
target_port = 9105
}
}
}
resource "kubernetes_deployment" "explorer" {
metadata {
name = "explorer"
namespace = kubernetes_namespace.swarm.metadata[0].name
labels = local.labels
}
spec {
replicas = 1
selector {
match_labels = { app = "explorer" }
}
template {
metadata {
labels = merge(local.labels, { app = "explorer" })
}
spec {
container {
name = "explorer"
image = var.simulator_image
image_pull_policy = "Never"
command = ["python", "explorer/server.py"]
env {
name = "DATA_DIR"
value = "/data"
}
env {
name = "EXPLORER_PORT"
value = "8088"
}
env {
name = "VIEW_REFRESH_S"
value = "30"
}
env {
name = "TREE_CACHE_S"
value = "15"
}
env {
name = "METRIC_FLIGHT_WINDOW"
value = "5"
}
port {
container_port = 8088
}
resources {
requests = { cpu = "50m", memory = "64Mi" }
limits = { cpu = "750m", memory = "256Mi" }
}
volume_mount {
name = "lake"
mount_path = "/data"
read_only = true
}
}
volume {
name = "lake"
host_path {
path = var.data_host_path
}
}
}
}
}
}
resource "kubernetes_service" "explorer" {
metadata {
name = "explorer"
namespace = kubernetes_namespace.swarm.metadata[0].name
}
spec {
type = "NodePort"
selector = { app = "explorer" }
port {
port = 8088
target_port = 8088
node_port = var.node_ports.explorer
}
}
}
resource "kubernetes_config_map" "prometheus" {
metadata {
name = "prometheus-config"
namespace = kubernetes_namespace.swarm.metadata[0].name
}
data = {
"prometheus.yml" = <<-EOT
global:
scrape_interval: 15s
scrape_configs:
- job_name: swarm
static_configs:
- targets: ["exporter:9105"]
- job_name: explorer
static_configs:
- targets: ["explorer:8088"]
- job_name: node
static_configs:
- targets: ["node-exporter:9100"]
EOT
}
}
resource "kubernetes_deployment" "prometheus" {
metadata {
name = "prometheus"
namespace = kubernetes_namespace.swarm.metadata[0].name
labels = local.labels
}
spec {
replicas = 1
selector {
match_labels = { app = "prometheus" }
}
template {
metadata {
labels = merge(local.labels, { app = "prometheus" })
}
spec {
container {
name = "prometheus"
image = "prom/prometheus:v2.53.0"
port {
container_port = 9090
}
volume_mount {
name = "config"
mount_path = "/etc/prometheus"
}
}
volume {
name = "config"
config_map {
name = kubernetes_config_map.prometheus.metadata[0].name
}
}
}
}
}
}
resource "kubernetes_service" "prometheus" {
metadata {
name = "prometheus"
namespace = kubernetes_namespace.swarm.metadata[0].name
}
spec {
type = "NodePort"
selector = { app = "prometheus" }
port {
port = 9090
target_port = 9090
node_port = var.node_ports.prometheus
}
}
}
resource "kubernetes_config_map" "grafana_provisioning" {
metadata {
name = "grafana-provisioning"
namespace = kubernetes_namespace.swarm.metadata[0].name
}
data = {
"datasource.yml" = <<-EOT
apiVersion: 1
deleteDatasources:
- name: Prometheus
orgId: 1
datasources:
- name: Prometheus
uid: swarm-prom
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
EOT
"dashboards.yml" = <<-EOT
apiVersion: 1
providers:
- name: swarm
folder: ""
type: file
options:
path: /var/lib/grafana/dashboards
EOT
}
}
resource "kubernetes_config_map" "grafana_dashboard" {
metadata {
name = "grafana-dashboard"
namespace = kubernetes_namespace.swarm.metadata[0].name
}
data = {
"swarm.json" = file("${path.module}/../../../simulator/monitoring/grafana/dashboards/swarm.json")
"swarm-platform.json" = file("${path.module}/../../../simulator/monitoring/grafana/dashboards/swarm-platform.json")
}
}
# Host metrics for the k3d node (CPU / memory on the platform dashboard)
resource "kubernetes_deployment" "node_exporter" {
metadata {
name = "node-exporter"
namespace = kubernetes_namespace.swarm.metadata[0].name
labels = local.labels
}
spec {
replicas = 1
selector {
match_labels = { app = "node-exporter" }
}
template {
metadata {
labels = merge(local.labels, { app = "node-exporter" })
}
spec {
host_network = true
host_pid = true
container {
name = "node-exporter"
image = "prom/node-exporter:v1.8.2"
args = [
"--path.procfs=/host/proc",
"--path.sysfs=/host/sys",
"--path.rootfs=/host/root",
"--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)",
]
port {
container_port = 9100
}
volume_mount {
name = "proc"
mount_path = "/host/proc"
read_only = true
}
volume_mount {
name = "sys"
mount_path = "/host/sys"
read_only = true
}
volume_mount {
name = "root"
mount_path = "/host/root"
read_only = true
}
resources {
requests = { cpu = "20m", memory = "32Mi" }
limits = { cpu = "200m", memory = "64Mi" }
}
}
volume {
name = "proc"
host_path { path = "/proc" }
}
volume {
name = "sys"
host_path { path = "/sys" }
}
volume {
name = "root"
host_path { path = "/" }
}
}
}
}
}
resource "kubernetes_service" "node_exporter" {
metadata {
name = "node-exporter"
namespace = kubernetes_namespace.swarm.metadata[0].name
}
spec {
selector = { app = "node-exporter" }
port {
port = 9100
target_port = 9100
}
}
}
resource "kubernetes_deployment" "grafana" {
metadata {
name = "grafana"
namespace = kubernetes_namespace.swarm.metadata[0].name
labels = local.labels
}
spec {
replicas = 1
selector {
match_labels = { app = "grafana" }
}
template {
metadata {
labels = merge(local.labels, { app = "grafana" })
}
spec {
container {
name = "grafana"
image = "grafana/grafana:11.1.0"
port {
container_port = 3000
}
env {
name = "GF_AUTH_ANONYMOUS_ENABLED"
value = "true"
}
env {
name = "GF_AUTH_ANONYMOUS_ORG_ROLE"
value = "Admin"
}
env {
name = "GF_AUTH_DISABLE_LOGIN_FORM"
value = "true"
}
# Air-gap hygiene — same flags as the Compose profile
env {
name = "GF_ANALYTICS_REPORTING_ENABLED"
value = "false"
}
env {
name = "GF_ANALYTICS_CHECK_FOR_UPDATES"
value = "false"
}
env {
name = "GF_ANALYTICS_CHECK_FOR_PLUGIN_UPDATES"
value = "false"
}
volume_mount {
name = "provisioning-datasources"
mount_path = "/etc/grafana/provisioning/datasources"
}
volume_mount {
name = "provisioning-dashboards"
mount_path = "/etc/grafana/provisioning/dashboards"
}
volume_mount {
name = "dashboards"
mount_path = "/var/lib/grafana/dashboards"
}
}
volume {
name = "provisioning-datasources"
config_map {
name = kubernetes_config_map.grafana_provisioning.metadata[0].name
items {
key = "datasource.yml"
path = "datasource.yml"
}
}
}
volume {
name = "provisioning-dashboards"
config_map {
name = kubernetes_config_map.grafana_provisioning.metadata[0].name
items {
key = "dashboards.yml"
path = "dashboards.yml"
}
}
}
volume {
name = "dashboards"
config_map {
name = kubernetes_config_map.grafana_dashboard.metadata[0].name
}
}
}
}
}
}
resource "kubernetes_service" "grafana" {
metadata {
name = "grafana"
namespace = kubernetes_namespace.swarm.metadata[0].name
}
spec {
type = "NodePort"
selector = { app = "grafana" }
port {
port = 3000
target_port = 3000
node_port = var.node_ports.grafana
}
}
}