
上个月帮一个做微服务改造的团队搭测试环境连着折腾了两天踩了不少坑也把好多细节彻底想通了。正好借这个机会把基于 kubeadm 搭建 Kubernetes 集群的完整过程写出来从环境准备到多节点 join到网络插件、证书轮换这些绕不开的坎全部走一遍。这篇面向的是已经知道 Pod、Service 大概是什么但还没亲手从零拉过一个集群的朋友。看完之后你至少能在自己的机器上复现出一个像模像样的多节点集群并且知道每一行命令到底在干什么。1. 搭集群前先想清楚这三件事1.1 为什么不用 Docker Swarm 或单机 Minikube很多新手学 Kubernetes第一步就在选型上卡住了。Minikube 确实能让你快速跑起来但它本质上是单机玩具很多网络插件、调度策略、跨节点通信的坑你根本碰不到。Docker Swarm 就更简单了但它在生产环境中的占有率已经说明了一切——生态太薄扩展能力有限。如果你想认真学会 Kubernetes用 kubeadm 从裸机开始搭建一个多节点集群是性价比最高的路径。这一步逃不掉也值得走。从学习价值看kubeadm 的方式能让你亲手接触到 kubelet、kube-apiserver、etcd、containerd 这些核心组件的真实运行状态。出了问题你得自己去看日志、查状态这个过程建立的排障能力比任何一键安装脚本都给得多。1.2 核心组件分别负责什么很多人上来就敲kubeadm init但对控制面板里到底跑着什么一无所知。这里用大白话捋一遍kube-apiserver所有组件和客户端操作的唯一入口相当于整个集群的“前台接待”你执行的kubectl命令最终都会请求到它这里。etcd集群的“数据库”所有配置、状态、元数据都存在这里。它挂了整个集群就全瞎了。kube-scheduler负责决定一个新 Pod 放到哪台节点上跑相当于“分派任务的小管家”。kube-controller-manager维护集群的期望状态比如某个 Deployment 要 3 个副本它盯着少了就补。kubelet每个节点上都有的“执行者”负责把调度器分过来的 Pod 真正拉起来。kube-proxy负责实现 Service 的负载均衡和转发规则。理解这些角色后你再看控制平面那堆 Pod就不慌了。1.3 集群拓扑规划这次我用的三节点方案这次实操我准备了三台干净的机器配置如下| 角色 | 主机名 | 配置 | 系统 | | 控制平面节点 | master01 | 4C / 8G / 50G | Ubuntu 22.04 | | 工作节点 | node01 | 4C / 8G / 50G | Ubuntu 22.04 | | 工作节点 | node02 | 4C / 8G / 50G | Ubuntu 22.04 |三台机器的网络是二层互通的IP 段为192.168.31.0/24。生产环境里控制平面节点一般至少三台做 HA入门阶段单控制平面节点已经足够把核心流程走透了。提示机器配置别低于 2C / 4G否则kubeadm init时资源检查那关就过不去。磁盘建议至少 30G镜像和日志撑起来很快。2. 环境初始化这一步偷懒后面全是泪2.1 修改主机名与 hosts 解析主机名是集群管理的基本功。每台机器单独设置并写进/etc/hosts。# master01 上执行 hostnamectl set-hostname master01 # node01 上执行 hostnamectl set-hostname node01 # node02 上执行 hostnamectl set-hostname node02 # 三台机器上都执行把下面的解析追加到 /etc/hosts cat /etc/hosts EOF 192.168.31.10 master01 192.168.31.11 node01 192.168.31.12 node02 EOF这里有个容易忽略的点Kubernetes 节点间的通信非常依赖主机名解析如果/etc/hosts不一致kubelet 注册节点时可能出现奇怪的问题。虽然理论上可以用 IP 通信但主机名统一会让后面看日志、排查节点状态时省很多事。2.2 关闭 swap 与调整内核参数Kubernetes 要求节点关闭 swap原因在于它的资源模型里内存是严格分配的一旦启用 swapkubelet 对内存使用情况的判断就会失真影响调度和驱逐决策。swapoff -a sed -i / swap / s/^/#/ /etc/fstab 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 --systembr_netfilter和net.ipv4.ip_forward是网络插件正常工作的重要前提尤其后面用到 Flannel 或 Calico 时iptables 规则转发全靠这些内核参数撑着。如果你刷完参数后发现 Pod 间网络不通优先检查这里。2.3 安装 containerd选它不选 Docker 的理由从 Kubernetes 1.24 开始dockershim 正式从 kubelet 中移除容器运行时直接使用 containerd 是最主流的做法。它更轻量也避免了 Docker 和 containerd 之间的多余嵌套。# 安装依赖 apt-get update apt-get install -y containerd # 生成默认配置 mkdir -p /etc/containerd containerd config default | tee /etc/containerd/config.toml生成默认配置后必须改一处关键设置——如果你在国内环境镜像加速必须配置。另外 systemd cgroup 驱动也必须显式开启。修改/etc/containerd/config.toml[plugins.io.containerd.grpc.v1.cri] systemd_cgroup true [plugins.io.containerd.grpc.v1.cri.registry.mirrors] [plugins.io.containerd.grpc.v1.cri.registry.mirrors.docker.io] endpoint [https://docker.m.daocloud.io]改完后重启 containerdsystemctl restart containerd systemctl enable containerd注意systemd_cgroup true和 kubelet 的 cgroup 驱动必须保持一致否则 kubelet 启动后会一直报 cgroup 相关的错误节点直接 NotReady。这个坑我在第一次实操时踩了整整两小时。国内环境还有一点必须说明Kubernetes 官方组件镜像大多托管在 gcr.io国内直接拉取基本不行。解决办法是用国内镜像源把镜像拉下来再打上原始 tag。下面这一段是绕不开的操作# 在控制平面节点执行先让 kubeadm 告诉我们它需要哪些镜像 kubeadm config images list # 写一个简单脚本把每个需要的镜像都用阿里云镜像源拉取并重新打 tag for image in $(kubeadm config images list); do name$(echo $image | awk -F/ {print $NF}) docker pull registry.aliyuncs.com/google_containers/$name docker tag registry.aliyuncs.com/google_containers/$name $image done这个环节直接用官方源肯定是不行的如果遇见一直拉取失败或者超时不用纠结就是网络源的问题。换好镜像源之后kubeadm init会顺畅很多。注意这个操作是在 containerd 环境你需要把docker命令换成crictl或ctr的对应逻辑或者临时装一个 docker 只做拉取打 tag 用也可以。这里我推荐的做法是控制平面节点临时安装 docker-ce用上面的脚本拉好镜像并打 tag然后kubeadm init时 containerd 直接从本地缓存里读取镜像。虽然不优雅但胜在踩坑少。实际上更标准的方式是用ctr或crictl但参数比 docker 繁琐新手容易出错。实操阶段怎么顺利怎么来后面再补课。2.4 安装 kubeadm、kubelet、kubectlKubernetes 的四个字母包裹。装这三个工具方式相对固定直接使用阿里云的 apt 源apt-get update apt-get install -y apt-transport-https ca-certificates curl curl -fsSL https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg echo deb [signed-by/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://mirrors.aliyun.com/kubernetes/apt kubernetes-xenial main | tee /etc/apt/sources.list.d/kubernetes.list apt-get update apt-get install -y kubelet kubeadm kubectl版本锁定这一条务必照做apt-mark hold kubelet kubeadm kubectl如果不锁版本哪天你用apt-get upgrade把 kubeadm 升到新版本控制平面和工作节点版本不一致kubeadm join或升级时会报错。版本管理在集群运维里是纪律性问题早锁早省心。3. 控制平面初始化与工作节点加入3.1 kubeadm init核心参数怎么定在 master01 上执行初始化。这是整个搭建过程最关键的一条命令。kubeadm init \ --apiserver-advertise-address192.168.31.10 \ --image-repositoryregistry.aliyuncs.com/google_containers \ --kubernetes-versionv1.28.2 \ --pod-network-cidr10.244.0.0/16 \ --service-cidr10.96.0.0/12参数说明如下--apiserver-advertise-addressapiserver 对外公告的地址也就是 master01 的 IP。多网卡机器必须显式指定否则可能选错网卡。--image-repository镜像仓库地址。国内用阿里云源很重要不然上面的镜像准备工作可以省了。--kubernetes-version指定版本号务必与 kubeadm 版本匹配。--pod-network-cidrPod 网段。这个网段必须和后面安装的网络插件对应上比如我选的 Calico 或 Flannel 默认都会用到 10.244.0.0/16。--service-cidrService 网段默认 10.96.0.0/12可以不改。注意--pod-network-cidr的规划很重要。这个网段必须和实际网络中已有的网段不冲突否则 Pod 访问外部服务或外部访问 Pod 时会遇到路由问题。你这台机器如果已经接了公司内网内网恰好用了 10.244 的网段那就得考虑换个内网不用的私有网段。初始化成功后会输出一大段信息里面包含kubeadm join命令。一定要保存好这一行否则后面加节点时找不到 token还得重新生成。Your Kubernetes control-plane has been initialized successfully! To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config You can now join any number of control-plane nodes by copying certificate authorities and service account keys on each node and then running the following as root: kubeadm join 192.168.31.10:6443 --token abcdef.0123456789abcdef \ --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx按提示把 kubeconfig 配置好就能用kubectl操作集群了。3.2 验证控制平面状态初始化完成后先别急着加节点验证一下控制平面组件是否健康kubectl get pods -n kube-system正常情况下你会看到 coredns、etcd、kube-apiserver 等 Pod 陆续进入 Running 状态。如果某个 Pod 一直 ContainerCreating八成是镜像拉取或网络问题用kubectl describe pod xxx -n kube-system看具体事件。此时如果用kubectl get nodes看节点状态master01 可能还是 NotReady。这个现象是正常的因为 Pod 网络插件还没部署节点网络没就绪等下一步装完网络插件就好了。3.3 安装网络插件我为什么选 CalicoKubernetes 本身不实现 Pod 间网络互通必须借助 CNI 插件。可选方案很多我这边在这类场景下一般直接选 Calico。Calico 的核心优势是基于 BGP 的路由方案性能好支持网络安全策略。相比 VxLAN 隧道方案Calico 的路由模式在跨节点通信时包转发延迟更低也更适合后续做 NetworkPolicy 的精细化管控。curl https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml -O # 如果默认配置里 Pod 网段不是 10.244.0.0/16需要修改 calico.yaml 中的 CALICO_IPV4POOL_CIDR 变量 sed -i s#10.244.0.0/16#10.244.0.0/16#g calico.yaml kubectl apply -f calico.yamlCalico 部署完成后再等一两分钟kubectl get pods -n calico-system kubectl get nodes这次你会看到 master01 的状态变成了 Ready。控制平面到这里就真正立住了。3.4 工作节点加入集群在 node01 和 node02 上执行kubeadm init输出的 join 命令。如果你当时没保存也可以在 master01 上重新生成 token在控制节点上执行kubeadm token create --print-join-command把输出的新命令复制到 node01 和 node02 上执行即可。join 完成后去 master01 上查看节点状态kubectl get nodes -o wide看到三个节点全部 Ready说明集群搭建成功了。node01 和 node02 加入时 kubelet 会自动注册如果一直是 NotReady大概率是 containerd 的 cgroup 配置没改对或者 6443 端口不通。4. 部署验证跑通第一个跨节点应用4.1 创建一个 Deployment 和 Service集群立住了得拿一个真实应用验证下跨节点调度和网络。我用 nginx 做例子kubectl create deployment nginx-demo --imagenginx:latest --replicas3 kubectl expose deployment nginx-demo --port80 --typeNodePort查看 Pod 分布kubectl get pods -o wide正常情况下三个副本会调度到不同的节点上这是 Kubernetes 调度器默认的 spread 策略。4.2 验证跨节点容器通信在 node01 上访问 node02 上的 Pod IP# 先查看 Pod IP kubectl get pods -o wide # 进入任意一个 Pod访问另一个节点的 Pod IP kubectl exec -it nginx-demo-xxx -- ping 其它节点的Pod IP如果你的集群网络配置正确ping 是通的。如果不通按优先级排查Calico 的 Pod 是否 Running、内核ip_forward是否为 1、iptables 规则是否被其他软件干扰。4.3 验证 NodePort 外部访问用 NodePort 方式暴露服务后访问方式为http://任意节点IP:NodePort端口kubectl get svc比如对外端口是 31500那么通过http://192.168.31.10:31500或http://192.168.31.11:31500都能访问到这套服务。到这里一个能跑能用的三节点集群就完整落地了。5. 常见问题与排查技巧实录5.1 kubelet 反复崩溃节点无法 Ready现象kubectl get nodes显示 NotReadySSH 到节点上看到 kubelet 服务一直在重启。排查先看日志journalctl -u kubelet -f常见原因和对应解法| 原因 | 解决方式 | | 容器运行时 cgroup 驱动不一致 | 确认 containerd 的systemd_cgroup true与 kubelet 一致 | | swap 未完全关闭 |swapoff -a并注释/etc/fstab中的 swap 行 | | 节点时间不同步 | 安装 chrony 或 ntpdate保证节点间时间误差在 1s 内 | | kubelet 无法连接 apiserver | 检查 6443 端口连通性和/etc/hosts解析 |5.2 拉镜像超时Pod 卡在 ContainerCreating这个在刚起步时最常遇到。能用环境变量或镜像仓库解决的就不要浪费时间在死磕上。我的习惯做法是统一给 containerd 配置优雅的加速器然后在有需要的节点保留已缓存镜像的 docker 服务只用来拉取和转移镜像不参与对接 kubelet。5.3kubeadm join报 token 过期token 默认有效期 24 小时。如果加入节点时提示过期重新在控制平面执行kubeadm token create --print-join-command5.4 证书过期问题kubeadm 部署的控制平面组件证书默认有效期一年。在测试环境跑久了之后有可能会突然发现 apiserver 不可用优先查证书kubeadm certs check-expiration快过期时执行kubeadm certs renew all kubeadm init phase update-cluster-info --kubeconfig /etc/kubernetes/admin.conf最后重启 kubelet 和相关组件这一块在生产环境建议配置证书的自动轮换监控。5.5 CoreDNS 一直 CrashLoopBackOff如果 CoreDNS 一直起不来可以先看日志。通常与网络插件冲突有关比如 Flannel 和 Calico 同时装了两套。kubectl logs -n kube-system -l k8s-appkube-dns解决办法删掉有问题的插件重装注意清理 CNI 配置目录/etc/cni/net.d下的残留文件然后重启 kubelet。5.6 节点 NotReady但控制平面 Pod 都正常首先确认节点本身的 kubelet 状态。最常见的隐蔽问题是我们前面提到的时间不同步证书校验全部失败。其次看节点上的 CNI 配置ls /etc/cni/net.d/如果目录里存在多个插件的配置kubelet 会按字母序加载第一个因此可能加载了错误的插件。6. 收尾前我还想再唠叨几句整套流程走完你收获的不只是集群能跑了而是对控制平面、工作节点、网络插件、运行时这几层关系有了真实触感。我第一次搭集群时在镜像和 cgroup 驱动上各卡了一晚上后来才知道这些都是入门标配。如果你照着这篇走还碰到问题建议按这顺序检查先看容器运行时状态再看 kubelet 日志然后看 apiserver 日志最后排查网络插件。多数问题都能在这条链路里定位。有一个细节想单独拎出来说生产环境千万不要用这篇文章里的单控制平面拓扑。控制和数据面没有隔离控制平面挂了整个集群就瘫痪了。真要上生产至少三台控制平面节点加负载均衡器etcd 独立部署或至少分离存储。入门实践的终点其实是生产架构设计的起点。最后留一句我的体会搭建集群不是背命令而是理解每个组件存在的理由。当你遇到问题时能不问“怎么办”而是先问“为什么”你的 Kubernetes 水平就已经上了一个台阶。