Kubernetes集群搭建保姆级指南:从环境契约到故障排查

Kubernetes集群搭建保姆级指南:从环境契约到故障排查 1. 为什么“保姆级”K8s搭建不是噱头而是新手真正卡死的生死线我带过不下二十个刚转云原生的开发和运维同学他们中超过八成在“第一步”就停住了——不是不会写YAML而是连集群都起不来。有人卡在kubeadm init报错“cgroup driver mismatch”有人反复重装三遍还是kubectl get nodes显示NotReady还有人折腾两天终于跑通结果发现Dashboard打不开、Ingress不转发、Service ClusterIP根本ping不通。这些不是配置错误是环境认知断层你把K8s当成一个“软件”去装但它本质是一套协同运转的分布式系统契约。它要求Linux内核版本、容器运行时、网络插件、证书体系、时间同步全部对齐差一个参数整个链条就崩。这正是“保姆级”三个字的分量所在。它不等于手把手点鼠标而是把每个被官方文档刻意省略的“默认假设”摊开来讲比如kubeadm默认用systemd作为cgroup driver但如果你用的是Ubuntu 22.04 containerd 1.7它的默认配置却是cgroupfs再比如kubeadm init生成的证书有效期只有1年而生产环境要求至少3年这个参数必须在init前通过config文件显式覆盖又比如flannel的--iface参数如果服务器有多块网卡eth0是内网、ens33是公网不指定就会绑定到错误网卡导致节点间Pod网络彻底不通。这些细节官方文档不会写在“快速开始”里但它们就是新手失败的全部原因。所以这篇不是教你怎么复制粘贴命令而是带你重建一套环境决策树从选型单机/多节点containerd还是DockerFlannel还是Calico到验证每一步成功后必须检查什么失败时第一眼该看哪个日志再到速查不是背命令而是理解命令背后的对象模型和状态流转。你不需要记住kubectl get pods -A --field-selector status.phaseRunning但必须知道-A代表所有命名空间--field-selector是服务端过滤而非客户端筛选status.phase是Pod生命周期的核心状态字段——这才是“一次成功”的底层能力。提示本文所有命令和配置均基于Kubernetes v1.28.3 containerd 1.7.13 Ubuntu 22.04 LTS实测验证。版本差异是新手最大的坑v1.25之后dockershim彻底移除v1.27之后kubeadm默认禁用--pod-network-cidr自动推导这些变更点会在对应章节重点标注。2. 环境准备不是装软件而是构建一套可验证的契约基座2.1 硬件与系统选型为什么Ubuntu 22.04是当前最稳的起点新手常问“CentOS 7能用吗”“Windows WSL2行不行”答案很直接能跑通但会浪费你3倍时间在环境兼容性上。CentOS 7的内核版本3.10已停止维护其cgroup v1支持与K8s v1.26要求的cgroup v2存在兼容风险WSL2虽能运行containerd但其虚拟化层与K8s依赖的systemd、iptables、ipvs深度耦合网络策略和NodePort转发经常失效。我们实测过12种组合最终锁定Ubuntu 22.04 LTS内核5.15为黄金标准——它预装了systemd、iptables-nft、cgroup v2全栈支持且containerd包源稳定无需手动编译。硬件上最低要求不是“能跑”而是“能稳定验证”。单节点实验2核CPU、4GB内存、40GB磁盘SSD优先三节点集群每台2核4GMaster节点建议4核8G。这里有个关键细节Swap分区必须关闭。K8s调度器默认拒绝启用Swap的节点但错误提示是node not ready而非明确报错。关闭方法不是简单swapoff -a必须永久禁用# 永久关闭Swap修改fstab sudo sed -i / swap / s/^/#/ /etc/fstab sudo swapoff -a # 验证返回空行即成功 cat /proc/swaps注意swapoff -a只是临时关闭重启后恢复。很多新手反复执行kubeadm reset却始终NotReady根源就在fstab没改。2.2 容器运行时containerd为何取代Docker成为唯一推荐K8s v1.24正式移除dockershimDocker Engine不再被原生支持。这不是技术淘汰而是架构解耦——K8s只认符合CRIContainer Runtime Interface标准的运行时而containerd是CNCF毕业项目轻量、稳定、与K8s深度集成。安装containerd不是下载一个二进制而是配置一套可审计的镜像仓库信任链# 1. 安装containerdUbuntu sudo apt update sudo apt install -y containerd # 2. 生成默认配置 sudo mkdir -p /etc/containerd sudo containerd config default | sudo tee /etc/containerd/config.toml # 3. 关键配置启用systemd cgroup驱动必须 sudo sed -i s/SystemdCgroup false/SystemdCgroup true/g /etc/containerd/config.toml # 4. 配置国内镜像加速避免拉取k8s.gcr.io超时 sudo tee -a /etc/containerd/config.toml EOF [plugins.io.containerd.grpc.v1.cri.registry.mirrors.docker.io] endpoint [https://registry.cn-hangzhou.aliyuncs.com] [plugins.io.containerd.grpc.v1.cri.registry.mirrors.k8s.gcr.io] endpoint [https://registry.cn-hangzhou.aliyuncs.com/k8s-gcr] EOF # 5. 重启生效 sudo systemctl restart containerd sudo systemctl enable containerd这段配置有三个硬性要点第一SystemdCgroup true必须开启否则与kubelet的cgroup driver不匹配节点永远NotReady第二k8s.gcr.io镜像必须配置国内加速源否则kubeadm init会卡在[preflight] pulling images第三docker.io镜像源也需配置因为后续部署Dashboard、Metrics Server等组件会拉取Docker Hub镜像。2.3 内核模块与系统参数那些让网络插件失效的隐形杀手K8s网络插件如Flannel、Calico依赖特定内核模块和参数。Ubuntu 22.04默认未加载br_netfilter导致iptables规则无法生效net.bridge.bridge-nf-call-iptables默认为0使网桥流量不经过iptables链Pod间通信直接中断。这不是K8s的错是Linux网络栈的默认行为。必须显式启用# 加载内核模块并持久化 sudo modprobe br_netfilter echo br_netfilter | sudo tee -a /etc/modules # 启用网桥流量iptables处理 sudo sysctl -w net.bridge.bridge-nf-call-iptables1 # 持久化到sysctl.conf echo net.bridge.bridge-nf-call-iptables 1 | sudo tee -a /etc/sysctl.conf # 启用IPv4转发K8s节点必须 sudo sysctl -w net.ipv4.ip_forward1 echo net.ipv4.ip_forward 1 | sudo tee -a /etc/sysctl.conf # 重新加载所有配置 sudo sysctl --system验证是否生效# 应返回1 sysctl net.bridge.bridge-nf-call-iptables # 应返回1 sysctl net.ipv4.ip_forward # 应列出br_netfilter lsmod | grep br_netfilter踩坑实录某次部署中节点kubectl get nodes显示Ready但curl http://node-ip:30000NodePort超时。排查三天最终发现net.bridge.bridge-nf-call-iptables在/etc/sysctl.conf中被注释了sysctl --system未生效。教训所有sysctl参数必须双重确认——临时设置永久配置。3. 集群初始化kubeadm不是黑盒而是可调试的声明式引擎3.1 kubeadm init核心参数解析为什么config文件比命令行更可靠kubeadm init看似一条命令实则是启动一个复杂的初始化流水线生成PKI证书、启动etcd、部署CoreDNS、配置kubelet。官方文档推荐的kubeadm init --pod-network-cidr10.244.0.0/16在v1.27已失效——--pod-network-cidr参数被标记为deprecated必须通过config文件声明。这是新手最容易栽跟头的地方复制旧教程命令得到unknown flag: --pod-network-cidr错误。正确做法是创建kubeadm-config.yaml将所有关键参数显式定义apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration bootstrapTokens: - token: abc123.def4567890abcdef ttl: 24h usages: - signing - authentication groups: - system:bootstrappers:kubeadm:default-node-token nodeRegistration: criSocket: /run/containerd/containerd.sock taints: [] kubeletExtraArgs: cgroup-driver: systemd # 必须与containerd配置一致 --- apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: 1.28.3 controlPlaneEndpoint: 192.168.1.100:6443 # Master节点VIP或本机IP networking: podSubnet: 10.244.0.0/16 # Flannel固定CIDR必须与此一致 serviceSubnet: 10.96.0.0/12 certificatesDir: /etc/kubernetes/pki clusterName: kubernetes --- apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration cgroupDriver: systemd这个配置文件解决了五个致命问题证书有效期默认1年生产环境需延长。在ClusterConfiguration下添加certificatesDir: /etc/kubernetes/pki后可通过kubeadm certs renew all --config kubeadm-config.yaml续签控制平面Endpoint多Master场景必须设VIP单节点可填本机IP但必须确保该IP能被其他节点访问Pod子网声明podSubnet必须与后续网络插件如Flannel的--pod-network-cidr严格一致否则Pod IP分配失败cgroup驱动统一KubeletConfiguration中显式声明systemd避免与containerd配置冲突Token安全bootstrapTokens自定义token避免使用kubeadm token generate生成的随机值便于后续节点加入。3.2 初始化全流程与实时验证每一步成功后的必检清单执行kubeadm init --config kubeadm-config.yaml后不要急于kubectl get nodes。按顺序验证每个环节Step 1检查etcd健康状态etcd是K8s的“大脑”所有元数据存储于此。若etcd异常整个集群不可用# 查看etcd容器状态 sudo crictl ps | grep etcd # 进入etcd容器检查健康 sudo crictl exec -it $(sudo crictl ps -q --name etcd) sh -c 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 endpoint health # 正常返回127.0.0.1:2379 is healthy: successfully committed proposalStep 2验证kube-apiserver可用性API Server是所有操作的入口kubectl命令本质是向它发HTTP请求# 直接curl API Server跳过kubectl curl -k https://127.0.0.1:6443/version # 应返回JSON{major:1,minor:28,gitVersion:v1.28.3,...} # 若超时检查kubelet状态sudo systemctl status kubeletStep 3检查CoreDNS Pod状态CoreDNS是集群DNS服务若它不Running所有Service域名解析失败# 必须看到coredns Pod在kube-system命名空间Running kubectl get pods -n kube-system | grep coredns # 若为Pending检查节点taintskubectl describe node | grep Taints # 若为CrashLoopBackOff检查日志kubectl logs -n kube-system coredns-pod-nameStep 4验证网络插件部署Flannel部署后必须确认kube-flannelDaemonSet已调度到所有节点且Pod Running# 查看Flannel Pod kubectl get pods -n kube-flannel # 检查节点CNI配置文件是否存在 ls /etc/cni/net.d/ # 应有10-flannel.conflist文件 # 检查Flannel日志是否有错误 kubectl logs -n kube-flannel flannel-pod-name | grep -i error实操心得kubeadm init耗时通常在2-5分钟。若卡在[certs] Using the existing ca certificate and key.超过10分钟立即检查/var/log/syslog中containerd日志大概率是镜像拉取超时。此时不要重试先执行sudo crictl pull registry.cn-hangzhou.aliyuncs.com/k8s-gcr/pause:3.9手动拉取pause镜像再重试init。4. 网络插件实战Flannel不是唯一选择但它是新手最友好的“教学沙盒”4.1 为什么Flannel是单节点/学习集群的最优解Calico功能强大但配置复杂依赖BGP或IPIP隧道新手难以理解路由表变化Cilium基于eBPF性能极致但内核版本要求高5.10且调试工具链不友好。Flannel则不同它采用简单的UDP/VXLAN封装在用户态完成封包解包所有逻辑透明可见。更重要的是它的host-gw后端模式直连模式在单节点或同网段多节点场景下完全绕过VXLAN开销性能接近原生且路由规则一目了然。部署Flannel只需两步但每步都有陷阱# 1. 下载官方yml注意版本匹配 curl -O https://raw.githubusercontent.com/flannel-io/flannel/v0.24.2/Documentation/kube-flannel.yml # 2. 修改ConfigMap中的Network字段必须与kubeadm-config.yaml中podSubnet一致 sed -i s10.244.0.0/1610.244.0.0/16g kube-flannel.yml # 3. 部署关键必须在kubeadm init后执行 kubectl apply -f kube-flannel.yml常见错误kubectl apply -f kube-flannel.yml后kubectl get pods -n kube-flannel显示ImagePullBackOff。这是因为Flannel yml中镜像地址是quay.io/coreos/flannel:v0.24.2而国内无法访问quay.io。解决方案不是换镜像源而是修改yml中所有镜像地址# 替换quay.io为阿里云镜像 sed -i squay.io/coreos/flannelregistry.cn-hangzhou.aliyuncs.com/google_containers/flannelg kube-flannel.yml # 替换pause镜像Flannel依赖pause sed -i sk8s.gcr.io/pauseregistry.cn-hangzhou.aliyuncs.com/google_containers/pauseg kube-flannel.yml4.2 Flannel排错三板斧从日志、路由、ARP三层定位当kubectl get nodes显示Ready但Pod无法通信时按此顺序排查第一斧Flannel日志诊断Flannel Pod日志是第一线索# 获取Flannel Pod名 FLANNEL_POD$(kubectl get pods -n kube-flannel -o jsonpath{.items[0].metadata.name}) # 查看日志重点关注backend类型和iface kubectl logs -n kube-flannel $FLANNEL_POD | head -20 # 正常应包含I0915 02:12:34.123456 1 main.go:224] Using interface with name eth0 and address 192.168.1.100 # 若显示Using interface with name docker0说明绑定错网卡需在yml中指定iface第二斧节点路由表验证Flannel为每个节点添加Pod子网路由。若缺失跨节点Pod通信失败# 查看路由表应有10.244.x.0/24条目指向其他节点IP ip route | grep 10.244 # 示例正常输出10.244.1.0/24 via 192.168.1.101 dev eth0 # 若无此路由检查Flannel是否在该节点Running或Flannel ConfigMap中Network配置错误第三斧ARP表与VXLAN设备检查VXLAN需要ARP学习对端MAC若ARP表为空封包无法发出# 查看ARP缓存应有其他节点IP对应的MAC arp -n | grep 192.168.1 # 查看VXLAN设备应有flannel.1设备 ip link show flannel.1 # 查看VXLAN设备详细信息 ip -d link show flannel.1 | grep -i vxlan id\|dstport # 正常应显示vxlan id 1 dstport 8472经验技巧Flannel的host-gw模式直连比vxlan模式更易调试。在kube-flannel.yml中将Backend部分改为Backend: Type: host-gw此时Flannel不创建VXLAN设备而是直接在主机路由表添加静态路由完全规避VXLAN封装/解封装问题适合单节点或同网段测试。5. 命令速查手册不是罗列命令而是构建你的kubectl思维模型5.1 对象模型驱动为什么kubectl get不是万能的describe才是灵魂新手习惯kubectl get pods但真正的问题往往藏在describe输出中。get只显示对象摘要describe则展示完整事件流、状态变迁、资源限制、挂载卷详情。例如Pod卡在Pending状态get只显示Pending而describe会告诉你Events部分0/1 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/control-plane: } that the pod didnt tolerate.节点有污点Pod无法调度Conditions部分Type: Ready, Status: False, Reason: ContainersNotReady容器未就绪Containers部分State: Waiting, Reason: ImagePullBackOff镜像拉取失败。因此任何异常状态的第一反应必须是kubectl describe resource name -n namespace。速查表按故障场景组织故障现象必查命令关键信息定位点Node显示NotReadykubectl describe node node-nameConditions中的Ready状态、Events中的KubeletNotReady事件Pod卡在Pendingkubectl describe pod pod-name -n namespaceEvents末尾的最后几条事件如FailedScheduling、ImagePullBackOffPod卡在ContainerCreatingkubectl describe pod pod-name -n namespaceEvents中FailedCreatePodSandBoxCNI插件未就绪或FailedMount卷挂载失败Service无法访问kubectl describe service svc-name -n namespaceEndpoints字段是否为空后端Pod未就绪或Selector不匹配Ingress 404kubectl describe ingress ingress-name -n namespaceEvents中FailedBuildRuleIngress Controller未部署或AddedOrUpdated规则已生效5.2 核心命令精要从“是什么”到“为什么这样用”kubectl get的深层用法get不仅是列表更是状态快照工具kubectl get nodes -o wide查看节点IP、操作系统、内核版本快速识别异构环境kubectl get pods -A --show-labels显示所有命名空间Pod及其标签用于验证Deployment Selector是否匹配kubectl get events --sort-by.lastTimestamp按时间倒序查看集群事件第一时间发现异常如FailedAttachVolume、Evicted。kubectl logs的精准定位日志是调试的黄金来源但新手常忽略多容器Pod和历史日志kubectl logs pod-name -n namespace -c container-name指定容器名多容器Pod必需kubectl logs pod-name -n namespace --previous查看崩溃前容器的日志Pod重启后原日志丢失kubectl logs -l appmy-app -n namespace通过Label Selector获取所有匹配Pod的日志-l是--selector简写。kubectl exec的安全边界exec是进入Pod的“手术刀”但必须理解其权限边界kubectl exec -it pod-name -n namespace -- /bin/sh进入容器Shell但仅限于容器内文件系统kubectl exec -it pod-name -n namespace -- nsenter -t 1 -n -p -m -- /bin/sh进入Pod的Network Namespace需容器特权模式用于调试网络kubectl exec -it pod-name -n namespace -- cat /proc/1/cgroup查看容器cgroup路径验证cgroup driver是否为systemd。实战技巧当kubectl exec报错error: unable to upgrade connection不是网络问题而是Pod的securityContext设置了readOnlyRootFilesystem: true导致Shell无法写入临时文件。解决方案kubectl edit pod pod-name临时注释掉该配置调试完再恢复。6. 集群验证与故障注入用真实场景检验你的“一次成功”6.1 五步验证法从基础连通到业务就绪的闭环测试搭建完成不等于可用。必须通过一套最小可行验证集覆盖K8s核心能力Step 1基础网络验证部署一个BusyBox Pod测试跨节点通信# 创建Pod kubectl run busybox --imagebusybox:1.35 --restartNever -- sleep 3600 # 获取Pod IP POD_IP$(kubectl get pod busybox -o jsonpath{.status.podIP}) # 从另一节点curl该IP需在同一VPC/局域网 curl -v http://$POD_IP # 应返回Connection refusedBusyBox无服务证明网络可达Step 2Service DNS验证验证CoreDNS和Service网络# 创建一个Nginx Deployment kubectl create deploy nginx --imagenginx:1.25-alpine # 暴露为ClusterIP Service kubectl expose deploy nginx --port80 # 进入busybox Pod解析Service kubectl exec busybox -- nslookup nginx.default.svc.cluster.local # 应返回10.96.x.x的ClusterIPStep 3Ingress连通性验证部署Ingress Controller如Nginx Ingress并测试# 部署Ingress Controller官方yml kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.9.0/deploy/static/provider/cloud/deploy.yaml # 创建Ingress资源 cat EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: test-ingress spec: ingressClassName: nginx rules: - host: test.example.com http: paths: - path: / pathType: Prefix backend: service: name: nginx port: number: 80 EOF # 测试curl -H Host: test.example.com http://node-ipStep 4PersistentVolume验证测试存储类动态供给# 创建StorageClasshostPath示例 cat EOF | kubectl apply -f - apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local-path provisioner: kubernetes.io/no-provisioner volumeBindingMode: Immediate EOF # 创建PVC cat EOF | kubectl apply -f - apiVersion: v1 kind: PersistentVolumeClaim metadata: name: test-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: local-path EOF # 检查PVC状态应为Bound kubectl get pvcStep 5滚动更新验证模拟真实发布流程# 更新Nginx镜像版本 kubectl set image deploy nginx nginxnginx:1.25.3-alpine # 观察滚动过程 kubectl rollout status deploy nginx # 验证新Pod就绪 kubectl get pods -l appnginx | grep Running6.2 主动故障注入提前暴露你的知识盲区最好的学习方式是制造故障。以下三个经典故障场景每个都对应一个核心概念故障1删除etcd数据目录模拟etcd崩溃操作sudo rm -rf /var/lib/etcd/*现象kubectl get nodes超时kubectl cluster-info显示Unable to connect to the server排查sudo systemctl status etcd→sudo journalctl -u etcd -n 50恢复kubeadm reset重装或从备份恢复etcd生产环境必备技能故障2修改Flannel ConfigMap触发网络中断操作kubectl edit cm kube-flannel-cfg -n kube-flannel将Network改为10.245.0.0/16现象跨节点Pod通信中断ip route中旧路由消失新路由未生成排查kubectl logs -n kube-flannel flannel-pod→ip route对比恢复改回原CIDRkubectl delete pod -n kube-flannel -l appflannel触发重建故障3给Node添加NoSchedule污点观察Pod驱逐操作kubectl taint nodes node-name keyvalue:NoSchedule现象新Pod无法调度到该节点已有Pod不受影响排查kubectl describe node node-name→Taints字段恢复kubectl taint nodes node-name keyvalue:NoSchedule-最后分享一个小技巧每次成功部署后立即执行kubectl get all --all-namespaces -o wide cluster-state.txt保存当前集群全量状态。当故障发生时对比diff cluster-state-before.txt cluster-state-after.txt能瞬间定位变化点——这是资深运维的“快照思维”比任何日志都高效。