Files
Maxwell MorrisandClaude Sonnet 5 110bd97cca Move to a three-node cluster with a Talos VIP; add Longhorn
Every node is now both control plane and worker, giving real tolerance
for losing any single machine (etcd quorum needs 3, not 1). A Talos VIP
gives the cluster one stable address regardless of which node answers.

Also pre-installs Longhorn for replicated storage, backed by a Talos
UserVolume on each node's second disk (>=900GB, non-system) rather than
the default path — mirroring the pattern from the homescale repo.

Docs renumbered to make room for 06-longhorn.md; checklist, glossary,
network map, and CLAUDE.md's guided-bootstrap instructions updated to
match, including asking the user for node IPs, VIP, and disk info before
generating any Talos config.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 18:25:38 +01:00

156 lines
6.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 05 — MetalLB: getting real IP addresses on bare metal
The app-of-apps you just applied ([`docs/04-app-of-apps.md`](04-app-of-apps.md))
already brought up `metallb` alongside `demo`. This doc explains what it
does, walks you through the one piece only you can decide (which IP
addresses to actually use), and finishes by giving Argo CD's own UI a real
address too.
## Why this is needed at all
On a cloud provider, a Kubernetes Service of `type: LoadBalancer` triggers
the cloud to provision an actual load balancer and hand your Service a
real, routable IP automatically. Bare metal — your Talos node, sitting on
your own network — has no such thing behind it. Without help, a
`LoadBalancer` Service on bare metal just sits with its address stuck at
`<pending>` forever.
**MetalLB** is that help: software that runs inside your cluster,
watches for `LoadBalancer` Services, and hands them a real address from a
range *you* configure — then makes that address actually reachable on your
network. It's the standard, widely-used answer to this exact gap, which is
why it's installed as a pre-installed app in this repo rather than left as
a "next step."
`apps/demo/service.yaml` is already `type: LoadBalancer` — that's what you
saw sitting at `<pending>` if you looked closely in the last doc. This doc
is what turns that into a real, reachable address.
## What's already running
**`apps/metallb.yaml`** → one Application, pointing at `apps/metallb`, a
Helm chart that does two things at once (see
[`apps/metallb/Chart.yaml`](../apps/metallb/Chart.yaml)):
- Pulls in the official upstream MetalLB chart as a dependency — this is
also the real, working example of a Helm-based app mentioned in
[`docs/08-adding-new-apps.md`](08-adding-new-apps.md).
- Carries two templates of its own
([`apps/metallb/templates/`](../apps/metallb/templates)): an
`IPAddressPool` and an `L2Advertisement` — MetalLB's own custom
resources, which tell it which addresses it's allowed to use and how to
advertise them. Right now these still contain **placeholder** IP
addresses that won't work on your network — that's what you're about to
fix.
Both the upstream chart and our two extra resources sync together as one
Application. Look at the `sync-wave` annotation on
[`apps/metallb/templates/ipaddresspool.yaml`](../apps/metallb/templates/ipaddresspool.yaml) —
it's what keeps this safe despite being one sync: MetalLB's own resources
(including its CRDs) apply first, and Argo CD waits for them to be healthy
before applying our two resources, which depend on those CRDs existing.
## Answer three questions before editing anything
> **Ask Claude:** literally ask — "help me pick MetalLB's IP range and the
> addresses for Argo CD and the demo app." This is the one part of this
> whole repo that depends entirely on *your* network, which nothing in
> this repo can know in advance. Have your router's admin page or DHCP
> settings handy; Claude will ask what your LAN's subnet is and what range
> your DHCP server already hands out, so the pool it suggests doesn't
> collide with anything.
You need three decisions:
1. **The pool range** — a handful of addresses (5–10 is plenty for this
repo) on your LAN's subnet that your DHCP server will never hand to
anything else. Commonly a slice just above or below your DHCP range,
e.g. if your router hands out `.100`–`.199`, something like
`.240`–`.250` is a safe, unused slice.
2. **Argo CD's UI address** — one address from that pool.
3. **The demo app's address** — a different address from that pool.
## Edit the placeholders
1. [`apps/metallb/templates/ipaddresspool.yaml`](../apps/metallb/templates/ipaddresspool.yaml)
— replace the `addresses` range with your chosen pool.
2. [`apps/demo/service.yaml`](../apps/demo/service.yaml) — replace the
`metallb.universe.tf/loadBalancerIPs` annotation with the demo app's
chosen address (must be inside the pool from step 1).
3. [`docs/network.md`](network.md) — record the pool range and the demo
app's address in the tables there (Argo CD's address goes in once you've
picked it below).
4. Commit and push all three:
```sh
git add apps/metallb/templates/ipaddresspool.yaml apps/demo/service.yaml docs/network.md
git commit -m "Configure MetalLB's address pool for my network"
git push
```
5. Force a sync rather than waiting for the next poll:
```sh
argocd app sync metallb demo
```
## Verify
```sh
kubectl -n demo get svc demo
```
`EXTERNAL-IP` should now show the address you chose instead of `<pending>`.
Visit `http://<that-address>` in a browser — same "It works!" page as
before, now on its own real IP instead of a NodePort.
```sh
kubectl -n metallb-system get ipaddresspools,l2advertisements
```
Confirms your pool and advertisement were applied as expected.
## Give Argo CD's UI a real address too
So far you've reached the Argo CD UI through `kubectl port-forward`
(see [`docs/03-installing-argocd.md`](03-installing-argocd.md)). Now that
MetalLB is running, you can give it a permanent address instead. Argo CD's
own install isn't managed through this repo's GitOps loop (it's the
imperative bootstrap step — see `docs/03`), so this one change is applied
directly rather than through Git:
```sh
kubectl -n argocd patch svc argocd-server --type merge -p \
'{"spec":{"type":"LoadBalancer"},"metadata":{"annotations":{"metallb.universe.tf/loadBalancerIPs":"<ARGOCD_IP>"}}}'
```
Replace `<ARGOCD_IP>` with the address you chose in step 2 above. Then:
```sh
kubectl -n argocd get svc argocd-server
```
Once `EXTERNAL-IP` shows your chosen address, visit
`https://<that-address>` directly — no port-forward needed from now on.
(You'll still hit the same self-signed certificate warning as before; see
[`docs/03-installing-argocd.md`](03-installing-argocd.md) for why that's
expected here.)
Update [`docs/network.md`](network.md) one more time with this address —
commit and push it. This one's a genuine exception to "everything's driven
by Git": the patch command above isn't a file in this repo, so this file is
the only place this address gets recorded at all.
> **Ask Claude:** "why did the demo app get its LoadBalancer IP through
> Git, but Argo CD's didn't?" — a good check that the boundary between
> "managed by this repo" and "the imperative bootstrap that starts it"
> has actually landed.
## Checklist
- [ ] Chosen a pool range, an Argo CD UI address, and a demo app address,
with Claude's help, that fit your actual network
- [ ] `apps/metallb/templates/ipaddresspool.yaml` edited to your real range
- [ ] `apps/demo/service.yaml` edited to your real demo address
- [ ] `docs/network.md` updated with the pool range and both addresses
- [ ] Committed, pushed, synced
- [ ] `kubectl -n demo get svc demo` shows a real `EXTERNAL-IP`, page loads in a browser
- [ ] `argocd-server` patched to `LoadBalancer`, reachable at its own address without port-forwarding
Continue to [`docs/06-longhorn.md`](06-longhorn.md).