Skip to main content

K8s Ep 1: Architecture & Your First Multi-Node Local Cluster Setup

Rachmat Hidayat
Author
Rachmat Hidayat
Learn & sharing insights on TypeScript, Go, Kubernetes, DevOps, DevSecOps, SRE, Platform Engineering, AI/ML Engineering, and MLOps.
Table of Contents
kubernetes - This article is part of a series.
Part 1: This Article
Many Kubernetes tutorials overwhelm you with hours of theory before ever touching a terminal. Here, we embrace the HowToForge philosophy: we build it first, then we dissect how it works.

TL;DR (Quick Summary)
#

  • Control Plane vs Worker Node: The Control Plane makes cluster decisions, tracks state in etcd, and schedules workloads. Worker Nodes execute containers via container runtimes (containerd/Docker) and kubelet.
  • Kind (Kubernetes in Docker): Runs Kubernetes nodes as Docker containers on your local laptop, enabling production-like multi-node topology without cloud VM costs.
  • The API Server: Port 6443 on the Control Plane node receives all kubectl requests via REST endpoints.
  • Multi-Node Setup: Use a kind-config.yaml manifest to define 1 Control Plane and 2 Worker Nodes in under 2 minutes.

1. Understanding Basic Kubernetes Architecture
#

Before typing your first command, you must understand the core architecture of a Kubernetes cluster.

A production Kubernetes cluster is split into two distinct planes:


graph TD
    Client["kubectl CLI"] -->|HTTPS REST API :6443| APIServer["kube-apiserver"]

    subgraph ControlPlane ["Control Plane"]
        APIServer <--> ETCD["(etcd Database)"]
        APIServer <--> Scheduler["kube-scheduler"]
        APIServer <--> Controller["kube-controller-manager"]
    end
    
    subgraph WorkerNode1 ["Worker Node 1"]
        Kubelet1["kubelet"] <--> APIServer
        Kubelet1 <--> Containerd1["containerd / CRI"]
        KubeProxy1["kube-proxy"]
    end

    subgraph WorkerNode2 ["Worker Node 2"]
        Kubelet2["kubelet"] <--> APIServer
        Kubelet2 <--> Containerd2["containerd / CRI"]
        KubeProxy2["kube-proxy"]
    end

Control Plane Components (The Brain)
#

  1. kube-apiserver: The central communication hub. Exposes the Kubernetes REST API (default port 6443). Every tool (including kubectl) interacts directly with kube-apiserver.
  2. etcd: A highly available, consistent key-value store used as Kubernetes’ backing store for all cluster data (state, secrets, configs).
  3. kube-scheduler: Watches for newly created Pods with no assigned node, and selects a node for them to run on based on resource requirements.
  4. kube-controller-manager: Runs controller processes in the background (Node Controller, ReplicaSet Controller, EndpointSlice Controller) to maintain the desired state of the cluster.

Worker Node Components (The Execution Engine)
#

  1. kubelet: An agent that runs on each node in the cluster. It makes sure that containers described in PodSpecs are running and healthy.
  2. kube-proxy: A network proxy that runs on each node, maintaining network rules (using iptables or IPVS) to allow network communication to your Pods from inside or outside of your cluster.
  3. Container Runtime (containerd / CRI-O): The software responsible for running containers.

2. Comparison: Control Plane vs Worker Node
#

FeatureControl PlaneWorker Node
Primary GoalCluster management & state tracking.Application workload execution.
Key Serviceskube-apiserver, etcd, kube-scheduler, kube-controller-manager.kubelet, kube-proxy, containerd.
User AccessReceives REST API calls on port 6443.Usually isolated behind Ingress or Load Balancers.
Failure ImpactCluster management freezes (existing workloads continue running).Workloads on the failed node are rescheduled to healthy nodes.

3. System Prerequisites
#

To follow along with this tutorial, ensure your system (Linux, macOS, or Windows WSL2) has the following software installed:

Step 1: Installing Docker
#

Kind requires the Docker engine to run Kubernetes “nodes”.

# Ubuntu/Debian
sudo apt update
sudo apt install docker.io -y
sudo systemctl enable --now docker
sudo usermod -aG docker $USER

(Note: Log out and log back in to activate the Docker group).

Step 2: Installing kubectl
#

kubectl is the primary CLI tool you will use to issue commands to the Kubernetes Control Plane.

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
sudo mv kubectl /usr/local/bin/

Verify the installation:

kubectl version --client

Step 3: Installing Kind (Kubernetes in Docker)
#

