Files
vps-k8s/docs/04-app-of-apps.md

138 lines
6.3 KiB
Markdown

# 04 — The app-of-apps pattern
> **Current Hetzner deployment:** Application sources point to the public
> `https://tea.ahmza.com/ahmza.com/vps-k8s.git` repository on `main`.
> The app-of-apps explicitly selects only `demo.yaml` via
> `spec.source.directory.include`; MetalLB and Longhorn remain unused
> reference manifests, not automatic installs. The demo uses `ClusterIP`
> (an internal-only Service address), reached via port-forward rather than
> a public load balancer. In the steps below, commit and push the changed
> manifests before applying the parent Application, and expect only `demo`
> beneath it. Skip the LAN MetalLB/Longhorn phases for this setup. When
> adding another app later, extend the parent's `include` filter too—for
> example, `'{demo.yaml,new-app.yaml}'`. The three-app walkthrough below
> describes the original LAN template, not this deployment.
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 apps/longhorn.yaml (more, later)
│ │ │
▼ ▼ ▼
apps/demo/*.yaml apps/metallb/* apps/longhorn/* (manifests / Helm charts)
```
Four layers wide, each watching the next. You only ever touch the bottom
two — the app-of-apps is set-and-forget. Notice `apps/metallb.yaml` and
`apps/longhorn.yaml` are already here — see
[`docs/05-metallb.md`](05-metallb.md) and
[`docs/06-longhorn.md`](06-longhorn.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`,
`apps/longhorn.yaml`) *and* subfolders holding the real workload manifests
those Applications point to (`apps/demo/*.yaml`, `apps/metallb/*`,
`apps/longhorn/*`). 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 four files: `argocd/app-of-apps/app-of-apps.yaml`,
`apps/demo.yaml`, `apps/metallb.yaml`, and `apps/longhorn.yaml`. Edit
`spec.source.repoURL` in each to point at your fork.
## Steps
1. Fix `repoURL` in all four files (above).
2. Commit and push — this part matters more than it looks: the
app-of-apps reads `apps/demo.yaml`, `apps/metallb.yaml`, and
`apps/longhorn.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 apps/longhorn.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`, `metallb`, and `longhorn` appear
underneath it:
```sh
argocd app list
```
You should see `app-of-apps`, `demo`, `metallb`, and `longhorn`, all
`Synced` and `Healthy` within a few minutes (MetalLB and Longhorn's own
installs take longer than the demo app — Longhorn especially, since it
has to find and prepare a disk on every node).
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`, `metallb`, and `longhorn` 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 four 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 four files listed above
- [ ] Fix committed and pushed
- [ ] `app-of-apps` applied and shows `Synced`/`Healthy`
- [ ] `demo`, `metallb`, and `longhorn` Applications appeared
automatically, all `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.