DEV Community

Cover image for Manual Kubernetes Cluster Setup on AWS (kubeadm + Flannel)
Krisha Arya
Krisha Arya

Posted on

Manual Kubernetes Cluster Setup on AWS (kubeadm + Flannel)

Introduction

Setting up a Kubernetes cluster manually is the best way to understand how Kubernetes works internally.
In this guide, we set up a 2-node Kubernetes cluster on AWS EC2, where:

  • One instance acts as the Master Node
  • One instance acts as a Worker Node

This documentation is based on real troubleshooting, not just ideal steps. It includes:

  • Correct setup steps
  • Common beginner mistakes
  • Real errors faced during setup
  • Why those errors happened
  • Exact commands used to fix them
  • AWS Security Group configuration
  • Final NGINX deployment and access

By the end, you will have:

  • A working Kubernetes cluster
  • Clear understanding of pod networking
  • Knowledge of CNI (Flannel)
  • Practical exposure to NodePort services

Cluster Components

Component Description
Master Node Kubernetes control plane
Worker Node Runs application pods
Runtime containerd
CNI Flannel (VXLAN)
Exposure NodePort

🔹 STEP A: Create AWS EC2 Instances

Create 2 EC2 instances in AWS.

Instance Details

  • OS: Ubuntu 22.04 / 24.04 LTS
  • Instance Type: t2.medium (minimum)
  • VPC/Subnet: Same for both instances
  • Key Pair: k8s.pem

Instance Names

  • Master Node: master-node
  • Worker Node: worker-node-1

🔹 STEP B: Configure Security Group (IMPORTANT)

Attach the same Security Group to both instances.

Inbound Rules

Type Protocol Port Source
SSH TCP 22 Your IP
Kubernetes API TCP 6443 VPC CIDR
NodePort TCP 30080 0.0.0.0/0

⚠️ Without port 30080 open, NodePort will not work in the browser.


🔹 STEP C: Get Public IPs

After instances start, note:

  • Master Public IP
  • Worker Public IP

Example:

Worker Node Public IP: 65.0.4.177
Enter fullscreen mode Exit fullscreen mode

🔹 STEP D: Prepare SSH Key on Windows (PowerShell)

Open PowerShell as Administrator in the folder containing k8s.pem.

Fix permission issues (VERY IMPORTANT on Windows)

icacls k8s.pem /inheritance:r
icacls k8s.pem /grant:r "$($env:USERNAME):(R)"
Enter fullscreen mode Exit fullscreen mode

This avoids:

  • Bad permissions
  • Permission denied (publickey)

🔹 STEP E: SSH into EC2 Instances

Connect to Master Node

ssh -i k8s.pem ubuntu@<MASTER_PUBLIC_IP>
Enter fullscreen mode Exit fullscreen mode

Connect to Worker Node

ssh -i k8s.pem ubuntu@65.0.4.177
Enter fullscreen mode Exit fullscreen mode

👉 You are now inside the Linux terminal of your EC2 instance.


🔹 STEP F: Verify Connection

Run on both nodes:

whoami
hostname
Enter fullscreen mode Exit fullscreen mode

Expected Output

ubuntu
master-node / worker-node-1
Enter fullscreen mode Exit fullscreen mode

STEP 1: Set Hostnames & Hosts File

Master Node

sudo hostnamectl set-hostname master-node
hostname
Enter fullscreen mode Exit fullscreen mode

Worker Node

sudo hostnamectl set-hostname worker-node-1
hostname
Enter fullscreen mode Exit fullscreen mode

Update /etc/hosts on BOTH nodes

sudo nano /etc/hosts
Enter fullscreen mode Exit fullscreen mode

Example:

172.31.xx.xx master-node
172.31.yy.yy worker-node-1
Enter fullscreen mode Exit fullscreen mode

STEP 2: Disable Swap & Enable Kernel Networking

Run on BOTH nodes.

Disable Swap

sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab
Enter fullscreen mode Exit fullscreen mode

Load Required Kernel Modules

sudo modprobe overlay
sudo modprobe br_netfilter
sudo modprobe vxlan
Enter fullscreen mode Exit fullscreen mode

Persist Modules

cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
vxlan
EOF
Enter fullscreen mode Exit fullscreen mode

Enable sysctl Settings

cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF

sudo sysctl --system
Enter fullscreen mode Exit fullscreen mode

STEP 3: Install containerd Runtime

Run on BOTH nodes.

sudo apt update
sudo apt install -y containerd
Enter fullscreen mode Exit fullscreen mode