# Download Kind binary
[ $(uname -m) = x86_64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind

4. Building Your First Multi-Node Cluster
#

By default, the kind create cluster command will only provision 1 node (acting as both the Control Plane and Worker simultaneously).

However, since we want to simulate a production-grade environment, we will provision a cluster with 1 Control Plane and 2 Worker Nodes.

Step 4.1: Creating the Kind Configuration File
#

Create a file named kind-config.yaml and populate it with the following configuration:

# kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
Tip

Tip: The configuration above instructs Kind to pull a specialized Docker image containing the complete Kubernetes components, and then boot up 3 isolated containers.

Step 4.2: Executing Cluster Creation
#

Run this command in the same directory as your configuration file:

kind create cluster --name k8s-lab --config kind-config.yaml

Expected Terminal Output:

Creating cluster "k8s-lab" ...
 βœ“ Ensuring node image (kindest/node:v1.27.3) πŸ–Ό 
 βœ“ Preparing nodes πŸ“¦ πŸ“¦ πŸ“¦  
 βœ“ Writing configuration πŸ“œ 
 βœ“ Starting control-plane πŸ•ΉοΈ 
 βœ“ Installing CNI πŸ”Œ 
 βœ“ Installing StorageClass πŸ’Ύ 
 βœ“ Joining worker nodes 🚜 
Set kubectl context to "kind-k8s-lab"
You can now use your cluster with:

kubectl cluster-info --context kind-k8s-lab

5. Verification and Cluster Testing
#

You now have a living, breathing Kubernetes cluster inside your laptop. Let’s ensure everything is running nominally.

Checking Node Status
#

This command asks the Control Plane: “List all connected nodes and their current status.”

kubectl get nodes -o wide

Expected Terminal Output:

NAME                    STATUS   ROLES           AGE     VERSION   INTERNAL-IP   OS-IMAGE       KERNEL-VERSION      CONTAINER-RUNTIME
k8s-lab-control-plane   Ready    control-plane   2m45s   v1.27.3   172.18.0.4    Ubuntu 22.04   5.15.0-88-generic   containerd://1.7.1
k8s-lab-worker          Ready    <none>          2m21s   v1.27.3   172.18.0.3    Ubuntu 22.04   5.15.0-88-generic   containerd://1.7.1
k8s-lab-worker2         Ready    <none>          2m21s   v1.27.3   172.18.0.2    Ubuntu 22.04   5.15.0-88-generic   containerd://1.7.1

Notice that the status is Ready. This implies the Control Plane has successfully established communication with both Worker Nodes.

Peeking Behind the Curtain (Docker)
#

Because we are utilizing Kind, every “Node” is actually just a Docker container. Let’s prove it:

docker ps

Expected Terminal Output:

CONTAINER ID   IMAGE                  COMMAND                  CREATED         STATUS         PORTS                       NAMES
d4e5f6g7h8i9   kindest/node:v1.27.3   "/usr/local/bin/entr…"   3 minutes ago   Up 3 minutes                               k8s-lab-worker
a1b2c3d4e5f6   kindest/node:v1.27.3   "/usr/local/bin/entr…"   3 minutes ago   Up 3 minutes                               k8s-lab-worker2
9z8y7x6w5v4u   kindest/node:v1.27.3   "/usr/local/bin/entr…"   3 minutes ago   Up 3 minutes   127.0.0.1:45678->6443/tcp   k8s-lab-control-plane

Do you see port 6443 on the control-plane container? That is the Kubernetes API Server port. The kubectl commands you type from your terminal are actually sending HTTP Requests to that exact port!


6. Troubleshooting & Common Errors
#

Error 1: docker: permission denied while trying to connect to the Docker daemon socket
#

The Cause: Your current Linux user is not in the docker group. The Fix:

sudo usermod -aG docker $USER
newgrp docker

Error 2: kind create cluster hangs or times out
#

The Cause: Insufficient memory or disk space allocated to Docker daemon. Kind requires at least 4GB of RAM allocated to Docker. The Fix: Increase Docker RAM allocation in Docker Desktop settings, or clean up unused Docker containers and images:

docker system prune -a --volumes

7. Tearing Down the Cluster (Optional)
#

If you wish to power down this lab and reclaim your RAM, you can destroy it just as easily as you created it:

kind delete cluster --name k8s-lab
Caution

This command will permanently delete all configurations, state, and applications running inside the cluster.


Summary & Next Steps
#

In this episode:

  • We dissected the Kubernetes architecture into Control Plane and Worker Node components.
  • We built a 3-node local Kubernetes cluster using Kind and Docker.
  • We verified node connectivity via kubectl get nodes -o wide.
  • We traced kubectl calls directly to port 6443 of the API Server.

In Episode 2: Pods and Deployments, we will deploy our first application, inspect container lifecycles, and explore self-healing deployment logic!

kubernetes - This article is part of a series.
Part 1: This Article