Skip to content

RFC2136: round-robin with multiple hosts couples read and write selection, causing asymmetric updates #6562

Description

@Yasminkundur

What happened?

With multiple --rfc2136-host values and --rfc2136-load-balancing-strategy=round-robin, reads and writes can become deterministically skewed across servers.

In practice, reconciles often do:

  1. AXFR/read
  2. DNS update/write

If both operations share the same round-robin counter, the read step advances the counter and the subsequent write step consistently lands on the next server. This can make writes appear stuck to one side of the pair.

What did you expect to happen?

Read balancing and write balancing should rotate independently.

If round-robin is enabled:

  • AXFR/list path should have its own rotation state
  • Send/update path should have its own rotation state

How to reproduce it?

  1. Configure ExternalDNS with:
    • multiple --rfc2136-host
    • --rfc2136-load-balancing-strategy=round-robin
  2. Enable debug logs.
  3. Watch repeated reconciles.
  4. Observe a pattern where:
    • Fetching records from nameserver ... alternates
    • Sending message to nameserver ... is offset by the shared counter and can repeatedly favor the opposite server

Root cause

The RFC2136 provider uses a single shared counter for both:

  • AXFR/list nameserver selection
  • Send/update nameserver selection

Because each reconcile typically performs a read before a write, the shared counter creates deterministic coupling between those two paths.

Impact

  • Round-robin does not behave independently for reads and writes
  • Writes can appear biased to one server
  • Troubleshooting is confusing because failover looks partially correct while update distribution is wrong

Proposed fix

Split the counter into independent state:

  • listCounter for AXFR/list
  • sendCounter for SendMessage/update

Example fix shape:

type rfc2136Provider struct {
    // ...
    listCounter int
    sendCounter int
    mu          sync.Mutex
}

func (r *rfc2136Provider) getNextNameserverForList() string {
    return r.getNextNameserver(&r.listCounter)
}

func (r *rfc2136Provider) getNextNameserverForSend() string {
    return r.getNextNameserver(&r.sendCounter)
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/bugCategorizes issue or PR as related to a bug.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions