Skip to content

Take a restarting Kibana replica out of the proxy rotation - #128

Merged
coutoPL merged 5 commits into
masterfrom
fix/kbn-proxy-failover
Sep 15, 2026
Merged

coutoPL merged 5 commits into
masterfrom
fix/kbn-proxy-failover

Conversation

@sscarduzio

@sscarduzio sscarduzio commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

What this fixes

The 9.4.6, docker leg has been failing on nearly every pull request, on different specs each time, and passing on a re-run. I treated it as a flaky spec. It is not — it is the proxy in front of Kibana.

The mechanism

environments/elk-ror/base.docker-compose.yml runs two Kibana replicas, and kbn-proxy round-robins across them. Kibana-config.cy.ts changes ROR settings, which restarts Kibana — restart: always on kbn-ror carries a comment saying that is exactly why it is there.

While a replica restarts, half of every request goes to a container that is not listening. A stopped container drops the packets rather than refusing them, so those requests hang instead of failing. The old config set no proxy_connect_timeout, so nginx waited its default 60 seconds — longer than every Cypress timeout (defaultCommandTimeout 20s, requestTimeout 10s). The browser gave up first, the failure never reached nginx to be counted against the peer, and the peer stayed in the rotation.

Cypress's retries: 2 did not help. Three attempts, same alternation.

That accounts for every symptom:

Observed Cause
Different specs fail each run Depends which request lands on the dead peer
Fails all 3 Cypress retries The peer is never ejected
Passes on a full job re-run Fresh stack, both replicas alive
docker legs only, never eck eck runs one Kibana (kind-cluster/ror/base/kbn.yml, count: 1) with no proxy

Sample failures, all timeouts on unrelated things:

AssertionError: Timed out retrying after 20000ms: Expected to find element:
  `[data-test-subj="lnsVisualizationContainer"]`, but never found it.
CypressError: Timed out retrying after 10000ms: cy.wait() timed out waiting 10000ms
  for the 1st request to the route: `spacesPlugin`. No request ever occurred.
CypressError: `cy.task('httpCall')` timed out after waiting `20000ms`.

The change

Three directives on the location, and the default max_fails/fail_timeout written out on the peer so the intent is on the page:

  • proxy_connect_timeout 2s — the important one. It turns a hung connect into a countable failure long before the client gives up. Two seconds is far above a healthy connect on the container network and far below the 10s Cypress spends in cy.wait().
  • proxy_next_upstream error timeout http_502 http_503 http_504 — send the request to the other replica. http_503 is in the list because a Kibana that is listening but still starting answers 503, which nginx does not treat as an error by default.
  • proxy_next_upstream_tries 2 — there are two replicas.

Measured

Two-replica reproduction, one replica stopped, same config shape:

before: 000 200 000 200 000 200 000 200 000 200     50% lost, each hanging until the client's limit
after:  200/2.002s 200/0.0007s 200/0.0005s ...      one failover, then the peer is out of rotation

Sustained for 30s with the peer down: 110 requests, 0 failures, 3 over 0.5s (the 10s re-probe), worst case 2.0s — inside every Cypress timeout. When the replica answers again nginx takes it back on its own, verified.

nginx -t passes on the real file.

What I did not test

The http_503 path is reasoned, not measured: I reproduced the dropped-packet case, which is what a restarting Kibana does, and not the "listening but answering 503" case. It is a standard directive and costs nothing if that state never occurs.

I also left proxy_read_timeout and proxy_send_timeout at their 60s defaults. Lowering them would catch a replica that hangs after accepting the request, but Reporting.cy.ts can legitimately wait a long time on a response, and I would rather not trade this flake for a new one.

Base branch

master, per docs/dev/branching.md case 3 — a fix for a test that fails against the released plugins, and a flaky test counts.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved proxy resilience when connecting to Kibana replicas.
    • Requests retry across replicas after connection errors, timeouts, or temporary server errors.
    • Replicas recovering from brief restarts remain available for requests instead of being excluded temporarily, reducing avoidable 502 responses.

The docker env runs two Kibana replicas behind kbn-proxy. The upstream block had no connect
timeout and no failover, so while one replica restarted - which Kibana-config.cy.ts makes it do,
and which `restart: always` on kbn-ror exists for - half of every request went to a container that
was not listening.

A stopped container drops the packets rather than refusing them, so those requests hung instead of
failing. nginx waited its default 60s connect timeout, which is longer than every Cypress timeout,
so the browser gave up first, the failure never counted against the peer, and the peer stayed in
the rotation. Cypress retries did not help: all three attempts hit the same alternation.

Measured on a two-replica reproduction with one replica stopped.

  before: 000 200 000 200 000 200 000 200 000 200      (50% lost, each hanging to the client's limit)
  after:  200/2.00s 200/0.0005s 200/0.0005s ...        (one failover, then the peer is out)

Sustained for 30s with the peer down: 110 requests, 0 failures, 3 over 0.5s (the 10s re-probe),
worst 2.0s - inside every Cypress timeout. The replica returns to the rotation on its own when it
answers again.

This fits the evidence: the failures only ever appear on the `docker` legs, never on `eck`, and
eck runs a single Kibana (kind-cluster/ror/base/kbn.yml, count: 1) with no proxy in front.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

This comment was marked as off-topic.

The first version of this change set max_fails=1 fail_timeout=10s, and it made the suite worse:
the 9.5.3 docker leg failed 10 of 27 specs, every one of them on `502: Bad Gateway`, with
`no live upstreams while connecting to upstream` in the nginx log.

Ejection cannot work here. There are two peers, and a settings change restarts Kibana, so BOTH
answer 503 for a few seconds. One 503 each ejects both, nginx has no peer left, and every request
gets a 502 — including for ten seconds after the replicas are healthy again.

Measured on a two-replica reproduction with controllable backends, 24 requests per case:

  scenario                  max_fails=1              max_fails=0
  one peer stopped          24 ok                    24 ok
  one peer answering 503    24 ok                    24 ok
  both peers 503            0 ok, 23x 502, 1x 503    0 ok, 24x 503
  both recover              first 200 at 10.49s      first 200 at 0.01s

http_503 stays in proxy_next_upstream. Taking it out looked like the fix at first, and it is not:
with one replica restarting and the other healthy it turned 0 failures into 12 of 24, because the
503 is then served to the client instead of being retried on the peer that can answer.

What is left is failover without ejection. A peer that cannot take a request costs that request
one failover, and a peer that recovers is used again on the next request.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sscarduzio

This comment was marked as outdated.

… suite fails

Temporary, to be reverted. On the red 9.4.6 docker legs the kbn-ror containers restart with exit
code 0 during Tenancy, Test-settings and User-settings; on the green legs they never restart. The
EXIT trap tears the stack down before any later step can look, so this prints `docker events` and
the shutdown-related Kibana log lines from inside the failure path.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@sscarduzio

This comment was marked as outdated.

@10hexdev

This comment was marked as outdated.

10hexdev[bot]

This comment was marked as outdated.

@10hexdev

This comment was marked as outdated.

Comment thread environments/elk-ror/conf/kbn/kbn-proxy-nginx.conf Outdated
Each comment states what the directive does and why the value is set.
Measurements and change history stay in the PR description.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sscarduzio
sscarduzio requested a review from coutoPL September 14, 2026 13:49

@coutoPL coutoPL left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Please take a look at proxy example in this repo. Maybe we should port the changes to it?

@coutoPL
coutoPL merged commit bc85b3d into master Sep 15, 2026
46 of 99 checks passed
@coutoPL
coutoPL deleted the fix/kbn-proxy-failover branch September 15, 2026 09:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants