Goutham K

TIL: making CRD lists behave like maps under server-side apply

I’d been quietly assuming Kubernetes lists were just lists. Turns out some of them are undercover maps, and not knowing which is which explains a whole class of server-side apply conflicts I used to debug by vibes.

Here’s the actual mechanism: when a CRD field is a list of objects, SSA treats the whole list as one atomic value by default. It doesn’t know entry 3 in my update and entry 3 in yours are supposed to be different things — as far as it’s concerned we’re both just yelling into slot 3. Two controllers each adding or updating their own entry look like they’re fighting over the same field, so you get spurious conflicts, or one write silently clobbering the other.

You can tell the API server to treat the list like a map instead, keyed by whichever field(s) uniquely identify an entry:

type: array
x-kubernetes-list-type: map
x-kubernetes-list-map-keys:
  - type
items:
  type: object
  properties:
    type:
      type: string
    status:
      type: string

With both markers set, SSA merges by key instead of by index — each controller can own and update its own entry (type: Ready, type: Progressing, …) without touching anyone else’s. The key fields have to be required and effectively immutable, or the schema gets rejected.

This is exactly how status.conditions works on most built-in types — turns out I’d been benefiting from this for years without knowing what made it possible. 🤦