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>
120 lines
5.1 KiB
Markdown
120 lines
5.1 KiB
Markdown
# 04 — The app-of-apps pattern
|
|
|
|
This is the last manual step. After this, Git drives everything.
|
|
|
|
## The idea
|
|
|
|
An Argo CD `Application` is a resource that says "watch this path in this
|
|
Git repo, and keep the cluster matching what's there." The simplest
|
|
possible setup is one Application per app you want to run — but then every
|
|
new app means one more manual `kubectl apply`, which quietly puts you back
|
|
to making changes by hand.
|
|
|
|
**App-of-apps** fixes this by making Applications themselves something Argo
|
|
CD manages. One Application — the app-of-apps — watches a folder
|
|
([`apps/`](../apps)) full of *other* Application manifests. Add a new app by
|
|
adding a new file to that folder and pushing it — the app-of-apps notices
|
|
the new file and creates the Application it describes, which in turn
|
|
deploys the app. No `kubectl apply` required past this point, ever, for a
|
|
new app.
|
|
|
|
```
|
|
argocd/app-of-apps/app-of-apps.yaml (you apply this ONE file, by hand, once)
|
|
│
|
|
│ watches apps/, non-recursively
|
|
├──────────────┬───────────────┐
|
|
▼ ▼ ▼
|
|
apps/demo.yaml apps/metallb.yaml (more, later — just add a file)
|
|
│ │
|
|
▼ ▼
|
|
apps/demo/*.yaml apps/metallb/* (the actual manifests / Helm chart)
|
|
```
|
|
|
|
Three layers, each watching the next. You only ever touch the bottom two —
|
|
the app-of-apps is set-and-forget. Notice `apps/metallb.yaml` is already
|
|
here — see [`docs/05-metallb.md`](05-metallb.md) right after this one.
|
|
|
|
## Why `apps/` isn't recursive
|
|
|
|
Look at [`argocd/app-of-apps/app-of-apps.yaml`](../argocd/app-of-apps/app-of-apps.yaml)
|
|
— specifically `directory.recurse: false`. The `apps/` folder contains both
|
|
Application manifests (`apps/demo.yaml`, `apps/metallb.yaml`) *and*
|
|
subfolders holding the real workload manifests those Applications point to
|
|
(`apps/demo/*.yaml`, `apps/metallb/*`). Non-recursive means the
|
|
app-of-apps only ever looks at files directly inside `apps/`, never inside
|
|
its subfolders — so it only ever sees Application definitions, and leaves
|
|
the actual workload manifests to the Application that owns them. Without
|
|
this, the app-of-apps would try to apply raw Deployments and Helm chart
|
|
files itself, duplicating and conflicting with what those Applications are
|
|
already doing.
|
|
|
|
## Every Application needs its own `repoURL`
|
|
|
|
Applications don't inherit `repoURL` from whatever applied them — each one
|
|
declares its own source repo independently. That means the placeholder
|
|
`https://github.com/YOUR_GITHUB_USERNAME/...` needs fixing in *every* file
|
|
that has it, not just the one you apply by hand:
|
|
|
|
```sh
|
|
grep -rl YOUR_GITHUB_USERNAME .
|
|
```
|
|
|
|
You should see three files: `argocd/app-of-apps/app-of-apps.yaml`,
|
|
`apps/demo.yaml`, and `apps/metallb.yaml`. Edit `spec.source.repoURL` in
|
|
each to point at your fork.
|
|
|
|
## Steps
|
|
|
|
1. Fix `repoURL` in all three files (above).
|
|
2. Commit and push — this part matters more than it looks: the
|
|
app-of-apps reads `apps/demo.yaml` and `apps/metallb.yaml` *from Git*,
|
|
not from your local edits, so they have to be pushed before you apply
|
|
anything.
|
|
```sh
|
|
git add argocd/app-of-apps/app-of-apps.yaml apps/demo.yaml apps/metallb.yaml
|
|
git commit -m "Point Applications at my fork"
|
|
git push
|
|
```
|
|
3. Apply the app-of-apps — the one manifest you apply by hand:
|
|
```sh
|
|
kubectl apply -f argocd/app-of-apps/app-of-apps.yaml
|
|
```
|
|
4. Watch it sync, then watch `demo` and `metallb` appear underneath it:
|
|
```sh
|
|
argocd app list
|
|
```
|
|
You should see `app-of-apps`, `demo`, and `metallb`, all `Synced` and
|
|
`Healthy` within a minute or two (MetalLB's own install takes a little
|
|
longer than the demo app).
|
|
5. Confirm the demo app is actually running:
|
|
```sh
|
|
kubectl -n demo get pods,svc
|
|
```
|
|
You'll notice the Service's `EXTERNAL-IP` column says `<pending>` —
|
|
that's expected. The demo app is a `LoadBalancer` Service, and nothing
|
|
hands those out real addresses on bare metal until MetalLB is
|
|
configured, which is exactly the next doc.
|
|
|
|
If `app-of-apps`, `demo`, and `metallb` all show `Synced`/`Healthy`, the
|
|
loop — Git → Argo CD → cluster — is already proven end to end; you just
|
|
can't see the demo app in a browser yet.
|
|
|
|
> **Ask Claude:** "walk me through exactly what happened, in order, from the
|
|
> moment I ran `kubectl apply -f argocd/app-of-apps/app-of-apps.yaml` to
|
|
> these three Applications existing." Making sure you can narrate that
|
|
> chain yourself is the real checkpoint for this doc — everything from
|
|
> here on is a variation on it.
|
|
|
|
## Checklist
|
|
|
|
- [ ] `repoURL` fixed in all three files listed above
|
|
- [ ] Fix committed and pushed
|
|
- [ ] `app-of-apps` applied and shows `Synced`/`Healthy`
|
|
- [ ] `demo` and `metallb` Applications appeared automatically, both
|
|
`Synced`/`Healthy`
|
|
- [ ] `kubectl -n demo get pods,svc` shows the demo app's Pod running
|
|
(Service `EXTERNAL-IP` still `<pending>` — expected for now)
|
|
|
|
Continue to [`docs/05-metallb.md`](05-metallb.md) to give the demo app —
|
|
and Argo CD's own UI — a real address.
|