Generate Default Config

sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
Enter fullscreen mode Exit fullscreen mode

Enable systemd Cgroup

sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' \
/etc/containerd/config.toml
Enter fullscreen mode Exit fullscreen mode

Restart & Enable

sudo systemctl restart containerd
sudo systemctl enable containerd
Enter fullscreen mode Exit fullscreen mode

STEP 4: Install Kubernetes Components

Run on BOTH nodes.

sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl gpg
Enter fullscreen mode Exit fullscreen mode

Add Kubernetes Repository

curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key | \
sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] \
https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /" | \
sudo tee /etc/apt/sources.list.d/kubernetes.list
Enter fullscreen mode Exit fullscreen mode

Install Components

sudo apt update
sudo apt install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
Enter fullscreen mode Exit fullscreen mode

STEP 5: Initialize Master Node

sudo kubeadm init --pod-network-cidr=10.244.0.0/16
Enter fullscreen mode Exit fullscreen mode

Note: Above IP is fixed. Use same IP.

STEP 6: Configure kubectl (Master Node)

mkdir -p $HOME/.kube
sudo cp /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Enter fullscreen mode Exit fullscreen mode

STEP 7: Install Flannel CNI

Initial Confusion

kubectl get pods -n kube-system | grep flannel
Enter fullscreen mode Exit fullscreen mode

➡️ Output was empty (Flannel does not run in kube-system)

Correct Installation Command

kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
Enter fullscreen mode Exit fullscreen mode

Correct Namespace Check

kubectl get ds -n kube-flannel
kubectl get pods -n kube-flannel
Enter fullscreen mode Exit fullscreen mode

❌ Errors Faced & How They Were Fixed

❌ Error 1: Flannel CrashLoopBackOff

Cause

  • br_netfilter not loaded
  • vxlan missing
  • /proc/sys/net/bridge not present

Fix

sudo modprobe br_netfilter
sudo modprobe vxlan
lsmod | egrep 'vxlan|br_netfilter'
Enter fullscreen mode Exit fullscreen mode

❌ Error 2: sysctl bridge error

cannot stat /proc/sys/net/bridge/bridge-nf-call-iptables
Enter fullscreen mode Exit fullscreen mode

Cause

  • br_netfilter not loaded on worker

Fix (Worker Node)

sudo modprobe br_netfilter
sudo sysctl -w net.bridge.bridge-nf-call-iptables=1
sudo sysctl -w net.bridge.bridge-nf-call-ip6tables=1
Enter fullscreen mode Exit fullscreen mode

Persist Settings

cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes.conf
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF

sudo sysctl --system
Enter fullscreen mode Exit fullscreen mode

❌ Error 3: CoreDNS stuck in ContainerCreating

Cause

  • Flannel networking not ready

Fix

kubectl delete pod -n kube-flannel -l app=flannel
kubectl get pods -n kube-flannel
Enter fullscreen mode Exit fullscreen mode

Once Flannel became Running, CoreDNS automatically turned Running.


STEP 8: Join Worker Node

sudo kubeadm join <MASTER-IP>:6443 --token <TOKEN> \
--discovery-token-ca-cert-hash sha256:<HASH>
Enter fullscreen mode Exit fullscreen mode

STEP 9: Validate Cluster

kubectl get nodes
kubectl get pods -n kube-system
kubectl get pods -n kube-flannel
Enter fullscreen mode Exit fullscreen mode

All components must be Running.


🚀 Deploy NGINX Application

nginx-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
Enter fullscreen mode Exit fullscreen mode
kubectl apply -f nginx-deployment.yaml
kubectl get pods
Enter fullscreen mode Exit fullscreen mode

nginx-service.yaml (NodePort)

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: NodePort
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 80
    nodePort: 30080
Enter fullscreen mode Exit fullscreen mode
kubectl apply -f nginx-service.yaml
kubectl get svc
Enter fullscreen mode Exit fullscreen mode

Access Application

From Worker Node

curl http://localhost:30080
Enter fullscreen mode Exit fullscreen mode

From Browser

http://<WORKER_PUBLIC_IP>:30080
Enter fullscreen mode Exit fullscreen mode

⚠️ Requires AWS Security Group port 30080 open


Conclusion

This setup demonstrated:

  • Kubernetes cluster creation using kubeadm
  • Real CNI networking issues and fixes
  • Importance of kernel modules for Flannel
  • AWS networking & NodePort behavior
  • End-to-end deployment of an application

Top comments (0)