«

使用 kubeadm 部署高可用 k8s 集群(containerd)

myluzh 发布于 阅读:101 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

规划如下:

所有节点先固定 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 只有 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_MINORK8S_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 配置里的 podSubnet10.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.81

ubuntu kubeadm kubernetes 高可用 keepalived haproxy containerd Calico


正文到此结束
版权声明:若无特殊注明,本文皆为 Myluzh Blog 原创,转载请保留文章出处。
文章内容:https://itho.cn/k8s/599.html
文章标题:《使用 kubeadm 部署高可用 k8s 集群(containerd)