Day 43 — Optional k3s
Day 43 — Optional k3s
Stage IV · ~4h (or ~30m skip path)
Goal: Either stand up a single-node k3s for literacy or skip cleanly with a written decision—Kubernetes is not required for Stage IV gate.
If you do not need k8s for work or curiosity this month, skip. A forced k3s install can destabilize a small lab (CPU, disk, conceptual overload) right before the gate.
Why this day exists
Some learners hit Stage IV and ask: “Should everything move to Kubernetes?” On NixOS the honest answer is usually no—not for a one-node lab slice with Caddy + Postgres.
This day exists to:
- Prevent FOMO-driven architecture
- Give a safe optional on-ramp if you truly need k8s APIs
- Practice skipping as a professional skill
Theory 1 — Decision rubric
| Choose skip if… | Choose k3s lite if… |
|---|---|
| Stage IV slice already works natively | You must learn kubectl for a job soon |
| Host is small on RAM/disk | You have ≥8–16 GB RAM lab resources |
| You have not finished secrets/proxy/DB | You already finished Days 33–42 solidly |
| Curiosity only, no time | You will actually deploy one manifest |
Default recommendation: skip, write the decision, move to Day 44.
Theory 2 — What k3s is
k3s is a lightweight Kubernetes distribution. On NixOS:
# modules/services/k3s.nix — only if you opt in
{
services.k3s = {
enable = true;
role = "server";
# extraFlags = [ "--write-kubeconfig-mode=0644" ]; # understand security implications
};
}Options and defaults change—read current module docs for 26.05 before enabling.
Implications:
| Area | Effect |
|---|---|
| Ports | API server, kubelet, etc.—firewall carefully |
| CPU/RAM | Non-trivial idle cost |
| Storage | Container images + etcd/sqlite data |
| Mental model | YAML/Helm vs NixOS modules |
Theory 3 — NixOS vs k8s configuration
| Concern | NixOS native | k3s |
|---|---|---|
| System packages | flake modules | images inside cluster |
| Host firewall | networking.firewall |
+ k8s Services/NodePorts |
| Secrets | sops-nix on host | also k8s Secrets (different!) |
| Rollback | generations | cluster state is separate |
Running k3s does not replace host flake discipline. You now have two control planes: NixOS + cluster.
Theory 4 — Single-node honesty
Single-node k3s is fine for learning APIs. It is not:
- Multi-AZ HA
- A reason to abandon PostgreSQL module for “cloud native” on one VPS without backups
- Automatically more secure
Theory 5 — Minimal literacy tasks (if enabled)
kubectl get nodes
- Deploy a tiny
Deployment+Service
- Expose via host proxy or port-forward—not random NodePort to the world
- Delete the workload
- Document how to disable
services.k3sand reclaim resources
Theory 6 — Skip path is first-class
Skipping is documented output:
# docs/k3s-decision.md
Decision: SKIP
Date: …
Reasons:
- Stage IV gate does not require k8s
- Host resources: …
- Will revisit if: job requirement / multi-service mesh needThat file passes the day.
Worked example — Disable / enable toggles
Prefer a role flag:
# modules/roles/server.nix
{ config, lib, ... }:
{
# imports = lib.optionals config.lab.enableK3s [ ../services/k3s.nix ];
}Or simply do not import k3s module. Avoid half-enabled clusters.
Lab S — Skip path (most readers)
- Write
docs/k3s-decision.mdwith SKIP + reasons
- Skim what k3s would open (ports, disk) from docs—no install
- List two future triggers to revisit
- Commit and proceed to Day 44
Time box: 30 minutes. Do not guilt-install.
Lab K1 — Opt-in: enable k3s
Only if rubric says go:
# add module; ensure plenty of disk
sudo nixos-rebuild switch --flake .#lab
sudo systemctl status k3s
sudo k3s kubectl get nodesKubeconfig location per module—follow module notes. Restrict access modes; do not world-chmod casually without understanding.
Lab K2 — Tiny workload
# manifests/hello.yaml — apply via kubectl
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello
spec:
replicas: 1
selector:
matchLabels: { app: hello }
template:
metadata:
labels: { app: hello }
spec:
containers:
- name: hello
image: docker.io/library/nginx:alpine
ports:
- containerPort: 80kubectl apply -f manifests/hello.yaml
kubectl get pods
kubectl port-forward deploy/hello 8088:80
curl -sS http://127.0.0.1:8088/ | headLab K3 — Tear-down plan
Document:
kubectl delete -f manifests/hello.yaml
# disable services.k3s; rebuild; confirm resources freedUpdate firewall if you opened API ports. Prefer API on localhost / VPN only.
Lab K4 — Compare control planes
Write a short note: what still belongs in the NixOS flake vs what you put in the cluster. If the answer is “everything in cluster,” push back—host still owns kernel, disks, proxy in many designs.
Theory 7 — When NixOS modules beat k8s on a single node
| Workload | Prefer |
|---|---|
| One reverse proxy + one DB | NixOS modules (Days 38–39) |
| Many identical microservices with k8s API dependency | k3s/k8s |
| GPU training cluster | Often specialized schedulers |
| Homelab “because CNCF” | Usually modules + compose/podman |
NixOS is already a declarative control plane for one machine. k3s adds a second control plane—justify it.
Theory 8 — Resource floor honesty
Single-node k3s wants real RAM/CPU. On a 2 GB VM:
k3s + postgres + caddy + prometheus = thrash
Skip is the professional choice when resources are tight before Day 44 gate.
Worked example — Feature flag module
# modules/services/k3s.nix
{ config, lib, ... }:
let cfg = config.myLab.k3s; in {
options.myLab.k3s.enable = lib.mkEnableOption "optional single-node k3s";
config = lib.mkIf cfg.enable {
services.k3s = {
enable = true;
role = "server";
# extraFlags = [ "--disable traefik" ]; # if you keep Day 38 proxy as edge
};
# networking.firewall.allowedTCPPorts = [ 6443 ]; # admin API — lock down
};
}Default enable = false on the gate host.
Lab K5 — API surface inventory (if enabled)
sudo k3s kubectl get nodes
sudo k3s kubectl get pods -A | head
ss -tlnp | grep -E '6443|10250' || trueAdd every k3s port to docs/ports.md with “cluster-internal / admin-only” labels.
Lab S2 — Skip artifact quality bar
Your skip note must include:
- Decision (skip)
- Resource or complexity reason
- What you use instead (NixOS modules / podman)
- Revisit criteria (“if job requires CKA lab…”)
A one-word “skipped” is not enough for the gate dossier.
Common gotchas
| Symptom / mistake | What to do |
|---|---|
| Guilt install before gate | Use skip path |
| Opened k8s API to world | Close; bind/firewall |
| Disk full of images | Prune; disable k3s |
| Secrets only in k8s, host ignored | Host sops still matters |
| Two proxies fighting | One ingress story |
| Undocumented cluster | Decision doc mandatory either way |
Checkpoint
If skipped
docs/k3s-decision.mdwith SKIP + reasons
- Future revisit triggers listed
- No half-installed k3s units left
If enabled
- Single-node ready;
kubectl get nodesworks
- One workload applied and removed or still intentional
- API not casually public
- Disable/tear-down plan written
- ports.md updated
Commit
git add .
git commit -m "day43: k3s optional — decision recorded"Write three personal gotchas before continuing.
Tomorrow
Day 44 — Stage IV gate: prove a secret-safe lab slice—proxy + secret + data/app path + firewall story—rebuildable from flake + keys.