refactor: make tf outputs the single source of operational facts (#104)

Signed-off-by: Devin Buhl <devin@buhl.casa>
This commit is contained in:
Devin Buhl
2026-07-16 11:17:33 -04:00
committed by GitHub
parent 5ea3585222
commit d2eb0b8ac0
7 changed files with 72 additions and 37 deletions
+15 -7
View File
@@ -31,23 +31,31 @@ ENVIRONMENT_SHORT = "{% set e = get_env(name='ENVIRONMENT', default='development
# Control planes need the image uploaded to OVH glance (talos:{dl,ul}:cp); workers
# are OVH BYOI, so OVH fetches their raw from the Factory at order time (no upload).
# Image facts (schematic, version, glance name, regions) come from the ovh/account
# cp_image output — one source, no must-match with the module.
[tasks."talos:dl:cp"]
run = """
: "${ENVIRONMENT:?set ENVIRONMENT=staging (or production) first}"
mkdir -p {{config_root}}/.private/dist
wget https://factory.talos.dev/image/bbfcb7053b1609712a977830952455432825890922cb6bac23cea34b980970f1/v1.13.5/openstack-amd64.raw.xz -O {{config_root}}/.private/dist/talos.1.13.5-qemu-netbird.raw.xz
unxz {{config_root}}/.private/dist/talos.1.13.5-qemu-netbird.raw.xz --force
CP_IMAGE=$(mise run tg run --working-dir deployment/modules/ovh/account output -- -json cp_image)
NAME=$(echo "$CP_IMAGE" | jq -r .glance_name)
wget "$(echo "$CP_IMAGE" | jq -r .download_url)" -O {{config_root}}/.private/dist/${NAME}.raw.xz
unxz {{config_root}}/.private/dist/${NAME}.raw.xz --force
"""
description = "Download the control-plane Talos image (OpenStack, qemu-guest-agent + netbird schematic)"
description = "Download the control-plane Talos image (OpenStack, from the ovh/account cp_image output)"
dir = "{{cwd}}"
[tasks."talos:ul:cp"]
run = """
for region in "RBX-A" "GRA9" "EU-WEST-PAR"; do
echo "Uploading to region ${region}..."
: "${ENVIRONMENT:?set ENVIRONMENT=staging (or production) first}"
CP_IMAGE=$(mise run tg run --working-dir deployment/modules/ovh/account output -- -json cp_image)
NAME=$(echo "$CP_IMAGE" | jq -r .glance_name)
for region in $(echo "$CP_IMAGE" | jq -r '.regions[]'); do
echo "Uploading ${NAME} to region ${region}..."
source {{config_root}}/.private/openstack/${ENVIRONMENT}/openrc.sh
openstack image create "talos-1.13.5-qemu-netbird" \
openstack image create "${NAME}" \
--os-region "${region}" \
--file {{config_root}}/.private/dist/talos.1.13.5-qemu-netbird.raw \
--file {{config_root}}/.private/dist/${NAME}.raw \
--disk-format raw \
--container-format bare \
--property hw_qemu_guest_agent=yes \
@@ -1,7 +0,0 @@
output "flux_operator_status" {
value = helm_release.flux_operator.status
}
output "flux_instance_status" {
value = helm_release.flux_instance.status
}
@@ -17,10 +17,7 @@ dependency "talos" {
# Cert mocks must be valid base64 — providers.tf base64-decodes on every plan.
mock_outputs = {
cluster = {
name = "mock"
endpoint = "https://mock:6443"
operator_endpoint = "https://mock:6443"
vip = "10.0.0.5"
client_certificate = "bW9jaw=="
client_key = "bW9jaw=="
ca_certificate = "bW9jaw=="
@@ -9,10 +9,7 @@ variable "tf_state_s3_endpoint" {}
variable "cluster" {
type = object({
name = string
endpoint = string
operator_endpoint = string
vip = string
client_certificate = string
client_key = string
ca_certificate = string
+10 -4
View File
@@ -20,10 +20,6 @@ output "worker_nodes" {
}
}
output "loadbalancer_ip" {
value = ovh_iploadbalancing.envoy.ipv4
}
# bare_metal for workers, public_cloud for CPs — sharing breaks upgrade on
# the other. Openstack URL is hand-built because the talos provider's
# data source returns null for platform = "openstack".
@@ -37,3 +33,13 @@ output "talos_installer_images" {
output "private_network_cidr" {
value = var.private_network_cidr
}
# Consumed by the talos:dl:cp / talos:ul:cp mise tasks, so the CP image facts
# (schematic, version, glance name, regions) live only in this module.
output "cp_image" {
value = {
download_url = "https://factory.talos.dev/image/${var.talos_schematic_id}/${var.talos_version}/openstack-amd64.raw.xz"
glance_name = var.talos_public_cloud_image_name
regions = distinct([for n in var.controlplane_nodes : n.region])
}
}
+44 -11
View File
@@ -1,10 +1,8 @@
# Exactly what downstream providers (kubernetes/helm) need to reach the cluster.
output "cluster" {
sensitive = true
value = {
name = local.cluster_name
endpoint = local.cluster_endpoint
operator_endpoint = local.operator_endpoint
vip = local.controlplane_vip
client_certificate = talos_cluster_kubeconfig.this.kubernetes_client_configuration.client_certificate
client_key = talos_cluster_kubeconfig.this.kubernetes_client_configuration.client_key
ca_certificate = talos_cluster_kubeconfig.this.kubernetes_client_configuration.ca_certificate
@@ -16,14 +14,49 @@ output "talos_client_configuration" {
value = data.talos_client_configuration.this.talos_config
}
# Server rewritten from the (in-cluster-only) VIP to the HA mesh endpoint, so the zone
# special-case lives only in netbird/cluster's mesh_dns_zone. Break-glass for bootstrap/DR
# before the gateway exists: kubectl --server=https://<cp-private-ip>:6443 (all cert SANs).
# TF-authored kubeconfig with two contexts: the HA mesh endpoint (default) and a
# direct-CP break-glass for bootstrap/DR before the gateway exists
# (kubectl --context <cluster>-direct). Both endpoints are apiserver cert SANs.
output "kubeconfig" {
sensitive = true
value = replace(
talos_cluster_kubeconfig.this.kubeconfig_raw,
"server: ${local.cluster_endpoint}",
"server: https://kube.${var.mesh_dns_zone}:6443",
)
value = yamlencode({
apiVersion = "v1"
kind = "Config"
"current-context" = local.cluster_name
clusters = [
{
name = local.cluster_name
cluster = {
server = "https://kube.${var.mesh_dns_zone}:6443"
"certificate-authority-data" = talos_cluster_kubeconfig.this.kubernetes_client_configuration.ca_certificate
}
},
{
name = "${local.cluster_name}-direct"
cluster = {
server = local.operator_endpoint
"certificate-authority-data" = talos_cluster_kubeconfig.this.kubernetes_client_configuration.ca_certificate
}
},
]
users = [
{
name = "admin@${local.cluster_name}"
user = {
"client-certificate-data" = talos_cluster_kubeconfig.this.kubernetes_client_configuration.client_certificate
"client-key-data" = talos_cluster_kubeconfig.this.kubernetes_client_configuration.client_key
}
},
]
contexts = [
{
name = local.cluster_name
context = { cluster = local.cluster_name, user = "admin@${local.cluster_name}" }
},
{
name = "${local.cluster_name}-direct"
context = { cluster = "${local.cluster_name}-direct", user = "admin@${local.cluster_name}" }
},
]
})
}
+3 -2
View File
@@ -84,9 +84,10 @@ mise run talos:kubeconfig # for kubectl
mise run talos:talosconfig # for talosctl
```
Each writes to `.private/$ENVIRONMENT/` (mode 600) from the Talos module's Terraform outputs. `talos:kubeconfig` also repoints the kubeconfig `server:` from the floating VIP (`10.150.200.5`) to a control-plane private IP (`10.150.200.10`) — the VIP doesn't ARP reliably across DCs over the NetBird network, and every CP IP is in the apiserver cert SANs so TLS still validates.
Each writes to `.private/$ENVIRONMENT/` (mode 600) from the Talos module's Terraform outputs. The kubeconfig is TF-authored with two contexts:
> **A highly-available operator API endpoint is TBD.** `kubectl` is pinned to a single control-plane IP, so if that CP is down you currently repoint to another by hand (any CP IP works — they're all cert SANs). The floating VIP is HA *inside* the cluster (kubelet and in-cluster clients use it) but doesn't ARP across DCs over the NetBird network, so there's no HA endpoint for operators yet.
* **`o11y-<env>`** (default) — the HA endpoint `kube.<mesh-zone>:6443`, fronted by the Envoy mesh gateway (TLS passthrough to every apiserver). Survives any single CP being down and never hairpins through a NetBird routing peer.
* **`o11y-<env>-direct`** — a control-plane private IP, for bootstrap/DR before the mesh gateway exists: `kubectl --context o11y-<env>-direct`. Every CP IP is an apiserver cert SAN, so TLS validates on both paths. (The floating VIP `.5` is in-cluster-only — it doesn't ARP across DCs.)
**Point your tools at them:**