feat: add IaC layer with shared var/t1 and var/t3 data paths
Ansible provisions the k3d cluster and Flux controllers; Terraform modules deploy the simulated fleet and ground warehouse. Compose and k3d share var/t1 (live lake) and var/t3 (warehouse). The prototype gains a live mode fed by the explorer read-only SQL API.
This commit is contained in:
@@ -0,0 +1,439 @@
|
||||
# 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"
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
port {
|
||||
container_port = 9105
|
||||
}
|
||||
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"
|
||||
}
|
||||
port {
|
||||
container_port = 8088
|
||||
}
|
||||
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: 5s
|
||||
scrape_configs:
|
||||
- job_name: swarm
|
||||
static_configs:
|
||||
- targets: ["exporter:9105"]
|
||||
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
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user