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
| 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"
sudo mkdir -p $X_DOWNLOAD_DIR $X_IMG_DIR $X_CFG_DIR sudo chown -R $USER:$USER $X_DIR
[[ ! -f ${X_BASE_IMG} ]] && curl -fsSL -o "${X_BASE_IMG}" "${QCOW2_URL}"
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
| sudo apt-get update && sudo apt-get upgrade -y
sudo apt-get install -y \ qemu-system-x86 \ libvirt-daemon-system \ libvirt-clients \ bridge-utils \ virt-manager \ virtinst \ cloud-image-utils \ wget \ curl
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
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
|
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
| [[ ! -f ~/.ssh/id_ed25519 ]] && ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N ""
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
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
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
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}"
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
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
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'"
ssh ubuntu@k8s-cp-01 "sudo systemctl is-active containerd" 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
| 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
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
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
| 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
| kubectl get nodes -o wide
kubectl get pods -n kube-system
kubectl get pods -n kube-flannel
kubectl create deployment nginx-test --image=nginx kubectl expose deployment nginx-test --port=80 --type=NodePort kubectl get pods,deployment,svc -o wide
curl -s $(kubectl get svc nginx-test -o jsonpath='{.spec.clusterIP}'):80
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
| 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
sudo virsh net-destroy ${X_NET} sudo virsh net-undefine ${X_NET}
|