kubepath

documentation/external-clusters.md

The edge/workload split

pl-waw is the edge and holds every public certificate; workload clusters hold none and are reachable only over mTLS. The seam is an ExternalName Service plus a ServersTransport.

Read this last, because it is the payoff: it shows the constraint from hops 1–3 becoming an architecture rather than a limitation. HTTP-01 needs port 80 at the hostname; only the edge has a port 80 in DNS; therefore only the edge can hold a public certificate — and therefore a workload cluster has no public attack surface at all. A limitation, read the right way round, turned into a security property.

The two things to make sure a class leaves with: the serverName failure mode, which presents as a 502 with nothing useful in the log, and the reason insecureSkipVerify is the wrong fix for it.

Along the path: hop 3 · TLS, by HTTP-01 only · hop 4 · Matching the request · hop 1 · The A record

documentation/external-clusters.md 7 annotated lines
1# External clusters
2
3## The split
4
6
7 - the A record, and therefore the node IP (hop 1)
8 - the node’s `:80` and `:443` via Traefik’s hostPort (hop 2)
9 - every public TLS certificate, issued by HTTP-01 (hop 3)
10
12It is reachable only from the edge, over mutual TLS.
13
14## The seam
15
16Two objects. An `ExternalName` Service names the far side:
17
18```yaml
19apiVersion: v1
20kind: Service
21metadata:
22 name: workload-fra
23spec:
25 externalName: ingress.workload-fra.internal.example
26```
27
28And a `ServersTransport` decides what the edge presents and accepts:
29
30```yaml
31apiVersion: traefik.io/v1alpha1
32kind: ServersTransport
33metadata:
34 name: workload-mtls
35spec:
37 certificatesSecrets:
39 rootCAsSecrets:
40 - workload-ca
42```
43
44## What this costs
45

line 5

One cluster is public. The others are not reachable at all.

This is the whole design in one sentence, and it is worth writing on a board before reading further. There is exactly one cluster with a public address, and it is the small single-node one. The clusters doing the work have no public address, no certificates, and no ACME.

The instinct is that this is a compromise forced by the missing load balancer. It is better than that: a workload cluster with no public ingress cannot be attacked from the internet directly, only through a component whose configuration you control entirely.

line 11

Why the edge must hold the certificates — it is not a preference

HTTP-01 validation answers a plain HTTP request on port 80 at the hostname on the certificate. A workload cluster has no A record, so nothing on the internet resolves to it, so Let’s Encrypt cannot reach it, so it cannot complete a challenge. It could not obtain a public certificate even if someone wanted it to.

Follow that one step further, because this is the good bit: the constraint from hop 3 is doing the enforcement. It is not a policy someone could relax by editing a manifest — it is a property of the validation method. Choose DNS-01 instead and this stops being true, and workload clusters become able to hold public certificates. That is a real argument for keeping HTTP-01 beyond simplicity.

line 24

This is the reason allowExternalNameServices is on

Here is the requirement that turns on the setting flagged in the Traefik values — the two files finally meet.

Traefik will not route to an ExternalName Service unless explicitly permitted, because doing so lets whoever can create a Service choose an arbitrary destination. The edge/workload split needs exactly that capability, for exactly one hostname.

The design question to put to a class: this grants the capability cluster-wide to satisfy one route. What would it take to scope it — provider namespace restrictions, RBAC on Service creation, an admission policy on spec.externalName values? Then have them find where in the repo that scoping happens, and notice it does not.

line 36

The failure that looks like a 502 and is not

This must match a SAN on the certificate the workload cluster presents. When it does not, the TLS handshake fails, Traefik has no backend, and the client gets a 502 — with an access log line that says 502 and a general log that, at INFO, says nothing useful at all.

So the symptom points at the backend being down. Every instinct says go and look at the workload cluster, where everything is healthy. This is the highest-value troubleshooting item in the document.

Diagnose it from the far side of the handshake instead of guessing:

openssl s_client -connect ingress.workload-fra.internal.example:443 \
  -servername ingress.workload-fra.internal.example \
  | openssl x509 -noout -text | grep -A1 "Subject Alternative Name"

Compare what comes back to the line above. A mismatch is the answer; anything else and the fault is elsewhere.

line 38

This is the mutual half, and it is the actual authorization

The edge presents a client certificate. The workload cluster’s ingress is configured to require one and to accept only this issuer — so the workload cluster does not need a firewall rule, an IP allowlist, or a VPN to know that a connection came from the edge. The certificate is the proof.

Note what that means for rotation: this Secret is a credential with an expiry, and when it expires every workload cluster becomes unreachable at once. It is the single most important expiry date in the fleet and it lives in a Secret the deployer cannot read — so the monitoring for it has to come from somewhere else. Ask a class where they would put that alert.

line 41

The wrong fix, which is why it is commented rather than absent

When the serverName mismatch above produces a 502, this is what a tired engineer at 6pm reaches for. It works — the 502 goes away, traffic flows, the incident closes.

What it actually did: the edge now accepts any certificate from anything that answers on that hostname. Server authentication is gone. The client certificate is still sent, so the workload cluster still authenticates the edge, and the connection still shows as mTLS in every dashboard. The failure is invisible in exactly the place someone would look to check.

It is left in the file as a commented line with a warning rather than deleted, because a deleted option is one somebody rediscovers under pressure, and a warned-about one is a decision they have to overrule in writing. That is a documentation pattern worth naming.

line 46

The number that ends the discussion

End the session here. A workload cluster can be three zones, nine nodes and a managed control plane with a 99.95% target — and if the only path to it is a single-node edge, the availability of the whole system is the availability of one machine in pl-waw.

Nothing in this document is wrong, and none of it is worth much against that number. It is the right trade for a platform of this size and it should be written down as a trade rather than discovered during an incident.

The question to leave a class with is not "how do we make the edge redundant" — they will say "a second node and a load balancer", which is the constraint they were told to work within. It is: what is the recovery procedure when the edge node is gone? That has an answer that fits inside the constraint, and writing it is a better exercise than removing the constraint.

Check yourself

3 questions. One attempt each is recorded; the explanation is the point, not the score.

A route to a workload cluster returns 502. The workload cluster is healthy. What is the first thing to check?

Setting insecureSkipVerify: true clears the 502. What did it actually change?

Why can a workload cluster not hold its own public certificate, even if someone wanted it to?