You now know what Kubernetes does, how it is built inside and what every term in the vocabulary means. All of that was necessary theory, but Kubernetes is not learned by reading: it is learned by breaking things in a cluster of your own. This lesson has two parts. First, an honest overview of the real options for getting hold of a cluster —local, self-managed or managed in the cloud— with criteria for choosing in a professional project. And then, the step-by-step practical guide to building the practice cluster you will use throughout the twelve modules of the course, where we will deploy the Rutas Norte platform. By the end you will have a working environment, you will know how to stop it, resume it and destroy it without fear, and you will know the causes of the most common start-up failures.
Contents
- The three routes to getting a cluster
- The course practice cluster: requirements
- Installing minikube and the driver
- Starting the cluster with enough resources
- Addons required for the course
- Verifying the cluster
- Lifecycle: stop, resume, destroy
- The kind alternative and a multi-node cluster
- Troubleshooting common start-up failures
- The three routes to getting a cluster
| Route | Tools | Who operates the control plane | Cost | When to choose it |
|---|---|---|---|---|
| Local | minikube, kind, k3d, Docker Desktop | You, but it is disposable | Only your machine | Learning, developing, testing manifests and CI |
| Self-managed | kubeadm, k3s, Rancher, vendor distributions | You, for real: patches, certificates, etcd, backups | Servers + people | Your own data centre, legal data-location requirements, total control |
| Managed in the cloud | EKS (AWS), AKS (Azure), GKE (Google) | The provider | Control plane fee + nodes | Most companies in production |
Practical criteria for deciding:
- Are you learning or developing? Local. No argument: it is free, quick to recreate and it exposes nothing.
- Do you have a platform team with 24×7 on-call? If the answer is no, avoid self-managing production. Maintaining etcd, rotating certificates and upgrading three times a year is not a small job.
- Are there data sovereignty constraints or special hardware? Self-managed, with kubeadm or a commercial distribution.
- Ordinary production for an ordinary company? Managed. Rutas Norte will end up here: the control plane is the provider's problem and the team concentrates on the platform.
Each route has its own lesson later on: Minikube and Local Environments with kind, Kubeadm and Managed Kubernetes: EKS, AKS and GKE. Here we only build what we need to have a working environment.
Why minikube for this course
We choose minikube as the main option because, through its addons, it ships two things we will need a lot: an Ingress Controller (module 4) and a dynamic storage provisioner (module 5). Installing those by hand in other tools is extra work that teaches nothing at this point. We also give the equivalent recipe with kind, which is lighter and makes multi-node clusters easy.
- The course practice cluster: requirements
Rutas Norte will end up with six components deployed at once plus the observability add-ons, so it is worth not skimping on resources.
| Resource | Minimum | Recommended for the whole course |
|---|---|---|
| CPU | 2 cores | 4 cores |
| RAM | 4 GiB free | 8 GiB free (16 GiB on the machine) |
| Disk | 20 GiB free | 40 GiB free |
| Operating system | Linux, macOS or Windows 10/11 with WSL2 | Linux or macOS |
| Virtualisation | Docker, or a hypervisor (KVM, HyperKit, Hyper-V) | Docker as the driver |
| Network | Internet access to pull images | — |
You also need kubectl, whose installation and use are covered in detail in the next lesson, The Kubernetes CLI: kubectl. If you do not have it yet, minikube can run it for you with minikube kubectl -- <command>, although the normal thing is to install it separately.
- Installing minikube and the driver
3.1. The driver
minikube creates the cluster inside something: a container or a virtual machine. That "something" is the driver.
| Driver | Platforms | Advantages | Drawbacks |
|---|---|---|---|
| docker (recommended) | Linux, macOS, Windows | Fast, no hypervisor, starts in seconds | The node is a container: less isolated |
| kvm2 | Linux | Real VM isolation | Requires KVM and permissions |
| hyperkit / vfkit | macOS | Lightweight VM | Less used today |
| hyperv | Windows Pro/Enterprise | Built into the system | Requires the Pro edition |
| none | Linux | Installs onto the machine itself | Modifies your system; only for dedicated servers |
We will use docker. Check that you have it and that your user can use it without sudo:
If the second command fails with permission denied, add your user to the docker group and log in again:
3.2. Installing minikube
Linux (x86_64):
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
rm minikube-linux-amd64macOS (with Homebrew):
Windows (with winget, in PowerShell):
Verify the installation:
- Starting the cluster with enough resources
This is the command that defines your working environment for the whole course:
minikube start \
--profile=rutas-norte \
--driver=docker \
--kubernetes-version=v1.30.0 \
--cpus=4 \
--memory=8192 \
--disk-size=40gWhat each option does, and why:
| Option | Meaning | Why like this |
|---|---|---|
--profile=rutas-norte |
The cluster and context name | Lets you keep several independent clusters on the same machine without clashing with other work |
--driver=docker |
Where the node is created | Fast and with no hypervisor |
--kubernetes-version=v1.30.0 |
A pinned version | The course uses 1.30+; pinning it stops a future minikube start changing the behaviour |
--cpus=4 |
Cores assigned | Enough for the six components plus module 7's Prometheus |
--memory=8192 |
MiB of RAM | Below 4096 you will start to see pods evicted |
--disk-size=40g |
The node's disk | Images and persistent volumes take up space |
Expected output (abridged):
😄 [rutas-norte] minikube v1.33.1 on Ubuntu 24.04
✨ Using the docker driver based on user configuration
👍 Starting "rutas-norte" primary control-plane node in "rutas-norte" cluster
🚜 Pulling base image ...
🔥 Creating docker container (CPUs=4, Memory=8192MB) ...
🐳 Preparing Kubernetes v1.30.0 on Docker 26.1.1 ...
🔎 Verifying Kubernetes components...
🌟 Enabled addons: default-storageclass, storage-provisioner
🏄 Done! kubectl is now configured to use "rutas-norte" cluster and "default" namespace by defaultTwo important details in that output:
- The last line says that minikube has modified your kubeconfig and has created and activated a context called
rutas-norte. That is what makeskubectltalk to this cluster. - Two addons have already been enabled by default:
default-storageclassandstorage-provisioner. They are the ones that will let us ask for persistent volumes in module 5 without any real infrastructure.
If your machine has fewer resources, start with --cpus=2 --memory=4096 and bear it in mind: in module 7 you will have to switch components off to make room.
- Addons required for the course
Addons are pre-packaged components that minikube installs into the cluster. Enable these three right away:
minikube addons enable ingress --profile=rutas-norte
minikube addons enable metrics-server --profile=rutas-norte
minikube addons list --profile=rutas-norte| Addon | What it installs | What we will need it for |
|---|---|---|
ingress |
An NGINX-based Ingress Controller | Publishing www.rutasnorte.example and api.rutasnorte.example (module 4) |
metrics-server |
A CPU and memory metrics collector | kubectl top and horizontal autoscaling (modules 7 and 9) |
storage-provisioner |
A dynamic volume provisioner | The bookings-postgres disk (module 5). Already active by default |
dashboard (optional) |
The cluster's web interface | Handy for exploring; opened with minikube dashboard |
Check that the Ingress controller has started before moving on:
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-9k2xq 0/1 Completed 0 62s
ingress-nginx-admission-patch-hn4vp 0/1 Completed 0 62s
ingress-nginx-controller-768f948f8f-7lxjp 1/1 Running 0 62sThe two pods in Completed are not an error: they are installation Jobs that have already finished their work (remember from the previous lesson that a Job finishes, it does not stay running). The one that matters is the third, in Running and 1/1.
- Verifying the cluster
Three checks that are always worth doing after creating a cluster.
1. The node is ready:
NAME STATUS ROLES AGE VERSION INTERNAL-IP OS-IMAGE CONTAINER-RUNTIME
rutas-norte Ready control-plane 3m v1.30.0 192.168.49.2 Ubuntu 22.04.4 LTS docker://26.1.1What matters: STATUS: Ready (the kubelet is reporting and the network plugin works) and ROLES: control-plane (in minikube the same node acts as control plane and as worker, something that would never happen in production).
2. The system components are healthy:
NAMESPACE NAME READY STATUS RESTARTS AGE
ingress-nginx ingress-nginx-controller-768f948f8f 1/1 Running 0 2m
kube-system coredns-7db6d8ff4d-4rzmt 1/1 Running 0 3m
kube-system etcd-rutas-norte 1/1 Running 0 3m
kube-system kube-apiserver-rutas-norte 1/1 Running 0 3m
kube-system kube-controller-manager-rutas-norte 1/1 Running 0 3m
kube-system kube-proxy-9wgbn 1/1 Running 0 3m
kube-system kube-scheduler-rutas-norte 1/1 Running 0 3m
kube-system metrics-server-7d9f8c6b5-x2klp 1/1 Running 0 1m
kube-system storage-provisioner 1/1 Running 0 3mRecognise this list: these are exactly the components from the lesson Kubernetes Architecture, now really running on your machine. Note that in minikube the control plane runs as pods inside the cluster itself (static pods managed by the kubelet), and that coredns appears, the internal DNS server we will study in module 4.
3. The API responds and you know which cluster you are talking to:
Kubernetes control plane is running at https://192.168.49.2:8443
CoreDNS is running at https://192.168.49.2:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
rutas-norteIf the three checks pass, you have a working cluster.
- Lifecycle: stop, resume, destroy
A local cluster consumes resources even when you are not using it. These are the four commands you need:
# Stop the cluster, keeping ALL its state (objects, images, volumes)
minikube stop --profile=rutas-norte
# Resume it exactly as it was, with the same configuration
minikube start --profile=rutas-norte
# See the current state
minikube status --profile=rutas-norte
# Destroy it completely: everything deployed is lost
minikube delete --profile=rutas-norte| Command | What it keeps | When to use it |
|---|---|---|
stop |
Everything: objects, volume data, pulled images | When you finish a study session |
start (on an existing profile) |
Everything above | When you pick the course back up |
delete |
Nothing from that profile | When the cluster is broken or when you finish the course |
Method tip: not being afraid of
minikube deleteis precisely the goal. If all your manifests are versioned in the repository'sk8s/directory —as we will do from lesson 01-07 onwards— recreating the cluster and reapplying them should take you five minutes. If deleting it scares you, that is a sign there is important state that is not in Git.
You will also find these useful:
# Open a shell inside the node (to inspect the runtime)
minikube ssh --profile=rutas-norte
# Get the node's IP, which we will need for module 4's Ingress
minikube ip --profile=rutas-norte
- The kind alternative and a multi-node cluster
kind (Kubernetes IN Docker) creates each node as a Docker container. It is lighter than minikube and its great advantage is how easy it makes building a multi-node cluster, something that will come in handy when we study scheduling and affinity (module 6). Its downside: it ships no addons, so Ingress and storage take extra steps.
Installing it on Linux:
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.23.0/kind-linux-amd64
sudo install ./kind /usr/local/bin/kind && rm ./kind
kind versionA configuration file with one control node and two workers, also prepared for Ingress:
# k8s/local-environment/kind-rutas-norte.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: rutas-norte
nodes:
- role: control-plane
# The label kind's Ingress Controller expects to find
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
# We publish the node's 80 and 443 on the host machine
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
- role: worker
labels:
zone: north
- role: worker
labels:
zone: southAn explanation of the parts that matter:
nodes: each entry is a Docker container that will act as a node. Here we will have three.extraPortMappings: without this, the cluster's port 80 would not be reachable from your browser. It is the equivalent ofminikube tunnel.labelson the workers: node labels we will use to practise affinity and anti-affinity in module 6, simulating two availability zones.
Creating and verifying:
NAME STATUS ROLES AGE VERSION
rutas-norte-control-plane Ready control-plane 75s v1.30.0
rutas-norte-worker Ready <none> 52s v1.30.0
rutas-norte-worker2 Ready <none> 52s v1.30.0Installing the Ingress Controller (kind does not ship it) and deleting the cluster when it is no longer needed:
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
kind delete cluster --name rutas-norteWhat to choose: if in doubt, minikube. Use kind when you need several nodes or when you build Kubernetes inside a continuous integration pipeline, where its creation speed is decisive.
- Troubleshooting common start-up failures
| Symptom | Likely cause | Fix |
|---|---|---|
Exiting due to RSRC_INSUFFICIENT_CORES |
You asked for more CPU than is available | Lower --cpus, or raise Docker Desktop's resources in Settings → Resources |
docker: permission denied while trying to connect to the Docker daemon socket |
Your user is not in the docker group |
sudo usermod -aG docker $USER && newgrp docker |
Unable to pick a default driver |
There is no Docker and no hypervisor available | Install Docker and start its service; then --driver=docker |
The start-up hangs at Pulling base image |
Slow network, corporate proxy or pull rate limit | Set HTTP_PROXY/HTTPS_PROXY, or retry; the base image is around 1 GB |
Node permanently NotReady |
The network plugin is not ready or there is not enough memory | kubectl describe node and check the conditions; usually solved with minikube delete and recreating |
Pods in ImagePullBackOff |
Non-existent image, misspelled tag or a private registry with no credentials | kubectl describe pod and read the events; check the exact image name |
System pods in CrashLoopBackOff after suspending the laptop |
Clock drift between host and node, which invalidates the certificates | minikube stop && minikube start on the same profile |
kubectl answers connection refused |
The cluster is stopped or the context points somewhere else | minikube status and kubectl config use-context rutas-norte |
The connection to the server localhost:8080 was refused |
There is no kubeconfig: kubectl falls back to the default | Start minikube, or export KUBECONFIG pointing at the right file |
| You run out of disk halfway through the course | Accumulated images | minikube ssh -- docker system prune -a, or recreate the cluster with a bigger --disk-size |
General-purpose diagnostic commands when something goes wrong at start-up:
minikube status --profile=rutas-norte
minikube logs --profile=rutas-norte | tail -50
kubectl get events -A --sort-by=.metadata.creationTimestamp | tail -20The third one is especially valuable and we will use it a lot: events are the cluster's diary and they almost always contain a plain-text explanation of why something did not work.
Common Mistakes and Tips
- Starting with the defaults (2 CPUs, 2 GiB). It works for the first modules and falls short as soon as you add observability. Sizing it properly from the start beats recreating the cluster halfway through the course.
- Not using
--profile. If you work with several local clusters, the default profile becomes a source of confusion: you end up applying manifests to the wrong cluster. Naming the profilerutas-nortealways makes it clear where you are. - Changing the Kubernetes version between sessions. If one day you start with 1.30 and another without pinning a version, behaviours and APIs change. Pin the version in the command and write it down in the repository.
- Confusing "the cluster started" with "the cluster is ready".
minikube startreturns before the addons are operational. Always check withkubectl get pods -Athat nothing is inPendingorContainerCreating. - Expecting a
Serviceof typeLoadBalancerto get an external IP. There is no cloud-controller-manager locally (we saw this in 01-02): it stays at<pending>. It is solved withminikube tunnel, and we will cover it in module 4. - Leaving the cluster running permanently. It eats CPU and battery.
minikube stopat the end of the session keeps absolutely everything. - Tip: save your cluster creation command in the repository itself (for example, in
k8s/local-environment/READMEor acreate-cluster.shscript). It is as much a part of the project's documentation as the manifests are.
Exercises
Exercise 1: Create and verify your cluster
Build the practice cluster with the rutas-norte profile, 4 CPUs, 8 GiB and Kubernetes 1.30, enable the ingress and metrics-server addons, and answer:
- How many nodes does it have and what role do they have?
- How many pods are there in the
kube-systemnamespace and which of them match the control plane components you studied in lesson 01-02? - What is the node's IP?
- Which kubectl context is active?
Exercise 2: Lifecycle and state persistence
Create the project's development namespace, stop the cluster, resume it and check whether the namespace still exists:
kubectl create namespace rutas-norte-dev
minikube stop --profile=rutas-norte
minikube start --profile=rutas-norte
kubectl get namespacesExplain why the result is what it is, mentioning where that object is stored. Then state which command would make that namespace disappear along with everything else.
Exercise 3: A multi-node cluster with kind
Use kind to create a cluster called rutas-norte-multi with one control node and two workers labelled zone: north and zone: south. Check that the three nodes are Ready and list only the nodes in the north zone. Explain in two lines why this cluster will be useful to you in module 6 and why we do not use it as the course's main environment.
Solutions
Solution 1
minikube start --profile=rutas-norte --driver=docker \
--kubernetes-version=v1.30.0 --cpus=4 --memory=8192 --disk-size=40g
minikube addons enable ingress --profile=rutas-norte
minikube addons enable metrics-server --profile=rutas-norte- A single node, with the
control-planerole. In minikube that same node also runs user workloads, which is not done in production: the control plane is reserved through taints. - Around eight pods. They match the architecture:
etcd,kube-apiserver,kube-scheduler,kube-controller-manager(control plane),kube-proxy(node), pluscoredns(internal DNS),storage-provisionerandmetrics-server.cloud-controller-managerdoes not appear because there is no cloud provider. minikube ip --profile=rutas-norte→ typically192.168.49.2.kubectl config current-context→rutas-norte.
Solution 2
The namespace still exists after stop and start. The reason is that stop destroys nothing: it shuts down the node's container or VM while keeping its disk, and on that disk lives etcd, which is where the Namespace object resides. On resuming, the apiserver reads from that same etcd again and all the declared state reappears.
The command that would destroy everything is minikube delete --profile=rutas-norte, which removes the node and its disk, etcd included. That is why manifests must live in Git: recreating the cluster is then a formality.
Solution 3
kind create cluster --config k8s/local-environment/kind-rutas-norte.yaml --name rutas-norte-multi
kubectl get nodes --show-labels
kubectl get nodes -l zone=northIt will be useful in module 6 because affinity, anti-affinity, taints and topology spread can only really be practised with several nodes: on a single-node cluster the scheduler has no decision to make. We do not use it as the main environment because kind includes no addons: you would have to install the Ingress Controller and a storage provisioner by hand, work that at this point in the course distracts from the goal.
Conclusion
You now have a Kubernetes cluster running on your machine, with the rutas-norte profile, version 1.30 pinned, enough resources for the whole course and the Ingress, metrics and dynamic storage addons enabled. You have also seen the full landscape of options —local, self-managed with kubeadm, managed in the cloud— and the criteria for choosing one or another in a real project, plus a multi-node alternative with kind for when we get to scheduling. And, above all, you know how to stop it, resume it and destroy it without fear, because the operational truth of this course is that the cluster is replaceable and the manifests are not.
You have the environment, but you have barely used the tool you are going to talk to it with. In the next lesson, The Kubernetes CLI: kubectl, we will master kubectl thoroughly: the kubeconfig file and contexts, the verbs you will use every day, the output formats, selection by labels and the productivity tricks that make the difference between fighting the cluster and working with ease.
Kubernetes Course
Module 1: Introduction to Kubernetes
- What Is Kubernetes?
- Kubernetes Architecture
- Key Concepts and Terminology
- Setting Up a Kubernetes Cluster
- The Kubernetes CLI: kubectl
- Objects, YAML Manifests and the Declarative Model
- The Course Project: the Rutas Norte Platform
Module 2: Core Kubernetes Components
- Pods
- ReplicaSets
- Deployments
- Updates, Rollbacks and Deployment Strategies
- Services
- Namespaces
- Labels, Selectors and Annotations
Module 3: Configuration and Secret Management
- ConfigMaps
- Secrets
- Environment Variables
- Resource Quotas and Limits
- LimitRanges and Quality of Service (QoS) Classes
- ServiceAccounts and API Access from Pods
Module 4: Networking in Kubernetes
- Cluster Networking
- Service Types
- Internal DNS and Service Discovery
- Ingress Controllers
- TLS and Certificate Management with cert-manager
- Network Policies
Module 5: Storage in Kubernetes
- Volumes
- Persistent Volumes
- Persistent Volume Claims
- Storage Classes
- Dynamic Provisioning, Expansion and Snapshots
- Backup and Restore of Persistent Data
Module 6: Advanced Kubernetes Concepts
- StatefulSets
- DaemonSets
- Jobs and CronJobs
- Init Containers, Sidecars and Multi-Container Patterns
- Scheduling: Affinity, Taints and Tolerations
- Custom Resource Definitions (CRDs)
- Operators and the Controller Pattern
Module 7: Monitoring and Logging
- Health Checks and Probes
- Metrics Server and kubectl top
- Monitoring with Prometheus
- Visualization and Alerting with Grafana and Alertmanager
- Centralized Logging with Elasticsearch, Fluentd and Kibana (EFK)
- Application Debugging and Cluster Events
Module 8: Kubernetes Security
- Role-Based Access Control (RBAC)
- Security Contexts and Container Hardening
- Pod Security Policies and Pod Security Standards
- Network Security
- Image Security
- Auditing, Scanning and Vulnerability Management
Module 9: Scaling and Performance
- Horizontal Pod Autoscaling
- Vertical Pod Autoscaling
- Cluster Autoscaling
- Event-Driven and Custom-Metric Scaling with KEDA
- High Availability: PodDisruptionBudgets and Topology
- Performance Tuning
Module 10: Kubernetes Ecosystem and Tooling
- Minikube and Local Environments with kind
- Kubeadm
- Helm
- Kustomize
- GitOps with Argo CD and Flux
- Managed Kubernetes: EKS, AKS and GKE
Module 11: Case Studies and Real-World Applications
- Deploying a Web Application
- Running Stateful Applications
- CI/CD with Kubernetes
- Deployment Strategies: Blue-Green and Canary
- Multi-Cluster Management
- Production Operations: Incidents, Runbooks and Costs
