user@homelab:~/docs$ cat gitops.md
CI/CD a GitOps
git push → GitHub Actions → GHCR → Flux → k3s: nasazení bez přístupu do clusteru zvenku
# obsah
Jak nasazení funguje
- Mac: změna v repozitáři,
git commitagit push. - GitHub Actions: sestaví image webu, nahraje ho do GHCR (GitHub Container Registry) a zapíše jeho nový tag do
k8s/apps/homelab-docs/kustomization.yaml. - Flux v clusteru každou minutu kontroluje repozitář. Nový commit stáhne a aplikuje na cluster.
- Kubernetes stáhne nový image ze soukromého GHCR a vymění pod (rolling update bez výpadku).
- Cloudflare Tunnel posílá provoz z internetu do Traefiku a ten na web.
| Fáze | Typická doba |
|---|---|
| build v GitHub Actions (s cache) | 30–60 s |
| Flux si všimne nového commitu | do 1 min |
| rolling update podu | 10–20 s |
od git push po web | ~1,5–3 min |
Proč GitOps (pull model)
| Push (CI nasazuje do clusteru) | Pull (GitOps, Flux) | |
|---|---|---|
| Kdo mění cluster | CI pipeline zvenku | agent uvnitř clusteru |
| Přístup do clusteru zvenku | CI potřebuje kubeconfig nebo VPN | žádný |
| Otevřené porty | často ano | žádné, Flux jen stahuje |
| Zdroj pravdy | stav posledního běhu pipeline | git: co je v gitu, to běží |
| Ruční zásah v clusteru | zůstane, dokud ho někdo nevrátí | Flux ho při další synchronizaci vrátí podle gitu |
| Rollback | znovu spustit starší pipeline | git revert |
Pro homelab za NAT bez veřejné IP je pull model přirozená volba. GitHub do domácí sítě nevidí a vidět nemusí.
Repozitář
Soukromý repozitář homelab na GitHubu:
homelab/
├── .github/workflows/docs-site.yml # CI: build → GHCR → bump tagu
├── docs-site/ # Hugo web (CS/EN), Dockerfile, nginx
├── k8s/
│ ├── apps/homelab-docs/ # Namespace, Deployment, Service, Ingress, kustomization
│ └── infra/cloudflared/ # Cloudflare Tunnel konektor
└── clusters/homelab/
├── flux-system/ # komponenty Fluxu (vygeneroval bootstrap)
├── infra.yaml # Flux Kustomization → ./k8s/infra
└── apps.yaml # Flux Kustomization → ./k8s/apps (dependsOn: infra)CI: GitHub Actions
Workflow .github/workflows/docs-site.yml se spustí při pushi do main, pokud se změnilo něco v docs-site/ nebo samotný workflow:
| Krok | Co dělá |
|---|---|
| Checkout | stáhne repozitář |
| Compute image name and tag | ghcr.io/<owner>/homelab-docs, tag YYYYMMDD-<číslo běhu>-<sha> |
| Log in to GHCR | přihlášení vestavěným GITHUB_TOKEN, žádný vlastní token |
| Build and push | multi-stage build (Hugo → nginx), cache ve GitHub Actions; tag a commit jdou jako build args do patičky webu |
| Bump image in kustomization | sed zapíše nový newName/newTag, commit jako github-actions[bot] a push |
- Tag nese původ:
20261029-2-bd47ef2= datum, číslo běhu a commit, ze kterého image vznikl. - Žádná smyčka: push provedený pomocí
GITHUB_TOKENnespouští další workflow a změna vk8s/stejně neodpovídá filtrupaths. - Minimální práva:
contents: write(commit tagu) apackages: write(push image), nic víc. - Verze akcí:
actions/checkout@v5,docker/login-action@v4,docker/setup-buildx-action@v4,docker/build-push-action@v7, všechny na Node.js 24.
Flux
Instalace CLI na server a kontrola předpokladů:
curl -s https://fluxcd.io/install.sh | sudo bash
flux check --preBootstrap nainstaluje Flux do clusteru a propojí ho s repozitářem. Potřebuje jednorázový fine-grained token (jen repozitář homelab, Administration a Contents: Read and write, platnost 7 dní). Token se načte bez zobrazení, řádek s read se spouští samostatně:
read -rsp "GitHub token: " GITHUB_TOKEN; echoexport GITHUB_TOKEN
flux bootstrap github --owner=<github-user> --repository=homelab --branch=main --path=clusters/homelab --personal
unset GITHUB_TOKENBootstrap:
- nainstaluje kontrolery do
flux-system(source, kustomize, helm, notification), - vytvoří v repozitáři deploy klíč jen pro čtení, kterým se Flux dál přihlašuje,
- nahraje vlastní manifesty do
clusters/homelab/flux-system/.
Po bootstrapu se token smaže. Na běžný provoz ho Flux nepotřebuje.
Kustomizations
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
dependsOn:
- name: infra
interval: 5m
retryInterval: 1m
path: ./k8s/apps
prune: true
wait: true
sourceRef:
kind: GitRepository
name: flux-systemdependsOn: infra: aplikace se nasadí až po infrastruktuře. Hláškadependency 'flux-system/infra' is not readypo novém commitu je proto normální a za chvíli zmizí.prune: true: co se smaže z gitu, smaže Flux i z clusteru.wait: true: Kustomization jeReadyaž ve chvíli, kdy jsou všechny objekty zdravé (např. Deployment dokončil rollout).- Existující objekty (web, cloudflared) Flux při prvním běhu převzal bez restartu, protože jejich definice v gitu odpovídala stavu v clusteru.
Soukromý registr image
Repozitář i image jsou soukromé. Kubernetes proto image stahuje s tokenem uloženým jako Secret.
- GitHub → Developer settings → Personal access tokens → Tokens (classic): scope jen
read:packages, platnost 1 rok. GHCR podporuje jen klasické tokeny. - Secret v clusteru (řádek s
readsamostatně):
read -rsp "GHCR token: " GHCR_TOKEN; echokubectl -n homelab-docs create secret docker-registry ghcr-pull \
--docker-server=ghcr.io --docker-username=<github-user> --docker-password="$GHCR_TOKEN"
unset GHCR_TOKEN- Deployment na Secret odkazuje:
spec:
template:
spec:
imagePullSecrets:
- name: ghcr-pullKontrola uloženého tokenu bez jeho vypsání:
kubectl -n homelab-docs get secret ghcr-pull -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d \
| python3 -c 'import json,sys,base64; a=json.load(sys.stdin)["auths"]["ghcr.io"]; u,p=base64.b64decode(a["auth"]).decode().split(":",1); print(u, len(p), p[:4])'Identita v gitu
Commity v osobním repozitáři mají jinou identitu než pracovní. Git ji vybírá automaticky podle složky:
git config --file ~/.gitconfig-personal user.name "<jméno>"
git config --file ~/.gitconfig-personal user.email "<id>+<user>@users.noreply.github.com"
git config --global 'includeIf.gitdir:~/Projects/.path' '~/.gitconfig-personal'- všechno ve
~/Projects/používá osobní identitu, pracovní config se nemění, - e-mail je noreply adresa GitHubu (Settings → Emails → Keep my email addresses private), takže skutečný e-mail se v historii neobjeví,
- Block command line pushes that expose my email je pojistka: GitHub odmítne push se skutečným e-mailem.
Kontrola v repozitáři:
git config --show-origin user.emailKaždodenní práce
cd ~/Projects/homelab
git pull # stáhnout commity od CI a Fluxu
# … úprava obsahu v docs-site/site/content/{en,cs}/ …
git commit -am "docs: …" && git push
gh run watch # průběh builduflux get kustomizations --watch # Flux aplikuje novou reviziLokální náhled bez nasazení: cd docs-site/site && hugo server → http://localhost:1313
Kontrola a návrat
flux get kustomizations
flux reconcile kustomization apps --with-source # synchronizovat hned, nečekat na interval
kubectl -n homelab-docs get deploy homelab-docs -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'Návrat k předchozí verzi přes git, aby se změna neztratila při další synchronizaci:
git log --oneline -- k8s/apps/homelab-docs/kustomization.yaml
git revert <commit-release>
git pushTajemství
V repozitáři nejsou žádná tajemství. Vznikají jednou ručně přímo v clusteru:
| Secret | Namespace | K čemu | Obnova |
|---|---|---|---|
tunnel-token | cloudflared | Cloudflare Tunnel | při úniku Rotate token v Cloudflare |
ghcr-pull | homelab-docs | stažení soukromého image | jednou ročně (expirace tokenu) |
flux-system | flux-system | deploy klíč Fluxu (jen čtení) | vytvořil bootstrap |