Single Master Cluster Deployment
- Minimum node requirements: 1 or 2 nodes.
- If the Master node goes down, management tools such as
kubectlwill be unable to operate the cluster, but running business containers are not affected. - Suitable for streamlined cluster deployments, typically used in environments with two microservice nodes.
This document covers Kubernetes cluster deployment based on CentOS 7.9 / Debian 12.
| Server IP | Host Role |
|---|---|
| 192.168.10.20 | Kubernetes 01 (Master, Node) |
| 192.168.10.21 | Kubernetes 02 (Node) |
Server Requirements
- No network policy restrictions between cluster servers
- Each node's hostname must be unique and cannot be changed after the cluster is deployed
- The primary NIC MAC address must not be duplicated (run
ip linkto check) product_uuidmust not be duplicated (runcat /sys/class/dmi/id/product_uuidto check)- Port 6443 must not be in use (run
nc -vz 127.0.0.1 6443to verify) - swap memory must be disabled (run
swapoff -ato temporarily disable it, and comment out the swap partition entry in/etc/fstab)
Install CRI Container Runtime
Required on all nodes of the Kubernetes cluster.
-
Download the Kubernetes cluster installation package
- Internet-accessible server
- Internet-restricted server
wget https://pdpublic.mingdao.com/private-deployment/offline/common/kubernetes-1.35.3/1.35-k8s-amd64-pkg.tar.gz# Download the installation package via the link below and upload it to the target serverhttps://pdpublic.mingdao.com/private-deployment/offline/common/kubernetes-1.35.3/1.35-k8s-amd64-pkg.tar.gz -
Extract the installation package
tar xzvf 1.35-k8s-amd64-pkg.tar.gz-
Verify the contents of the installation package
ls -l 1.35-k8s-amd64-pkgExample output
-rw-r--r-- 1 root 197121 331360 Apr 14 16:38 calico.yaml-rw-r--r-- 1 root 197121 33899693 Apr 13 12:06 containerd-static-2.2.2-linux-amd64.tar.gz-rw-r--r-- 1 root 197121 19185064 Apr 13 13:18 crictl-v1.35.0-linux-amd64.tar.gz-rw-r--r-- 1 root 197121 28576212 Apr 13 13:22 istio-1.29.1-linux-amd64.tar.gz-rw-r--r-- 1 root 197121 72372408 Apr 13 13:20 kubeadm-rw-r--r-- 1 root 197121 58601656 Apr 13 13:18 kubectl-rw-r--r-- 1 root 197121 58110244 Apr 13 13:20 kubelet-rw-r--r-- 1 root 197121 11373900 Apr 13 13:17 nerdctl-2.2.2-linux-amd64.tar.gz-rw-r--r-- 1 root 197121 12741088 Apr 13 12:12 runc.amd64
-
-
Install containerd
Run this command from within the
1.35-k8s-amd64-pkgdirectorycd 1.35-k8s-amd64-pkgtar -zxvf containerd-static-2.2.2-linux-amd64.tar.gzmv -f bin/* /usr/local/bin/ -
Create the containerd configuration directory
mkdir /etc/containerd -
Install runc
Run this command from within the
1.35-k8s-amd64-pkgdirectorymv runc.amd64 /usr/local/bin/runcchmod +x /usr/local/bin/runcrunc -v -
Generate the containerd configuration file and modify the relevant parameters
containerd config default > /etc/containerd/config.tomlsed -i \-e 's|SystemdCgroup =.*|SystemdCgroup = true|g' \-e 's|bin_dirs =.*|bin_dirs = ["/usr/local/kubernetes/cni/bin"]|' \-e 's|sandbox =.*|sandbox = "127.0.0.1:5000/pause:3.10.1"|' \-e 's|^root =.*|root = "/data/containerd"|' \/etc/containerd/config.toml-
Verify that the configuration has taken effect
grep "SystemdCgroup\|bin_dirs\|sandbox =\|^root =" /etc/containerd/config.tomlExample output
root = "/data/containerd"sandbox_image = "127.0.0.1:5000/pause:3.10.1"bin_dir = "/usr/local/kubernetes/cni/bin"SystemdCgroup = true
-
-
Configure the containerd systemd service file
cat > /etc/systemd/system/containerd.service <<EOF[Unit]Description=containerdAfter=network-online.targetWants=network-online.target[Service]Type=notifyExecStart=/usr/local/bin/containerd --config /etc/containerd/config.tomlLimitNOFILE=1024000LimitNPROC=infinityLimitCORE=0TimeoutStartSec=0Delegate=yesKillMode=processRestart=on-failureStartLimitBurst=3StartLimitInterval=60s[Install]WantedBy=multi-user.targetEOF -
Start containerd and enable it on boot
systemctl daemon-reload && systemctl restart containerd && systemctl enable containerd
Install Required K8S Commands
Installs crictl / kubeadm / kubelet / kubectl. Required on all nodes of the Kubernetes cluster.
-
Create the command installation directory
mkdir -p /usr/local/kubernetes/bin -
Move command binaries to the installation directory
Run this command from within the
1.35-k8s-amd64-pkgdirectorytar -zxvf crictl-v1.35.0-linux-amd64.tar.gz -C /usr/local/kubernetes/bincp ./{kubeadm,kubelet,kubectl} /usr/local/kubernetes/bin/ -
Grant executable permissions to the command binaries
chmod +x /usr/local/kubernetes/bin/*chown $(whoami):$(groups) /usr/local/kubernetes/bin/* -
Configure systemd to manage kubelet
cat > /etc/systemd/system/kubelet.service <<\EOF[Unit]Description=kubelet: The Kubernetes Node AgentDocumentation=https://kubernetes.io/docs/home/Wants=network-online.targetAfter=network-online.target[Service]ExecStart=/usr/local/kubernetes/bin/kubeletRestart=alwaysStartLimitInterval=0RestartSec=10[Install]WantedBy=multi-user.targetEOF -
Configure kubeadm drop-in parameters for kubelet
mkdir -p /etc/systemd/system/kubelet.service.dcat > /etc/systemd/system/kubelet.service.d/10-kubeadm.conf <<\EOF# Note: This dropin only works with kubeadm and kubelet v1.11+[Service]Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf"Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"# This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamicallyEnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env# This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use# the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file.EnvironmentFile=-/etc/default/kubeletExecStart=ExecStart=/usr/local/kubernetes/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGSEOF -
Start kubelet and enable it on boot
systemctl daemon-reload && systemctl restart kubelet && systemctl enable kubelet- kubelet remains in a waiting state until
kubeadm init/kubeadm joincompletes. There is no need to check the service status afterrestart; it will start automatically once the cluster is initialized.
- kubelet remains in a waiting state until
-
Add the K8S command path to the environment variables
cat > /etc/profile.d/kubernetes.sh <<'EOF'export PATH=/usr/local/kubernetes/bin/:$PATHEOFsource /etc/profile.d/kubernetes.sh -
Configure the crictl runtime endpoint
crictl config runtime-endpoint unix:///run/containerd/containerd.sock-
Verify the configuration
crictl config --get runtime-endpoint
-
Install nerdctl (Optional)
nerdctl is a Docker-compatible CLI for containerd, providing a familiar interface for managing containers, images, and other resources.
-
Extract and install nerdctl
Run this command from within the
1.35-k8s-amd64-pkgdirectorytar -zxvf nerdctl-2.2.2-linux-amd64.tar.gzrm -f containerd-rootless*.shmv nerdctl /usr/local/kubernetes/bin/ -
Configure the command alias and apply it
echo 'alias nerdctl="nerdctl -n k8s.io"' >> ~/.bashrcsource ~/.bashrc- Run
nerdctl -v. Output ofnerdctl version 2.2.2confirms a successful installation.
- Run
Install Environment Dependencies
Required on all nodes of the Kubernetes cluster.
-
Install socat / conntrack
- Internet-accessible server
- Internet-restricted server
# CentOS / RedHatyum install -y socat conntrack-tools# Debian / Ubuntuapt install -y socat conntrack# socat offline package download link (for CentOS 7.9; re-download for other OS versions as needed)https://pdpublic.mingdao.com/private-deployment/offline/common/kubernetes-1.25.4/socat-deps-centos7.tar.gz# Extract and installtar -zxvf socat-deps-centos7.tar.gzrpm -Uvh --nodeps socat-deps-centos7/*.rpm# conntrack offline package download link (for CentOS 7.9; re-download for other OS versions as needed)https://pdpublic.mingdao.com/private-deployment/offline/common/kubernetes-1.25.4/conntrack-tools-deps-centos7.tar.gz# Extract and installtar -zxvf conntrack-tools-deps-centos7.tar.gzrpm -Uvh --nodeps conntrack-tools-deps-centos7/*.rpm -
Verify all required commands are available
crictl --version && containerd --version && runc -v|grep version && kubeadm version && kubelet --version && kubectl version --client=true && socat -V | grep 'socat version' && conntrack --version && echo ok || echo error- A final output of
okindicates success;errormeans one or more commands are missing and must be installed based on the error output.
- A final output of
Configure Kernel Settings
Required on all nodes of the Kubernetes cluster.
-
Persist the required kernel modules on boot
cat > /etc/modules-load.d/kubernetes.conf <<EOFoverlaybr_netfilterip_vsip_vs_rrip_vs_wrrip_vs_shEOF -
Load the kernel modules immediately
modprobe overlaymodprobe br_netfiltermodprobe ip_vsmodprobe ip_vs_rrmodprobe ip_vs_wrrmodprobe ip_vs_sh -
Append kernel parameters and apply them
cp -rfp /etc/sysctl.d/99-sysctl.conf /etc/sysctl.d/99-sysctl.conf.backup-$(date +%Y%m%d%H%M%S)cat >> /etc/sysctl.d/99-sysctl.conf <<EOFnet.bridge.bridge-nf-call-iptables = 1net.bridge.bridge-nf-call-ip6tables = 1net.ipv4.ip_forward = 1vm.max_map_count = 262144# MD Confignet.nf_conntrack_max = 524288net.ipv4.tcp_max_tw_buckets = 5000net.ipv4.tcp_window_scaling = 1net.ipv4.tcp_rmem = 8192 87380 16777216net.ipv4.tcp_wmem = 8192 65536 16777216net.ipv4.tcp_max_syn_backlog = 32768net.core.netdev_max_backlog = 32768net.core.netdev_budget = 600net.core.somaxconn = 32768net.core.wmem_default = 8388608net.core.rmem_default = 8388608net.core.rmem_max = 16777216net.core.wmem_max = 16777216net.ipv4.tcp_timestamps = 1net.ipv4.tcp_synack_retries = 2net.ipv4.tcp_syn_retries = 2net.ipv4.tcp_tw_recycle = 0net.ipv4.tcp_tw_reuse = 1net.ipv4.tcp_fin_timeout = 2net.ipv4.tcp_mem = 8388608 12582912 16777216net.ipv4.ip_local_port_range = 1024 65000net.ipv4.tcp_max_orphans = 16384net.ipv4.tcp_keepalive_intvl = 10net.ipv4.tcp_keepalive_probes = 3net.ipv4.tcp_keepalive_time = 600vm.max_map_count = 262144net.netfilter.nf_conntrack_tcp_be_liberal = 0net.netfilter.nf_conntrack_tcp_max_retrans = 3net.netfilter.nf_conntrack_tcp_timeout_max_retrans = 300net.netfilter.nf_conntrack_tcp_timeout_established = 86400fs.inotify.max_user_watches=10485760fs.inotify.max_user_instances=10240EOFsysctl --system
K8S Environment Image Preparation
Required on all nodes of the Kubernetes cluster.
-
Download and import offline images
- Internet-accessible server
- Internet-restricted server
wget https://pdpublic.mingdao.com/private-deployment/offline/common/kubernetes-1.35.3/kubeadm-1.35.3-images-amd64.tar.gz# Download the image package via the link below and upload it to the target serverhttps://pdpublic.mingdao.com/private-deployment/offline/common/kubernetes-1.35.3/kubeadm-1.35.3-images-amd64.tar.gz-
Extract and import the images
gunzip -d kubeadm-1.35.3-images-amd64.tar.gzctr -n k8s.io image import kubeadm-1.35.3-images-amd64.tar -
After import, run
crictl imagesto verifyExample output
IMAGE TAG IMAGE ID SIZE127.0.0.1:5000/cni v3.31.4 c433a27dd94ce 72.2MB127.0.0.1:5000/coredns v1.13.1 aa5e3ebc0dfed 23.6MB127.0.0.1:5000/etcd 3.6.6-0 0a108f7189562 23.6MB127.0.0.1:5000/kube-apiserver v1.35.3 0f2b96c93465f 27.6MB127.0.0.1:5000/kube-controller-manager v1.35.3 0eb506280f9bc 23MB127.0.0.1:5000/kube-controllers v3.31.4 ff033cc89dab5 54MB127.0.0.1:5000/kube-proxy v1.35.3 53ed370019059 25.7MB127.0.0.1:5000/kube-scheduler v1.35.3 87c9b0e4f80d3 17.1MB127.0.0.1:5000/node v3.31.4 e6536b93706ed 160MB127.0.0.1:5000/pause 3.10.1 cd073f4c5f6a8 320kB127.0.0.1:5000/pilot 1.29.1 cd8219a164d79 73.4MB127.0.0.1:5000/proxyv2 1.29.1 599aea7eee05d 89.1MB
Master Node Configuration
Perform operations on Kubernetes 01 only.
-
Initialize the Master Node
-
Create the Initialization Configuration File
Run the following command to write the node, network, and certificate settings:
cat > /usr/local/kubernetes/kubeadm-config.yaml <<EOFapiVersion: kubeadm.k8s.io/v1beta4kind: InitConfigurationlocalAPIEndpoint:advertiseAddress: $(hostname -I | awk '{print $1}')bindPort: 6443nodeRegistration:name: $(hostname)criSocket: unix:///var/run/containerd/containerd.sock---apiVersion: kubeadm.k8s.io/v1beta4kind: ClusterConfigurationkubernetesVersion: 1.35.3imageRepository: 127.0.0.1:5000networking:serviceSubnet: 10.96.0.0/12podSubnet: 10.244.0.0/16etcd:local:dataDir: /data/etcdcertificateValidityPeriod: 876000hcaCertificateValidityPeriod: 876000hEOFadvertiseAddressandnameuse the local machine's first IP address and hostname, respectively. On servers with multiple network interfaces, check thatadvertiseAddressis reachable from the other cluster nodes.
-
Check the Configuration and Imported Images
# Review the configuration and verify the node IP and hostnamecat /usr/local/kubernetes/kubeadm-config.yaml# Validate the configuration filekubeadm config validate --config=/usr/local/kubernetes/kubeadm-config.yaml# List the required imageskubeadm config images list --config=/usr/local/kubernetes/kubeadm-config.yaml# List the images imported on this nodecrictl imagesVerify that the required images were imported in the earlier steps, with matching image names and tags. Import any missing images from the corresponding offline image package. If configuration validation reports errors, correct them before continuing.
-
Run Initialization
# Run preflight checkskubeadm init phase preflight --config=/usr/local/kubernetes/kubeadm-config.yaml# Initialize after the preflight checks passkubeadm init --config=/usr/local/kubernetes/kubeadm-config.yaml --v=5After initialization succeeds, save the
kubeadm joincommand from the output to use when adding worker nodes to the cluster.If the initial setup fails, investigate the cause, then run
kubeadm reset -fto clean up the failed initialization before retrying. Do not run this reset on an existing running cluster.
-
-
Expand the NodePort available port range
sed -i '/- kube-apiserver/a\ \ \ \ - --service-node-port-range=1024-32767' /etc/kubernetes/manifests/kube-apiserver.yaml -
Configure kubectl Access Credentials on the Master Node
echo 'export KUBECONFIG=/etc/kubernetes/admin.conf' >> /etc/profile.d/kubernetes.shsource /etc/profile.d/kubernetes.sh -
Increase the Pod limit on the Master node
echo "maxPods: 300" >> /var/lib/kubelet/config.yamlsystemctl restart kubelet -
Allow the Master node to schedule Pods
kubectl taint node $(kubectl get node | grep control-plane | awk '{print $1}') node-role.kubernetes.io/control-plane:NoSchedule--
Wait approximately 2 minutes after initialization before running this command.
-
Expected output:
-
First run: Output includes
<node-name> untainted, indicating the taint was removed and the node can now schedule Pods. -
Subsequent runs: If the output is
error: taint "node-role.kubernetes.io/control-plane:NoSchedule" not found, the taint no longer exists and no further action is required. -
Note: If the output is unexpected, system components may not yet be fully ready. Wait a moment and retry.
-
-
-
Configure kubectl command completion (optional)
For Bash, configure completion on nodes where you use kubectl, using the account that runs kubectl. The server must have internet access or access to an internal yum/apt repository. Skip this step if bash-completion cannot be installed. The configuration is written to the current user's
~/.bashrc.Install bash-completion using the commands for your operating system:
# Debian-based systemsapt-get updateapt-get install -y bash-completion# CentOS-based systemsyum install -y bash-completionAdd the Bash completion dependency and kubectl autocompletion configuration without appending duplicate entries:
for line in \'[ -f /usr/share/bash-completion/bash_completion ] && source /usr/share/bash-completion/bash_completion' \'source <(kubectl completion bash)'dogrep -Fqx -- "$line" ~/.bashrc 2>/dev/null || printf '%s\n' "$line" >> ~/.bashrcdone# Apply the configuration to the current terminal sessionsource ~/.bashrc- Type
kubectl verand press Tab. It should complete tokubectl version. - Type
kubectl apply -f ./followed by the first few characters of a local filename, then press Tab to complete the file path. If multiple matches exist, press Tab twice to list the candidates. Do not press Enter to execute the command during this check. - If pressing Tab produces
_init_completion: command not foundor_filedir: command not found, check that bash-completion is installed and its dependency script is loaded.
- Type
-
Install the Calico Network Plugin
-
Move calico.yaml to the installation directory
Run this command from within the
1.35-k8s-amd64-pkgdirectorymv calico.yaml /usr/local/kubernetes/ -
Modify the configuration file
-
Replace the image registry address with the local registry
sed -ri 's|image: quay.io/calico|image: 127.0.0.1:5000|g' /usr/local/kubernetes/calico.yamlgrep image: /usr/local/kubernetes/calico.yamlExample output
image: 127.0.0.1:5000/cni:v3.31.4image: 127.0.0.1:5000/cni:v3.31.4image: 127.0.0.1:5000/node:v3.31.4image: 127.0.0.1:5000/node:v3.31.4image: 127.0.0.1:5000/kube-controllers:v3.31.4 -
Configure the Pod CIDR
sed -ri '/# - name: CALICO_IPV4POOL_CIDR/,/# value: ".*"/ {s/# - name: CALICO_IPV4POOL_CIDR/- name: CALICO_IPV4POOL_CIDR/s/# value: ".*"/ value: "10.244.0.0\/16"/}' /usr/local/kubernetes/calico.yamlgrep -C 2 CALICO_IPV4POOL_CIDR /usr/local/kubernetes/calico.yamlExample output
# chosen from this range. Changing this value after installation will have# no effect. This should fall within `--cluster-cidr`.- name: CALICO_IPV4POOL_CIDRvalue: "10.244.0.0/16"# Disable file logging so `kubectl logs` works. -
Configure the CNI binary path
sed -i '/- name: cni-bin-dir/,/type:/s|path: .*|path: /usr/local/kubernetes/cni/bin|' /usr/local/kubernetes/calico.yamlgrep -C 2 cni-bin-dir /usr/local/kubernetes/calico.yamlExample output
name: host-local-net-dir- mountPath: /host/opt/cni/binname: cni-bin-dirsecurityContext:privileged: true--volumeMounts:- mountPath: /host/opt/cni/binname: cni-bin-dir- mountPath: /host/etc/cni/net.dname: cni-net-dir--path: /proc# Used to install CNI.- name: cni-bin-dirhostPath:path: /usr/local/kubernetes/cni/bin
-
-
Deploy Calico
kubectl apply -f /usr/local/kubernetes/calico.yaml -
Check Calico service status
kubectl get pod -n kube-system -l k8s-app=calico-nodekubectl get pod -n kube-system -l k8s-app=calico-kube-controllersUnder normal conditions, all Pods should show
READYas1/1andSTATUSasRunning:NAME READY STATUS RESTARTS AGEcalico-node-4xbtk 1/1 Running 0 2mcalico-node-9fzwp 1/1 Running 0 2mcalico-kube-controllers-7d9d9b7b9c-x2kqt 1/1 Running 0 2m- If
STATUSremainsInitorPendingfor an extended period, runkubectl describe pod <pod-name> -n kube-systemto view detailed events and diagnose the issue.
- If
-
Worker Node Configuration
Perform operations on Kubernetes 02 only.
-
Join the Kubernetes cluster
kubeadm join 192.168.10.20:6443 --token 3nwjzw.pdod3r27lnqqhi0x \--discovery-token-ca-cert-hash sha256:a84445303a0f8249e7eae3059cb99d46038dc275b2dc2043a022de187a1175a2- The command above is an example. Use the actual
joincommand output bykubeadm initon the Master node. - If the command has been lost, run
kubeadm token create --print-join-commandon the Master node to retrieve it.
- The command above is an example. Use the actual
-
Increase the Pod limit on the worker node
echo "maxPods: 300" >> /var/lib/kubelet/config.yamlsystemctl restart kubelet
Cluster Status Check
-
Check node and Pod status
kubectl get pod -n kube-system # READY column should show "1/1"kubectl get node # STATUS column should show "Ready" -
Download and import the base image (required on all K8s cluster nodes)
Image download link:
https://pdpublic.mingdao.com/private-deployment/offline/common/centos7.9.2009.tar.gzDistribute the image file
centos7.9.2009.tar.gzto all K8s cluster nodes and run the following to import it:gunzip -d centos7.9.2009.tar.gzctr -n k8s.io image import centos7.9.2009.tar -
Deploy a test application on the Master node
cat > /usr/local/kubernetes/test.yaml <<'EOF'apiVersion: apps/v1kind: Deploymentmetadata:name: testnamespace: defaultspec:replicas: 3selector:matchLabels:app: testtemplate:metadata:labels:app: testannotations:md-update: '20200517104741'spec:containers:- name: testimage: centos:7.9.2009command:- sh- -c- |echo $(hostname) > hostname.txtpython -m SimpleHTTPServerresources:limits:memory: 512Micpu: 1requests:memory: 64Micpu: 0.01volumeMounts:- name: tz-configmountPath: /etc/localtimevolumes:- name: tz-confighostPath:path: /usr/share/zoneinfo/Etc/GMT-8---apiVersion: v1kind: Servicemetadata:name: testnamespace: defaultspec:selector:app: testports:- name: external-testport: 8000targetPort: 8000nodePort: 8000type: NodePortEOF -
Start the service
kubectl apply -f /usr/local/kubernetes/test.yaml -
Check Pod running status
kubectl get pod -o wide -
Verify service access
curl 127.0.0.1:8000/hostname.txt- Multiple executions should return different Pod hostnames, confirming that load balancing is working correctly.
-
Once testing is complete, delete the test service
kubectl delete -f /usr/local/kubernetes/test.yaml