KVM Deployment of Kubernetes Cluster Operations Manual
Preface
This manual provides detailed steps for deploying a complete Kubernetes cluster using KVM on Ubuntu 22.04 LTS system.
Environment Requirements
Hardware Configuration
- CPU: Supports hardware virtualization (Intel VT-x or AMD-V must be enabled)
- Memory: Minimum 8GB (2GB per node)
- Storage: Minimum 120GB free space
- Network: Stable network connection supporting virtual bridges
Software Versions
- OS: Ubuntu 22.04 LTS
- Kubernetes: v1.32.1
- Container Runtime: containerd (latest stable version)
1. Basic Environment Preparation
1.1 Environment Variable Configuration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| # Create working directory structure
export X_DIR="/opt/k8s"
export X_DOWNLOAD_DIR="${X_DIR}/download"
export X_IMG_DIR="${X_DIR}/images"
export X_CFG_DIR="${X_DIR}/configs"
export X_NET="k8s-net"
export X_OS_VARIANT="ubuntu22.04"
export X_BASE_IMG="${X_DOWNLOAD_DIR}/jammy-server-cloudimg-amd64.img"
export QCOW2_URL="https://cloud-images.ubuntu.com/jammy/releases/jammy/release/jammy-server-cloudimg-amd64.img"
# Initialize directories
sudo mkdir -p $X_DOWNLOAD_DIR $X_IMG_DIR $X_CFG_DIR
sudo chown -R $USER:$USER $X_DIR
# Download base image
[[ ! -f ${X_BASE_IMG} ]] && curl -fsSL -o "${X_BASE_IMG}" "${QCOW2_URL}"
# Clean
sudo virsh net-destroy ${X_NET} 2>/dev/null
sudo virsh net-undefine ${X_NET} 2>/dev/null
sudo virsh destroy k8s-cp-01 2>/dev/null
sudo virsh destroy k8s-worker-01 2>/dev/null
sudo virsh destroy k8s-worker-02 2>/dev/null
sudo virsh undefine k8s-cp-01 --remove-all-storage 2>/dev/null
sudo virsh undefine k8s-worker-01 --remove-all-storage 2>/dev/null
sudo virsh undefine k8s-worker-02 --remove-all-storage 2>/dev/null
sudo rm -rf ${X_CFG_DIR}/* ${X_IMG_DIR}/*
|
1.2 Install Required Components
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # System update
sudo apt-get update && sudo apt-get upgrade -y
# Install KVM and related tools
sudo apt-get install -y \
qemu-system-x86 \
libvirt-daemon-system \
libvirt-clients \
bridge-utils \
virt-manager \
virtinst \
cloud-image-utils \
wget \
curl
# Verify KVM installation
kvm-ok
sudo systemctl enable --now libvirtd
sudo systemctl status libvirtd
lsmod | grep kvm
|
2. Virtual Network Configuration
2.1 Create Dedicated Network
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| cat << EOF > ${X_CFG_DIR}/k8s-network.xml
<network>
<name>${X_NET}</name>
<forward mode="nat"/>
<bridge name="virbr-k8s" stp="on" delay="0"/>
<ip address="192.168.122.1" netmask="255.255.255.0">
<dhcp>
<range start="192.168.122.2" end="192.168.122.254"/>
</dhcp>
</ip>
</network>
EOF
# Deploy network
sudo virsh net-define ${X_CFG_DIR}/k8s-network.xml
sudo virsh net-start ${X_NET}
sudo virsh net-autostart ${X_NET}
sudo virsh net-list --all # Verify network status
|
3. Virtual Machine Deployment
3.1 Create Cloud-Init Configuration File
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
| # Generate SSH key (if not exists)
[[ ! -f ~/.ssh/id_ed25519 ]] && ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N ""
# Create cloud-init template
cat << EOF > ${X_CFG_DIR}/cloud-init.yml
#cloud-config
# Basic system configuration
hostname: myhost
fqdn: myhost.example.com
# User setup configuration
users:
- name: ubuntu
sudo: ALL=(ALL) NOPASSWD:ALL
groups: sudo
homedir: /home/ubuntu
shell: /bin/bash
ssh_authorized_keys:
- $(cat ~/.ssh/id_ed25519.pub)
# Password setup
password: ubuntu
chpasswd:
expire: false
ssh_pwauth: true
# Package management
package_update: true
package_upgrade: true
packages:
- curl
- apt-transport-https
- ca-certificates
- gnupg
- containerd.io
write_files:
- path: /etc/sysctl.d/k8s.conf
content: |
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
- path: /etc/modules-load.d/k8s.conf
content: |
overlay
br_netfilter
- path: /etc/containerd/certs.d/docker.io/hosts.toml
content: |
server = "https://docker.io"
[host."https://docker.m.daocloud.io"]
capabilities = ["pull", "resolve"]
[host."https://dockerproxy.net"]
capabilities = ["pull", "resolve"]
- path: /etc/containerd/certs.d/registry.k8s.io/hosts.toml
content: |
server = "https://registry.k8s.io"
[host."https://k8s.m.daocloud.io"]
capabilities = ["pull", "resolve"]
[host."https://k8s.dockerproxy.net"]
capabilities = ["pull", "resolve"]
- path: /etc/containerd/certs.d/gcr.io/hosts.toml
content: |
server = "https://gcr.io"
[host."https://gcr.m.daocloud.io"]
capabilities = ["pull", "resolve"]
[host."https://gcr.dockerproxy.net"]
capabilities = ["pull", "resolve"]
- path: /etc/containerd/certs.d/ghcr.io/hosts.toml
content: |
server = "https://ghcr.io"
[host."https://ghcr.m.daocloud.io"]
capabilities = ["pull", "resolve"]
[host."https://ghcr.dockerproxy.net"]
capabilities = ["pull", "resolve"]
- path: /etc/containerd/config.toml
content: |
version = 2
[plugins]
[plugins."io.containerd.grpc.v1.cri"]
[plugins."io.containerd.grpc.v1.cri".containerd]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
[plugins."io.containerd.grpc.v1.cri".registry]
config_path = "/etc/containerd/certs.d"
# Commands to run at the end of the cloud-init process
runcmd:
- curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
- sudo chmod 644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg
- sudo apt-get update
- sudo apt-get install -y kubelet kubeadm kubectl kubernetes-cni
- sudo apt-mark hold kubelet kubeadm kubectl
- sudo systemctl enable --now containerd
- sudo systemctl enable --now kubelet
- sudo kubeadm config images pull --image-repository=registry.aliyuncs.com/google_containers
# Configure apt sources
apt:
primary:
- arches: [default]
uri: https://mirrors.aliyun.com/ubuntu/
search:
- https://repo.huaweicloud.com/ubuntu/
- https://mirrors.cloud.tencent.com/ubuntu/
- https://mirrors.cernet.edu.cn/ubuntu/
- https://archive.ubuntu.com
sources:
docker.list:
source: deb [arch=amd64] https://mirrors.cernet.edu.cn/docker-ce/linux/ubuntu jammy stable
keyid: 0EBFCD88
kubernetes.list:
source: deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /
keyid: 0D811D58
power_state:
mode: reboot
EOF
|
3.2 Deploy Virtual Machines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
| cp ${X_BASE_IMG} ${X_IMG_DIR}/k8s-cp-01.img
cp ${X_BASE_IMG} ${X_IMG_DIR}/k8s-worker-01.img
cp ${X_BASE_IMG} ${X_IMG_DIR}/k8s-worker-02.img
qemu-img resize ${X_IMG_DIR}/k8s-cp-01.img 20G
qemu-img resize ${X_IMG_DIR}/k8s-worker-01.img 20G
qemu-img resize ${X_IMG_DIR}/k8s-worker-02.img 20G
sed -i "s/^hostname: .*/hostname: k8s-cp-01/g" ${X_CFG_DIR}/cloud-init.yml
sed -i "s/^fqdn: .*/fqdn: k8s-cp-01.example.com/g" ${X_CFG_DIR}/cloud-init.yml
cloud-localds ${X_IMG_DIR}/k8s-cp-01.img.seed ${X_CFG_DIR}/cloud-init.yml
sed -i "s/^hostname: .*/hostname: k8s-worker-01/g" ${X_CFG_DIR}/cloud-init.yml
sed -i "s/^fqdn: .*/fqdn: k8s-worker-01.example.com/g" ${X_CFG_DIR}/cloud-init.yml
cloud-localds ${X_IMG_DIR}/k8s-worker-01.img.seed ${X_CFG_DIR}/cloud-init.yml
sed -i "s/^hostname: .*/hostname: k8s-worker-02/g" ${X_CFG_DIR}/cloud-init.yml
sed -i "s/^fqdn: .*/fqdn: k8s-worker-02.example.com/g" ${X_CFG_DIR}/cloud-init.yml
cloud-localds ${X_IMG_DIR}/k8s-worker-02.img.seed ${X_CFG_DIR}/cloud-init.yml
# Control Plane Node
sudo virt-install \
--name k8s-cp-01 \
--memory 2048 \
--vcpus 2 \
--disk path=${X_IMG_DIR}/k8s-cp-01.img,size=20 \
--disk path=${X_IMG_DIR}/k8s-cp-01.img.seed,device=cdrom \
--network network=${X_NET} \
--os-variant ${X_OS_VARIANT} \
--import \
--graphics none \
--noautoconsole
# Worker Node 01
sudo virt-install \
--name k8s-worker-01 \
--memory 2048 \
--vcpus 2 \
--disk path=${X_IMG_DIR}/k8s-worker-01.img,size=20 \
--disk path=${X_IMG_DIR}/k8s-worker-01.img.seed,device=cdrom \
--network network=${X_NET} \
--os-variant ${X_OS_VARIANT} \
--import \
--graphics none \
--noautoconsole
# Worker Node 02
sudo virt-install \
--name k8s-worker-02 \
--memory 2048 \
--vcpus 2 \
--disk path=${X_IMG_DIR}/k8s-worker-02.img,size=20 \
--disk path=${X_IMG_DIR}/k8s-worker-02.img.seed,device=cdrom \
--network network=${X_NET} \
--os-variant ${X_OS_VARIANT} \
--import \
--graphics none \
--noautoconsole
|
3.3 Wait for VM Boot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| K8S_CP_IP=$(sudo virsh domifaddr "k8s-cp-01" | awk '/ipv4/ {print $4}' | cut -d'/' -f1)
K8S_WORKER_01_IP=$(sudo virsh domifaddr "k8s-worker-01" | awk '/ipv4/ {print $4}' | cut -d'/' -f1)
K8S_WORKER_02_IP=$(sudo virsh domifaddr "k8s-worker-02" | awk '/ipv4/ {print $4}' | cut -d'/' -f1)
echo "K8S_CP_IP: ${K8S_CP_IP}"
echo "K8S_WORKER_01_IP: ${K8S_WORKER_01_IP}"
echo "K8S_WORKER_02_IP: ${K8S_WORKER_02_IP}"
# Update /etc/hosts
sudo sed -i "/k8s-cp-01/d" /etc/hosts
sudo sed -i "/k8s-worker-01/d" /etc/hosts
sudo sed -i "/k8s-worker-02/d" /etc/hosts
echo "${K8S_CP_IP} k8s-cp-01" | sudo tee -a /etc/hosts >/dev/null
echo "${K8S_WORKER_01_IP} k8s-worker-01" | sudo tee -a /etc/hosts >/dev/null
echo "${K8S_WORKER_02_IP} k8s-worker-02" | sudo tee -a /etc/hosts >/dev/null
# Clean SSH known hosts
ssh-keygen -R "k8s-cp-01" >/dev/null 2>&1
ssh-keygen -R "k8s-worker-01" >/dev/null 2>&1
ssh-keygen -R "k8s-worker-02" >/dev/null 2>&1
ssh-keygen -R "${K8S_CP_IP}" >/dev/null 2>&1
ssh-keygen -R "${K8S_WORKER_01_IP}" >/dev/null 2>&1
ssh-keygen -R "${K8S_WORKER_02_IP}" >/dev/null 2>&1
# Retry the following command until cloud-init completes, maybe need to wait for 10 minutes
ssh ubuntu@k8s-cp-01 "test -f /var/lib/cloud/instance/boot-finished && echo 'cloud-init completed'"
ssh ubuntu@k8s-worker-01 "test -f /var/lib/cloud/instance/boot-finished && echo 'cloud-init completed'"
ssh ubuntu@k8s-worker-02 "test -f /var/lib/cloud/instance/boot-finished && echo 'cloud-init completed'"
# Continue to check some services
ssh ubuntu@k8s-cp-01 "sudo systemctl is-active containerd" # it will output "active" if containerd is running
ssh ubuntu@k8s-worker-01 "sudo systemctl is-active containerd"
ssh ubuntu@k8s-worker-02 "sudo systemctl is-active containerd"
|
4. Kubernetes Cluster Initialization
4.1 Control Plane Initialization
NOTES:
EOF with single quote to avoid variable expansion, i.e. it will keep the raw string
EOF with no single quote to expand variables
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # Login to control plane node, initialize it
ssh ubuntu@k8s-cp-01 << EOF
sudo kubeadm init \
--image-repository=registry.aliyuncs.com/google_containers \
--kubernetes-version=v1.32.1 \
--apiserver-advertise-address=${K8S_CP_IP} \
--apiserver-bind-port=6443 \
--pod-network-cidr=10.244.0.0/16 \
--service-cidr=169.169.0.0/16 \
--token=abcdef.0123456789abcdef \
--token-ttl=0"
EOF
# Configure kubectl
ssh ubuntu@k8s-cp-01 << 'EOF' >/dev/null 2>&1
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
EOF
# Deploy Flannel network plugin
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
|
4.2 Add Worker Nodes
Join worker nodes:
1
2
3
4
5
6
7
| ssh ubuntu@k8s-worker-01 << EOF
sudo $(ssh ubuntu@k8s-cp-01 kubeadm token create --print-join-command)
EOF
ssh ubuntu@k8s-worker-02 << EOF
sudo $(ssh ubuntu@k8s-cp-01 kubeadm token create --print-join-command)
EOF
|
(Optional) If kubectl access is required on worker nodes:
1
2
3
4
| # Login to worker node
mkdir -p $HOME/.kube
scp control-plane-node:/etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
|
5. Cluster Verification
5.1 Basic Functionality Verification
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # Check node status
kubectl get nodes -o wide
# Verify system components
kubectl get pods -n kube-system
# Verify CNI network plugin
kubectl get pods -n kube-flannel
# Deploy test application
kubectl create deployment nginx-test --image=nginx
kubectl expose deployment nginx-test --port=80 --type=NodePort
kubectl get pods,deployment,svc -o wide
# Access test application
curl -s $(kubectl get svc nginx-test -o jsonpath='{.spec.clusterIP}'):80
# Clean test application
kubectl delete service,deployment nginx-test
|
6. Maintenance Operations
6.1 Cluster Reset
To redeploy, execute on all nodes:
1
2
3
| sudo kubeadm reset -f
sudo rm -rf $HOME/.kube
sudo rm -rf /etc/cni/net.d
|
6.2 Clean Virtual Environment
1
2
3
4
5
6
7
8
9
10
11
| # Clean virtual machines
sudo virsh destroy k8s-cp-01
sudo virsh destroy k8s-worker-01
sudo virsh destroy k8s-worker-02
sudo virsh undefine k8s-cp-01 --remove-all-storage
sudo virsh undefine k8s-worker-01 --remove-all-storage
sudo virsh undefine k8s-worker-02 --remove-all-storage
# Clean network
sudo virsh net-destroy ${X_NET}
sudo virsh net-undefine ${X_NET}
|