Skip to content

Add kind + Traefik variant of the tutorial - #22

Open
SirHephaistos wants to merge 18 commits into
Kapernikov:mainfrom
SirHephaistos:docs/chapter-3-kind-alternative
Open

Add kind + Traefik variant of the tutorial#22
SirHephaistos wants to merge 18 commits into
Kapernikov:mainfrom
SirHephaistos:docs/chapter-3-kind-alternative

Conversation

@SirHephaistos

@SirHephaistos SirHephaistos commented Apr 27, 2026

Copy link
Copy Markdown

Summary

Umbrella PR adding a kind + Traefik variant of the tutorial, intended for learners whose host machine already has services running on ports 80/443 (existing reverse proxy, hosted site, etc.). The variant runs Kubernetes inside Docker via kind and uses Traefik as the cluster ingress controller. Parallel *-kind.md chapter files are added alongside the originals; the originals are untouched.

Chapters in this PR

  • chapters/03-install-kind.md — kind cluster + local registry + Traefik ingress + cert-manager with self-signed CA. 6 review questions w/ spoilers (namespace, ClusterIssuer vs Certificate, ingress controller vs Ingress object, registry push/pull asymmetry, apply vs create, resource-limit hierarchy).
  • chapters/04-kubernetes-kind.md — Deployments + StatefulSet + ConfigMap. Image refs localhost:5001/*, no imagePullSecrets, postgres pinned to :16, kind storage notes. 6 review questions.
  • chapters/05-service-kind.md — Services. Pure k8s concept, near-identical to original; 6 review questions (pod IP churn, ClusterIP as virtual IP, Service types, headless services, selector matching, why no port mapping).
  • chapters/06-helm-kind.md — Helm chart. Same 2-bug puzzle as the original, full inline db.yaml, explicit file-creation vs file-edit instructions. 6 review questions.
  • chapters/07-skaffold-kind.md — Skaffold with -d localhost:5001. 6 review questions (build/run/dev/delete, setValueTemplates, inputDigest tagging, why registry stays out of skaffold.yaml, sync vs rebuild, Ctrl-C vs kill -9).
  • chapters/08-frontend-production-kind.md — multi-stage Dockerfile + nginx + Skaffold profiles. Node base bumped to node:20-alpine. 6 review questions.
  • chapters/09-ingress-kind.md — Ingress on Traefik using the standard Ingress resource (no nginx-specific annotations). Uses tutorial.localhost (RFC 6761) so no DNS setup. Includes optional TLS via cert-manager. 6 review questions.

Chapters NOT covered (use originals)

  • Chapter 10 (10-third-party-software.md) — installing a third-party helm chart (CVAT) and intro to operators (Crunchy Data Postgres). Pure k8s concepts, works as written on kind. One adjustment: replace the chapter's hostname cvat.kube-public with cvat.localhost (the RFC 6761 .localhost zone the kind variant uses throughout). Same /etc/hosts workflow if your distro doesn't auto-resolve *.localhost.
  • Chapter 11 (11-gitops.md) — Flux gitops. Completely cluster-distro-agnostic. Works on kind unchanged.

Why

The current chapter 3 assumes the host has ports 80/443 free; many learners who already host other services don't. The original chapter 3 also uses k3s + nginx-ingress with nginx-specific annotations (use-regex, etc.) that don't transfer to other ingress controllers. This variant fixes both: kind cluster doesn't touch host ports 80/443, and Traefik makes the standard Ingress resource the primary teaching surface (no controller-specific annotations).

Conventions used

  • File naming: <N>-<topic>-kind.md. Originals untouched.
  • Helm installs use helm upgrade --install so retries don't error with "cannot re-use a name".
  • Image refs: localhost:5001/* (kind's local-registry pattern).
  • No imagePullSecrets blocks (registry has no auth).
  • Each chapter ends with a "Review questions" section using collapsible <details> spoiler answers.
  • Image versions pinned (e.g. postgres:16, node:20-alpine).

SirHephaistos and others added 8 commits April 27, 2026 14:07
Drop-in replacement for chapters/03-install-k3s.md aimed at machines
that already have other services on host ports 80/443. Uses kind
(Kubernetes in Docker) with the official local-registry sidecar
pattern, ingress on 8080/8443, and an unchanged cert-manager setup.
Chapters 4+ work unchanged aside from swapping the registry name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the nginx-ingress install with a Traefik helm install,
configured for kind's single-node control-plane + extraPortMappings
setup. Same Kubernetes `Ingress` resource concept, no nginx-specific
annotations carried through to later chapters — keeps the YAML in
chapters 4-9 portable across controllers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Without the sleep, the container exits before kubectl finishes its TTY
attach handshake, producing a cosmetic "couldn't attach... falling back
to streaming logs" warning. Adding `sleep 1` to the echo command makes
the verification output clean while not affecting correctness.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Helm's --set auto-converts unquoted true/false into Go booleans, but
Kubernetes' nodeSelector schema requires string values. Without
--set-string the install fails with:

    Deployment in version "v1" cannot be handled as a Deployment:
    json: cannot unmarshal bool into Go struct field
    PodSpec.spec.template.spec.nodeSelector of type string

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Plain `helm install` errors with "cannot re-use a name that is still
in use" if a previous attempt left a release in helm's history (even
a failed one). `helm upgrade --install` is the standard idempotent
pattern: install on first run, update on subsequent runs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Expands chapter 3 alt §7 with a short conceptual intro explaining
  what cert-manager is (operator that issues/renews TLS certs from a
  Kubernetes-native API), the Issuer/ClusterIssuer/Certificate object
  triad, and how the result lands in a regular tls Secret.
- Switches the helm install to `helm upgrade --install` (idempotent on
  retry) and replaces the deprecated `--set installCRDs=true` with
  `--set crds.enabled=true` (the new flag in cert-manager v1.15+).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Parallel kind-flavored versions of chapters 4+ will exist alongside
the originals and already use the right image refs, hostnames, etc.,
so a translation table in chapter 3 is redundant. Removed section 9
(translation table + skaffold -d note); renumbered the cleanup
section to 9.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Each end-of-chapter question now has a collapsible <details> block
with a teaching-style answer: namespaces, ClusterIssuer vs Certificate,
ingress controller vs Ingress object, push/pull asymmetry through a
registry, and apply vs create semantics. Lets learners self-check
without revealing answers until they've tried.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@SirHephaistos SirHephaistos changed the title Add chapter 3 alternative: install cluster with kind instead of k3s Add kind + Traefik variant of the tutorial May 21, 2026
SirHephaistos and others added 10 commits May 21, 2026 14:10
Adds a sixth review question with a detailed spoiler answer covering:
- Pod-level resources.requests vs resources.limits (scheduler vs cgroups)
- Namespace-level ResourceQuota and LimitRange (API admission)
- Node-level allocatable capacity (kubelet)
- Cluster-level aggregation (sum of nodes, not a separate knob)
- Who enforces what at which stage
- What happens when you over-declare at each level
  (Pending vs admission-rejected vs legal oversubscription)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drop-in replacement for chapters/04-kubernetes.md aimed at learners
using the kind cluster from 03-install-kind.md. Differences from the
original:

- Image refs use `localhost:5001/...` (the kind local-registry sidecar)
- No `imagePullSecrets` block (registry is plain HTTP, no auth)
- Skips the `kubectl create secret docker-registry registry-creds` step
- Pins `postgres:16` instead of `:latest` (data-compat reason explained)
- Adds notes on where PVC data actually lives on kind
  (local-path-provisioner in the node container's filesystem)

Includes six review questions with collapsible spoiler answers covering
scaling semantics for Deployment vs StatefulSet, port-forward, pod
deletion vs StatefulSet deletion (data persistence), latest vs pinned
images, rollout ordering rationale, and the localhost-from-inside-pod
gotcha.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drop-in replacement for chapters/05-service.md. Service is a pure
Kubernetes concept so the content is near-identical to the original;
this variant exists to keep chapter numbering parallel for the
kind+Traefik track and to add six spoiler-answer review questions:

- Why pod IPs change on every restart (CNI pool + per-node CIDR)
- What the Service ClusterIP actually is (virtual IP, kube-proxy
  iptables/IPVS rewrite — not a real listener)
- Service types compared (ClusterIP / NodePort / LoadBalancer /
  ExternalName) with practical "which one" guidance
- Headless Services (`clusterIP: None`) for per-pod DNS and
  client-side load balancing
- Selector matching: zero matches vs NotReady pods, plus
  diagnosing via `kubectl get endpoints`
- Why Services don't need host:container port mappings the way
  `docker -p` does

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drop-in replacement for chapters/06-helm.md. Walks through creating
a Helm chart for the demo app, adapted for the kind+Traefik track:

- Image refs use `localhost:5001/...`
- No `imagePullSecrets` block in the Deployment template
- No registry-creds secret step
- Uses `helm upgrade --install` as the idiomatic re-runnable command
- Preserves the original chapter's two-deliberate-bugs puzzle in the
  API yaml (selector/labels mismatch, values-section name mismatch),
  with the same collapsible "click to reveal" hint
- Database StatefulSet becomes optional via `db.enabled` flag

Adds six spoiler-answer review questions covering: repository/tag
split rationale, helm upgrade --install semantics, helm template use
cases, replicas change vs rolling update, manual kubectl edit being
overwritten by Helm, and what happens when you delete Helm's release
secrets directly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The original wording was ambiguous about which files already exist
and which ones the learner should create vs edit. Tightened §2, §3,
and §6:

- §2: "Now create a new file myapp/templates/frontend.yaml" instead
  of "Now move the frontend deployment yaml..."
- §3: explicit "open the already-existing myapp/values.yaml (you
  emptied it in §2)" and "open the already-existing
  myapp/templates/frontend.yaml (the file you wrote in §2)". Also
  added an explicit `helm uninstall` step so the user doesn't fight
  a stale install when re-running.
- §6: "Create a new file myapp/templates/db.yaml" and "open the
  already-existing myapp/values.yaml and add" — disambiguates the
  file-creation chain.

No content changes, only clarifications.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Earlier "already-existing" phrasing was clumsy and didn't say WHERE
in each file the new content should go. Now uses a clearer pattern:

- §3: replaces "rewrite the whole file" with a single-line find/replace
  ("find this line, replace it with that line"), with the full file
  available as a collapsible sanity-check rather than the primary
  instruction.
- §4 (Service for frontend): "At the end of the file, append..."
- §5 (backend): "Add a backend block alongside the frontend one — order
  doesn't matter, Helm reads it all into one map", and shows the file
  end-state instead of vague "add this".
- §6 (database): numbered list of which YAMLs go in the new file,
  explicit "alongside the existing frontend: and backend: blocks",
  explicit "the entire content of the file" for the conditional wrap.

No content changes, only clearer where-to-put-what instructions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previous wording told the learner to "paste the three YAMLs from
chapter 4" which forced flipping between chapters. Now §6 gives the
complete file content (ConfigMap + StatefulSet + Service already
wrapped in the if/end conditional) so the learner can copy-paste a
single block without bouncing between files.

Also flips the order so values.yaml comes before db.yaml — by the time
the learner pastes the templated file they already have the
db.enabled value defined.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drop-in replacement for chapters/07-skaffold.md. Same skaffold.yaml
content as the original (registry name lives in the -d flag, not in
the yaml), with kind-specific notes:

- `-d localhost:5001` flag throughout (vs `-d registry.kube-public`)
- Note that Skaffold uses bare names `frontend`/`api`, not the
  `myfrontend`/`myapi` from chapters 4-6 (different image refs
  belong to different ownership boundaries)
- setValueTemplates wired to `backend.image.*` to match the values
  structure canonicalized in the chapter 6 kind variant
- Explanation that `default .Chart.AppVersion` only matters when
  Skaffold isn't driving the deploy

Adds six spoiler-answer review questions: build/run/dev/delete
distinction, setValueTemplates and IMAGE_REPO_/IMAGE_TAG_ wiring,
why inputDigest beats latest/gitSHA for dev loops, why the registry
name is outside skaffold.yaml, sync vs rebuild semantics (and when
sync is wrong), and Ctrl-C vs kill -9 cleanup behaviour.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drop-in replacement for chapters/08-frontend-production.md. Walks
through creating a multi-stage production Dockerfile for the frontend
(node build stage + nginx serve stage), the matching nginx default.conf
with SPA `try_files` fallback, and a Skaffold profile that swaps in
the dev Dockerfile when `-p dev` is passed.

Kind-specific bits: `-d localhost:5001` flag in all skaffold commands;
node base image bumped from `node:14-alpine` to `node:20-alpine` to
match the chapter 2 / Dockerfile.dev fix.

Adds six spoiler-answer review questions covering: where the dev vs
prod image size difference comes from, the SPA try_files trick,
JSON-Patch index fragility (and the strategic-merge alternative),
profile cleanup gotcha when release names diverge, adding a third
profile (staging with replicas override), and PID 1 / signal handling
implications of running webpack-dev-server vs nginx as the container's
entry point.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drop-in replacement for chapters/09-ingress.md. Uses the standard
Kubernetes Ingress resource with `ingressClassName: traefik` and
plain `pathType: Prefix` rules — no nginx-specific annotations like
`nginx.ingress.kubernetes.io/use-regex`, keeping the YAML portable
across ingress controllers.

Key adaptations:

- Hostname: `tutorial.localhost` (RFC 6761 reserved zone, resolves
  to 127.0.0.1 without DNS setup) instead of duckdns
- Access via http://tutorial.localhost:8080 (kind's extraPortMappings
  surface ingress 80 -> host 8080)
- Path matching: four separate prefix rules instead of one regex
  rule, with a note that longest-prefix wins regardless of YAML order
- IngressClass mechanism (k8s 1.18+) instead of the deprecated
  kubernetes.io/ingress.class annotation
- Optional §8 promotes the Ingress into the Helm chart with
  ingress.enabled / ingress.host values for env-specific overrides
- Optional §9 adds TLS termination via the cert-manager ClusterIssuer
  from chapter 3

Preserves the original chapter's HelloWorld.vue URL gotcha — the
hardcoded `localhost:9999` URL can't be fixed by Helm templating
because the URL is frozen at docker-build time, not deploy time.
Fix is relative URLs in the JS.

Adds six spoiler-answer review questions: Ingress/Service namespace
coupling, path-order independence (longest-prefix wins), why TLS
terminates at the edge not at the pod, the build-time vs deploy-time
vs runtime URL trap, IngressClass mechanics, and how to swap from
Traefik to nginx-ingress without rewriting YAML.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant