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>
156 lines
6.9 KiB
Markdown
156 lines
6.9 KiB
Markdown
# 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).
|