Skip to content

Kubernetes Cluster Installation Guide - Proxmox Ubuntu#

System Requirements and Preparation#

Machine Configurations#

  • Master: 2+ CPU, 4+ GB RAM, 20+ GB Disk
  • Worker1: 2+ CPU, 2+ GB RAM, 20+ GB Disk
  • Worker2: 2+ CPU, 2+ GB RAM, 20+ GB Disk

IP Addresses (Example - adjust to your own network)#

  • Master: 192.168.1.10
  • Worker1: 192.168.1.11
  • Worker2: 192.168.1.12

STEP 1: Cleanup of Existing Docker and Kubernetes#

Run on all machines:#

# Stop Kubernetes services
sudo systemctl stop kubelet kubeadm kubectl

# Stop Docker
sudo systemctl stop docker

# Remove Kubernetes packages
sudo apt-get purge -y kubeadm kubectl kubelet kubernetes-cni kube*

# Remove Docker packages
sudo apt-get purge -y docker.io docker-ce docker-ce-cli containerd.io

# Clean up configuration files
sudo rm -rf /etc/kubernetes/
sudo rm -rf /var/lib/kubelet/
sudo rm -rf /var/lib/etcd/
sudo rm -rf /etc/docker/
sudo rm -rf /var/lib/docker/
sudo rm -rf /var/lib/containerd/
sudo rm -rf ~/.kube/

# Update system packages
sudo apt-get autoremove -y
sudo apt-get autoclean
sudo apt-get update

STEP 2: System Settings and Preparation#

Run on all machines:#

# Set hostname (different for each machine)
# For Master:
sudo hostnamectl set-hostname k8s-master

# For Worker1:
sudo hostnamectl set-hostname k8s-worker1

# For Worker2:
sudo hostnamectl set-hostname k8s-worker2

# Edit the hosts file
sudo nano /etc/hosts

# Add the following lines (adjust your IP addresses):
192.168.1.10    k8s-master
192.168.1.11    k8s-worker1
192.168.1.12    k8s-worker2

# Disable swap
sudo swapoff -a
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab

# Load kernel modules
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

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

sudo sysctl --system

# System update
sudo apt-get update && sudo apt-get upgrade -y

STEP 3: Docker Installation#

Run on all machines:#

# Install required packages
sudo apt-get update
sudo apt-get install -y \
    ca-certificates \
    curl \
    gnupg \
    lsb-release \
    apt-transport-https

# Add the Docker GPG key
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# Add the Docker repository
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Install Docker
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Start and enable Docker
sudo systemctl start docker
sudo systemctl enable docker

# Add the user to the docker group
sudo usermod -aG docker $USER

# Configure containerd
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml

# Enable SystemdCgroup
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml

# Restart containerd
sudo systemctl restart containerd
sudo systemctl enable containerd

STEP 4: Kubernetes Installation#

Run on all machines:#

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

# Add the Kubernetes repository
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

# Update the package list
sudo apt-get update

# Install Kubernetes packages
sudo apt-get install -y kubelet kubeadm kubectl

# Hold the packages (prevent automatic updates)
sudo apt-mark hold kubelet kubeadm kubectl

# Start and enable kubelet
sudo systemctl enable kubelet

STEP 5: Master Node Installation#

Run only on the Master machine:#

# Initialize the cluster
sudo kubeadm init \
  --pod-network-cidr=10.244.0.0/16 \
  --apiserver-advertise-address=192.168.1.10 \
  --control-plane-endpoint=k8s-master

# Save the join command from the output! Example:
# kubeadm join k8s-master:6443 --token abcd12.1234567890123456 \
#     --discovery-token-ca-cert-hash sha256:1234567890abcdef...

# kubectl configuration
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# Set up for the root user as well
sudo mkdir -p /root/.kube
sudo cp -i /etc/kubernetes/admin.conf /root/.kube/config

# Check the cluster status
kubectl get nodes
kubectl get pods -A

STEP 6: Pod Network (Flannel) Installation#

Run only on the Master machine:#

# Install the Flannel network plugin
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

# Check the installation
kubectl get pods -n kube-flannel
kubectl get nodes

# Wait for all pods to be Running
watch kubectl get pods -A

STEP 7: Joining Worker Nodes to the Cluster#

Run on the Worker1 and Worker2 machines:#

# Run the join command you got from the Master
# Example (use your own token):
sudo kubeadm join k8s-master:6443 --token abcd12.1234567890123456 \
    --discovery-token-ca-cert-hash sha256:1234567890abcdef...

# If you forgot the token, create a new token on the Master:
# kubeadm token create --print-join-command

Check on the Master:#

# Check the nodes
kubectl get nodes

# Detailed info
kubectl get nodes -o wide

# Wait for all nodes to be Ready
watch kubectl get nodes

