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
| Path | Role |
|---|---|
lumie-infra/provision/terraform/provider.tf | OCI provider aliases for tenancies 0214 and 0213 |
lumie-infra/provision/terraform/variables.tf | Network CIDRs, node maps, tenancy OCIDs, and SSH key inputs |
lumie-infra/provision/terraform/network_0214.tf | Master-account VCN, subnet, route table, and security list |
lumie-infra/provision/terraform/network_0213.tf | Worker-account VCN, subnet, route table, and security list |
lumie-infra/provision/terraform/peering.tf | Local peering gateways between the two OCI VCNs |
lumie-infra/provision/terraform/compute_0214.tf | Master and tenancy-0214 worker instances plus selected MinIO disks |
lumie-infra/provision/terraform/compute_0213.tf | Tenancy-0213 worker instances plus MinIO disks |
lumie-infra/provision/terraform/nlb_0214.tf | Public app ingress NLB on TCP 443 |
lumie-infra/provision/terraform/nlb_teleport_0213.tf | Separate Teleport NLB on TCP 443 to NodePort 30443 |
lumie-infra/provision/terraform/outputs*.tf | Operator outputs consumed by humans and the Ansible dynamic inventory |
Public Surface
Terraform creates these infrastructure contracts:
| Surface | Contract |
|---|---|
| Network | Two public OCI VCNs, one per tenancy, with local peering gateways and route rules between them |
| Compute | ARM64 VM.Standard.A1.Flex Ubuntu 24.04 instances with public SSH access and private K3s traffic |
| Storage | 50 GiB block volumes attached for MinIO data placement |
| Public ingress | Reserved public IP plus OCI NLB forwarding TCP 443 to worker private IPs |
| Teleport edge | Separate reserved public IP plus OCI NLB forwarding TCP 443 to Teleport NodePort 30443 |
| Automation outputs | Instance 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_0214is the default profile and owns the master VCN, master node, some workers, and the main public ingress NLB.oci.account_0213is 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, and6443from0.0.0.0/0; - pod CIDR
10.42.0.0/16and service CIDR10.43.0.0/16; - full traffic from the local VCN and peered VCN;
8472/udpfor Flannel VXLAN and51820/udpfor WireGuard on the0213side.
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.examplestill modelsworker-1, but the live cluster inspected on June 14, 2026 did not include that node.outputs.tfstill contains a legacy manualk3s_install_guideblock 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 point | Impact |
|---|---|
| Reserved public IP loses NLB association | Public HTTPS traffic times out even if workers stay healthy |
| Peering or route rule drift | Cross-tenancy workers cannot reach the master or NLB backends |
| Node map mismatch with operator expectations | Ansible inventory and docs can describe nodes that no longer exist |
| Security list changes too tight | K3s 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 -jsonreturns the expected bridge values, including master IPs, worker IP maps, and the public NLB outputs consumed by operators and Ansible.terraform planshows only the intended infrastructure drift for the change you are making../inventory/terraform_inventory.py --listrendersmasters,workers_0214, andworkers_0213from Terraform outputs without manual host edits.- After apply,
kubectl get nodes -o wideshows the expected node set andkubectl get applications -n argocdconfirms the provisioned cluster still reaches the GitOps layer.