1. 安装方式选型与前置环境准备
我先说结论:如果目标是“极速”且可持续维护,kubeadm是当前的唯一合理选择。二进制手工部署在 v1.35.2 时代已经不是“专业人士的浪漫”,而是给自己挖坑——你需要手动处理 kubelet、kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、etcd 六套组件的证书签发、静态 Pod 清单、健康检查探针,任何一个环节拼错一个参数,排查时间直接以小时计。而 kubeadm 把这些步骤全部收敛成了两条命令:kubeadm init和kubeadm join。
版本选择上,v1.35.2 这个号段属于 Kubernetes 的常规迭代版本,它带来的变化主要是:CRI 运行时对 containerd 的集成更紧密,核心组件对证书轮换机制更成熟,以及更严格的废弃 API 清理。对于新建集群,直接用最新稳定版问题不大,但如果你所在团队有旧的监控、备份、网络插件生态,建议先确认目标插件对 Kubernetes 版本的兼容矩阵,再决定是否追新。我的习惯是:新集群用新版本,线上存量集群保守升级,这个原则到 v1.35.2 依然适用。
1.1 三节点最小架构
极速不等于单机。我强烈建议你至少准备三台机器:master-01作为控制平面节点,node-01和node-02作为工作节点。可以采用虚拟机,也可以采用云厂商的临时实例。
硬件配置参考:
- master-01:2C4G,系统盘 40G
- node-01:4C8G,系统盘 100G
- node-02:4C8G,系统盘 100G
控制平面 2C4G 够用,如果你后续要跑 Webhook、CRD Controller、Metrics Server 的聚合层,建议加到 4C8G。工作节点 4C8G 属于比较舒服的起步配置,推荐 system 盘和数据盘分离,但为了快速演示可以先用一块盘。
操作系统统一使用 Ubuntu 22.04 LTS 或 CentOS 7.9 都行。我实测下来,Ubuntu 在 containerd 以及 iptables 行为上坑更少,弹性更好,所以下面的操作步骤以 Ubuntu 为例,同时会在关键位置标注 CentOS 的差异。所有节点的主机名、IP 规划提前列好:
| 角色 | 主机名 | IP 地址 |
|---|---|---|
| 控制平面 | k8s-master | 192.168.10.10 |
| 工作节点 | k8s-node01 | 192.168.10.11 |
| 工作节点 | k8s-node02 | 192.168.10.12 |
注意:所有节点都要关闭 swap、关闭防火墙(或放行端口)、配置主机名与 hosts 映射。这三点是 kubelet 初始化检查的硬性要求,不先处理干净,后面 kubeadm init 会直接报错终止。
1.2 系统优化:别跳过,这是排障减半的关键
在安装 Kubernetes 之前,先把各节点的操作系统基础优化做完。这些操作与 Kubernetes 版本无关,但直接影响集群初始化能否一次通过。
第一件事,修改主机名。Ubuntu 上执行:
hostnamectl set-hostname k8s-master然后编辑每台节点的/etc/hosts,把三个节点的 IP 与主机名映射写进去:
192.168.10.10 k8s-master 192.168.10.11 k8s-node01 192.168.10.12 k8s-node02否则后面kubeadm init时 apiserver 的 advertise 地址以及证书里的 SAN 会对不上,导致 node 节点报"Unable to connect to the server"这种看似是网络问题、实则是证书域名问题的故障。
第二件事,关闭 swap。Kubernetes 从 v1.22 开始支持部分场景下的 swap 开启,但 v1.35.2 的 kubelet 默认仍建议关闭 swap,因为 swap 会干扰 Pod 的内存 QoS 与驱逐判断。临时关闭加永久关闭:
swapoff -a sed -ri 's/.*swap.*/#&/' /etc/fstab第三件事,加载内核模块并配置网络参数。Kubernetes 依赖overlay和br_netfilter两个模块,前者用于 containerd 的 overlayfs 存储驱动,后者用于 iptables 对 bridge 流量的处理。没有br_netfilter,你会在 kube-proxy 使用 iptables 模式时遇到 Service 访问不通的诡异问题。
cat <<EOF | tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay modprobe br_netfilter二 接着配置 sysctl:
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.ipv4.ip_forward如果不开启,Pod 与外部网络的通信、NodePort 的外部访问都会失败。这三个参数请在每台节点都确认生效。
第四件事,Ubuntu 上关闭防火墙或者放行端口。简单起见,直接 ufw disable:
systemctl stop ufw systemctl disable ufw如果你在生产环境有防火墙策略,需要放行以下端口:6443(kube-apiserver)、2379-2380(etcd)、10250(kubelet)、10259(kube-scheduler)、10257(kube-controller-manager)、30000-32767(NodePort 范围)。排查时你会发现,大多数情况下“初始化成功但 Node 状态 NotReady”“kubectl 连不上”都跟安全组或防火墙端口没放行有关,而不是 Kubernetes 本身的问题。
2. 容器运行时与 Kubernetes 组件安装
这一步是“极速安装”中最容易出现版本不匹配的环节。容器运行时我选择 containerd,它是目前 Kubernetes 默认的 CRI 实现,既不引入 Docker 的额外复杂性,组件的唯一性也让排障更简单。v1.35.2 对容器运行时的最低版本要求,本质上只有一条:实现完整的 CRI 接口,containerd 满足,而且在我实测的所有运行时中,它的安装路径最短。
2.1 安装 containerd
先安装系统依赖:
apt-get update apt-get install -y containerdUbuntu 的软件源里自带的 containerd 版本可能偏旧,你可以从 containerd 官方 GitHub 下载指定版本。安装完成后,第一步永远是生成默认配置:
mkdir -p /etc/containerd containerd config default | tee /etc/containerd/config.toml这里有一个新手必踩的坑:Kubernetes 国内环境或公司内网环境从registry.k8s.io拉取镜像速度极慢,导致kubeadm init卡在 Pulling images 这一步。解决方案是修改 containerd 的 config.toml,把 sandbox 镜像改为国内可访问的镜像源,同时配置 registry 镜像加速:
[plugins."io.containerd.grpc.v1.cri"] sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9" [plugins."io.containerd.grpc.v1.cri".registry.mirrors] [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"] endpoint = ["https://docker.m.daocloud.io"] [plugins."io.containerd.grpc.v1.cri".registry.mirrors."registry.k8s.io"] endpoint = ["https://k8s.m.daocloud.io"]配置文件改完后重启 containerd,并设置开机自启:
systemctl daemon-reload systemctl restart containerd systemctl enable containerd提示:
sandbox_image的版本号要跟 Kubelet 期望的 pause 版本一致。v1.35.x 的默认 pause 版本通常是 3.10,你在配置里写 3.9 也能用,但后续 kubelet 会尝试拉取它期望的 pause 版本,如果某个节点配置不一致,Pod 会卡在 ContainerCreating。保险起见,用kubeadm config images list先确认准确的 pause 版本号。
2.2 安装 kubeadm、kubelet、kubectl
三件套的版本号必须一致,建议直接指定 v1.35.2 安装。添加 Kubernetes 软件源:
apt-get update apt-get install -y apt-transport-https ca-certificates curl gpg顺手把证书和源配置好,Ubuntu 22.04 下推荐这种方式:
mkdir -p -m 755 /etc/apt/keyrings curl -fsSL "https://pkgs.k8s.io/core:/stable:/v1.35.2/deb/Release.key" | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.35.2/deb/ /' | tee /etc/apt/sources.list.d/kubernetes.list然后安装:
apt-get update apt-get install -y kubelet kubeadm kubectl apt-mark hold kubelet kubeadm kubectlapt-mark hold很重要。Kubernetes 小版本升级节奏很快,不加锁的话,某天你执行apt-get upgrade会把三件套升到其他版本,导致版本错位,集群初始化直接崩盘。锁定版本是运维层面的基础动作。
此时先不要启动 kubelet,因为在 kubeadm init 之前,kubelet 会由于缺少集群配置而不断重启,这不算故障,等初始化完就好了。CentOS 上对应的命令是把apt换成yum,然后systemctl enable --now kubelet即可,差异不大。
2.3 预拉取镜像,真正加速的关键操作
很多人认为极速安装就是命令敲得快,其实是慢在镜像拉取上。有一个隐藏的提速技巧:在主节点上先用 kubeadm 预拉取所需的控制平面镜像,提前确认镜像源可以连通,再执行正式初始化。
kubeadm config images list kubeadm config images pull这一条命令会按照 kubeadm 内置的镜像列表逐一拉取。如果你在前面已经给 containerd 配置过 registry mirror,这一步会非常快。如果你的网络环境对主流镜像源不通,就在这里提前看到拉取失败的具体原因,而不是在kubeadm init时被动失败。
3. 集群初始化与工作节点加入
3.1 控制平面初始化
镜像拉取完成后,真正执行初始化:
kubeadm init \ --apiserver-advertise-address=192.168.10.10 \ --pod-network-cidr=10.244.0.0/16 \ --kubernetes-version=v1.35.2如果不需要自定义 apiserver 的 advertise 地址,也可以直接kubeadm init,它会自动检测本机 IP。但检测到的 IP 可能是虚拟网卡或内网路由的 IP,为了避免后续 node 节点连接错误,显式指定是一个好习惯。
参数--pod-network-cidr=10.244.0.0/16是为 Flannel 准备的。如果后面你打算用 Calico,默认网段一般是 192.168.0.0/16。这里要提前想清楚,一旦初始化完成后 Pod 网段就不能随意更改,更改需要重建集群。
初始化成功后会输出三部分关键信息:
一 是 kubeconfig 配置文件的路径和用法,一般是:
mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config二 是 Pod 网络插件必须安装的提示,不装插件的话,CoreDNS 会一直处于 Pending,node 节点也会一直是 NotReady。
三 是 node 节点加入集群的完整命令,包含 token 和证书散列值。如果这条命令丢了,可以用kubeadm token create --print-join-command重新生成。
3.2 部署网络插件
整个安装过程中,选择 Pod 网络插件是一个决策点。我实际测试过 Flannel、Calico、Cilium,如果想在“极速安装”和“低维护成本”之间取平衡,Flannel 的 VXLAN 模式最省心,配置最少;如果你的集群需要 NetworkPolicy 或性能压测,选 Calico;如果对 eBPF 和可观测性有需求,选 Cilium。
下面以 Flannel 为例。初始化完成后直接应用它提供的一个命令即可,kube-flannel.yml中会读取你指定 Pod 网段 10.244.0.0/16:
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml大约 30 秒后再看节点状态:
kubectl get nodes kubectl get pods -A正常的话,节点状态为 Ready,CoreDNS 处于 Running。如果 Flannel 一直 CrashLoopBackOff,大概率是 containerd 的 sandbox_image 问题,或者/etc/cni/net.d/目录权限不对。这两个问题我们在后面排障章节细讲。
3.3 工作节点加入集群
把kubeadm init输出的 join 命令复制到 node-01 和 node-02 上执行。如果 join 命令丢了,在 master 上重新生成:
kubeadm token create --print-join-command一般长这样:
kubeadm join 192.168.10.10:6443 --token xxxxxx --discovery-token-ca-cert-hash sha256:xxxx在每台工作节点上执行完 join 后,回到 master 查看:
kubectl get nodes -o wide等一分钟后,node-01 和 node-02 应该都会变成 Ready。到这里,一套最小可用的 Kubernetes 集群就建好了。
注意:join 命令里的 token 有效期默认是 24 小时。如果是隔天再追加节点,token 可能已经失效,需要重新创建。另外,token 是敏感信息,拿到 token 的人可以直接接入集群,使用完毕后建议执行
kubeadm token delete清理。
3.4 关于高可用控制平面的必要性
很多人装完单 master 集群后就用于生产,这是在赌运气。后续只要你重启 master,或者在 master 上执行kubeadm upgrade,整个集群都会短暂不可用。如果你的场景是个人学习、Demo 或内部开发环境,单 master 够用,但从 3.1 的架构图来说,如果要上生产,至少要做 HA 控制平面:三台 master 前面挂一层负载均衡(Keepalived 或云 LB),apiserver 的 6443 端口通过 VIP 对外提供服务。kubeadm 支持--control-plane-endpoint参数,在 init 时指定一个负载均衡地址即可。这样即使其中一台 master 挂了,集群 API 仍然可用。
4. 常见故障排查:从 init 到 Ready 的实录
这一部分,我把从 v1.35.2 安装和运行过程中收集到的真实频率最高的问题列一下,并给出排查命令和修复方法。虽然版本在迭代,但这些故障在多数 Kubernetes 集群中高度相似。
4.1 kubeadm init 失败,卡在拉镜像或证书校验
现象:命令执行到 “Running kubelet” 或 “Fetching configuration from cluster” 时长时间无响应,或者直接超时报错。
排查思路:
先看 kubelet 日志:
journalctl -u kubelet -f --no-pager如果是镜像拉取问题,日志中会出现failed to pull image字样,然后无限重试。解决办法是回到 2.1 修改 containerd 的 mirror 配置,重点检查sandbox_image是否被正确配置。很多问题其实不是拉不到,而是配置文件语法解析出错导致 containerd 没重启。
如果是证书校验问题,日志可能位于/var/log/kubeadm/目录下,常见的一种报错是[ERROR FileContent--proc-sys-net-bridge-bridge-nf-call-iptables]。这个错误是因为 1.1 中的 sysctl 参数没有生效,重新执行sysctl --system后再 init 即可。
排查顺序很关键:先看 kubelet 日志,再看 containerd 日志,最后看 kubeadm 自身的报错。不要一上来就 reset 重来。
4.2 节点状态 NotReady 且 kube-system 下有大量 Pending
现象:kubectl get nodes显示 master 和 node 都是 NotReady,kubectl get pods -A看到 CoreDNS Pending,Flannel Pending。
排查思路:
Post 网桥问题优先排查:
kubectl describe pod -n kube-system calico-kube-controllers-xxxx # 或 flannel pod如果 describe 的 Events 里显示Failed create pod sandbox: unable to find image,那就是 pause 镜像在 containerd 里不存在或者镜像地址不对。kubelet 拉取 pause 镜像失败时,所有 Pod 都会卡在 ContainerCreating。
如果 Events 显示network plugin is not ready: cni config uninitialized,这是 Flannel 没有在/etc/cni/net.d/目录写入配置、或者 configmap 里的 Network 与你 init 时--pod-network-cidr不一致导致的。这种情况下,删除 Flannel DaemonSet 重新 apply 一次 kube-flannel.yml 就能解决。
4.3 master 节点 Ready,但工作节点加入后一直 NotReady
现象:join 命令执行成功,但在 master 上看到 node-01 / node-02 的 STATUS 一直是 NotReady,描述信息显示kubelet has not posted heartbeat yet。
排查思路:
这类问题的特征集中在:kubelet 无法访问 master 的 10250 端口。在 node-01 上执行:
nc -vz 192.168.10.10 6443 nc -vz 192.168.10.10 10250如果 6443 通、10250 不通,十有八九是 master 节点的 kubelet 绑定地址或者安全组规则问题。master 上的 kubelet 默认监听所有网卡的 10250,如果 master 在云环境里,检查云平台安全组是否只放行了 6443 而忘了放行 10250。
还有一种容易被忽略的情况:工作节点的 kubelet 与 master 的 kube-apiserver 之间的时钟漂移超过 5 分钟也会导致心跳上报失败。配置 chrony 或 ntpdate 做时钟同步,这是 Kubernetes 集群一个很重要的运维项。
4.4 kubectl 执行命令报 “No route to host” 或 “Connection refused”
现象:在任意节点上执行kubectl get nodes,报错内容包含Unable to connect to the server: dial tcp 192.168.10.10:6443: connect: connection refused。
排查思路:
Connection refused 通常代表 apiserver 没有监听 6443。确认 apiserver 的容器状态:
crictl ps -a kubectl get --raw='/healthz' 2>/dev/null || curl -k https://127.0.0.1:6443/healthz如果 apiserver 容器反复重启,查看其日志:
crictl logs CONTAINER_ID最常见的诱因有:etcd 挂了导致 apiserver 无法存储状态;证书过期;apiserver 的 admission plugin 配置写错了导致启动失败。v1.35.2 里证书有效期已经被大大延长,但早前版本留下的旧集群如果直接升级,证书过期问题仍然是非本地化边缘场景中最高频的一个。
还有一个细节问题:如果 apiserver 监听正常,但依然 No route to host,那可能是 iptables 规则被清空了。比如某些发行版在重启后没有自动加载 Kubernetes 的 iptables 规则,导致 Service 转发异常。重启 kube-proxy 即可恢复:
kubectl -n kube-system rollout restart daemonset kube-proxy如果没有外部负载均衡器的帮助,这个操作在集群故障时可能是最直接的救命措施。
4.5 CoreDNS 一直 CrashLoopBackOff
现象:集群初始化后,CoreDNS 副本一直重启,Pod 日志显示No nameservers defined或者Unable to connect to DNS server。
排查思路:
围绕 CoreDNS 的问题,大多是 Pod 网段与宿主机网段冲突。Flannel 默认把 Pod 流量封装到宿主机的网段上,如果宿主机的 10.244.0.0/16 已经被其他设备占用,Pod 内 DNS 查询会全部超时。解决方式是在 init 时就选择与宿主机不冲突的网段,冲突之后只能重新初始化。
如果网段没有问题,查看 CoreDNS 的 ConfigMap 是否被破坏:
kubectl -n kube-system get configmap coredns -o yaml新版 CoreDNS 的 forward 指令需要配合插件开启,常见的错误是配置里写forward . /etc/resolv.conf而不是forward . .,导致上游 DNS 设置无效。
5. 集群初始化后必做的检查与体验调优
5.1 自动补全与别名
装了 Kubernetes 之后,第一件事就是配置 kubectl 补全,否则后面的排障和巡检会非常痛苦。
apt-get install -y bash-completion echo "source <(kubectl completion bash)" >> ~/.bashrc echo "alias k=kubectl" >> ~/.bashrc echo "complete -o default -F __start_kubectl k" >> ~/.bashrc source ~/.bashrc补全配置好之后,操作速度能提升很多。
5.2 验证集群功能正常
极速安装完成不等于集群可用,要做一次端到端的功能验证。先部署一个测试应用:
kubectl create deployment nginx-test --image=nginx:latest --replicas=3 kubectl expose deployment nginx-test --port=80 --type=NodePort kubectl get svc用 NodePort 端口访问工作节点的 IP,看是否能正常返回 nginx 页面。如果返回 503 或超时,优先排查 Service 与 Endpoint:
kubectl get endpoints nginx-test kubectl describe svc nginx-test如果 Endpoints 里没有 IP,说明 label selector 没匹配上 Pod;如果 Endpoints 正常但访问不通,大概率是 kube-proxy 的 iptables 规则异常。
接下来验证 DNS 功能。进入一个临时 Pod:
kubectl run test-dns --image=busybox --rm -it --restart=Never -- nslookup kubernetes.default.svc.cluster.local能正确解析出10.96.0.1一样的结果,说明 CoreDNS 工作正常。
5.3 资源限额与治理
当集群真正要承接业务时,不要裸奔运行。给命名空间设置资源配额:
kubectl create namespace app-prod kubectl create quota prod-quota --namespace=app-prod --hard=cpu=20,memory=20Gi,pods=50再给关键工作负载设置 LimitRange 与 HPA。这个步骤容易被忽略,但缺少配额管控的集群很容易在某个业务应用内存泄漏时拖垮整个节点。Kubernetes 本身是一个资源调度系统,不利用这些原生机制等于白装了。
5.4 版本升级思路
v1.35.2 发布之后,后面还会有 v1.36、v1.37。升级集群时,顺序必须严格遵守:先升级 kubeadm,再kubeadm upgrade plan,然后逐台升级 kubelet 和 kubectl。
关于升级的稳定习惯,我个人的建议是:先从 node 节点开始升级验证,再升级 master,不要反着来。同时升级之前务必做 etcd 的备份,直接用 kubeadm 自带的 etcd 备份机制:
kubectl -n kube-system exec etcd-k8s-master -- sh -c "ETCDCTL_API=3 etcdctl --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key snapshot save /var/lib/etcd-backup.db"6. 最后的调试心得:那些手册里查不到的细节
这篇文章写到这里,核心安装和排障流程已经完整了。最后分享几个我实际使用中反复吃到教训的细节。
第一,不要在地下阶段使用 Docker 作为 CRI。kubeadm 从 v1.24 之后就默认不维护 dockershim,虽然第三方插件可以兼容,但一旦碰到运行时层面的问题,排查路径会非常曲折。直接选 containerd,既符合 Kubernetes 生态标准,又不会在升级时被提前废弃的接口坑到。
第二,kubeadm 的--pod-network-cidr一定要最终确定,不要在集群初始化后又想着改网段。这个参数一旦定了,在网络插件层面也要跟着改,很多用户跑到一半发现更换网段导致节点不断重启,最后只能重新搭集群。搭集群的成本虽然不高,但业务迁移的成本很高。
第三,遇到 flannel 问题时,优先检查 Docker/containerd 的 CNI 目录残留。如果机器之前装过其他容器网络工具,/etc/cni/net.d/里会残留旧配置文件,Flannel 的安装不会主动清理这些文件,于是节点就一直无法正常初始化网络。删掉残留文件,重启 kubelet,问题即刻消失。
第四,尽量把 Kubernetes 安装部署的流程沉淀成脚本或 Ansible Playbook。这就是为什么有人说“极速安装”不是一次性技能,而是流程自动化。我个人的体验是,手工安装一次可能需要半小时,写成脚本之后,新环境五分钟就能复现一套完全一致的集群,这才是 K8s 部署的真正价值所在。
最后,安装 v1.35.2 只用 kubeadm 两条命令确实快,但不代表这是终点。在生产环境里真正值得投入时间的,往往是集群安装完毕之后的一系列稳定性工作:监控告警、日志收集、备份恢复、容量规划。先用这套手册把集群跑起来,再把运维体系一层层铺上去,这条路我会在后续的文章里接着拆。