STEP 8: Cluster Test and Verification#

Run a test application on the Master machine:#

# Create a test deployment
kubectl create deployment nginx-test --image=nginx

# Create a service
kubectl expose deployment nginx-test --port=80 --type=NodePort

# Scale the deployment
kubectl scale deployment nginx-test --replicas=3

# Check the status
kubectl get deployments
kubectl get pods -o wide
kubectl get services

# Check that pods run on different nodes
kubectl get pods -o wide

# Test the service
kubectl get svc nginx-test

STEP 9: Dashboard Installation (Optional)#

Run on the Master machine:#

# Install the Dashboard
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml

# Create an admin user
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: admin-user
  namespace: kubernetes-dashboard
EOF

# Create a token
kubectl -n kubernetes-dashboard create token admin-user

# Start a proxy to access the Dashboard
kubectl proxy --address='0.0.0.0' --accept-hosts='^*$'

# Dashboard URL:
# http://192.168.1.10:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/

STEP 10: Monitoring and Logging (Optional)#

Metrics Server installation:#

# Install the metrics server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

# Edit the metrics server (for self-signed certificates)
kubectl patch deployment metrics-server -n kube-system --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--kubelet-insecure-tls"}]'

# Check resource usage
kubectl top nodes
kubectl top pods -A

Useful Commands and Troubleshooting#

Cluster Management:#

# List all nodes
kubectl get nodes -o wide

# Cluster info
kubectl cluster-info

# List namespaces
kubectl get namespaces

# List all pods
kubectl get pods -A

# Node details
kubectl describe node k8s-master

# View events
kubectl get events --sort-by=.metadata.creationTimestamp

Troubleshooting:#

# Kubelet logs
sudo journalctl -u kubelet -f

# Container runtime logs
sudo journalctl -u containerd -f

# Cluster status
kubectl get componentstatuses

# Pod logs
kubectl logs <pod-name> -n <namespace>

# Drain the node (for maintenance)
kubectl drain <node-name> --ignore-daemonsets

# Reactivate the node
kubectl uncordon <node-name>

Removing a worker node:#

# On the Master:
kubectl drain <node-name> --delete-emptydir-data --force --ignore-daemonsets
kubectl delete node <node-name>

# On the worker node:
sudo kubeadm reset
sudo rm -rf /etc/kubernetes/
sudo rm -rf ~/.kube/

Security and Optimization#

Security settings:#

# Enable network policies
# (Flannel does not support them by default, you can use Calico)

# Check RBAC
kubectl get clusterrolebindings

# Pod security policies
kubectl get psp

Performance optimization:#

# Node resources
kubectl describe nodes

# Resource quotas
kubectl get resourcequotas -A

# CPU and Memory limits
kubectl get pods -A -o custom-columns=NAME:.metadata.name,CPU:.spec.containers[*].resources.requests.cpu,MEMORY:.spec.containers[*].resources.requests.memory

Backup and Restore#

ETCD Backup:#

# Take an ETCD snapshot
sudo ETCDCTL_API=3 etcdctl snapshot save /tmp/etcd-backup.db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

# Verify the snapshot
sudo ETCDCTL_API=3 etcdctl snapshot status /tmp/etcd-backup.db

Conclusion#

By following this guide, you have: - ✅ Performed a clean Kubernetes cluster installation - ✅ Created a 3-node (1 master, 2 worker) cluster - ✅ Installed a network plugin (Flannel) - ✅ Run a test application - ✅ Installed the Dashboard (optional) - ✅ Installed monitoring tools (optional)

Your cluster is now production-ready. You can start deploying applications!

Connectivity Test:#

# Test that all nodes communicate with each other
kubectl get nodes
kubectl get pods -A -o wide

Guide to Possible Errors#

CD-ROM Repository Error#

# Back up the existing file
sudo mv /etc/apt/sources.list /etc/apt/sources.list.old

# Create a new sources.list
sudo tee /etc/apt/sources.list > /dev/null <<EOF
# Ubuntu 22.04 LTS (Jammy) Repository List

deb http://archive.ubuntu.com/ubuntu/ jammy main restricted
deb-src http://archive.ubuntu.com/ubuntu/ jammy main restricted

deb http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted
deb-src http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted

deb http://archive.ubuntu.com/ubuntu/ jammy universe
deb-src http://archive.ubuntu.com/ubuntu/ jammy universe
deb http://archive.ubuntu.com/ubuntu/ jammy-updates universe
deb-src http://archive.ubuntu.com/ubuntu/ jammy-updates universe

deb http://archive.ubuntu.com/ubuntu/ jammy multiverse
deb-src http://archive.ubuntu.com/ubuntu/ jammy multiverse
deb http://archive.ubuntu.com/ubuntu/ jammy-updates multiverse
deb-src http://archive.ubuntu.com/ubuntu/ jammy-updates multiverse

