Initial commit: GitOps Starter Pack

A teaching-first, single-cluster GitOps setup: Talos Linux + Argo CD with
an app-of-apps layout, MetalLB pre-installed for real LoadBalancer IPs on
bare metal, a demo app, and a progressive docs/ walkthrough with a
CLAUDE.md tutor persona and guided bootstrap checklist.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Maxwell Morris
2026-08-22 17:30:45 +01:00
co-authored by Claude Sonnet 5
commit 92c3ad3191
29 changed files with 2083 additions and 0 deletions
+155
View File
@@ -0,0 +1,155 @@
# 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/07-adding-new-apps.md`](07-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-your-first-change.md`](06-your-first-change.md).