Flip one switch in Docker Desktop and you'll have a real, multi‑node Kubernetes cluster built using kubeadm running in Docker containers - no VMs or Raspberry Pi clusters, no scripts, no waiting. The perfect infrastructure for learning on your Kubestronaut journey!
Why this matters:
- Multi‑Node Kubernetes in Docker - start clusters in seconds, pin versions, and keep resource use low
- Easily available - Kind is built into Docker Desktop; nothing extra to install
- Study smarter - Ideal for KCNA, KCSA, CKA, CKAD practice; this can be used to cover the vast majority of exam scenarios (CKS is an exception, more details below)
- Upstream release‑gated - Kind is the official test bed for Kubernetes releases. If it’s good enough to validate an official Kubernetes release, it’s good enough for your laptop!
- Built upon kubeadm - This isn't a fork or an IoT distribution of Kubernetes. Kind uses the gold standard for Kubernetes installations - kubeadm
TL;DR - Docker Desktop’s native Kind provisioner makes local Kubernetes trivial, reproducible, and certification‑ready.
What is Kind?
Kind stands for Kubernetes IN Docker. It runs Kubernetes nodes as Docker containers with the control plane and workers running on your machine. No hypervisor/vm/cloud instance juggling, no hand‑rolled kubeadm scripts: a working cluster appears in seconds and behaves as Kubernetes should.
Out of the box it appears, as a convenient Kubernetes cluster that you can access locally, however, with some CLI magic we can access each and every node as if you'd built a cluster using kubeadm yourself!
Perfect for advanced scenarios and study, especially for those in the CKA/CKAD.
Design goals:
- Docker container nodes (no per‑node VMs to manage)
- Consistent, upstream‑aligned behavior (the project lives under Kubernetes SIGs)
- Made for automation (great for CI, reproducible labs, and teaching)
Why Kind matters (it’s not another lightweight Kubernetes distro)
Kind isn’t a convenience fork of Kubernetes; it’s the distribution Kubernetes itself is tested on. Core contributors and SIGs rely on Kind for end‑to‑end testing and PR validation. That pedigree means:
- Reliable behavior that closely tracks upstream versions
- Reproducibility across laptops, CI runners, and teaching labs
- Confidence that if it works in kind, it’s very likely to work on real clusters
If it’s good enough to gate a Kubernetes release, it’s good enough for everyone’s laptop.
What’s new: Native Kind in Docker Desktop
Docker Desktop recently added Kind as a built‑in cluster provisioner alongside the legacy single node kubeadm option. Benefits:
- Zero manual install - no separate
kind
binary or scripts required - Multi‑node clusters - pick 1, 2, 3, 4, 5... nodes from the UI
- Version selector - choose your Kubernetes version from a dropdown in the Docker Desktop UI; great for testing specific releases, upgrades and for reproducing bugs
- Faster boot - typical clusters start in ~tens of seconds
- Security alignment - integrates cleanly with Docker Desktop’s isolation features (Docker Desktop uses a hidden/isolated VM to run containers)
Enable it (1-2 minutes)
- Open Docker Desktop → Settings → Kubernetes
- Check Enable Kubernetes
- Under Cluster provisioning method, choose Kind
- Pick your Kubernetes version and number of nodes
- Click Apply
When it’s ready, your kubeconfig context will be docker-desktop
and kubectl get nodes
will work from your command prompt or terminal.
Version selection at cluster create time
When creating the cluster, use the Kubernetes version selector to pin a release (e.g. 1.33.x). This is invaluable for:
- Exam prep: match the exam control plane minor version, test upgrades
- Bug repro: roll back/forward to confirm version‑specific behaviour
- Upgrade drills: recreate at the next minor and validate workloads
Why this is a big deal for day‑to‑day work
1) Streamlined for education, testing, and local dev
- Workshops & self‑study: learners can build multi‑node labs with everyday laptops
- Team testing: verify scheduling, anti‑affinity, PodDisruptionBudgets and upgrades-locally
- Inner‑loop dev: iterate Helm charts, operators, and CI pipelines against the exact version you target in production
2) Minimal hardware requirements
- Nodes are containers rather than VMs, so memory and CPU footprints are typically much smaller
- Ideal for laptops and cloud dev environments (WSL2 etc.)
3) Version parity & repeatability
- Pin a Kubernetes version per cluster to reproduce bugs or pre‑validate upgrades
Under the hood (and how to "look inside")
Kind clusters are built using kubeadm inside the node containers. That means you can explore the control plane like you would on a bare‑metal install-without maintaining VMs. We just need to be able to traverse from our local machine, to the internals of the cluster.
Let's check it out. With kind installed, open a terminal or command prompt. You'll see a Kubernetes cluster ready to use -
# Show available contexts james@Mac ~ % kubectl config get-contexts CURRENT NAME CLUSTER AUTHINFO NAMESPACE * docker-desktop docker-desktop docker-desktop # List our nodes james@Mac ~ % kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME desktop-control-plane Ready control-plane 3m18s v1.32.5 172.18.0.2 <none> Debian GNU/Linux 12 (bookworm) 6.10.14-linuxkit containerd://2.1.1 desktop-worker Ready <none> 3m9s v1.32.5 172.18.0.3 <none> Debian GNU/Linux 12 (bookworm) 6.10.14-linuxkit containerd://2.1.1 desktop-worker2 Ready <none> 3m9s v1.32.5 172.18.0.4 <none> Debian GNU/Linux 12 (bookworm) 6.10.14-linuxkit containerd://2.1.1 # Check the current version james@Mac ~ % kubectl version Client Version: v1.32.2 Kustomize Version: v5.5.0 Server Version: v1.32.5
At this point, you have a fully functional cluster which is great for learning. However, we can take this further by navigating to the kubernetes containers direct. By default, the containers used via Kind in Docker Destkop will be hidden, you'll need to enable these by toggling the Show system containers option -
Show System Containers
- Open Docker Desktop → Settings → Kubernetes
- Check Show system containers (advanced)
- Click Apply
After doing so, we'll now be able to see our kind containers, in docker ps
. Scroll to the right to see the container names of desktop-control-plane
, desktop-worker
and desktop-worker2
.
james@Mac ~ % docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 283aacaa252a docker/desktop-cloud-provider-kind:v0.3.0-desktop.3 "/bin/cloud-provider…" 10 minutes ago Up 10 minutes 2375-2376/tcp kind-cloud-provider 36f079951c02 docker/desktop-containerd-registry-mirror:v0.0.2 "/bin/registry" 10 minutes ago Up 10 minutes kind-registry-mirror a9041e01140e kindest/node:v1.32.5 "/usr/local/bin/entr…" 10 minutes ago Up 10 minutes desktop-worker d4a4720f06a7 kindest/node:v1.32.5 "/usr/local/bin/entr…" 10 minutes ago Up 10 minutes 127.0.0.1:60054->6443/tcp desktop-control-plane 529fdbb8946b kindest/node:v1.32.5 "/usr/local/bin/entr…" 10 minutes ago Up 10 minutes desktop-worker2
Exec into a node container
# Docker exec into the Kubernetes control plane instance james@Mac ~ % docker exec -it desktop-control-plane bash # Check the version, this time using the short value root@desktop-control-plane:/# kubeadm version -o short v1.32.5 # Check the version of the kubelet root@desktop-control-plane:/# kubelet --version Kubernetes v1.32.5 # Use crictl ps to list containerd containers (kind uses containerd as its container runtime) root@desktop-control-plane:/# crictl ps CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD NAMESPACE 5b51c317e67ab 653e2348b2d4a 2 hours ago Running local-path-provisioner 0 cf4c29517e03e local-path-provisioner-7dc846544d-bb2br local-path-storage 4a871609e5dc1 2f6c962e7b831 2 hours ago Running coredns 0 eb6641db5e8ae coredns-668d6bf9bc-ffhk6 kube-system 860987010859d 2f6c962e7b831 2 hours ago Running coredns 0 edbce365359f3 coredns-668d6bf9bc-f22j4 kube-system 62a4696a31517 b1a8c6f707935 2 hours ago Running kindnet-cni 0 65813d3224d03 kindnet-279nj kube-system 73e39d461156c 69b7afc06f22e 2 hours ago Running kube-proxy 0 aadd9f61bbd05 kube-proxy-84mv4 kube-system 711517fec1763 7fc9d4aa817aa 2 hours ago Running etcd 0 4ebbebc01185d etcd-desktop-control-plane kube-system b0fbb87a97820 42968274c3d27 2 hours ago Running kube-apiserver 0 53713994d399c kube-apiserver-desktop-control-plane kube-system 7120f86644eb6 82042044d6ea1 2 hours ago Running kube-controller-manager 0 f07b3eb44ccb9 kube-controller-manager-desktop-control-plane kube-system 49c1996b161fc e149336437f90 2 hours ago Running kube-scheduler 0 a1b08e56a44ff kube-scheduler-desktop-control-plane kube-system # Demonstrate that we are in a kubeadm cluster, list /etc/kubernetes/manifests root@desktop-control-plane:/# ls /etc/kubernetes/manifests/ etcd.yaml kube-apiserver.yaml kube-controller-manager.yaml kube-scheduler.yaml
What you’ll notice:
/etc/kubernetes
contains the usual manifests and certscontainerd
runs pods;crictl
lets you inspect them- You can safely explore without the overhead of a full VM per node
Certification & Training: where Kind shines
If you’re preparing for Kubernetes certifications, here’s the pragmatic guidance I give students:
- KCNA (Associate): Perfect on Kind. Practice core objects, architecture, and ecosystem tooling locally
- KCSA (Security Associate): Great fit - RBAC, network policies etc
- CKA / CKAD: Excellent. You can replicate almost all admin/developer tasks, including upgrades, scheduling, debugging, and controllers
- CKS: Mostly fine for cluster‑level security topics (RBAC, NetworkPolicy, image security, admission, etc.), but less ideal for kernel‑level tasks (e.g. distro‑specific hardening) that expect host access beyond container boundaries. Keep Kind for 80-90% of your practice and supplement with a lab VM when you need kernel modules or OS hardening (the shared Kernel in Docker Desktop isn't upgradable by default). Again, you'll be able to use kind but, with a native linux distribution
CKA‑style upgrade exercise: 1.32 → 1.33 (3 nodes)
Let's truly see this in action, we'll dive into a Kubernetes upgrade. Upgrading from what was at the time the previous version of Kubernetes (1.32) to the current version (1.33).
Why this matters: Upgrades are a classic CKA scenario. With Docker Desktop + Kind you can practice the kubeadm flow inside the node containers.
0) Start at v1.32 (3 nodes)
- Docker Desktop → Settings → Kubernetes
- Provisioner: Kind
- Kubernetes version: 1.32.x, Nodes: 3
- Click Apply
1) Verify current version (on your local system)
# Confirm that we're currently running 1.32.x james@Mac ~ % kubectl get nodes NAME STATUS ROLES AGE VERSION desktop-control-plane Ready control-plane 150m v1.32.5 desktop-worker Ready <none> 150m v1.32.5 desktop-worker2 Ready <none> 150m v1.32.5
2) Exec into the node (prepare kubeadm/kubelet/kubectl)
# Kind provides bundled binaries for kubeadm/kubelet/kubectl root@desktop-control-plane:/# which kubeadm kubelet kubectl /usr/bin/kubeadm /usr/bin/kubelet /usr/bin/kubectl # We can see that these are not provided by an installed package root@desktop-control-plane:/# dpkg -S /usr/bin/kubeadm /usr/bin/kubelet /usr/bin/kubectl dpkg-query: no path found matching pattern /usr/bin/kubeadm dpkg-query: no path found matching pattern /usr/bin/kubelet dpkg-query: no path found matching pattern /usr/bin/kubectl # We'll remove these, and later on will install newer versions, via a kubernetes package - Mimicking what we would have in a fully built kubeadm cluster root@desktop-control-plane:/# rm /usr/bin/kubeadm /usr/bin/kubelet /usr/bin/kubectl
3) Upgrade the control plane
# Update apt for base packages root@desktop-control-plane:/# apt-get update Get:1 http://deb.debian.org/debian bookworm InRelease [151 kB] Get:2 http://deb.debian.org/debian bookworm-updates InRelease [55.4 kB] Get:3 http://deb.debian.org/debian-security bookworm-security InRelease [48.0 kB] Get:4 http://deb.debian.org/debian bookworm/main arm64 Packages [8693 kB] Get:5 http://deb.debian.org/debian bookworm-updates/main arm64 Packages [6936 B] Get:6 http://deb.debian.org/debian-security bookworm-security/main arm64 Packages [270 kB] Fetched 9225 kB in 1s (6696 kB/s) Reading package lists... Done # Install prerequisites root@desktop-control-plane:/# apt-get install -y apt-transport-https ca-certificates curl gpg Reading package lists... Done Building dependency tree... Done Reading state information... Done curl is already the newest version (7.88.1-10+deb12u12). The following additional packages will be installed: dirmngr gnupg gnupg-l10n gnupg-utils gpg-agent gpg-wks-client gpg-wks-server gpgconf gpgsm libassuan0 libksba8 libnpth0 pinentry-curses Suggested packages: dbus-user-session libpam-systemd pinentry-gnome3 tor parcimonie xloadimage scdaemon pinentry-doc The following NEW packages will be installed: apt-transport-https dirmngr gnupg gnupg-l10n gnupg-utils gpg gpg-agent gpg-wks-client gpg-wks-server gpgconf gpgsm libassuan0 libksba8 libnpth0 pinentry-curses The following packages will be upgraded: ca-certificates 1 upgraded, 15 newly installed, 0 to remove and 8 not upgraded. Need to get 7868 kB of archives. <snip> # Configure keyrings root@desktop-control-plane:/# mkdir -p -m 755 /etc/apt/keyrings root@desktop-control-plane:/# curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg # Add Kubernetes repositories root@desktop-control-plane:/# echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /' | tee /etc/apt/sources.list.d/kubernetes.list deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ / # Update repositories root@desktop-control-plane:/# apt-get update Hit:1 http://deb.debian.org/debian bookworm InRelease Hit:2 http://deb.debian.org/debian bookworm-updates InRelease Hit:3 http://deb.debian.org/debian-security bookworm-security InRelease Get:4 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb InRelease [1186 B] Get:5 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb Packages [7611 B] Fetched 8797 B in 1s (15.1 kB/s) Reading package lists... Done # Update available packages including our newly added Kubernetes repository root@desktop-control-plane:/# apt-get update Hit:1 http://deb.debian.org/debian bookworm InRelease Hit:2 http://deb.debian.org/debian bookworm-updates InRelease Hit:3 http://deb.debian.org/debian-security bookworm-security InRelease Get:4 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb InRelease [1186 B] Get:5 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb Packages [7611 B] Fetched 8797 B in 1s (15.1 kB/s) Reading package lists... Done # Install kubelet, kubeadm and kubectl as packages from the newly added repository root@desktop-control-plane:/# apt-get install -y kubelet kubeadm kubectl Reading package lists... Done Building dependency tree... Done Reading state information... Done The following additional packages will be installed: cri-tools kubernetes-cni The following NEW packages will be installed: cri-tools kubeadm kubectl kubelet kubernetes-cni 0 upgraded, 5 newly installed, 0 to remove and 8 not upgraded. Need to get 85.6 MB of archives. After this operation, 339 MB of additional disk space will be used. Get:1 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb cri-tools 1.33.0-1.1 [15.8 MB] Get:2 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb kubeadm 1.33.4-1.1 [10.9 MB] Get:3 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb kubectl 1.33.4-1.1 [9986 kB] Get:4 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb kubernetes-cni 1.6.0-1.1 [35.3 MB] Get:5 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb kubelet 1.33.4-1.1 [13.5 MB] <snip> # Along the way you'll see this, Kind uses a custom configuration for the kubelet # allowing the kubelet to run with swap enabled (required when running in a container where # the parent system (i.e. Docker Desktop) has swap enabled) # # We can continue with 'keep your currently-installed version' - ignore this message Configuration file '/etc/default/kubelet' ==> File on system created by you or by a script. ==> File also in package provided by package maintainer. What would you like to do about it ? Your options are: Y or I : install the package maintainers version N or O : keep your currently-installed version D : show the differences between the versions Z : start a shell to examine the situation The default action is to keep your current version. *** kubelet (Y/I/N/O/D/Z) [default=N] ? <snip> # Hold the new version root@desktop-control-plane:/# apt-mark hold kubelet kubeadm kubectl kubelet set on hold. kubeadm set on hold. kubectl set on hold. # Plan the upgrade root@desktop-control-plane:/# kubeadm upgrade plan [preflight] Running pre-flight checks. [upgrade/config] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"... [upgrade/config] Use 'kubeadm init phase upload-config --config your-config-file' to re-upload it. [upgrade] Running cluster health checks [upgrade] Fetching available versions to upgrade to [upgrade/versions] Cluster version: 1.32.5 [upgrade/versions] kubeadm version: v1.33.4 [upgrade/versions] Target version: v1.33.4 [upgrade/versions] Latest version in the v1.32 series: v1.32.8 Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply': COMPONENT NODE CURRENT TARGET kubelet desktop-control-plane v1.32.5 v1.32.8 kubelet desktop-worker v1.32.5 v1.32.8 kubelet desktop-worker2 v1.32.5 v1.32.8 Upgrade to the latest version in the v1.32 series: COMPONENT NODE CURRENT TARGET kube-apiserver desktop-control-plane v1.32.5 v1.32.8 kube-controller-manager desktop-control-plane v1.32.5 v1.32.8 kube-scheduler desktop-control-plane v1.32.5 v1.32.8 kube-proxy 1.32.5 v1.32.8 CoreDNS v1.11.3 v1.12.0 etcd desktop-control-plane 3.5.16-0 3.5.21-0 You can now apply the upgrade by executing the following command: kubeadm upgrade apply v1.32.8 _____________________________________________________________________ Components that must be upgraded manually after you have upgraded the control plane with 'kubeadm upgrade apply': COMPONENT NODE CURRENT TARGET kubelet desktop-control-plane v1.32.5 v1.33.4 kubelet desktop-worker v1.32.5 v1.33.4 kubelet desktop-worker2 v1.32.5 v1.33.4 Upgrade to the latest stable version: COMPONENT NODE CURRENT TARGET kube-apiserver desktop-control-plane v1.32.5 v1.33.4 kube-controller-manager desktop-control-plane v1.32.5 v1.33.4 kube-scheduler desktop-control-plane v1.32.5 v1.33.4 kube-proxy 1.32.5 v1.33.4 CoreDNS v1.11.3 v1.12.0 etcd desktop-control-plane 3.5.16-0 3.5.21-0 You can now apply the upgrade by executing the following command: kubeadm upgrade apply v1.33.4 _____________________________________________________________________ The table below shows the current state of component configs as understood by this version of kubeadm. Configs that have a "yes" mark in the "MANUAL UPGRADE REQUIRED" column require manual config upgrade or resetting to kubeadm defaults before a successful upgrade can be performed. The version to manually upgrade to is denoted in the "PREFERRED VERSION" column. API GROUP CURRENT VERSION PREFERRED VERSION MANUAL UPGRADE REQUIRED kubeproxy.config.k8s.io v1alpha1 v1alpha1 no kubelet.config.k8s.io v1beta1 v1beta1 no _____________________________________________________________________ # Apply the update root@desktop-control-plane:/# kubeadm upgrade apply v1.33.4 [upgrade] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"... [upgrade] Use 'kubeadm init phase upload-config --config your-config-file' to re-upload it. [upgrade/preflight] Running preflight checks [upgrade] Running cluster health checks [upgrade/preflight] You have chosen to upgrade the cluster version to "v1.33.4" [upgrade/versions] Cluster version: v1.32.5 [upgrade/versions] kubeadm version: v1.33.4 [upgrade] Are you sure you want to proceed? [y/N]: y [upgrade/preflight] Pulling images required for setting up a Kubernetes cluster [upgrade/preflight] This might take a minute or two, depending on the speed of your internet connection [upgrade/preflight] You can also perform this action beforehand using 'kubeadm config images pull' [upgrade/control-plane] Upgrading your static Pod-hosted control plane to version "v1.33.4" (timeout: 5m0s)... [upgrade/staticpods] Writing new Static Pod manifests to "/etc/kubernetes/tmp/kubeadm-upgraded-manifests2488743584" [upgrade/staticpods] Preparing for "etcd" upgrade [upgrade/staticpods] Renewing etcd-server certificate [upgrade/staticpods] Renewing etcd-peer certificate [upgrade/staticpods] Renewing etcd-healthcheck-client certificate [upgrade/staticpods] Moving new manifest to "/etc/kubernetes/manifests/etcd.yaml" and backing up old manifest to "/etc/kubernetes/tmp/kubeadm-backup-manifests-2025-08-22-18-01-04/etcd.yaml" [upgrade/staticpods] Waiting for the kubelet to restart the component [upgrade/staticpods] This can take up to 5m0s [apiclient] Found 1 Pods for label selector component=etcd [upgrade/staticpods] Component "etcd" upgraded successfully! [upgrade/etcd] Waiting for etcd to become available [upgrade/staticpods] Preparing for "kube-apiserver" upgrade [upgrade/staticpods] Renewing apiserver certificate [upgrade/staticpods] Renewing apiserver-kubelet-client certificate [upgrade/staticpods] Renewing front-proxy-client certificate [upgrade/staticpods] Renewing apiserver-etcd-client certificate [upgrade/staticpods] Moving new manifest to "/etc/kubernetes/manifests/kube-apiserver.yaml" and backing up old manifest to "/etc/kubernetes/tmp/kubeadm-backup-manifests-2025-08-22-18-01-04/kube-apiserver.yaml" [upgrade/staticpods] Waiting for the kubelet to restart the component [upgrade/staticpods] This can take up to 5m0s [apiclient] Found 1 Pods for label selector component=kube-apiserver [upgrade/staticpods] Component "kube-apiserver" upgraded successfully! [upgrade/staticpods] Preparing for "kube-controller-manager" upgrade [upgrade/staticpods] Renewing controller-manager.conf certificate [upgrade/staticpods] Moving new manifest to "/etc/kubernetes/manifests/kube-controller-manager.yaml" and backing up old manifest to "/etc/kubernetes/tmp/kubeadm-backup-manifests-2025-08-22-18-01-04/kube-controller-manager.yaml" [upgrade/staticpods] Waiting for the kubelet to restart the component [upgrade/staticpods] This can take up to 5m0s [apiclient] Found 1 Pods for label selector component=kube-controller-manager [upgrade/staticpods] Component "kube-controller-manager" upgraded successfully! [upgrade/staticpods] Preparing for "kube-scheduler" upgrade [upgrade/staticpods] Renewing scheduler.conf certificate [upgrade/staticpods] Moving new manifest to "/etc/kubernetes/manifests/kube-scheduler.yaml" and backing up old manifest to "/etc/kubernetes/tmp/kubeadm-backup-manifests-2025-08-22-18-01-04/kube-scheduler.yaml" [upgrade/staticpods] Waiting for the kubelet to restart the component [upgrade/staticpods] This can take up to 5m0s [apiclient] Found 1 Pods for label selector component=kube-scheduler [upgrade/staticpods] Component "kube-scheduler" upgraded successfully! [upgrade/control-plane] The control plane instance for this node was successfully upgraded! [upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace [kubelet] Creating a ConfigMap "kubelet-config" in namespace kube-system with the configuration for the kubelets in the cluster [upgrade/kubeconfig] The kubeconfig files for this node were successfully upgraded! W0822 18:03:56.953751 10262 postupgrade.go:117] Using temporary directory /etc/kubernetes/tmp/kubeadm-kubelet-config1216020020 for kubelet config. To override it set the environment variable KUBEADM_UPGRADE_DRYRUN_DIR [upgrade] Backing up kubelet config file to /etc/kubernetes/tmp/kubeadm-kubelet-config1216020020/config.yaml [kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml" [upgrade/kubelet-config] The kubelet configuration for this node was successfully upgraded! [upgrade/bootstrap-token] Configuring bootstrap token and cluster-info RBAC rules [bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to get nodes [bootstrap-token] Configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials [bootstrap-token] Configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token [bootstrap-token] Configured RBAC rules to allow certificate rotation for all node client certificates in the cluster [addons] Applied essential addon: CoreDNS [addons] Applied essential addon: kube-proxy [upgrade] SUCCESS! A control plane node of your cluster was upgraded to "v1.33.4". [upgrade] Now please proceed with upgrading the rest of the nodes by following the right order. # Check the nodes, currently the control-plane is updated root@desktop-control-plane:/# kubectl get nodes NAME STATUS ROLES AGE VERSION desktop-control-plane Ready control-plane 179m v1.33.4 desktop-worker Ready <none> 179m v1.32.5 desktop-worker2 Ready <none> 179m v1.32.5
We can now proceed with updating the nodes, repeating a similar process on desktop-worker
and desktop-worker2
. We'll hop on to the worker nodes from the local system.
# Exit from the control-plane back to our local system root@desktop-control-plane:/# exit exit # Show the docker containers running the kubernetes nodes james@Mac ~ % docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 283aacaa252a docker/desktop-cloud-provider-kind:v0.3.0-desktop.3 "/bin/cloud-provider…" 3 hours ago Up 3 hours 2375-2376/tcp kind-cloud-provider 36f079951c02 docker/desktop-containerd-registry-mirror:v0.0.2 "/bin/registry" 3 hours ago Up 3 hours kind-registry-mirror a9041e01140e kindest/node:v1.32.5 "/usr/local/bin/entr…" 3 hours ago Up 3 hours desktop-worker d4a4720f06a7 kindest/node:v1.32.5 "/usr/local/bin/entr…" 3 hours ago Up 3 hours 127.0.0.1:60054->6443/tcp desktop-control-plane 529fdbb8946b kindest/node:v1.32.5 "/usr/local/bin/entr…" 3 hours ago Up 3 hours desktop-worker2 # Access the first worker node james@Mac ~ % docker exec -it desktop-worker bash # Remove the kind bundled kubeadm, kubelet and kubectl root@desktop-worker:/# rm /usr/bin/kubeadm /usr/bin/kubelet /usr/bin/kubectl # Update apt for base packages root@desktop-worker:/# apt-get update Get:1 http://deb.debian.org/debian bookworm InRelease [151 kB] Get:2 http://deb.debian.org/debian bookworm-updates InRelease [55.4 kB] Get:3 http://deb.debian.org/debian-security bookworm-security InRelease [48.0 kB] Get:4 http://deb.debian.org/debian bookworm/main arm64 Packages [8693 kB] Get:5 http://deb.debian.org/debian bookworm-updates/main arm64 Packages [6936 B] Get:6 http://deb.debian.org/debian-security bookworm-security/main arm64 Packages [270 kB] Fetched 9225 kB in 1s (6644 kB/s) Reading package lists... Done # Install prerequisites root@desktop-worker:/# apt-get install -y apt-transport-https ca-certificates curl gpg Reading package lists... Done Building dependency tree... Done Reading state information... Done curl is already the newest version (7.88.1-10+deb12u12). The following additional packages will be installed: dirmngr gnupg gnupg-l10n gnupg-utils gpg-agent gpg-wks-client gpg-wks-server gpgconf gpgsm libassuan0 libksba8 libnpth0 pinentry-curses Suggested packages: dbus-user-session libpam-systemd pinentry-gnome3 tor parcimonie xloadimage scdaemon pinentry-doc The following NEW packages will be installed: apt-transport-https dirmngr gnupg gnupg-l10n gnupg-utils gpg gpg-agent gpg-wks-client gpg-wks-server gpgconf gpgsm libassuan0 libksba8 libnpth0 pinentry-curses The following packages will be upgraded: ca-certificates 1 upgraded, 15 newly installed, 0 to remove and 8 not upgraded. Need to get 7868 kB of archives. After this operation, 16.8 MB of additional disk space will be used. Get:1 http://deb.debian.org/debian bookworm-updates/main arm64 ca-certificates all 20230311+deb12u1 [155 kB] Get:2 http://deb.debian.org/debian bookworm/main arm64 apt-transport-https all 2.6.1 [25.2 kB] Get:3 http://deb.debian.org/debian bookworm/main arm64 libassuan0 arm64 2.5.5-5 [45.9 kB] Get:4 http://deb.debian.org/debian bookworm/main arm64 gpgconf arm64 2.2.40-1.1 [557 kB] Get:5 http://deb.debian.org/debian bookworm/main arm64 libksba8 arm64 1.6.3-2 [119 kB] Get:6 http://deb.debian.org/debian bookworm/main arm64 libnpth0 arm64 1.6-3 [18.6 kB] <snip> # Configure keyrings root@desktop-worker:/# mkdir -p -m 755 /etc/apt/keyrings root@desktop-worker:/# curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.33/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg # Add Kubernetes repositories root@desktop-worker:/# echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ /' | tee /etc/apt/sources.list.d/kubernetes.list deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.33/deb/ / # Update repositories root@desktop-worker:/# apt-get update Hit:1 http://deb.debian.org/debian bookworm InRelease Hit:2 http://deb.debian.org/debian bookworm-updates InRelease Hit:3 http://deb.debian.org/debian-security bookworm-security InRelease Get:4 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb InRelease [1186 B] Get:5 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb Packages [7611 B] Fetched 8797 B in 1s (14.4 kB/s) Reading package lists... Done # Install kubelet, kubeadm and kubectl root@desktop-worker:/# apt-get install -y kubelet kubeadm kubectl Reading package lists... Done Building dependency tree... Done Reading state information... Done The following additional packages will be installed: cri-tools kubernetes-cni The following NEW packages will be installed: cri-tools kubeadm kubectl kubelet kubernetes-cni 0 upgraded, 5 newly installed, 0 to remove and 8 not upgraded. Need to get 85.6 MB of archives. After this operation, 339 MB of additional disk space will be used. Get:1 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb cri-tools 1.33.0-1.1 [15.8 MB] Get:2 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb kubeadm 1.33.4-1.1 [10.9 MB] Get:3 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb kubectl 1.33.4-1.1 [9986 kB] Get:4 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb kubernetes-cni 1.6.0-1.1 [35.3 MB] Get:5 https://prod-cdn.packages.k8s.io/repositories/isv:/kubernetes:/core:/stable:/v1.33/deb kubelet 1.33.4-1.1 [13.5 MB] Fetched 85.6 MB in 2s (37.1 MB/s) debconf: delaying package configuration, since apt-utils is not installed Selecting previously unselected package cri-tools. (Reading database ... 9884 files and directories currently installed.) Preparing to unpack .../cri-tools_1.33.0-1.1_arm64.deb ... Unpacking cri-tools (1.33.0-1.1) ... Selecting previously unselected package kubeadm. Preparing to unpack .../kubeadm_1.33.4-1.1_arm64.deb ... Unpacking kubeadm (1.33.4-1.1) ... Selecting previously unselected package kubectl. Preparing to unpack .../kubectl_1.33.4-1.1_arm64.deb ... Unpacking kubectl (1.33.4-1.1) ... Selecting previously unselected package kubernetes-cni. Preparing to unpack .../kubernetes-cni_1.6.0-1.1_arm64.deb ... Unpacking kubernetes-cni (1.6.0-1.1) ... Selecting previously unselected package kubelet. Preparing to unpack .../kubelet_1.33.4-1.1_arm64.deb ... Unpacking kubelet (1.33.4-1.1) ... Setting up kubectl (1.33.4-1.1) ... Setting up cri-tools (1.33.0-1.1) ... Setting up kubernetes-cni (1.6.0-1.1) ... Setting up kubeadm (1.33.4-1.1) ... Setting up kubelet (1.33.4-1.1) ... Configuration file '/etc/default/kubelet' ==> File on system created by you or by a script. ==> File also in package provided by package maintainer. What would you like to do about it ? Your options are: Y or I : install the package maintainers version N or O : keep your currently-installed version D : show the differences between the versions Z : start a shell to examine the situation The default action is to keep your current version. *** kubelet (Y/I/N/O/D/Z) [default=N] ? # Hold the new version root@desktop-worker:/# apt-mark hold kubelet kubeadm kubectl kubelet set on hold. kubeadm set on hold. kubectl set on hold. # Upgrade node root@desktop-worker:/# kubeadm upgrade node [upgrade] Reading configuration from the "kubeadm-config" ConfigMap in namespace "kube-system"... [upgrade] Use 'kubeadm init phase upload-config --config your-config-file' to re-upload it. [upgrade/preflight] Running pre-flight checks [upgrade/preflight] Skipping prepull. Not a control plane node. [upgrade/control-plane] Skipping phase. Not a control plane node. [upgrade/kubeconfig] Skipping phase. Not a control plane node. W0822 18:14:56.518328 8123 postupgrade.go:117] Using temporary directory /etc/kubernetes/tmp/kubeadm-kubelet-config133089756 for kubelet config. To override it set the environment variable KUBEADM_UPGRADE_DRYRUN_DIR [upgrade] Backing up kubelet config file to /etc/kubernetes/tmp/kubeadm-kubelet-config133089756/config.yaml [kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml" [upgrade/kubelet-config] The kubelet configuration for this node was successfully upgraded! [upgrade/addon] Skipping the addon/coredns phase. Not a control plane node. [upgrade/addon] Skipping the addon/kube-proxy phase. Not a control plane node. # Restart the kubelet root@desktop-worker:/# systemctl restart kubelet
Repeat the process for the remaining node desktop-worker2
and then, afterwards we can confirm with kubectl get nodes
, that the cluster was updated!
james@Mac ~ % docker exec -it desktop-control-plane bash root@desktop-control-plane:/# kubectl get nodes NAME STATUS ROLES AGE VERSION desktop-control-plane Ready control-plane 3h10m v1.33.4 desktop-worker Ready <none> 3h10m v1.33.4 desktop-worker2 Ready <none> 3h10m v1.33.4
Gotchas & Notes
- Containerd image store: Kind uses containerd
- Context conflicts: If
kubectl
points to another environment (e.g. minikube), switch todocker-desktop
- System containers hidden by default: toggle Show system containers to inspect them with
docker ps
Educator’s Take & Call To Action
For learners and teams, Kind in Docker Desktop is a great option:
- It removes the friction from "getting a real cluster"
- We don't need to overcomplicate this for learning purposes
- It mirrors upstream behavior and versions
- Kind is used to test and validate Kubernetes
- It scales from a single laptop to a CI fleet
Get Started
- Enable Kind in Docker Desktop: Settings → Kubernetes → Enable → Provisioner: Kind
- Practice with purpose:
If you enjoy deep dives, here are related reads from my blog:
- The Kubernetes Pause Container - https://diveinto.com/blog/kubernetes-pause-container
- CRI‑dockerd - https://diveinto.com/blog/cri-dockerd
- Custom Tools: idle - https://diveinto.com/blog/custom-tools-idle
- Custom Tools: snooze - https://diveinto.com/blog/custom-tools-snooze
- Docker Model Runner: The Dev Whisperer - https://diveinto.com/blog/docker-model-runner-dev-whisperer
Useful links:
- Kind: https://kind.sigs.k8s.io
- Quick start: https://kind.sigs.k8s.io/docs/user/quick-start/
If this helped, share it with your team. The less time we spend wrestling local clusters, the more time we spend learning, building, and contributing.