Phase 3 — K3s¶
Objective¶
Set up a K3s (lightweight Kubernetes distribution) management cluster on 3 nodes (control plane on deborah, workers on invincible and oliver), with Argo CD controller ready for GitOps.
What You Learn¶
K3s as a lightweight bare-metal Kubernetes: server/agent, CNI (Container Network Interface) configurable (Flannel by default in K3s; in this homelab, Calico), disable Traefik/ServiceLB to control the Gateway and LoadBalancer via GitOps later.
Learn this phase
In simple terms: Kubernetes is the manager of a restaurant: you tell it "I want three cooks working at all times" and it takes care of replacing any that are missing. The control plane is the management (decides) and the workers are the kitchen (work). K3s is a version of Kubernetes designed for small teams.
Key concepts
- Node: a machine in the cluster; can be control plane (decides) or worker (executes).
- Pod: the smallest unit of execution: one or more containers that live together.
- Deployment: declares how many copies of a pod should exist and keeps them running.
- CNI: the component that provides network to pods; here, Calico.
kubectland kubeconfig: the command-line tool and the file with the cluster access data.
Practical challenge (safe): create a test deployment, delete one of its pods, and watch Kubernetes replace it.
kubectl get nodes -o wide
kubectl create deployment demo --image=nginx --replicas=2
kubectl get pods -l app=demo
kubectl delete pod -l app=demo --wait=false
# aparecen pods nuevos
kubectl get pods -l app=demo
# limpieza
kubectl delete deployment demo
What is the difference between a pod and a deployment?
A pod is a running copy; a deployment is the rule that says how many copies should exist and recreates them if they disappear.
What happens if a worker fails?
Kubernetes detects that the pods have stopped responding and recreates them on other healthy nodes.
Why is a CNI required?
Because Kubernetes does not come with its own pod network in this setup: the CNI provides addresses and inter-node communication.
Have questions? Use the Learn with AI button next to each title, or the page Learn.
Stack for this phase¶
flowchart TB
subgraph cp [Control_plane]
DEB[deborah_server]
VIP[kube_vip_opcional]
end
subgraph workers [Workers]
INV[invincible_agent]
NOL[oliver_agent]
end
DEB --> API[API_6443]
VIP -.-> API
INV --> API
NOL --> API
DEB --> ArgoCD[ArgoCD_controller]
- K3s — Management cluster; Phase 3.
- CNI — Flannel (default), Canal, Calico, Cilium; Phase 3.
- kube-vip — Optional VIP (virtual IP)
:6443; fork Phase 3. - Argo CD — Controller bootstrap; Phase 3 → GitOps Phase 4.
- Ansible —
playbook-k3s.yml.
Further reading: Playbook options · Ansible role catalog · K3s manual (legacy)
This decision in other environments
A single control plane on deborah, with the default K3s datastore (SQLite), and two workers. It is simple, uses little memory, and is sufficient for learning; if deborah goes down, the pods continue running but you cannot make changes until it comes back up. If you want high availability later, K3s can switch to embedded etcd.
- Benefits: Kubernetes provides a unique way to deploy, scale, and recover applications on any server; a small team operates many services with the same manifests, and K3s achieves this with a lightweight binary that installs and updates in minutes.
- High availability: three control planes are used (an odd number to maintain quorum in etcd), with embedded or external etcd, behind a virtual IP or a load balancer. SQLite does not work with multiple servers.
- Zero-downtime updates: with multiple control planes, one is updated at a time and the API never goes down.
- Commercial support: it is common to pay for a distribution with support and security patches, such as RKE2 within SUSE Rancher Prime or Red Hat OpenShift.
- When K3s is suitable: at the edge (stores, branches, factories, IoT), where there is little hardware, little technical staff on-site, or intermittent connectivity. There, a small and easy-to-replace cluster is worth more than a large one.
| Cloud | Equivalent service |
|---|---|
| GCP | Google Kubernetes Engine (GKE) |
| AWS | Amazon Elastic Kubernetes Service (EKS) |
| Azure | Azure Kubernetes Service (AKS) |
| Oracle Cloud (OCI) | OCI Kubernetes Engine (OKE) |
Gain a managed control plane provided by the cloud provider: high availability, patches, and SLA without you having to manage etcd. You lose control over that control plane, pay for the service, and become more tied to that cloud's integrations.
Before You Start¶
- Phases 0, 1, and 2 completed.
- Healthy Incus cluster (optional but recommended before CAPN —Cluster API Provider for Incus—).
Run¶
Playbook options are activated in
group_vars/incus_cluster/k3s_install.yml,
not via command line. Today, Argo CD
(gitops_argocd), node labels (node_labels), and local kubeconfig
(fetch_kubeconfig) are enabled, with CNI Calico.
Review the file and run:
Do not use -e k3s_install.<opción>=…
Ansible ignores it: k3s_install is a dictionary and a key with
dots in -e does not modify it (the default CNI would be installed without warning). Passing -e
with JSON is worse: it replaces the entire dictionary and loses core.version,
cluster_cidr, etc. That's why the playbook fails in both cases. To change the CNI use
-e k3s_cni=canal (or edit the file); see
playbook options.
Manual curl steps in K3s — manual installation. Use only if Ansible is not available; the supported path is the playbook.
Options¶
| If you need… | In k3s_install.yml |
Today | Doc |
|---|---|---|---|
| Stable API VIP | kube_vip.enabled: true |
disabled | playbook-options |
| Longhorn/arch labels | node_labels.enabled: true |
enabled | playbook-options |
| Local kubeconfig | fetch_kubeconfig.enabled: true |
enabled | playbook-options |
| OS hardening | os_hardening.enabled: true |
disabled | playbook-options |
| Decision | Option A (default) | Option B |
|---|---|---|
| K3s API | Name k3s.homelab.local (variable k3s_api_endpoint) to the control plane IP, today deborah 192.168.20.5 |
kube-vip 192.168.20.4 |
| CNI | Calico (configured in this repo) | Flannel vxlan (default of K3s), Canal or Cilium (k3s_install.core.cni) |
CNI¶
| CNI | Variable | Notes |
|---|---|---|
| Flannel (default of K3s) | cni: flannel |
Embedded in K3s; flannel_backend: vxlan or host-gw |
| Canal | cni: canal |
Flannel overlay + Calico policies; role k3s_cni |
| Calico | cni: calico |
NetworkPolicy, optional BGP; --flannel-backend=none |
| Cilium | cni: cilium |
eBPF, Hubble; validate on ARM64 (deborah) |
The CNI is chosen in k3s_install.core.cni (today: calico), before
installing K3s: changing it in an already installed cluster requires reinstalling K3s.
# group_vars/incus_cluster/k3s_install.yml
k3s_install:
core:
cni: calico # flannel | canal | calico | cilium
Details: playbook-options — CNI.
playbook-k3s.yml)group_vars; role k3s_cni--disable traefik192.168.20.5192.168.20.4node_labels / fetch_kubeconfigVerify¶
# En deborah o con kubeconfig
sudo k3s kubectl get nodes
# 3 nodos Ready
sudo k3s kubectl get pods -n argocd
# argocd-server Running (si gitops_argocd.enabled=true)
Wait for pods argocd-server in Running before Phase 4.
If it fails¶
| Symptom | Check |
|---|---|
| Agent does not join | Token, K3S_URL, firewall :6443 |
| Residual Traefik | --tags traefik or role k3s_cleanup_traefik |
| CP down after reboot | systemctl enable --now k3s in deborah |