使用 kubeadm 部署高可用 k8s 集群(containerd)
myluzh 发布于 阅读:100 Kubernetes
0x00 环境说明
我这边一共 5 台节点,3 个 Master,2 个 Worker。
| 主机名 | 内网 IP | 角色 |
|---|---|---|
| k8s-master-01 | 192.168.11.187 | control-plane |
| k8s-master-02 | 192.168.11.222 | control-plane |
| k8s-master-03 | 192.168.11.250 | control-plane |
| k8s-worker-01 | 192.168.11.13 | worker |
| k8s-worker-02 | 192.168.11.81 | worker |
规划如下:
- Kubernetes 版本:
1.36.1 - Kubernetes APT minor:
v1.36 - container runtime:
containerd - CNI:
Calico/Flannel/Cilium三选一 - Pod CIDR:
10.244.0.0/16 - Service CIDR:
10.96.0.0/12 - kube-apiserver VIP:
192.168.11.10 - kube-apiserver VIP 端口:
8443 - kube-apiserver 本机端口:
6443
所有节点先固定 hostname 和 hosts。
# 确认 VIP 没有被占用,网卡名按实际替换
arping -D -I <网卡名> 192.168.11.10
# 每台机器执行自己的 hostname
hostnamectl set-hostname k8s-master-01
# hostnamectl set-hostname k8s-master-02
# hostnamectl set-hostname k8s-master-03
# hostnamectl set-hostname k8s-worker-01
# hostnamectl set-hostname k8s-worker-02
# 所有节点写同一份 hosts
cat >>/etc/hosts <<'EOF'
192.168.11.10 k8s-ha-api
192.168.11.187 k8s-master-01
192.168.11.222 k8s-master-02
192.168.11.250 k8s-master-03
192.168.11.13 k8s-worker-01
192.168.11.81 k8s-worker-02
EOF
0x01 系统初始化
所有节点执行。
# 安装基础工具
apt-get update
apt-get install -y apt-transport-https ca-certificates curl gpg \
ipset ipvsadm conntrack socat chrony
# 关闭 swap
swapoff -a
sed -ri '/\sswap\s/s/^/#/' /etc/fstab
# 加载 Kubernetes 需要的内核模块
cat >/etc/modules-load.d/k8s.conf <<'EOF'
overlay
br_netfilter
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh
nf_conntrack
EOF
modprobe overlay
modprobe br_netfilter
modprobe ip_vs
modprobe ip_vs_rr
modprobe ip_vs_wrr
modprobe ip_vs_sh
modprobe nf_conntrack
# 配置内核参数
cat >/etc/sysctl.d/99-kubernetes.conf <<'EOF'
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sysctl --system
# 验证
lsmod | grep -E 'br_netfilter|overlay|ip_vs|nf_conntrack'
sysctl net.ipv4.ip_forward
0x02 数据盘挂载
所有节点都有一块额外的数据盘,我这里是 /dev/sdb。这块盘用来放 Kubernetes 相关数据:
/var/lib/etcd/var/lib/kubelet/var/lib/containerd
/var/lib/etcd 只有 Master 会实际使用。Worker 节点也可以按同一套模板创建和 bind,目录空着不影响。
# 确认磁盘,sda 是系统盘,sdb 是额外数据盘
lsblk
# 格式化会清空 /dev/sdb,执行前一定确认盘符
mkfs.xfs -f /dev/sdb
# 挂载数据盘到 /data/k8s
mkdir -p /data/k8s
DATA_UUID=$(blkid -s UUID -o value /dev/sdb)
echo "UUID=${DATA_UUID} /data/k8s xfs defaults,noatime 0 0" >>/etc/fstab
systemctl daemon-reload
mount -a
# 创建实际数据目录和 bind 目标目录
mkdir -p /data/k8s/{etcd,kubelet,containerd}
mkdir -p /var/lib/{etcd,kubelet,containerd}
# 写入 bind mount
cat >>/etc/fstab <<'EOF'
/data/k8s/etcd /var/lib/etcd none defaults,bind 0 0
/data/k8s/kubelet /var/lib/kubelet none defaults,bind 0 0
/data/k8s/containerd /var/lib/containerd none defaults,bind 0 0
EOF
systemctl daemon-reload
mount -a
# 验证 bind mount,多个路径逐个查更直观
for p in /data/k8s /var/lib/etcd /var/lib/kubelet /var/lib/containerd; do
echo "== $p =="
findmnt -T "$p"
done
df -h /data/k8s /var/lib/etcd /var/lib/kubelet /var/lib/containerd
这一步建议在安装 containerd、kubelet、kubeadm 之前完成。新机器直接这样做最干净;如果已经跑过服务,需要先停服务,再迁移原目录数据,最后再 bind。
0x03 安装 containerd
所有节点执行。
# 查询可用版本
apt-get update
apt-cache madison containerd | head
# 指定版本安装
apt-get install -y containerd=1.7.28-0ubuntu1~24.04.2
# 生成默认配置,并把 cgroup driver 改成 systemd
mkdir -p /etc/containerd
containerd config default >/etc/containerd/config.toml
sed -ri 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
systemctl enable --now containerd
systemctl restart containerd
# 先写 crictl 配置文件,cri-tools 后面再安装
cat >/etc/crictl.yaml <<'EOF'
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
debug: false
EOF
# 验证 containerd
systemctl status containerd --no-pager
ctr version
0x04 安装 kubeadm/kubelet/kubectl
所有节点执行。
# 安装源配置需要的工具
apt-get update
apt-get install -y apt-transport-https ca-certificates curl gpg
# 配置 Kubernetes APT 源。这里先确定 minor,再配置对应仓库
K8S_MINOR=v1.36
install -m 0755 -d /etc/apt/keyrings
curl -fsSL "https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/deb/Release.key" \
| gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
chmod 0644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/deb/ /" \
>/etc/apt/sources.list.d/kubernetes.list
chmod 0644 /etc/apt/sources.list.d/kubernetes.list
# 查询可用版本
apt-get update
apt-cache madison kubeadm | head
# 指定版本安装 kubelet/kubeadm/kubectl
K8S_VERSION=1.36.1-1.1
apt-get install -y kubelet=${K8S_VERSION} kubeadm=${K8S_VERSION} kubectl=${K8S_VERSION}
apt-mark hold kubelet kubeadm kubectl
# 安装 crictl 工具
apt-get install -y cri-tools
# 验证版本和 CRI
kubeadm version
kubelet --version
kubectl version --client
crictl --version
crictl info | grep -i runtime
后面如果要调整 Kubernetes 版本,K8S_MINOR 和 K8S_VERSION 要一起改,保持同一个 minor。
0x05 配置 API 高可用入口
这一步在 3 个 Master 节点执行。
HAProxy 三台 Master 都一样。
# 安装 HAProxy 和 Keepalived
apt-get install -y haproxy keepalived
# HAProxy 监听 VIP 的 8443,后端转发到每个 Master 的 6443
cat >/etc/haproxy/haproxy.cfg <<'EOF'
global
log /dev/log local0
log /dev/log local1 notice
daemon
maxconn 4096
defaults
log global
mode tcp
option tcplog
option dontlognull
timeout connect 10s
timeout client 1m
timeout server 1m
frontend kube_apiserver
bind *:8443
default_backend kube_apiserver_backend
backend kube_apiserver_backend
balance roundrobin
option tcp-check
server k8s-master-01 192.168.11.187:6443 check
server k8s-master-02 192.168.11.222:6443 check
server k8s-master-03 192.168.11.250:6443 check
EOF
systemctl enable --now haproxy
systemctl restart haproxy
ss -lntp | grep 8443
Keepalived 每台 Master 只改变量。下面先按当前节点取消对应变量注释,再执行后半段。
# k8s-master-01
NODE_NAME=k8s-master-01
STATE=MASTER
PRIORITY=120
LOCAL_IP=192.168.11.187
PEER_1=192.168.11.222
PEER_2=192.168.11.250
# k8s-master-02
# NODE_NAME=k8s-master-02
# STATE=BACKUP
# PRIORITY=110
# LOCAL_IP=192.168.11.222
# PEER_1=192.168.11.187
# PEER_2=192.168.11.250
# k8s-master-03
# NODE_NAME=k8s-master-03
# STATE=BACKUP
# PRIORITY=100
# LOCAL_IP=192.168.11.250
# PEER_1=192.168.11.187
# PEER_2=192.168.11.222
# 自动取默认网卡名,也可以手动改成 eth0、ens18、ens160
IFACE=$(ip route show default | awk '/default/ {print $5; exit}')
cat >/etc/keepalived/keepalived.conf <<EOF
global_defs {
router_id ${NODE_NAME}
}
vrrp_script chk_haproxy {
script "pidof haproxy"
interval 2
weight -20
}
vrrp_instance VI_1 {
state ${STATE}
interface ${IFACE}
virtual_router_id 51
priority ${PRIORITY}
advert_int 1
authentication {
auth_type PASS
auth_pass k8sha123
}
unicast_src_ip ${LOCAL_IP}
unicast_peer {
${PEER_1}
${PEER_2}
}
virtual_ipaddress {
192.168.11.10/24
}
track_script {
chk_haproxy
}
}
EOF
systemctl enable --now keepalived
systemctl restart keepalived
# 验证 VIP 和监听端口
ip addr | grep 192.168.11.10 || true
ss -lntp | grep 8443
在 kubeadm init 前,后端 kube-apiserver 还没起来,HAProxy 后端检查失败是正常的。这里主要确认 VIP 和监听端口没问题。
0x06 准备 kubeadm 配置
在 k8s-master-01 执行。
# 生成默认配置作为参考
kubeadm config print init-defaults \
--component-configs KubeProxyConfiguration,KubeletConfiguration \
>kubeadm-init-default.yaml
# kubeadm-init-default.yaml 只是参考模板,不直接拿来 init
# 创建正式初始化配置,后面 kubeadm init 用这个文件
cat >kubeadm-init.yaml <<'EOF'
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 192.168.11.187
bindPort: 6443
nodeRegistration:
criSocket: unix:///run/containerd/containerd.sock
imagePullPolicy: IfNotPresent
name: k8s-master-01
taints:
- key: node-role.kubernetes.io/control-plane
effect: NoSchedule
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
clusterName: kubernetes
kubernetesVersion: v1.36.1
controlPlaneEndpoint: "192.168.11.10:8443"
imageRepository: registry.k8s.io
apiServer:
certSANs:
- 192.168.11.10
- 192.168.11.187
- 192.168.11.222
- 192.168.11.250
- k8s-ha-api
networking:
dnsDomain: cluster.local
serviceSubnet: 10.96.0.0/12
podSubnet: 10.244.0.0/16
---
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: ipvs
ipvs:
strictARP: true
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
EOF
如果使用外部 etcd,把下面这一段加到 ClusterConfiguration 里。
etcd:
external:
endpoints:
- https://192.168.11.191:2379
- https://192.168.11.192:2379
- https://192.168.11.193:2379
caFile: /etc/kubernetes/pki/etcd/ca.crt
certFile: /etc/kubernetes/pki/apiserver-etcd-client.crt
keyFile: /etc/kubernetes/pki/apiserver-etcd-client.key
0x07 初始化第一个 Master
在 k8s-master-01 执行。
解决镜像问题
如果 registry.k8s.io 镜像拉不下来,先处理镜像访问问题。常用两个办法:要么给 containerd 配代理,要么把 kubeadm 的 imageRepository 改成一个当前网络能访问的镜像仓库。第二种方式只影响 kubeadm 管理的 Kubernetes 核心镜像,Calico 这类插件还是按自己的 YAML 拉镜像。
# 方式一:给 containerd 配 HTTP/HTTPS 代理
# PROXY_URL 改成节点能访问到的代理地址,不要写成本机以外机器的 127.0.0.1
PROXY_URL="http://192.168.11.1:7890"
mkdir -p /etc/systemd/system/containerd.service.d
cat >/etc/systemd/system/containerd.service.d/http-proxy.conf <<EOF
[Service]
Environment="HTTP_PROXY=${PROXY_URL}"
Environment="HTTPS_PROXY=${PROXY_URL}"
Environment="NO_PROXY=127.0.0.1,localhost,::1,192.168.11.0/24,10.96.0.0/12,10.244.0.0/16,k8s-ha-api,.svc,.cluster.local"
EOF
systemctl daemon-reload
systemctl restart containerd
# 验证代理是否生效
systemctl show --property=Environment containerd
crictl pull registry.k8s.io/pause:3.10.2
# 方式二:让 kubeadm 直接从可访问的仓库拉控制面镜像
# 这里用阿里云示例,也可以换成自己的 Harbor
KUBEADM_IMAGE_REPO="registry.aliyuncs.com/google_containers"
sed -ri "s#^imageRepository: .*#imageRepository: ${KUBEADM_IMAGE_REPO}#" kubeadm-init.yaml
# kubeadm 的 imageRepository 不会修改 containerd 的 sandbox_image
# 这里把 sandbox_image 的 registry.k8s.io 前缀也换掉,pause 版本保留原值
sed -ri 's#registry.k8s.io/pause#registry.aliyuncs.com/google_containers/pause#' /etc/containerd/config.toml
# 重启 containerd 让 sandbox_image 生效
systemctl restart containerd
# 如果 kubelet 已经启动过,也一起重启,让它重新创建 Pod sandbox
systemctl restart kubelet
grep sandbox_image /etc/containerd/config.toml
crictl info | grep -i sandbox -A2
# 确认 kubeadm 生成的镜像地址已经切到新仓库
kubeadm config images list --config kubeadm-init.yaml
初始化第一个 Master
# 查看并预拉取镜像
kubeadm config images list --config kubeadm-init.yaml
kubeadm config images pull --config kubeadm-init.yaml
crictl images
# 初始化第一个 Master
kubeadm init --config kubeadm-init.yaml --upload-certs | tee kubeadm-init.log
# 配置 kubectl
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
# 先看控制平面状态。此时还没装 CNI,节点 NotReady 是正常的
kubectl get nodes
kubectl get pods -A
0x08 安装 CNI
在 k8s-master-01 执行。
下面三个 CNI 任选其一安装即可,不要在同一个集群里同时安装多个 CNI。本文 kubeadm 配置里的 podSubnet 是 10.244.0.0/16,所以 CNI 的 Pod 网段也要保持一致。
方式一:Calico
calico.yaml 这种安装方式会把 calico-node 放在 kube-system,每个节点一个。
从 GitHub 下载 calico.yaml 后,如果 quay.io 镜像拉不下来,可以先把里面的镜像地址改成自己的 Harbor 或可用的国内镜像源。没有多网卡或特殊网络模式需求时,只需要确认 Pod CIDR,其他配置不用改,直接 kubectl apply -f calico.yaml 即可。
# 下载 Calico 官方 YAML
CALICO_VERSION=v3.32.0
curl -fsSL -O https://raw.githubusercontent.com/projectcalico/calico/${CALICO_VERSION}/manifests/calico.yaml
# 如果 quay.io 拉不下来,把镜像地址换成自己的 Harbor 或可用镜像源
# sed -ri 's#quay.io/calico/#<你的镜像仓库>/calico/#g' calico.yaml
# Calico 默认示例里 Pod 网段是 192.168.0.0/16,这里改成 kubeadm 里的 10.244.0.0/16
sed -ri \
-e 's|^([[:space:]]*)# - name: CALICO_IPV4POOL_CIDR|\1- name: CALICO_IPV4POOL_CIDR|' \
-e 's|^([[:space:]]*)# value: "192.168.0.0/16"|\1 value: "10.244.0.0/16"|' \
calico.yaml
# 安装 Calico
kubectl apply -f calico.yaml
# 验证 Calico
kubectl -n kube-system get pods -o wide | grep -E 'calico|coredns'
kubectl get nodes -o wide
需要改多网卡或网络模式时,重点看下面几个字段。
# 注意此字段,ip/mask 修改为集群 Pod CIDR
- name: CALICO_IPV4POOL_CIDR
value: "10.244.0.0/16"
# 注意此字段,指定节点的内网网卡
# 直接搜索搜不到,搜 DATASTORE_TYPE,添加到同级 env 位置即可
- name: IP_AUTODETECTION_METHOD
value: "interface=bond1"
# 注意此字段,默认 Always 是 IPIP 模式;如果改成 Never,就是 BGP 模式
- name: CALICO_IPV4POOL_IPIP
value: "Always"
方式二:Flannel
Flannel 官方 YAML 默认就是 10.244.0.0/16,和本文的 podSubnet 一致,可以直接安装。
# 安装 Flannel
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
# 验证 Flannel
kubectl -n kube-flannel get pods -o wide
kubectl -n kube-system get pods -o wide | grep coredns
kubectl get nodes -o wide
方式三:Cilium
Cilium 这里用 Helm 安装。1.9.4 太老,不适合 Kubernetes 1.36,这里使用 1.19.4。因为 kubeadm 已经创建了 kube-proxy,所以这里不启用 kube-proxy replacement。
# 安装 Helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# 安装 Cilium
helm repo add cilium https://helm.cilium.io/
helm repo update
helm install cilium cilium/cilium --version 1.19.4 \
--namespace kube-system \
--set ipam.mode=kubernetes \
--set kubeProxyReplacement=false
# 验证 Cilium
kubectl -n kube-system get pods -o wide | grep -E 'cilium|coredns'
kubectl get nodes -o wide
如果 CNI 一直起不来,先看事件和对应 Pod 日志。
kubectl get pods -A -o wide
kubectl get events -A --sort-by=.lastTimestamp | tail -100
ip route
0x09 加入其他 Master
kubeadm init --upload-certs 会输出 control-plane join 命令,里面应该包含 --control-plane 和 --certificate-key。
# k8s-master-02
kubeadm join 192.168.11.10:8443 \
--token <token> \
--discovery-token-ca-cert-hash sha256:<hash> \
--control-plane \
--certificate-key <certificate-key> \
--apiserver-advertise-address 192.168.11.222
# k8s-master-03
kubeadm join 192.168.11.10:8443 \
--token <token> \
--discovery-token-ca-cert-hash sha256:<hash> \
--control-plane \
--certificate-key <certificate-key> \
--apiserver-advertise-address 192.168.11.250
如果证书 key 过期了,在已经初始化成功的 Master 上重新生成。
kubeadm init phase upload-certs --upload-certs
kubeadm token create --print-join-command --certificate-key <new-certificate-key>
0x0A 加入 Worker
k8s-worker-01、k8s-worker-02 执行前面的系统初始化、数据盘、containerd、kubeadm/kubelet 安装后,用普通 join 命令加入。
# Worker join 命令不带 --control-plane 和 --certificate-key
kubeadm join 192.168.11.10:8443 \
--token <token> \
--discovery-token-ca-cert-hash sha256:<hash>
# 如果 token 过期,在 Master 上重新生成
kubeadm token create --print-join-command
# 加入后在 k8s-master-01 查看
kubectl get nodes -o wide
期望结果类似这样。
NAME STATUS ROLES INTERNAL-IP
k8s-master-01 Ready control-plane 192.168.11.187
k8s-master-02 Ready control-plane 192.168.11.222
k8s-master-03 Ready control-plane 192.168.11.250
k8s-worker-01 Ready <none> 192.168.11.13
k8s-worker-02 Ready <none> 192.168.11.81ubuntu kubeadm kubernetes 高可用 keepalived haproxy containerd Calico