
1. 环境规划与前置准备1.1 高可用集群架构思路做Kubernetes高可用集群核心就一句话所有关键组件都不能有单点故障。控制平面的三个核心组件——API Server、Controller Manager、Scheduler必须通过某种方式冗余部署数据存储etcd也不能只跑一份。Kubernetes 1.28对高可用的支持已经非常成熟常见方案有堆叠式Stacked高可用和外部etcd高可用两种。我在实际生产中更推荐堆叠式高可用也就是把etcd直接跑在控制平面节点上配合Keepalived VIP或负载均衡器对外提供一个稳定的API Server入口。这套方案的好处是省机器、架构简单、运维成本低三台控制平面节点跑下来一个挂掉另外两个照常工作。缺点也很明确控制平面节点挂了etcd也会跟着挂所以需要至少三个控制平面节点才能保证多数派存活。外部etcd高可用适合大规模生产环境把etcd独立出来跑在专门的机器上控制平面和数据面物理隔离。但这需要额外的机器资源复杂度也高不少。中小规模集群几百个节点以内用堆叠式完全够用没必要一上来就上外部etcd。1.2 服务器与系统需求先看硬件要求。三台控制平面节点CPU建议4核起步内存8G以上磁盘50G以上。工作节点可以按业务量来配但每台至少也要2核4G磁盘按镜像和容器日志估算我建议至少100G。别小看磁盘容器日志爆盘是生产环境最常见的故障之一。操作系统方面我这个指南用的是CentOS 7.9配置方法在后续步骤里Ubuntu 20.04/22.04和Rocky Linux 8.x/9.x操作基本类似只是包管理器和部分系统配置命令不一样。需要注意Kubernetes 1.28要求内核版本在3.10以上CentOS 7.9的默认内核一般是3.10.0-1160够用但如果你要用一些较新的网络插件比如Cilium的eBPF功能建议把内核升到4.19以上能少踩不少坑。网络方面的规划也很关键。所有节点之间要能互通主机名不能重DNS解析建议配好。我这里用的网段规划如下资源类型网段/地址说明服务器网段192.168.1.0/24物理网络Pod网段10.244.0.0/16ClusterIP和Pod内部通信Service网段10.96.0.0/12Service虚拟IPVIP192.168.1.100对外提供API Server入口注意Pod网段和Service网段不能和物理网络重叠否则路由直接乱掉。1.3 证书规划与组件清单高可用集群需要签发不少证书我整理了一份清单搞清楚每个证书是给谁用的后面排错才有思路证书名称用途有效期ca.crt / ca.key集群根证书签发所有其他证书10年apiserver.crt / apiserver.keyAPI Server对外提供服务1年apiserver-kubelet-client.crt / keyAPI Server访问kubelet时使用1年front-proxy-ca.crt / keyAPI Server聚合层专用CA10年front-proxy-client.crt / key聚合层API Server客户端1年etcd/ca.crt / etcd/ca.keyetcd内部通信根证书10年etcd/server.crt / keyetcd服务端证书1年etcd/peer.crt / keyetcd节点间通信证书1年kube-controller-manager.crt / keycontroller-manager访问API Server1年kube-scheduler.crt / keyscheduler访问API Server1年kubelet和admin的证书不在这里kubelet证书由kubeadm自动轮转admin的集群管理证书在kubeconfig里用kubeadm init的时候自动生成。1.4 组件版本选型Kubernetes 1.28系列我用的这套版本组合是经过多次实测的Kubernetesv1.28.2etcd3.5.9kubeadm内置CoreDNSv1.10.1kubeadm内置containerd1.7.x虽然标题说Docker运行时但kubelet是通过CRI和容器运行时通信Docker这边需要装shimCNI插件Calico v3.26.xKeepalived2.0.x开VIP用这里要特别说明一下Docker运行时的问题。Kubernetes从1.24开始就不再用dockershim但这不代表不能用Docker。现在的做法是装Docker再用cri-dockerd这个适配器把Docker的API转成CRI接口给kubelet用。很多人以为Kubernetes 1.28不能用Docker了这是个常见的误区。实际上只需要装一个cri-dockerdDocker作为底层容器运行时完全没问题。2. 基础环境配置2.1 主机名与hosts文件第一步是规划主机名我这边三台控制平面三台工作节点k8s-master01 192.168.1.11 k8s-master02 192.168.1.12 k8s-master03 192.168.1.13 k8s-worker01 192.168.1.21 k8s-worker02 192.168.1.22用hostnamectl设置主机名hostnamectl set-hostname k8s-master01然后每台机器都要配hosts文件把上面六行写进去。这里有个很坑的地方如果机房有内网DNShosts和DNS解析冲突会导致API Server调用kubelet地址时无法判断该用哪个IP后面的集群状态经常变成NotReady。建议直接把内网DNS的K8s相关解析记录配好别混着来。2.2 内核参数与模块加载Kubernetes网络依赖iptables和ipvs这里的内核参数不配好后面的kube-proxy和Calico各种异常。执行以下命令cat EOF /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 net.ipv4.tcp_tw_reuse 1 vm.swappiness 0 vm.overcommit_memory 1 fs.file-max 1000000 EOF sysctl -p /etc/sysctl.d/k8s.confnet.bridge.bridge-nf-call-iptables这个参数特别重要如果不开基于网桥的容器通信流量不会经过iptables规则Service的负载均衡完全失效。vm.swappiness 0是让系统尽可能少用swap避免内存回收影响容器性能。加载内核模块modprobe br_netfilter modprobe ip_vs modprobe ip_vs_rr modprobe ip_vs_wrr modprobe ip_vs_sh modprobe nf_conntrack echo -e br_netfilter\nip_vs\nip_vs_rr\nip_vs_wrr\nip_vs_sh\nnf_conntrack /etc/modules-load.d/k8s.confip_vs相关的模块是给kube-proxy的IPVS模式准备的性能和功能都比默认的iptables模式好一个档次尤其在大规模Service场景下。2.3 关闭防火墙与SELinux很多云服务器自带安全组规则就不用再折腾iptables了。物理机或虚拟化环境里直接关闭firewalldsystemctl stop firewalld systemctl disable firewalldSELinux不改掉后面容器往宿主机挂载目录时各种Permission denied非常头大setenforce 0 sed -i s/^SELINUXenforcing/SELINUXdisabled/ /etc/selinux/config2.4 时间同步与系统更新集群节点之间时间不一致证书校验直接失败最典型的表现就是日志里报x509证书过期。配置chrony做时间同步yum install -y chrony systemctl enable chronyd --now chronyc sources -v这里提醒一个细节内网环境如果访问不了外部的NTP服务器需要自己搭一台NTP服务器然后在chrony.conf里把server指向内网NTP地址否则时间根本同步不了。3. 容器运行时安装与配置3.1 Docker cri-dockerd方案既然标题明确说了Docker运行时这里就讲完整的Docker加cri-dockerd安装方案。先装Dockeryum install -y yum-utils yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum install -y docker-ce docker-ce-cli containerd.io systemctl enable docker --now然后装cri-dockerd。这里有两种装法一种是直接用GitHub releases里的二进制包另一种是用官方提供的yum源。我习惯用二进制干净可控wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.9/cri-dockerd-0.3.9-3.9.el7.x86_64.rpm yum localinstall -y cri-dockerd-0.3.9-3.9.el7.x86_64.rpm systemctl enable cri-docker --now注意cri-dockerd的版本要和Kubernetes版本匹配1.28对应cri-dockerd 0.3.x版本。如果版本不匹配kubelet创建Pod时会报CRI相关错误。3.2 Docker配置优化Docker的默认cgroup driver是cgroupfs而kubelet默认用systemd这俩不一致的话kubelet会拒绝启动。所以要改Docker的cgroup drivercat /etc/docker/daemon.json EOF { exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 }, storage-driver: overlay2 } EOFlog-driver这个配置很重要容器日志不限制大小的话一天能写出几个G的日志这我在生产环境见过太多次了。限制100MB每个文件、保留3个文件日志量可控。重启Dockersystemctl daemon-reload systemctl restart dockercri-dockerd默认监听在/var/run/cri-dockerd.sockkubelet配置里要用这个socket。这个老是被搞混我再三强调一下Docker本身的socket是/var/run/docker.sockcri-dockerd的socket是/var/run/cri-dockerd.sockkubelet跟cri-dockerd通信kubeadm配置的containerRuntimeEndpoint要写cri-dockerd的socket。3.3 containerd的备选方案如果想用containerd而不是Docker只需要装containerd并配置好yum install -y containerd.io mkdir -p /etc/containerd containerd config default /etc/containerd/config.toml systemctl enable containerd --now然后要改/etc/containerd/config.toml里的SystemdCgroup为true同时把sandbox_image拉取地址改成国内能访问的镜像源。用containerd做运行时kubeadm的containerRuntimeEndpoint要写unix:///var/run/containerd/containerd.sock。3.4 镜像拉取预配置Kubernetes组件镜像在Docker Hub上国内大多数环境拉取速度慢或者直接拉不下来。kubeadm支持通过--image-repository指定镜像仓库地址。我这里用的是阿里云镜像仓库kubeadm config images list --kubernetes-version v1.28.2先看需要哪些镜像再通过kubeadm init的时候指定--image-repositoryregistry.aliyuncs.com/google_containers。如果kubeadm init之前想先把镜像拉好可以用kubeadm config images pull --image-repositoryregistry.aliyuncs.com/google_containers --kubernetes-version v1.28.2这个小操作能省不少时间预先把镜像拉好后面kubeadm init的等待时间会短很多也不用担心某个节点拉镜像超时。4. Kubernetes核心组件安装4.1 kubeadm、kubelet、kubectl安装配置Kubernetes的yum源。阿里云源速度快、稳定性好cat EOF /etc/yum.repos.d/kubernetes.repo [kubernetes] nameKubernetes baseurlhttps://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled1 gpgcheck1 repo_gpgcheck1 gpgkeyhttps://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF然后安装yum install -y kubeadm-1.28.2 kubelet-1.28.2 kubectl-1.28.2 systemctl enable kubelet注意kubelet现在enable但不start因为还没有集群配置直接start会报错属于正常现象。kubeadm、kubelet、kubectl三个组件版本必须一致不然API Server和各组件通信时各种版本兼容问题这在排错时是最常被忽略的点。4.2 Keepalived VIP配置高可用集群需要一个稳定的API Server入口。Keepalived方案简单可靠三台控制平面节点上部署KeepalivedVIP漂移在三个节点之间yum install -y keepalived以master01为例配置文件/etc/keepalived/keepalived.confglobal_defs { router_id LVS_DEVEL } vrrp_script check_apiserver { script /etc/keepalived/check_apiserver.sh interval 2 weight -2 fall 2 rise 1 } vrrp_instance VI_1 { state MASTER interface ens192 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass k8s12345 } virtual_ipaddress { 192.168.1.100/24 } track_script { check_apiserver } }这中间有个关键操作用到了脚本cat /etc/keepalived/check_apiserver.sh EOF #!/bin/bash if curl -sk --max-time 2 https://127.0.0.1:6443/healthz /dev/null 21; then exit 0 else exit 1 fi EOF chmod x /etc/keepalived/check_apiserver.sh这个脚本的意义在于如果本机的API Server挂了Keepalived就降级VIP自动漂移到其他健康的节点。master01的priority设100master02设99master03设98正常情况下VIP在master01上master01的API Server挂了VIP自动切到master02或master03。这个过程之所以重要是因为集群里的kubelet、kube-proxy都通过VIP访问API ServerVIP不漂移就没法对外提供服务。4.3 负载均衡方案补充Keepalived解决了VIP问题但这只是IP层面的高可用。如果条件允许推荐在前面加一个负载均衡器比如HAProxy或者云厂商的SLB把API Server的请求分发到三个控制平面节点。KeepalivedHAProxy的组合在生产中非常常见HAProxy监听VIP的8443端口转发到后端三个节点的6443端口。我这个环境用了纯Keepalived方案kubeadm配置里直接把apiserver地址填VIP:6443。实际生产规模更大、并发更高的场景建议加HAProxy性能更好还能做L4负载均衡对API Server这种长连接服务也更稳定。5. 集群初始化与证书签发5.1 kubeadm配置文件kubeadm init如果直接用命令行参数配置改动不方便也不利于后期维护和追踪变更。推荐用配置文件方式cat kubeadm-config.yaml EOF apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.28.2 controlPlaneEndpoint: 192.168.1.100:6443 imageRepository: registry.aliyuncs.com/google_containers networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 apiServer: certSANs: - 192.168.1.100 - 192.168.1.11 - 192.168.1.12 - 192.168.1.13 - 127.0.0.1 - k8s-master01 --- apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.1.11 bindPort: 6443 nodeRegistration: criSocket: unix:///var/run/cri-dockerd.sock --- apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration cgroupDriver: systemd EOFcontrolPlaneEndpoint填的是VIP地址这是高可用集群最重要的一个配置。如果这里填错了或者填成某个节点的IP后面加入的第二个、第三个控制平面节点会全部连错地址。criSocket写的是cri-dockerd的socket说明我是用Docker运行时的。certSANs里的IP列表是证书允许的地址这里如果漏了VIP地址通过VIP访问API Server的组件会报证书不匹配这个坑我见过不少。5.2 初始化第一个控制平面节点在master01上执行kubeadm init --config kubeadm-config.yaml输出末尾会有一段join命令这个要保存下来后面加节点用kubeadm join 192.168.1.100:6443 --token xxx --discovery-token-ca-cert-hash sha256:xxx --control-plane --certificate-key xxx这里有个细节如果加入的是控制平面节点需要--control-plane参数并且要提供--certificate-key这个key在init的时候也会输出。如果join输出内容太多看不到token和certificate-key直接用kubeadm token create --print-join-command在master01上生成新的token。初始化完成后配置kubectlmkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config5.3 添加节点工作节点的join不需要--control-plane参数kubeadm join 192.168.1.100:6443 --token xxx --discovery-token-ca-cert-hash sha256:xxx第二、第三个控制平面节点的join命令和第一个控制平面节点一样需要加--control-plane --certificate-keykubeadm join 192.168.1.100:6443 --token xxx --discovery-token-ca-cert-hash sha256:xxx --control-plane --certificate-key xxx验证节点加入情况kubectl get nodes看到所有节点都是NotReady状态是正常的因为网络插件还没装节点之间的Pod通信不通。接下来就要装CNI插件。6. 网络插件与集群验证6.1 Calico安装Calico是目前Kubernetes生态里用得最广的CNI插件之一性能和功能都很好。下载Calico的manifest文件curl https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/calico.yaml -O默认的Pod网段是192.168.0.0/16如果你按上面的配置用了10.244.0.0/16需要改一下。在calico.yaml里搜索CALICO_IPV4POOL_CIDR把value改成value: 10.244.0.0/16然后应用kubectl apply -f calico.yamlCalico会创建一堆Pod观察是否正常运行kubectl get pods -n kube-system -w看到calico-node这个DaemonSet在每个节点都有一个Running状态的Pod集群网络就通了。有个小技巧如果Calico Pod起不来先看日志大概率是IP池冲突或者内核模块缺失。Calico的IP池配置是集群级别的一旦定下来后期修改很麻烦初始化前就要规划好。6.2 Flannel等其他CNI选择如果不想用CalicoFlannel也是一个轻量选择kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/v0.22.0/Documentation/kube-flannel.ymlFlannel默认也是10.244.0.0/16网段和上文配置正好匹配。Flannel配置简单、开销低但功能比较基础没有network policy、encryption这些能力。生产环境选型时Calico的完整功能和稳定性更值得选。6.3 集群状态验证网络插件装好后执行以下命令确认集群健康kubectl get nodes kubectl get pods -n kube-system正常状态是所有节点Ready所有系统组件Running或Completed。再看一下集群的核心组件状态kubectl get componentstatus注意从Kubernetes 1.25开始componentstatus被标记为Deprecated不再实时反映组件健康状态。更可靠的做法是直接查看etcd和API Server的Pod状态。6.4 部署示例应用验证用之前提到的搜索热词部署一个nginx应用来验证集群能正常工作kubectl create deployment nginx-test --imagenginx:latest --replicas3 kubectl expose deployment nginx-test --port80 --target-port80 --typeNodePort查看Service分配的NodePortkubectl get svc nginx-test访问任意节点的NodePort端口能返回nginx的欢迎页就算成功。再验证一下跨节点通信把Pod分布到不同节点上kubectl get pods -o widePod分布在多个节点说明调度正常跨节点访问nginx返回正常说明Calico的网络转发没问题。7. 证书管理与高可用加固7.1 证书有效期与自动续期Kubernetes 1.28的证书默认有效期一年kubeadm在一年后自动续期。但如果集群中途停过一段时间可能错过后面的续期窗口。查看证书有效期kubeadm certs check-expiration如果想手动强制续期kubeadm certs renew all续期后需要重启相关组件才能加载新证书。这里有个容易忽略的点admin.conf里的客户端证书不在自动续期范围内这个证书有效期一年过期后kubectl访问集群就会报错。解决办法是在续期后重新生成admin.confkubeadm certs renew admin.conf7.2 etcd备份策略etcd是Kubernetes集群的单一数据源etcd挂了集群就算废了。生产环境必须做etcd备份最简单的方式是使用etcd快照ETCDCTL_API3 etcdctl --endpointshttps://127.0.0.1:2379 \ --cacert/etc/kubernetes/pki/etcd/ca.crt \ --cert/etc/kubernetes/pki/etcd/server.crt \ --key/etc/kubernetes/pki/etcd/server.key \ snapshot save /backup/etcd-snapshot-$(date %Y%m%d).db配合crontab做定时备份恢复的时候用etcdctl snapshot restore。这个恢复操作我实际试过过程比想象中复杂要先把etcd停掉清空数据目录再导入快照。建议每一两周做一次备份生产环境变更频繁的话每天备份一次。7.3 组件高可用配置细节Controller Manager和Scheduler默认通过--leader-elect参数实现选主kubeadm初始化时已经默认开启。只有一个控制平面节点时这个参数没有实际意义三个控制平面节点时其中一个挂了另外两个会自动接管。API Server本身是无状态服务前面挂了Keepalived后面三台都能提供API服务这就是高可用的核心逻辑。8. 常见问题与排查技巧8.1 节点NotReady节点NotReady是群里问得最多的问题。原因排查路径如下现象可能原因排查方法kubelet状态异常未正确启动或崩溃systemctl status kubelet -l容器运行时连接失败cri-dockerd未运行或socket路径不对systemctl status cri-dockerCNI插件异常Calico/Flannel Pod没有正常运行kubectl get pods -n kube-system容器无法获取IPPod网段与物理网段冲突dmesg这里有很大一部分人的问题出在cri-dockerd没装好或者cgroup driver不一致导致的kubelet拒绝启动。修改Docker的cgroup driver为systemd之后重启Docker和cri-dockerd重启kubeletsystemctl daemon-reload systemctl restart docker cri-docker kubelet8.2 kubeadm init报错汇总初始化失败最常见的情况是证书校验失败、网络不通、镜像拉取超时。镜像拉取超时的话优先配置--image-repository指向国内可访问的仓库。kubeadm init失败之后怎么清理重来kubeadm reset --cri-socket unix:///var/run/cri-dockerd.sock rm -rf /var/lib/cni/ /etc/cni/ /var/lib/kubelet /etc/kubernetes用kubeadm reset清理干净后重新init。注意不要手动删除kubeadm管理的目录容易留下脏数据导致后续初始化异常。8.3 证书过期与轮转异常这个入侵式故障很常见证书过期后集群状态看起来一切正常但kubectl执行任何操作都报证书错误。先看具体报错信息kubectl get nodes报certificate has expired or is not yet valid那就是证书过期了。按7.1的流程续期就行。如果是kubelet证书轮转失败看一下kubelet的KubeletConfiguration里rotateCertificates是否开启默认是true。8.4 集群扩展与升级路径集群需要扩容时新的工作节点执行上面5.3的join命令就行。新节点需要提前装好Docker、cri-dockerd和kubeadm、kubelet、kubectl并完成基础环境和内核参数的配置。升级路径方面从1.28往上升级时不能跨大版本跳要一个一个版本升。升级流程是先升级kubeadm、再kubelet、最后kubectl控制平面节点逐台升级再升级工作节点。在动手升级之前先把etcd备份做好这是我每次升级前必做的动作一次都没落下过。8.5 控制平面多个节点证书同步这是高可用集群特有的问题控制平面节点的证书必须保持一致。如果某个节点上的证书和其他节点不一致这个节点的API Server和etcd通信会直接失败。排查方法kubectl get csr查看CSR请求是否异常。如果某个节点的CSR被拒绝可以批准kubectl certificate approve csr-name另外控制平面节点之间有个经常被忽略的配置文件/etc/kubernetes/pki/下除了CA证书之外还需要把etcd的证书同步过去。kubeadm join的时候会自动处理这些文件复制但如果join失败就需要手动同步。我个人在这个安装过程中踩过最多的坑集中在cri-dockerd版本不匹配、cgroup driver不一致和VIP配置的certSANs遗漏这三类问题上。每次排错都是先看日志、再查配置、最后对比版本。这个高可用集群方案从1.15版本用到现在经历了好几代Kubernetes版本升级核心架构一直没变过稳定性我很放心。如果你是第一次搭建议先在虚拟机上完整走一遍流程把证书流程和每个组件的启动逻辑摸清楚再上生产环境去操作。毕竟生产环境的每一条命令都有后果冲着责任去把每一步吃透比求快更重要。