1. 动手前先把账算清楚:版本、发行版与部署方式
1.1 版本选型:为什么我推荐从 1.28+ 起步而不是盲目追新
很多人一看到 K8S 安装就想着"我要装最新版本",这是第一个坑。K8S 社区迭代非常快,每个小版本发布间隔大约三个月,1.36 这类新版本确实带来新特性,但随之而来的是生态组件兼容性问题。容器运行时、网络插件、存储驱动、监控套件的适配往往滞后于核心版本,你装一个刚出两周的最新版,很可能连 Calico 官方都还在适配通道里调试。
我做集群选型时,逻辑很简单:用新不用太新,用稳定不用刚发布。当前阶段,1.28、1.29、1.30 这类已经过了大半年淬炼的版本是稳妥之选,生态里的配套组件基本都完成了适配,社区里踩坑的帖子也攒够了,你碰到任何问题都能搜到解法。热词里提到的 Rocky 安装 K8S 1.36,我建议你装之前先做一件事:打开 K8S 官方变更日志,看看从 1.32 到 1.36 之间有没有移除某些 API 和命令行参数,否则你按旧教程写的配置清单可能直接报错。
还有一个决策点:你是在做生产集群还是学习环境?这个问题直接决定后面的所有选择。如果只是学习、考 CKA、搭一套本地实验环境,我推荐你看 Debian/Ubuntu 系,原因是 apt 源的 kubeadm 全家桶安装体验最顺,网上资料也最多。如果你的服务器跑的是 Rocky/AlmaLinux/CentOS Stream,注意防火墙策略默认是 firewalld,SELinux 默认是 enforcing,这两样东西是新手最容易忽略的"隐形炸弹"。
1.2 kubeadm、二进制还是发行版快照:三种方式的取舍
安装 K8S 集群的主流路径有三条:kubeadm、纯二进制部署、发行版自带的一键式方案(比如 RKE、K3s、MiniKube)。我在不同阶段都折腾过,说下真实感受。
kubeadm 是绝大多数人的最优解,它把控制面组件(kube-apiserver、kube-controller-manager、kube-scheduler)、etcd、kubelet 的初始化逻辑抽象成了一条命令,你不需要手动维护 systemd 单元文件和证书签发流程,但它又保留了足够的透明度——生成的静态 Pod 清单都在 /etc/kubernetes/manifests 下,想改什么直接改 YAML,kubelet 会自己检测并重启容器。
纯二进制部署是把所有组件二进制手动拷到服务器、手写 systemd 文件、手动签证书。这条路我走过一次,过程极其痛苦,大约需要两天时间才能把证书、APIServer 地址、etcd 集群成员全部理顺。它的唯一优势是企业内部极端受限环境下的可定制性,正常人没必要自己造轮子。
发行版快照(RKE2、K3s)是另一个赛道,RKE2 由 Rancher 维护,把 containerd、kubelet、kube-apiserver 等组件打包成一个可执行文件和一套目录结构,一条命令装完,升级也方便。如果你是第一次接触 K8S、希望最短时间看到集群起来,K3s 或者 RKE2 确实香,但它们的目录结构、证书路径和社区原生态有差异,你后续要依赖官方文档排查问题时,有些路径对不上号,容易产生困惑。
我的建议很简单:学习场景无脑 kubeadm,生产场景看团队运维能力——如果团队能维护好 etcd 和证书策略,kubeadm 足够;如果想省心,RKE2 值得考虑。下面所有实操我都按照 kubeadm 标准流程来写,因为它是理解 K8S 集群原理的最佳路径。
2. 环境准备:把地基夯实,后面才不返工
2.1 主机规划与硬件评估
K8S 不是一台机器能扛住的,它的最小集是 3 台节点:1 台控制平面(master)+ 2 台工作节点(worker)。如果你只有两台机器,可以把控制平面和工作负载混跑,但这是测试环境才建议干的省事方案,生产环境绝对不要这样玩——控制平面一旦因为业务流量震荡而 Carpool 失衡,整个集群的调度决策都会受影响。
硬件上的硬性指标,我直接给你一个经过实测的参考值:
| 节点角色 | CPU 要求 | 内存要求 | 磁盘要求 | 备注 |
|---|---|---|---|---|
| 控制平面 | 2 核起步,推荐 4 核 | 4GB 起步,推荐 8GB | 系统盘 50GB+ | 所有组件共用一个节点时,内存要求翻倍 |
| 工作节点 | 2 核起步 | 2GB 起步(跑业务另算) | 系统盘 50GB+ | 节点上的 Pod 存储需要额外规划 |
| etcd 节点 | 4 核+ | 8GB+ | SSD 盘,100GB+ | 如果独立部署,磁盘 IOPS 直接影响集群响应 |
我这里重点说下内存。kubeadm 默认要求控制平面节点至少 2GB 内存,否则 kubelet 会直接报"memory pressure"甚至初始化失败。我见过太多人在这上面翻车:机器配置只有 1GB,拿过去做练习,kubeadm init 跑一半 kubelet 就挂了。内存不足时根本轮不到看镜像拉取问题,因为容器还没创建就已经 OOM 了。
磁盘方面,建议用 SSD。etcd 是集群的存储底座,它的性能取决于磁盘 fsync 耗时,机械盘在这种高频率写入场景下延迟动辄几百毫秒,K8S 集群会频繁报告 etcd leader election 超时。这个经验不是玄学,是 etcd 官方文档反复强调的硬件要求。
2.2 操作系统基线配置:内核参数、模块与 sysctl
拿到全新服务器后,别急着装 Docker,我的习惯是先做一套统一的基线配置,保证所有节点的内核行为一致。这一步能避免 80% 的后续诡异问题。
首先禁用 swap。K8S 的 kubelet 默认情况下不允许节点使用交换分区,因为 Pod 的内存隔离在 cgroup 层面依赖物理内存控制,一旦 swap 介入,内存回收节奏不可控,Pod 被 OOM Kill 的时机就变得不可预测。执行:
swapoff -a sed -i '/swap/s/^/#/' /etc/fstab这里要留意,swapoff -a 只对当前会话有效,真正持久化必须改 /etc/fstab 或使用 systemd 挂载单元,否则重启后 swap 又回来了,kubelet 启动直接失败。
然后是内核模块和 sysctl 参数。K8S 的网络方案(无论 Calico 还是 Flannel)都要用到 iptables 和 IPVS,同时容器网络需要转发 IPv4 包,br_netfilter 模块必须加载。我直接附上实践过的配置:
cat <<EOF | tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay modprobe br_netfilter cat <<EOF | tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF sysctl --systemnet.bridge.bridge-nf-call-iptables这个参数的作用,是把桥上流经的数据包也交给宿主机的 iptables 规则处理。容器与宿主机之间靠虚拟网桥通信,如果不开启桥接包过滤,Service 的流量转发规则在跨节点访问时会莫名失效。我遇到过一次"Pod 之间能通、Service 访问超时"的诡异故障,最后定位到这个参数没配置。
在 Rocky/AlmaLinux 系系统上,我要特别提醒 SELinux。安装完成后可以用getenforce查看状态,默认一般是 Enforcing。可以临时切到 Permissive 模式跑通流程,但最稳妥的做法是关掉它再装:
sed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config完全没有贬低 SELinux 的意思,它在传统服务场景下很安全,但在 K8S 的容器网络命名空间迁移场景下,经常会拦掉一些 Pod 访问行为,排查起来非常费劲。生产严肃使用场景可以基于 SELinux 策略做精细化适配,但那是个大工程,入门阶段直接关掉是投入产出比最高的选择。
2.3 容器运行时:containerd vs Docker 的抉择
K8S 从 1.24 开始移除了对 Docker 的底层运行时支持,移除了 dockershim,但这不代表 Docker 完全不能用了——Docker 会通过 cri-dockerd 这个适配器帮 K8S 调用它的容器接口。可实操下来,没人愿意走这层多余转换,containerd 已经成为事实标准。
containerd 是 Docker 团队拆出来的独立项目,它直接实现了 K8S 需要的 CRI 接口(Container Runtime Interface),不需要适配层,镜像格式跟 Docker 兼容,命令虽然长得不太一样,但核心概念一致。装它的过程在各个发行版上有差异,Debian/Ubuntu 可以直接 apt 安装:
apt install -y containerdRocky 系则是:
dnf install -y containerd.io无论哪种方式,装完之后一定要重写 containerd 的默认配置,因为默认配置里 sandbox 镜像地址(pause 镜像)仍然是 registry.k8s.io 的路径,国内网络环境大概率拉不到。具体做法:先导出默认配置,再改两个关键地方,然后重启:
mkdir -p /etc/containerd containerd config default | tee /etc/containerd/config.toml编辑 config.toml 文件,找到plugins."io.containerd.grpc.v1.cri".sandbox_image这一行,把registry.k8s.io/pause:3.9改成你本地镜像仓库或镜像加速器里可用的 pause 镜像地址。如果还开启了 systemd cgroup,要把SystemdCgroup = false改成true:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] ... [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] SystemdCgroup = true这一项很多人漏掉,后果是节点在初始化集群时,kubelet 与 containerd 的 cgroup 驱动不一致,直接报failed to run Kubelet" err="failed to run kubelet"。
3. 核心环节:控制平面初始化与工作节点加入
3.1 kubeadm init 完整实录与参数说明
万事俱备之后,进入关键操作:在控制平面节点执行 kubeadm init。kubeadm 的工作方式是先拉镜像,生成证书,生成加密密钥和 token,然后启动 etcd 和三个控制面组件(apiserver、controller-manager、scheduler)作为静态 Pod,最终给出一段 join 命令。
执行之前先安装 kubeadm、kubelet、kubectl 三件套,注意版本要一致。Debian 系使用 apt 源时需要先把 K8S 的软件源配置到系统:
apt update && apt install -y kubelet kubeadm kubectl apt-mark hold kubelet kubeadm kubectlapt-mark hold的含义是把这三个包锁住,禁止自动升级。K8S 集群组件版本一旦不一致,集群就会进入不可控状态,所以把版本冻结在源里是必须的。
接着执行初始化:
kubeadm init \ --apiserver-advertise-address=192.168.1.10 \ --pod-network-cidr=10.244.0.0/16 \ --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers \ --kubernetes-version v1.28.2有几个参数挑重点说:
--apiserver-advertise-address:指定 apiserver 对外广播的 IP,这个 IP 是节点们和 kubectl 连接的入口。别写成 eth0 上绑定的私有 IP 就开始 self-routing,会造成跨网段访问失败。--pod-network-cidr:Pod 的网段。我选择 10.244.0.0/16,因为这是 Flannel 默认使用的网段,如果你后面换 Calico,可以改用 10.244.0.0/16 或 192.168.0.0/16,总之这个网段不能和你的局域网冲突。--image-repository:指向可用的镜像仓库。K8S 默认仓库是 registry.k8s.io,国内拉取经常超时,这里换成阿里云的公共镜像仓库可以少碰几个坑。--kubernetes-version:必须跟 kubeadm 的实际版本匹配,否则校验阶段就过不去,实际可以省略,kubeadm 默认用当前版本执行。
初始化成功后会给出类似这样的提示,这几行信息非常关键:
Your Kubernetes control-plane has initialized successfully! To start using your cluster, you need to run the following as a regular user: ... kubeadm join 192.168.1.10:6443 --token abcdef.0123456789abcdef \ --discovery-token-ca-cert-hash sha256:xxxxxx...这个输出很容易被忽略,因为人总是盯着屏幕等着最后那句话,但实际上你要先保存好 token 和 CA 散列,一旦窗口关闭,它们不会重新显示。我建议拿到后立刻复制到一个临时文件里。
3.2 节点加入:token、CA 散列与常见重试
工作节点加入集群,本质上是让 kubelet 通过 kubeadm join 这条命令完成三个动作:从 APIServer 获取集群证书信息、向 APIServer 发起注册请求、启动 kubelet 并等待调度。
在每台工作节点上先准备好 containers 运行时(上一节的配置都做完),然后直接执行控制平面输出的 join 命令。如果 token 过期或者你想定制节点角色,也可以自己生成:
# 在控制平面节点生成新的 token kubeadm token create --print-join-command这里有个容易踩的坑:工作节点上执行 join 前,必须先把主机名、DNS 映射处理好。K8S 会拿节点的主机名去注册节点对象,如果两台机器的主机名相同,第二台节点加入时会直接冲突。我习惯把所有节点的主机名规划成 k8s-master、k8s-node-01、k8s-node-02,并写进 /etc/hosts:
192.168.1.10 k8s-master 192.168.1.11 k8s-node-01 192.168.1.12 k8s-node-02join 完成之后,回到控制平面节点执行:
kubectl get nodes正常情况下会看到类似输出:
NAME STATUS ROLES AGE VERSION k8s-master NotReady control-plane 2m v1.28.2 k8s-node-01 NotReady <none> 35s v1.28.2这里 STATUS 是 NotReady 很正常,因为集群还没有安装网络插件,Pod 之间的网络通路还没建立,节点心跳也不健康。这是下一个环节要处理的事情。
3.3 网络插件部署:Calico 与 Flannel 的选择
K8S 本身不实现 Pod 间网络互通,它只定义了一个 CNI(Container Network Interface)规范,具体通信用什么方案,是插件的事。安装网络插件是集群进入 Ready 状态的临门一脚,这个环节我踩过不少坑,重点展开说。
Flannel是最轻量的方案,基于 VXLAN 隧道封装,配置简单到一条命令:
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml使用 Flannel 时,Pod 网段必须符合它的默认网段10.244.0.0/16,所以我在 init 阶段就把--pod-network-cidr设置成了这个地址。如果你之前顺手填了别的网段,后面 Flannel 的分配规则就会和 kubelet 要求对不上,典型的报错是Failed to create pod sandbox: ... failed to setup network for pod"。
Calico是功能更丰富的选择,支持网络策略、BGP 路由模式,性能比 VXLAN 更好一些,同时它不需要指定固定网段。Calico 的安装方式让新手犯迷糊,官方文档给的是一张清单文件,但里面包含数百个对象,直接 apply 有点不负责任。我的做法是先下载到本地,检查里面的 IP 池配置段:
curl -O https://raw.githubusercontent.com/projectcalico/calico/master/manifests/calico.yaml vim calico.yaml # 搜索 CALICO_IPV4POOL_CIDR在文件中设置CALICO_IPV4POOL_CIDR为你 init 时声明的网段,然后 apply。Calico 对内核模块有一点额外要求,需要确认__ip_tables等模块存在,在主流发行版内核里一般都带着。
我个人的经验是:如果只是想快速跑通集群,Flannel 是首选;如果是为了生产、需要网络策略精细管控,上 Calico。换网络插件时需要先把原先插件的所有 DaemonSet 和资源清掉,再重新 apply 新的清单,否则网卡上残留的配置会导致 Pod 网络冲突。我一度以为先 apply Calico 再删 Flannel 也没事,结果数据路径全乱了,最后只能把所有节点上的 CNI 配置目录清空重来。
4. 集群落地后的验证与补完
4.1 集群健康检查的几条命脉命令
安装完不等于装好了,一套完整的验收动作是必须的。我的习惯是依次检查三个维度:节点状态、核心组件状态、工作负载实际通信。
第一,节点状态确认:
kubectl get nodes如果所有节点都是 Ready,第一关过了。
第二,控制面组件状态:
kubectl get pods -n kube-systemnamed space 一长串 kube-* pod,重点看 coredns 和 kube-proxy。CoreDNS 至少要有两个副本处于 Running 状态,否则集群内 Service 域名解析不可用,你会发现 Pod 内请求对端服务名永远超时。
第三,做一个最朴素的连通性测试:
kubectl run nginx-test --image=nginx kubectl expose pod nginx-test --port=80 --name=nginx-service kubectl get service nginx-serviceService 创建后,在集群内任意节点上用curl http://nginx-service:80试试能否访问。这一步覆盖了 DNS 解析、Service 负载均衡、Pod 网络三层通路。如果通了,说明整个集群的核心数据链路没问题,后面部署业务基本不会因为网络层出怪问题。
很多时候新手在这一步会发现 Service 能创建成功,但 curl 超时,这时十有八九是 kube-proxy 的 iptables 模式与内核参数配合不准。检查这台机器上 iptables 规则是否存在:
iptables -t nat -L -n | grep nginx-service如果没有任何规则输出,说明 kube-proxy 的准入失败了,典型的诱因是节点上安装了旧版 iptables 或者 kube-proxy 使用的 mode 与主机不匹配。
4.2 存储、负载均衡与应用市场的补全
K8S 集群装好只是起点,真正要让业务跑起来,还需要三样配套:存储方案、负载均衡方案、应用分发方案。
存储方面,从零搭建一套持久化存储可以说是第二个大坑,因为 K8S 的 PVC(PersistentVolumeClaim)不会自动创建存储后端。生产环境通常接云厂商的块存储、NAS,或者自建 NFS、Ceph RBD、Longhorn。作为入门,我建议先接 NFS。NFS 的部署非常简单,在整个集群里找一台机器开启 NFS 服务,然后装一个 nfs-subdir-external-provisioner 组件,让 PV 可以自动按 PVC 动态创建:
helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/ helm install nfs-client nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \ --set nfs.server=192.168.1.100 \ --set nfs.path=/data/nfs别看这条命令短,它背后解决的问题是:以后你声明 PVC,不再需要手动建 PV 等管理员来分配存储,provisioner 会自动在 NFS 目录下创建子目录并在集群里匹配 PVC。我接触过的项目里,至少有一半的麻烦来自"PVC 一直 Pending",原因就是存储后端没搭或者 StorageClass 没设置默认。
负载均衡层面,如果你的集群是裸机环境,Service 的 LoadBalancer 类型并不会自动生效——因为它本质上是调用云厂商的 API 来创建云上的负载均衡实例。裸机上这个操作需要额外安装 MetalLB,它用 ARP/BGP 协议把一组 IP 伪装成外部 IP,将 Service 流量引入节点。实现方式:
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.10/config/manifests/metallb-native.yaml然后声明一个 IP 地址池。这一步在公网环境(比如云服务器)上要特别小心,ARP 广播只能在同一二层网络内生效,跨网段还是得靠真实的负载均衡器。
应用分发方面,现在大家基本都用 Helm 来管理应用清单,把几十个 YAML 打包成一个 Chart,用一条命令部署和升级。K8S 生态里没有 Helm 寸步难行,装 Helm 本身只花三分钟:
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 chmod 700 get_helm.sh ./get_helm.sh后面装 Prometheus、Grafana、Ingress Controller、Nginx,都会用到 helm install 这种形式,省心得多。
4.3 Namespace 与多租户的基础认知
很多新手装好集群后,默认把所有东西一股脑全丢到 default 命名空间,等到有第二个项目进来才发现乱了套。Namespace(命名空间)是 K8S 内置的资源隔离机制,它在集群内部创建出逻辑分区,不同分区里的资源重名也没关系,配合 RBAC 还能做权限隔离。
创建命名空间的常用方式:
kubectl create namespace dev kubectl create namespace prod kubectl create namespace middleware建议从第一天开始就按环境或者按团队划分命名空间,比如 dev / staging / prod,再叠加 ResourceQuota 限制每个命名空间的 CPU、内存上限,防止某个测试任务把集群资源吃光影响到生产环境:
kubectl apply -f - <<EOF apiVersion: v1 kind: ResourceQuota metadata: name: dev-quota namespace: dev spec: hard: requests.cpu: 10 requests.memory: 16Gi limits.cpu: 20 limits.memory: 32Gi EOFResourceQuota 这个对象不设置的话,集群就是一个"公共食堂":谁都能来吃,谁都能吃撑。在共享的测试集群里,一个死循环的 Pod 就能整垮全集群,这也是"集群拥塞器"类问题频发的根源。要限流只有两个手段:节点亲和调度 + 命名空间资源配额。
5. 故障记录与排障速查
5.1 我踩过的坑:镜像拉取、cgroup 不匹配、kubelet 起不来
这里把我这些年碰到的高频问题无保留地列一下,每一条都是真实踩过坑后的总结。
坑一:镜像拉取超时。国内网络环境下,registry.k8s.io 的连接经常处于"超时—重试—超时"的死循环。除了用--image-repository指定阿里云镜像仓库外,还可以预先把需要的镜像从 阿里云同步到本地,再标记成 K8S 期望的镜像名。操作方式如下:
# 先拉镜像并打 tag docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:v1.28.2 docker tag registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:v1.28.2 registry.k8s.io/kube-apiserver:v1.28.2 # 所有控制平面镜像都 pull 完 tag 完,再执行 kubeadm init注意,如果用的容器运行时是 containerd 而不是 Docker,那 ctr 命令也可以做 tag,但更直观的做法是直接把 containerd 的 sandbox_image 配置改成可访问的地址,避免改标签这种笨方法。
坑二:cgroup 驱动不匹配。这是新手期最容易出现的错误之一。kubelet 默认使用 systemd 作为 cgroup 驱动,而 containerd 默认配置可能还是 cgroupfs,两者一对比直接报错。在配置 containerd 时,把 SystemdCgroup 设为 true,同时在 kubelet 的配置里保持默认。检查是否一致可以用一条命令:
kubectl get nodes -o jsonpath='{.items[*].status.conditions}'坑三:kubelet 起不来,systemctl status kubelet 直接红。这个现象比较普遍,原因是多方面的,但大多数可以归因于节点上没有配置好 kubelet 所需的内核参数、cgroup 驱动不一致或 swap 未关闭。排查套路是:先看 kubelet 日志:
journalctl -u kubelet --since now --reverse日志一行一行盯着看,大概率能看到ERROR级别的提示,例如failed to run kubelet" err="failed to run kubelet"或者failed to set cgroup。顺着提示改完配置再重启 kubelet。
坑四:节点加入失败,报错信息里有Connection refused。检查控制平面的 APIServer 服务是否正常:kubectl get pods -n kube-system,如果 kube-apiserver 的 Pod 不是 Running,再看看它的日志:
kubectl logs -n kube-system $(kubectl get pods -n kube-system | grep apiserver | awk '{print $1}')常见原因是 etcd 存储的 PVC 没有挂载好,或者证书失效。
5.2 排障速查表
做一张速查表,方便遇到问题时快速定位。
| 故障现象 | 可能原因 | 排查命令 / 操作 |
|---|---|---|
| 节点一直 NotReady | 网络插件未安装;cgroup 不匹配;CNI 配置残留 | kubectl get pods -n kube-system;清理 /etc/cni/net.d |
| kubelet 日志报 cgroup 错误 | containerd SystemdCgroup 未打开 | 修改 config.toml 后重启 containerd |
| PVC 一直 Pending | StorageClass 不存在或默认未设置 | kubectl get storageclass;确认 provisioner |
| Service 访问超时 | kube-proxy 未生效;iptables 规则缺失 | iptables -t nat -L -n;kubectl get pods -n kube-system |
| Pod 一直 ContainerCreating | 镜像拉取失败;存储卷挂载失败 | kubectl describe pod <pod>看事件 |
| 解析 Service 域名失败 | CoreDNS 异常;集群 DNS 配置错误 | kubectl get pods -n kube-system;kubectl get configmap -n kube-system |
| token 过期 | token 默认有效期 24 小时 | kubeadm token create --print-join-command重新生成 |
| 控制平面 Pod 崩溃循环 | etcd 磁盘性能差;存储异常 | kubectl logs -n kube-system <etcd-pod> |
这个表没法覆盖所有异常,但能解决 90% 的新手问题。真是剩下那 10%,我的建议还是那句话:要会看日志,kubectl describe 和 logs 是两个最有用的命令,任何一个 Pod 出问题,都先跑这两条,再判断。
5.3 关于集群调度与故障转移的一些认知补充
热词里提到集群调度和集群故障转移,这两块是 K8S 安装后必然会接触到的概念,提前说一下能让你少走弯路。
集群调度就是 kube-scheduler 组件在为新创建的 Pod 选节点,默认的调度策略会考虑节点资源余量、污点容忍、亲和性等。你可以通过给节点打标签、设置亲和性规则、调整资源请求值来影响调度决策。但有一点必须明确:调度不是一个你装上就能自动做好的模块,它需要你用 requests 和 limits 两个字段把应用资源画像描绘清楚,调度器才有数据可算。如果你的 Pod 都没有写 requests,那么调度器只会认为它零开销,于是所有 Pod 全堆在一台机器上,集群负载完全倾斜,这是典型的"集群拥塞"现象,根源还是当初没做好资源规划。
故障转移也是一个常被误解的概念。K8S 默认的故障转移能力其实很有限:节点宕机后,Node Controller 最多等待pod-eviction-timeout(默认 5 分钟)之后,才开始给有状态副本或无主控的 Pod 重新调度。而且并没有"自动把数据迁走"的能力,数据还得靠云盘、分布式块存储做跨节点随挂随用。集群层面要做得更好,可以装 descheduler 来重新平衡负载,可以开 PodDisruptionBudget 来保证业务不中断,但这些都在安装之后,属于集群治理层面的进阶话题,入门阶段能跑通集群、理解基本架构就够了。
写在最后
K8S 集群安装这件事,说难,其实每一步都有模板可循;说不难,中间却藏了太多"状态不一致"的问题。我的经验是,把每个环节的"为什么"想清楚——为什么禁用 swap,因为 kubelet 要精确控制内存;为什么改 SystemdCgroup,因为 kubelet 和 runc 要采用同一套 cgroup 管理方式;为什么 Pod 网段不能跟局域网冲突,因为路由表会打架。把这些逻辑想通了,下次面对一个陌生发行版、一个全新版本时的慌乱就能少大半。
最后再给小建议:装好集群后,务必做一次完整备份,包括 etcd 快照和关键配置文件。K8S 集群最大的噩梦不是装不上去,而是运行到一半数据坏了,没有做事后恢复的方案。我自己的工作流里,每周都会跑一次 etcd 快照脚本,这个习惯救过我至少两次,真心推荐你也养成。