deb http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted universe multiverse
deb-src http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted universe multiverse

deb http://security.ubuntu.com/ubuntu/ jammy-security main restricted
deb-src http://security.ubuntu.com/ubuntu/ jammy-security main restricted
deb http://security.ubuntu.com/ubuntu/ jammy-security universe
deb-src http://security.ubuntu.com/ubuntu/ jammy-security universe
deb http://security.ubuntu.com/ubuntu/ jammy-security multiverse
deb-src http://security.ubuntu.com/ubuntu/ jammy-security multiverse
EOF

## Or

deb http://mirror.kku.ac.th/ubuntu jammy main restricted universe multiverse
deb http://mirror.kku.ac.th/ubuntu jammy-updates main restricted universe multiverse
deb http://mirror.kku.ac.th/ubuntu jammy-backports main restricted universe multiverse
deb http://mirror.kku.ac.th/ubuntu jammy-security main restricted universe multiverse

Port Conflict Left Over from an Old System#

# Stop all Kubernetes services
sudo systemctl stop kubelet
sudo systemctl stop docker
sudo systemctl stop containerd

# Reset the Kubernetes cluster
sudo kubeadm reset --force

# Stop and remove all containers
docker ps -a -q | xargs -r docker stop
docker ps -a -q | xargs -r docker rm

# Remove all images
docker images -q | xargs -r docker rmi -f

# Find processes using Kubernetes ports
sudo netstat -tulnp | grep -E ":(6443|10250|10259|10257|2379|2380)"
sudo lsof -i :10259
sudo lsof -i :10257  
sudo lsof -i :10250
sudo lsof -i :6443

# If there are processes, kill them
sudo pkill -f kube-apiserver
sudo pkill -f kube-controller-manager
sudo pkill -f kube-scheduler
sudo pkill -f kubelet
sudo pkill -f etcd

# Completely remove Kubernetes files
sudo rm -rf /etc/kubernetes/
sudo rm -rf /var/lib/kubelet/
sudo rm -rf /var/lib/etcd/
sudo rm -rf /var/lib/dockershim/
sudo rm -rf /var/run/kubernetes/
sudo rm -rf /var/lib/cni/
sudo rm -rf /etc/cni/
sudo rm -rf ~/.kube/
sudo rm -rf /root/.kube/

# Clean up container runtime files
sudo rm -rf /var/lib/containerd/
sudo rm -rf /var/lib/docker/
sudo rm -rf /run/containerd/
sudo rm -rf /run/docker/

# Clean up network interfaces
sudo ip link delete cni0 2>/dev/null || true
sudo ip link delete flannel.1 2>/dev/null || true
sudo ip link delete docker0 2>/dev/null || true

# Clean up iptables rules
sudo iptables -F
sudo iptables -X
sudo iptables -t nat -F
sudo iptables -t nat -X
sudo iptables -t mangle -F
sudo iptables -t mangle -X

# Reconfigure containerd
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml

# Restart the services
sudo systemctl restart containerd
sudo systemctl restart docker
sudo systemctl daemon-reload

# Check the service statuses
sudo systemctl status containerd
sudo systemctl status docker

# Check that the ports are free
sudo netstat -tulnp | grep -E ":(6443|10250|10259|10257|2379|2380)"

# If there are still processes, a system restart may be needed
# sudo reboot

# Check whether the system is ready
sudo kubeadm init phase preflight

# Run kubeadm init
sudo kubeadm init \
  --pod-network-cidr=10.244.0.0/16 \
  --apiserver-advertise-address=<K8S_MASTER_IP> \
  --control-plane-endpoint=master \
  --v=5

# Restart the system
sudo reboot

# Try again after the restart
sudo kubeadm init \
  --pod-network-cidr=10.244.0.0/16 \
  --apiserver-advertise-address=<K8S_MASTER_IP> \
  --control-plane-endpoint=master

HTTPS Error and Making the Token Persistent#

# Get the token on the Master
kubectl -n kubernetes-dashboard create token admin-user

# Expose the Dashboard as NodePort
kubectl patch svc kubernetes-dashboard -n kubernetes-dashboard -p '{"spec":{"type":"NodePort"}}'

# Find out the port number
kubectl get svc kubernetes-dashboard -n kubernetes-dashboard

# Use the HTTPS URL
# https://<K8S_MASTER_IP>:<nodeport-number>

# Create a long-lived token
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
  name: admin-user-secret
  namespace: kubernetes-dashboard
  annotations:
    kubernetes.io/service-account.name: admin-user
type: kubernetes.io/service-account-token
EOF

# Get the token
kubectl get secret admin-user-secret -n kubernetes-dashboard -o jsonpath='{.data.token}' | base64 -d