Skip to content

azure: aks_route_table and vnet fight over the subnet's routeTable, so the stack never converges #474

Description

@gdrojas

Symptom

An Azure stack using infrastructure/azure/vnet + infrastructure/azure/aks + infrastructure/azure/aks_route_table never reaches No changes. Every plan, forever:

Plan: 2 to add, 1 to change, 2 to destroy.

Applying it succeeds and leaves the cluster healthy — the route table ends up correctly attached — but the very next plan is byte-identical. Verified by applying and immediately re-planning:

$ tofu apply -auto-approve
Apply complete! Resources: 2 added, 1 changed, 2 destroyed.

$ tofu plan
Plan: 2 to add, 1 to change, 2 to destroy.     # unchanged

Root cause — two modules own the same field

1. vnet wants to remove the route table. The AVM subnet resource manages the whole request body, and nothing in its config mentions a route table, so every plan proposes:

# module.vnet...azapi_resource.subnet[0] will be updated in-place
  ~ properties = {
      ~ routeTable = {
          - id = ".../MC_<rg>_<cluster>_<region>/.../routeTables/aks-agentpool-...-routetable"
        } -> null

That route table is created by AKS itself for kubenet clusters, after the vnet exists, so the vnet module has no way to know about it.

2. aks_route_table re-attaches it unconditionally. From infrastructure/azure/aks_route_table/main.tf:7:

triggers_replace = timestamp()

timestamp() differs on every plan, so terraform_data.trigger always replaces, and azapi_update_resource.aks_subnet_route_table follows it through replace_triggered_by.

The two cancel out on every apply and reset for the next one.

Why it matters

  • The stack can never be used as a convergence check. Real drift is indistinguishable from this permanent noise, which is precisely how an unrelated bug went unnoticed for us: a stale registry hostname sat in a plan nobody could read as clean.
  • Every apply detaches and re-attaches the route table on a live kubenet cluster. It is brief, but pod-to-pod traffic across nodes degrades in that window — a recurring cost paid on every unrelated change.

Suggested direction

Fixing only the trigger is not enough: even with a stable trigger, vnet still proposes routeTable -> null on every plan. Both halves need to move.

  1. aks_route_table — derive triggers_replace from the values that actually matter (subnet id + route table id) instead of timestamp(), so it re-runs only when the attachment really changes.
  2. vnet — stop fighting for the field: either expose a per-subnet route_table input, or add lifecycle { ignore_changes = [...routeTable] } on the subnet so an externally-managed route table survives.

Happy to send the PR once you decide which shape you prefer for (2) — exposing the input is cleaner but needs the id up front, which is awkward because AKS only creates the route table after the cluster comes up.

Environment

tofu-modules v6.8.1 · AKS kubenet (podCidr 10.244.0.0/16) · OpenTofu with azapi + azurerm.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions