深入剖析 Kubernetes三从 0 到 1 搭建 Kubernetes 集群实测系列专栏《深入剖析 Kubernetes》· 基于张磊极客时间专栏思想整理 · 适合 CSDN 发布本篇所有命令均在 4 台真实服务器上执行输出均为实测截图级文本。在上一篇里我们聊清了“容器到底是什么”。这一篇动手——用 kubeadm 从零搭出一个 1 Master 3 Worker 的真实 Kubernetes 集群。搭集群不是目的目的是让你在后续每一篇里都有“可以动手验证”的环境而不是只读文档。一、集群规划我们用了 4 台华为云 FlexusX 服务器同可用区、同 VPC内网互通节点公网 IP私网 IP角色规格ecs-eb8a-0001120.46.41.136192.168.0.62Master (control-plane)8vCPU / 16Gecs-eb8a-0002120.46.135.31192.168.0.74Worker-18vCPU / 16Gecs-eb8a-0003123.60.222.30192.168.0.149Worker-28vCPU / 16Gecs-eb8a-0004120.46.150.197192.168.0.78Worker-38vCPU / 16G系统统一为Ubuntu 24.04.4 LTS内核6.8.0-106-generic。┌──────────────────────────────────────────┐ │ Kubernetes v1.28.2 │ │ │ Worker-1 ─┤ Worker-2 ─┤ Worker-3 ─┤ Master │ 192.168.0.74│ 192.168.0.149│ 192.168.0.78│ 192.168.0.62│ (kubelet) │ (kubelet) │ (kubelet) │ (kube-apiserver│ │ │ │ /scheduler/ │ │ │ │ controller) │ └───────────────┴────────────┴───────────────┘ 容器网络 Pod CIDR: 10.244.0.0/16 (Flannel)设计要点Master 上跑控制面静态 Podkube-apiserver / kube-scheduler / kube-controller-manager和 etcdWorker 上跑业务 Pod。apiserver 通过内网 IP192.168.0.62:6443暴露给 Worker 加入。二、前置准备4 节点并行K8s 对节点有几条硬要求不达标初始化必失败禁用 swapkubelet 要求 swap 关闭否则资源核算失真。开启内核模块overlay、br_netfilter容器网络和网桥转发需要。sysctl 转发net.bridge.bridge-nf-call-iptables1等让桥接流量走 iptables。容器运行时K8s v1.28 起仅支持符合 CRI 的运行时我们用 containerd。kubeadm / kubelet / kubectl集群的“安装器/节点代理/客户端”。国内加速apt 源全部走阿里云镜像mirrors.aliyun.com镜像仓库走registry.aliyuncs.com/google_containers避免访问 k8s.gcr.io 超时。完整初始化脚本k8s_setup.sh已放在仓库deploy/目录。核心片段# 1) 关 swap 注释 fstabswapoff-ased-i/ swap / s/^\(.*\)$/#\1/g/etc/fstab# 2) 内核模块cat/etc/modules-load.d/k8s.confEOF overlay br_netfilter EOFmodprobe overlaymodprobe br_netfilter# 3) sysctlcat/etc/sysctl.d/k8s.confEOF net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 EOFsysctl--system# 4) 安装 containerdDocker CE 阿里云源# 5) 安装 kubeadm/kubelet/kubectlKubernetes 阿里云源关键坑实测containerd v2 的默认配置里pause 镜像字段名不是sandbox_image而是sandbox# /etc/containerd/config.toml (containerd v2) sandbox registry.k8s.io/pause:3.10.1 # ← 默认走 registry.k8s.io国内拉不动必须改成阿里云镜像并开启 SystemdCgroupSystemdCgroup true sandbox registry.aliyuncs.com/google_containers/pause:3.9改完systemctl restart containerd并预拉取 pause 镜像ctr-nk8s.io images pull registry.aliyuncs.com/google_containers/pause:3.94 节点全部就绪后校验Kubernetes v1.28.2 # kubelet --version v1.28.2 # kubeadm version -o short containerd containerd v2.2.6 SystemdCgroup true active # systemctl is-active containerd br_netfilter / overlay # lsmod 已加载三、初始化 Mastercontrol-plane在 0001 上执行kubeadm init。指定三个关键参数--apiserver-advertise-address192.168.0.62apiserver 监听内网 IPWorker 走内网加入又快又省。--pod-network-cidr10.244.0.0/16Pod 网段必须和后面 Flannel 一致。--image-repositoryregistry.aliyuncs.com/google_containers控制面镜像走阿里云。kubeadm init\--apiserver-advertise-address192.168.0.62\--pod-network-cidr10.244.0.0/16\--image-repositoryregistry.aliyuncs.com/google_containers\--kubernetes-versionv1.28.2实测输出节选[control-plane] Creating static Pod manifest for kube-apiserver [control-plane] Creating static Pod manifest for kube-scheduler [control-plane] Creating static Pod manifest for kube-controller-manager [wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods ... [apiclient] All control plane components are healthy after 4.00 seconds [addons] Applied essential addon: CoreDNS [addons] Applied essential addon: kube-proxy Your Kubernetes control-plane has initialized successfully! kubeadm join 192.168.0.62:6443 --token kcj4oe.3v6a76fgz9g0bp7g \ --discovery-token-ca-cert-hash sha256:265c7ff0ce77c12f8a66fc827baa6b58493275aaa60c6844f97140764e7040e4控制面组件apiserver / scheduler / controller-manager是以静态 Pod 形式由 kubelet 直接拉起的——这正是“控制器模式”的下沉连 K8s 自己的大脑都是 Pod。CoreDNS、kube-proxy 则是集群必需的基础插件。初始化完成后配置 kubectl 访问权限root 用户mkdir-p$HOME/.kubecp-f/etc/kubernetes/admin.conf$HOME/.kube/configexportKUBECONFIG/etc/kubernetes/admin.conf此时 Master 处于NotReady——因为还没有网络插件NAME STATUS ROLES AGE VERSION ecs-eb8a-0001 NotReady control-plane 23s v1.28.2四、安装 CNI 网络插件FlannelK8s 自己不实现网络只定义CNIContainer Network Interface标准由插件落地。我们选 Flannel最易上手基于 VXLAN overlay。第二个国内大坑官方 Flannel 镜像在ghcr.io而docker.io在部分国内环境被墙。我们的解法——把镜像前缀换成南京大学开源镜像ghcr.nju.edu.cn代理 ghcr.io# 下载官方 yamlcurl-fsSLhttps://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml\-o/root/kube-flannel.yml# 把 ghcr.io/flannel-io/ 统一换成国内可达的 ghcr.nju.edu.cn/flannel-io/sed-is#ghcr.io/flannel-io/#ghcr.nju.edu.cn/flannel-io/#g/root/kube-flannel.yml# 先在全部节点预拉取避免 Pod 因拉镜像卡在 Pending/Initctr-nk8s.io images pull ghcr.nju.edu.cn/flannel-io/flannel:v0.28.7 ctr-nk8s.io images pull ghcr.nju.edu.cn/flannel-io/flannel-cni-plugin:v1.9.1-flannel2# 应用kubectl apply-f/root/kube-flannel.yml经验CNI 镜像动辄几十 MB国内直连 ghcr/docker.io 经常超时。先ctr pull预热再到各节点能大幅缩短节点 Ready 时间。Flannel 与--pod-network-cidr10.244.0.0/16必须匹配Flannel 默认就是这段。五、加入 Worker 节点在 0002 / 0003 / 0004 上分别执行 Master 初始化结束时给出的 join 命令token 24 小时内有效kubeadmjoin192.168.0.62:6443--tokenkcj4oe.3v6a76fgz9g0bp7g\--discovery-token-ca-cert-hash sha256:265c7ff0ce77c12f8a66fc827baa6b58493275aaa60c6844f97140764e7040e4节点成功加入会看到This node has joined the cluster: * Certificate signing request was sent to apiserver and a response was received. * The Kubelet was informed of the new secure connection details. Run kubectl get nodes on the control-plane to see this node join the cluster.六、验证集群回到 Master等网络插件就绪后所有节点变Readykubectl get nodes-owide实测输出全部节点ReadyNAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME ecs-eb8a-0001 Ready control-plane 10m v1.28.2 192.168.0.62 none Ubuntu 24.04.4 LTS 6.8.0-106-generic containerd://2.2.6 ecs-eb8a-0002 Ready none 61s v1.28.2 192.168.0.74 none Ubuntu 24.04.4 LTS 6.8.0-106-generic containerd://2.2.6 ecs-eb8a-0003 Ready none 58s v1.28.2 192.168.0.149 none Ubuntu 24.04.4 LTS 6.8.0-106-generic containerd://2.2.6 ecs-eb8a-0004 Ready none 55s v1.28.2 192.168.0.78 none Ubuntu 24.04.4 LTS 6.8.0-106-generic containerd://2.2.6辅助插件也都在跑Flannel DaemonSet 4 个 Pod 全Running每节点一个CoreDNS 2 副本Runningkube-proxy 4 个 Pod 全Running。至此一个生产形态的最小集群已经成型。小技巧kubectl get nodes看到STATUS Ready才算真正可用NotReady通常是 CNI 没起来或镜像没拉到按本篇踩坑清单排查即可。七、牛刀小试部署第一个工作负载集群能跑业务才是真集群。这里我们用一个Deployment NodePort Service验证三件事跨节点调度、Pod 拿到独立 IP、Service 聚合 Endpoint。关于镜像的选择本系列所有服务器均在国内众多公共镜像docker.io、ghcr.io的部分仓库直连不稳定。为 100% 可复现示例用全节点已预置的registry.aliyuncs.com/google_containers/pause:3.9作占位工作负载它不监听端口仅用于验证调度与网络。真实业务请把它换成你的镜像并通过镜像加速如docker.1ms.run、ghcr.nju.edu.cn等拉取。完整 manifest 见deploy/nginx-demo.yaml。kubectl apply-fnginx-demo.yaml kubectl get pods-owide# 看 3 个 Pod 是否散到 3 个 Workerkubectl get svc nginx# 看 NodePort 与 ClusterIPkubectl get endpoints nginx# 看 Service 背后的 Pod IP 列表实测输出 PODS NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE nginx-6975c55f96-phrgg 1/1 Running 0 40s 10.244.3.3 ecs-eb8a-0004 none nginx-6975c55f96-qjlx6 1/1 Running 0 40s 10.244.2.3 ecs-eb8a-0003 none nginx-6975c55f96-xhv5z 1/1 Running 0 40s 10.244.1.3 ecs-eb8a-0002 none SVC NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx NodePort 10.103.169.146 none 80:30080/TCP 9m51s ENDPOINTS NAME ENDPOINTS AGE nginx 10.244.1.3:80,10.244.2.3:80,10.244.3.3:80 9m51s三点验证全部成立跨节点调度3 个 Pod 分别落在 0002 / 0003 / 0004 三个 Worker 上Deployment 的replicas:3 调度器默认打散。Pod 独立 IP每个 Pod 拿到集群内唯一的10.244.x.x地址这正是 Flannel 在10.244.0.0/16里分配的——印证了“每个 Pod 一个 IP”的容器网络模型。Service 聚合nginxService 的 Endpoints 自动收编了 3 个 Pod IP访问ClusterIP:80或任意节点NodePort:30080都会被负载均衡到后端 Pod。这就是 K8s 编排的最小闭环你只声明“要 3 个副本”调度器决定放哪网络插件给 IPService 做发现与负载均衡——你完全不用关心具体节点。清理示例kubectl delete -f nginx-demo.yaml。八、本篇踩坑清单建议收藏swap 必须关swapoff -a 注释/etc/fstab否则 kubelet 启动失败。containerd v2 的 pause 字段是sandbox不是sandbox_image且SystemdCgroup必须true。镜像源全部走国内K8s 控制面registry.aliyuncs.com/google_containersFlannel 用ghcr.nju.edu.cn代理docker.io在部分环境被墙。CNI 必须先装节点才 Ready装之前先在各节点预拉 CNI 镜像。Pod CIDR 必须与 CNI 配置一致本例 10.244.0.0/16。join token 24h 过期过期后用kubeadm token create --print-join-command重新生成。九、小结 / 核心要点kubeadm 是搭建集群的最短路径init在 Master 拉控制面镜像并写admin.confjoin让 Worker 带着 token 加入。CNI 是节点 Ready 的前置Flannel 必须先生效节点才会脱离NotReady。containerd v2 改了字段名sandbox而非sandbox_image且SystemdCgroup必须开——这是 Ubuntu 24.04 新 containerd 的最大坑。镜像源全走国内是国内部署的第一原则能省下 80% 的「镜像拉取失败」排错时间。最小闭环已验证声明 3 副本 → 调度器打散到 3 个 Worker → Flannel 给 Pod 独立 IP → Service 聚合 Endpoint 做负载均衡。下一篇为什么我们需要 Pod —— 最小的调度单元为什么是 Pod 而不是容器配套仓库deploy/目录含k8s_setup.sh、kubeadm初始化说明与示例 manifest已推送到 Gitee。