Skip to content

5k scale test: 1-master vs 3-master HA comparison #37454

Description

@Jefftree

HA improves performance, but surfaces an interesting behavior with the node authorizer graph.

Comparison of the 5000-node scale test on a 1-master vs a 3-master control plane (see details at bottom). Serves as additional data point for promoting HA test to release-blocking: #37360

Throughput

Barrier 1-master 3-master HA
Waiting for 'create objects' 38.2 min 15.3 min -60%
Job wall clock 119.4 min 75.8 min -37%

Differences

Load phase 1-master 3-master HA
Peak watches, busiest apiserver 174,476 106,612 1.6x lower
Peak goroutines, busiest 385,559 232,634 1.7x lower
Peak heap, busiest 55.4 GB 40.3 GB -27%
Watch events delivered, busiest apiserver 729 M 340 M 2.1x less fan-out
Peak apiserver request rate 9,329 /s 15,879 /s 1.7x
apiserver->etcd p99 0.20-0.70 s 0.03-0.10 s ~5x lower
APF rejects (all apiservers, whole run) 1,098,233 240,336 4.6x fewer
APF system queue peak, busiest apiserver 3,707 3,118 -16%
Terminated watchers 38,039 17,276 2.2x fewer
403s (node authorizer) 100 78,848 790x worse
WATCHLIST configmaps + secrets (served syncs) 422,979 399,121 unchanged
LIST configmaps + secrets (fallbacks) 8,120 27,535 3.4x more
-> fallback rate 1.9% 6.5% 3.4x

Node Authorizer 403s and WATCHLIST fallback

Kubelets get 403 no relationship found when fetching their own pods' configmaps, secrets and SA tokens. The single-master run sees 100 of these across the whole test, the HA run sees 78,848.

Each apiserver builds its own node authorizer graph from its own pod informer, so the three graphs are independently stale and disagree for a short window after every pod binding, while a single master has one graph and cannot disagree with itself.

The flow:

  1. The scheduler binds pod P to node N.
  2. The kubelet on N learns about P from its node-filtered pod watch, served by apiserver A, and starts fetching what P mounts: a configmap, a secret, an SA token.
  3. Kubelets are not pinned to one apiserver, so requesting those other resources can land on a different apiserver, B. 2,584 of 5,004 nodes talked to more than one apiserver during the run.
  4. B authorizes it against B's own node authorizer graph, which B builds from B's own pod informer. If B has not seen pod P yet, there is no node -> pod -> secret edge, so B denies with 403.
  5. The kubelet reflector's watch-list fails and it falls back to a plain LIST, retrying until B catches up. B catches up within seconds, so the denial is a window, not a permanent failure.

The 403 causes the WATCHLIST to fail and fall back to a LIST. The same number of kubelet configmap/secret syncs happen in both runs (423k vs 399k), but HA fails 6.5% of them on the first try versus 1.9%. The fallback LISTs are single-object reads (~940 bytes), so the actual cost is very low, but it appears as metric inflation.

Full time series

Image

x = minutes since load-phase start. Red = 1-master, blue = 3-master HA. At minute 19, apiserver restarts and its watches get redistributed to the other apiservers.

Runs compared

1-master 3-master HA
Build 2075264067504705536 (07-09) 2075807663861534720 (07-11)
Control plane 1 x c4-standard-96 3 x c4-standard-96
etcd 1 member 3 members
Extra variant - apiserver restart mid-test

/sig scalability

Metadata

Metadata

Assignees

No one assigned

    Labels

    sig/scalabilityCategorizes an issue or PR as relevant to SIG Scalability.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions