Add kind + Traefik variant of the tutorial - #22
Open
SirHephaistos wants to merge 18 commits into
Open
Conversation
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>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.mdchapter 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 refslocalhost:5001/*, noimagePullSecrets, 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 inlinedb.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 tonode:20-alpine. 6 review questions.chapters/09-ingress-kind.md— Ingress on Traefik using the standardIngressresource (no nginx-specific annotations). Usestutorial.localhost(RFC 6761) so no DNS setup. Includes optional TLS via cert-manager. 6 review questions.Chapters NOT covered (use originals)
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 hostnamecvat.kube-publicwithcvat.localhost(the RFC 6761.localhostzone the kind variant uses throughout). Same/etc/hostsworkflow if your distro doesn't auto-resolve*.localhost.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 standardIngressresource the primary teaching surface (no controller-specific annotations).Conventions used
<N>-<topic>-kind.md. Originals untouched.helm upgrade --installso retries don't error with "cannot re-use a name".localhost:5001/*(kind's local-registry pattern).imagePullSecretsblocks (registry has no auth).<details>spoiler answers.postgres:16,node:20-alpine).