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
+149
View File
@@ -0,0 +1,149 @@
# 00 — What is all this?
You're comfortable with servers, SSH, config files, and the command line.
You've probably never touched Kubernetes. This doc gives you the mental
model before you type a single command — everything after this builds on
it.
## The problem this stack solves
Say you run a few services on a server. The traditional way: SSH in,
install things, edit config files by hand, restart services, and remember
what you did (or don't, and regret it later). It works, but nothing
records *why* the server looks the way it does — the truth lives only on
that one machine, in your head, and in shell history if you're lucky.
This stack solves that by making a **Git repository** the single source of
truth for "what should be running," and using software to continuously make
the real cluster match it. You stop making changes by hand. You edit a file,
push it, and a robot applies it for you — the same way every time, with a
record of every change in `git log`.
## The five pieces, in plain terms
**Kubernetes** is software that runs on one or more machines and manages
containers for you: starts them, restarts them if they crash, and moves
them to a different machine if one dies (once you have more than one — see
[`docs/08-growing-up.md`](08-growing-up.md)). You describe the end state you
want ("run one copy of this"); Kubernetes' job is to make that true and keep
it true. This is what people mean by **declarative**: you state the
destination, not the steps to get there.
**Talos Linux** is the operating system your machine runs. It's built for
exactly one purpose — running Kubernetes — and nothing else. There's no SSH,
no shell, no package manager to fiddle with by hand. You configure it by
sending it a config file over the network (a "machine config"), the same
declarative idea as Kubernetes itself, just one layer lower. Less to
install, less to secure, less that can drift out of a known state.
**Argo CD** is the piece that watches a Git repository — this one — and
continuously makes the cluster match whatever it finds there. This is
called **GitOps**: Git isn't just where your config *lives*, it's where
change *happens*. You don't run `kubectl apply` for day-to-day changes;
you edit a file, commit, push, and Argo CD does the applying.
**App of apps** is the pattern this repo uses to organize what Argo CD
manages. Rather than one Application resource pointing at everything, one
Application — the app-of-apps — watches a folder (`apps/`) full of *other*
Application definitions, each pointing at its own small piece. Add a new
app by adding a new file — see
[`docs/07-adding-new-apps.md`](07-adding-new-apps.md).
**MetalLB** solves a specific problem: on a cloud provider, asking for a
public IP address for a service is one API call away; on your own bare
metal machine, nothing hands those out automatically. MetalLB runs inside
the cluster and fills that gap, so apps here can get a real, dedicated IP
address on your network instead of an awkward fixed port. It's pre-installed
in this repo, not a "next step" — see [`docs/05-metallb.md`](05-metallb.md).
## Architecture — the shape of what you'll build
```
┌─────────────────────────┐
│ Git repository (this │
│ repo, on GitHub) │
│ │
│ apps/demo.yaml │
│ apps/demo/*.yaml │──┐
│ apps/metallb.yaml │ │ watched by
│ apps/metallb/* │ │
└─────────────────────────┘ │
▼
┌───────────────────┐
│ Argo CD │ runs inside the cluster
│ (installed once, │
│ imperatively) │
└─────────┬──────────┘
│ applies manifests
▼
┌─────────────────────────────┐
│ Talos Linux node │
│ (control plane + worker, │
│ single node) │
│ │
│ ┌────────────┐ ┌──────────┐ │
│ │demo │ metallb- │ │
│ │namespace: │ system: │ │
│ │nginx Pod ⇄ │ hands out │ │
│ │Service │ a real IP │ │
│ │(LoadBalancer)◄─address to │ │
│ │ │ that Service│ │
│ └────────────┘ └──────────┘ │
└─────────────────────────────┘
```
Everything below the Git repo is a *consequence* of what's in Git. If the
box labelled "Talos Linux node" burned to the ground and you rebuilt it from
scratch, replaying this repo against it would recreate everything except the
raw OS bootstrap (docs/02), the one-time Argo CD install (docs/03), and the
handful of network-specific addresses you choose in docs/05 — all
deliberately kept outside the loop, since something has to start it and
nothing in Git can know your LAN's IP layout for you.
## The reconciliation loop
This is the mechanism that makes GitOps actually work, not just a nice
diagram. Argo CD repeats this forever, by default every few minutes (and
instantly, if you ask it to):
```
┌────────────────────────────────────────────────┐
│ │
│ 1. Read desired state from Git │
│ │ │
│ ▼ │
│ 2. Read actual state from the cluster │
│ │ │
│ ▼ │
│ 3. Diff them │
│ │ │
│ ┌────┴────┐ │
│ │ │ │
│ no diff diff found │
│ │ │ │
│ │ ▼ │
│ │ 4. Apply the change so the │
│ │ cluster matches Git │
│ │ │ │
│ └────┬─────┘ │
│ ▼ │
│ wait, then repeat │
│ │
└────────────────────────────────────────────────┘
```
"Reconciliation" just means step 3 and 4: noticing a difference between what
should exist and what does, and fixing it. This loop is also what makes
Argo CD self-healing — if someone changes the cluster by hand (`kubectl edit`
during an emergency, say), the very next loop notices the drift from Git and
reverts it. Git wins, always. That's the whole point.
> **Ask Claude:** before moving on, try asking "why does Git winning matter —
> what goes wrong without it?" It's worth being able to answer in your own
> words before you start building.
## What's next
Continue to [`docs/01-prerequisites.md`](01-prerequisites.md), or jump
straight to [`docs/checklist.md`](checklist.md) if you'd rather work through
a condensed step-by-step list with Claude alongside you.