Skip to main content

Terraform

Purpose

Lumie's Terraform layer provisions the OCI infrastructure that K3s runs on. This page is a reference document for the OCI topology, cross-tenancy routing, node and disk layout, and load balancer contracts that later bootstrap steps depend on.

Source Paths

PathRole
lumie-infra/provision/terraform/provider.tfOCI provider aliases for tenancies 0214 and 0213
lumie-infra/provision/terraform/variables.tfNetwork CIDRs, node maps, tenancy OCIDs, and SSH key inputs
lumie-infra/provision/terraform/network_0214.tfMaster-account VCN, subnet, route table, and security list
lumie-infra/provision/terraform/network_0213.tfWorker-account VCN, subnet, route table, and security list
lumie-infra/provision/terraform/peering.tfLocal peering gateways between the two OCI VCNs
lumie-infra/provision/terraform/compute_0214.tfMaster and tenancy-0214 worker instances plus selected MinIO disks
lumie-infra/provision/terraform/compute_0213.tfTenancy-0213 worker instances plus MinIO disks
lumie-infra/provision/terraform/nlb_0214.tfPublic app ingress NLB on TCP 443
lumie-infra/provision/terraform/nlb_teleport_0213.tfSeparate Teleport NLB on TCP 443 to NodePort 30443
lumie-infra/provision/terraform/outputs*.tfOperator outputs consumed by humans and the Ansible dynamic inventory

Public Surface

Terraform creates these infrastructure contracts:

SurfaceContract
NetworkTwo public OCI VCNs, one per tenancy, with local peering gateways and route rules between them
ComputeARM64 VM.Standard.A1.Flex Ubuntu 24.04 instances with public SSH access and private K3s traffic
Storage50 GiB block volumes attached for MinIO data placement
Public ingressReserved public IP plus OCI NLB forwarding TCP 443 to worker private IPs
Teleport edgeSeparate reserved public IP plus OCI NLB forwarding TCP 443 to Teleport NodePort 30443
Automation outputsInstance public and private IPs, master URL, and hints for the Ansible inventory script

Runtime Flow

Multi-Tenancy At The OCI Layer

Terraform intentionally spans two OCI accounts:

  • oci.account_0214 is the default profile and owns the master VCN, master node, some workers, and the main public ingress NLB.
  • oci.account_0213 is the second profile and owns additional workers plus the dedicated Teleport NLB.

The VCN CIDRs default to 10.0.0.0/16 for tenancy 0214 and 10.1.0.0/16 for tenancy 0213. peering.tf wires the two with local peering gateways, and both route tables add destination rules for the peer CIDR.

Node And Volume Model

The instance resources create:

  • one server plus any workers listed in nodes_0214;
  • workers listed in nodes_0213;
  • a 50 GiB boot volume on each instance via the base image definition;
  • extra 50 GiB block volumes for MinIO data.

compute_0214.tf intentionally limits MinIO disks to selected nodes through local.minio_nodes_0214, while compute_0213.tf attaches a MinIO disk to every 0213 worker.

Ingress NLB Contract

The main public ingress path is layer-4 TCP passthrough, not HTTP termination in OCI. The critical part is the reserved IP lifecycle rule:

resource "oci_core_public_ip" "nlb_public_ip_0214" {
lifetime = "RESERVED"

lifecycle {
ignore_changes = [private_ip_id]
}
}

That ignore_changes is not cosmetic. The inline comment in nlb_0214.tf documents an OCI provider behavior where later runs can clear the NLB-managed association and silently break ingress if private_ip_id is not ignored.

The app NLB forwards TCP 443 to all worker private IPs and relies on Traefik to terminate TLS by SNI on the nodes.

Security List Model

Both VCN security lists are permissive and assume higher-layer enforcement:

  • ingress 22, 80, 443, and 6443 from 0.0.0.0/0;
  • pod CIDR 10.42.0.0/16 and service CIDR 10.43.0.0/16;
  • full traffic from the local VCN and peered VCN;
  • 8472/udp for Flannel VXLAN and 51820/udp for WireGuard on the 0213 side.

That keeps bootstrap simple, but it means application-level auth, Kubernetes RBAC, and network policies remain important safety layers.

Outputs And Handoff To Ansible

outputs.tf and outputs_ansible.tf bridge Terraform into the next phase:

  • public and private IP maps for both tenancies;
  • SSH helper strings;
  • master private API URL;
  • NLB public IPs for Cloudflare DNS;
  • JSON-friendly values for inventory/terraform_inventory.py.

The dynamic inventory script does not parse state files directly. It shells out to terraform output -json, which keeps Ansible aligned with the current applied state.

Contract Drift

  • terraform.tfvars.example still models worker-1, but the live cluster inspected on June 14, 2026 did not include that node.
  • outputs.tf still contains a legacy manual k3s_install_guide block that disables Traefik, while the actual Ansible roles and live cluster keep the bundled Traefik addon enabled.

Treat the resource files as authoritative and the example outputs as operator hints only.

Failure Modes

Failure pointImpact
Reserved public IP loses NLB associationPublic HTTPS traffic times out even if workers stay healthy
Peering or route rule driftCross-tenancy workers cannot reach the master or NLB backends
Node map mismatch with operator expectationsAnsible inventory and docs can describe nodes that no longer exist
Security list changes too tightK3s control-plane join, Flannel, or public ingress can fail

Verification

cd lumie-infra/provision/terraform
terraform output -json
terraform plan
cd ../ansible
./inventory/terraform_inventory.py --list

For live confirmation after apply:

kubectl get nodes -o wide
kubectl get applications -n argocd

Success signals:

  • terraform output -json returns the expected bridge values, including master IPs, worker IP maps, and the public NLB outputs consumed by operators and Ansible.
  • terraform plan shows only the intended infrastructure drift for the change you are making.
  • ./inventory/terraform_inventory.py --list renders masters, workers_0214, and workers_0213 from Terraform outputs without manual host edits.
  • After apply, kubectl get nodes -o wide shows the expected node set and kubectl get applications -n argocd confirms the provisioned cluster still reaches the GitOps layer.