Kubernetes实操全记录:从kubeadm部署到Pod调度与故障排查
发布时间:2026/10/16 16:34:03 锦皓数字建站

1. 为什么叫“k8s - 2”从一个看似随意的命名说起起这个标题的时候我其实在整理自己的第二份 Kubernetes 实操笔记。上一次折腾 K8s 还是大半年以前当时跟着教程把集群搭起来跑了一个 demo之后就再没深入。这次重新上手我给自己定的目标很明确不再满足于“能用”而是要把整个环境的搭建逻辑、组件职责、网络方案、工作负载部署、故障排查从头到尾吃透。所以这个“- 2”不光是第二遍的意思更代表一种“从零开始但比上次更深”的实战状态。如果你也处在差不多的阶段——听说过 K8s 很火看过一些概念图但真正上手时被 apiserver、etcd、CNI、kubelet 这些名词砸得头晕那这篇笔记就是给你准备的。我会用一套完整的实操过程把 K8s 从初始化到跑通业务服务的全过程拆开揉碎每步都讲讲我为什么要这么选以及实际执行时踩过的坑。想要快速复现一套可用集群的同学可以直接照着一路敲下去。2. 环境规划与选型第二轮部署的正确姿势2.1 节点规划三个机器还是单机模拟K8s 的最小完整集群逻辑上至少需要一个控制平面节点control-plane和一个工作节点work node。生产环境里控制平面一般会做高可用但个人学习或者测试环境主流做法是用三台机器一台做控制面两台做工作节点。这样既能体验 kubeadm join 的真实流程也方便测试 Pod 跨节点调度。我没有额外的物理服务器所以选择用虚拟化方式在本地模拟三台虚拟机。这里有个关键点虚拟机的网络模式必须选“桥接”或者“仅主机”模式确保三台机器的 IP 能互通。我踩过一次坑最开始用的是 NAT 模式结果控制平面初始化没问题工作节点 join 时怎么都连不上 apiserver排查了半天发现是网络模式导致 IP 段不一致。节点规划建议如下角色主机名配置建议IP 段示例控制平面k8s-master2 核 4G磁盘 40G192.168.10.10工作节点 1k8s-node12 核 4G磁盘 40G192.168.10.11工作节点 2k8s-node22 核 4G磁盘 40G192.168.10.12内存方面建议每个节点至少 2G实际上 4G 更稳。如果机器总内存不够可以先只起两台虚拟机一台控制面一台工作节点K8s 完全支持单工作节点集群Pod 都会调度到这一台上面去学习阶段完全够用。2.2 版本选型别再去追最新的坑我这次选的是 Kubernetes 1.28 系列具体小版本选择 1.28.x 的某个稳定补丁版。为什么不定到最新版本K8s 的版本迭代非常快大概每三个月发一个大版本而很多周边生态组件比如 CNI 插件、Ingress Controller、监控套件对新版本的支持会有滞后。生产环境选型一般会采用“当前最新稳定版往回退一到两个大版本”的策略给生态组件留出适配时间。另外还要注意一个细节kubeadm 工具、kubelet、kubectl 的版本号必须保持在同一大版本内。我见过有人控制面装的是 1.29节点上 kubelet 还是 1.27导致节点一直 NotReadykubelet 日志里报版本不匹配的警告。版本差异在一个小版本以内通常没问题超过一个大版本就容易出幺蛾子。2.3 部署工具选型kubeadm 为什么是默认选择K8s 的部署方式大致分三类一键托管型云厂商的托管集群、半自动型kubeadm、完全手动型二进制部署。我的建议是学习阶段务必用 kubeadm。它在帮你完成组件部署的同时保留了足够的透明性——你可以通过kubeadm init看到控制平面组件是怎么以静态 Pod 方式生成的通过kubeadm join理解节点注册的凭证机制。完全手动的二进制部署我不推荐新手做因为涉及证书生成、apiserver 与 etcd 的 TLS 配置、systemd 服务单元编写等大量细节很容易在某个环节卡死而且排错成本高。托管型集群虽然最省心但屏蔽了太多底层实现不利于建立对 K8s 的整体认知。3. 核心概念拆解控制面、工作节点和容器运行时3.1 控制平面的五个组件都在干什么很多人对 K8s 的恐惧源于一上来就要面对一大堆以字母 k 开头的名词。我换个方式帮你理解把控制平面想象成一个公司的管理层。apiserver 是唯一的入口相当于前台接待处所有外部请求kubectl 命令、节点注册、组件间的通信都必须经过它。它负责身份认证、权限校验然后把合法请求写入 etcd。etcd 是集群的数据库保存了所有对象的状态好比公司的档案室记录着每个人在哪、每个项目进行到哪一步。scheduler调度器决定一个新 Pod 该放在哪台机器上相当于人事部门分派任务。controller manager控制器管理器负责让实际状态不断靠近期望状态比如 Deployment 期望 3 个副本现在只有 2 个它就会触发补副本的流程相当于监督执行的角色。还有一个 cloud-controller-manager在自建集群中通常不需要启用只有对接云厂商时才用到这里不展开。3.2 工作节点上的三个核心角色控制平面负责决策真正干活的是工作节点。每个工作节点上有三个关键组件kubelet、kube-proxy、容器运行时。kubelet 是节点的代理人负责接收 apiserver 下发的 Pod 清单然后调用容器运行时去创建或销毁容器并持续上报节点和 Pod 的状态。kube-proxy 主要负责把 Service 的虚拟 IPClusterIP的流量转发到具体的 Pod 上它实现的是负载均衡和转发规则。容器运行时的职责最好理解——真正把容器跑起来的东西比如 containerd。3.3 容器运行时为什么选 containerd 而不是 Docker这里有一个很常见的困惑K8s 不是依赖 Docker 吗现在的版本里还要不要装 Docker实际上K8s 从 1.24 版本开始就不再直接支持以 Docker 作为运行时而是通过 CRIContainer Runtime Interface容器运行时接口与 containerd 这类 runtime 通信。Docker 本身包含的 dockershim 组件已经被移除keepalived 式的“Docker 套娃”时代已经过去了。有人说那我还是想用 Docker 怎么办可以用 cri-dockerd 做适配层但日常学习和生产部署直接装 containerd 是更简洁、更符合官方推荐的做法。我这次就是用 containerd 作为运行时。安装 containerd 之后有个关键配置默认配置里SystemdCgroup通常是 false必须改成 true否则 kubelet 在检查节点状态时会报 cgroup 驱动不一致的错误。这个配置项在/etc/containerd/config.toml里修改改完重启 containerd 服务即可。判断是否生效可以用containerd config dump | grep SystemdCgroup检查。4. 实操部署从初始化到集群就绪4.1 基础环境配置三台机器必须做的统一动作以下操作要在所有节点上执行。我按顺序列一下顺便解释每条命令的意义。# 设置主机名每台机器改成自己的 sudo hostnamectl set-hostname k8s-master # 关闭交换分区否则 kubelet 会报错 sudo swapoff -a # 永久关闭需要注释掉 /etc/fstab 里的 swap 行 # 加载内核模块 cat EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter关闭 swap 这个操作很多第一次接触 K8s 的同学会忽略注释 fstab 这步结果重启之后 swap 又回来了。K8s 在 kubelet 启动时会检查 swap 状态开启 swap 会导致节点无法通过 kubelet 的健康检查。至于为什么要关 swap官方给出的理由是保证 CPU 和内存的资源配额可以被精确执行。如果 Pod 的内存使用被 swap 挡住了limit 实际不起作用OOM 的判断和回收都会出问题。接下来是网络配置cat EOF | sudo 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 sudo sysctl --system这里的net.bridge.bridge-nf-call-iptables是让 iptables 规则对桥接流量也生效K8s 的网络插件比如 Calico 的 felix依赖 iptables 来转发流量不开启的话跨节点 Pod 通信会有问题。装 kubeadm、kubelet、kubectl 的过程各家教程都大同小异重点说一下版本锁定。安装时指定版本号避免 apt 或 yum 自动升级。# 以 apt 系为例先添加 K8s 软件源 sudo apt-get update sudo apt-get install -y kubelet1.28.* kubeadm1.28.* kubectl1.28.*装上之后立刻执行sudo apt-mark hold kubelet kubeadm kubectl防止系统更新时被误升。我这次就因为这个吃了亏第一次部署时没执行 hold第二次实操发现 kubelet 被自动升级了只能全部降级重来非常浪费时间。4.2 初始化控制平面在控制平面节点上执行初始化命令。这里我不是直接裸跑kubeadm init而是先把配置导出来看一眼kubeadm config print init-defaults kubeadm-init.yaml打开文件之后主要改两个地方一个是localAPIEndpoint.advertiseAddress改成控制平面节点的 IP另一个是nodeRegistration.name改成控制面的主机名。还有一个很关键的配置criSocket默认写的是unix:///var/run/cri-dockerd.sock而我们用的是 containerd所以要改成unix:///var/run/containerd/containerd.sock否则初始化时会报找不到 socket。设置好之后再指定 Pod 网段kubeadm init --config kubeadm-init.yaml --pod-network-cidr10.244.0.0/1610.244.0.0/16是 Flannel 默认的网段。如果后面你打算用 Calico可以改成192.168.0.0/16。这个网段一旦定下来后面不好改所以要提前想好。我建议初始化之前就把网络插件定下来避免因为网段冲突导致后面 Flannel 和 Calico 之间切换麻烦。初始化成功后末尾会输出一段命令照着执行即可配置 kubectlmkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config还有一个很重要的输出是kubeadm join命令的完整形式它包含一个 token 和一段证书哈希工作节点加入集群时要用。记得把这条命令保存下来。如果丢了也没关系可以在控制面上重新生成kubeadm token create --print-join-command4.3 安装 CNI 网络插件初始化成功之后控制面节点会显示 NotReady这很正常。核心原因是集群里缺少网络插件Pod 与 Pod 之间没法通信。检查一下 coredns Pod大概率是 Pending 状态因为没有可用网络调度器无法为它分配有效 IP。我选择装 Calico因为它功能更强支持 NetworkPolicy。安装方式很简单kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/...用 kubectl apply 之前我建议先把 yaml 下载到本地看一眼至少确认里面引用的镜像版本能正常拉取。在国内环境经常出现拉不到镜像的情况这种情况可以先把镜像拉到本地给镜像打个 tag 改成对应 registry 的地址实体急躁的时候这招很管用。安装完成后等一会儿用kubectl get pods -n kube-system查看。重点看 calico-node 是否 Runningcoredns 是否从 Pending 变成 Running。如果一直是 Initializing 或者 CrashLoopBackOff看日志一般能定位问题常见的有以下几类我后面专门开一节讲。4.4 工作节点加入集群把之前保存的kubeadm join命令拿到工作节点上执行。注意必须在工作节点上安装好 containerd、kubelet、kubeadm并且完成基础环境配置之后才能 join。安装过程跟控制面节点一模一样只是不需要装 kubectl。kubeadm join 192.168.10.10:6443 --token token \ --discovery-token-ca-cert-hash sha256:hashjoin 本质上是节点向 apiserver 发起注册请求通过 token 做身份验证。执行成功之后回到控制面上运行kubectl get nodes如果看到两个工作节点都显示 Ready恭喜你集群搭建完成了。这个过程最快大概几分钟前提是镜像拉取顺利。如果卡住不动第一步永远是journalctl -u kubelet -f看 kubelet 日志不要盲目重启服务。4.5 集群完整验证不只是看节点状态节点 Ready 只是第一步我还会做一轮完整的验证避免后面用的时候才发现问题。先看所有系统组件的状态kubectl get pods -n kube-system -o wide然后跑一个测试 Pod 验证调度和网络kubectl run nginx-test --imagenginx:1.25 kubectl get pod nginx-test -o wide执行kubectl exec -it nginx-test -- curl 另一个Pod的IP能通说明 Pod 间网络正常。把容器删除后再验证一下 DNS 解析进入 Pod 内部去 ping 一下 coredns 的 Service 名称比如nslookup kubernetes.default.svc.cluster.local。5. 跑起第一个真实工作负载从 Pod 到对外访问5.1 创建 Deployment让控制器帮你管理 Pod手动用kubectl run创建的 Pod挂了不会被自动拉起这不是 K8s 的正常用法。真实场景都是用 Deployment 这类工作负载资源来管理 Pod。我写了一个最简单的编排文件apiVersion: apps/v1 kind: Deployment metadata: name: nginx-demo labels: app: nginx-demo spec: replicas: 3 selector: matchLabels: app: nginx-demo template: metadata: labels: app: nginx-demo spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80这里有三个关键概念需要理解replicas 声明期望副本数selector 决定控制器管理哪些 Podtemplate 是创建 Pod 的模板。三者配合就构成了控制器的闭环逻辑——你只需要声明期望状态控制器会不断调整实际状态去逼近它。为了验证控制器的作用我故意删掉一个 Podkubectl delete pod nginx-demo-xxxxx几秒钟后一个新的 Pod 会自动创建名字不同但前缀相同。这就是 Deployment 最基础也最核心的价值。5.2 用 Service 提供稳定的访问入口Pod 的 IP 是漂移的每次重建都会变。我们总不能每次去查 IP 再连服务。Service 提供了稳定的虚拟 IP 和负载均衡。最常用的是 ClusterIP 类型kubectl expose deployment nginx-demo --port80 --target-port80--port是 Service 对外暴露的端口--target-port是 Pod 上的容器端口。Service 选定 Pod 的方式就是上面 Deployment 里的 label所以 label 的命名规范从一开始就要想好后面 Service 和 Ingress 都要靠它关联。验证 Service 和 Pod 的映射关系kubectl get endpoints能看到 endpoints 里列出的三个 Pod IP这才算真正确认了 Service 到 Pod 的链路。5.3 用 Ingress 做 7 层路由按域名访问服务ClusterIP 只能在集群内部访问。想从集群外面访问服务有三种典型方式NodePort、LoadBalancer、Ingress。自建集群没有云负载均衡器LoadBalancer 用不了直接 NodePort 虽然简单但端口管理混乱不支持按域名转发。所以 Igress 是更合适的方案。安装一个 Ingress Controller我用的是社区主流的 Nginx Ingress Controller。它本质上是一堆带有hostNetwork: true或 NodePort 的 Pod负责监听 Ingress 资源并生成 Nginx 配置再配合转发规则把流量带到对应 Service。然后定义 IngressapiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx-demo-ingress spec: rules: - host: demo.local http: paths: - path: / pathType: Prefix backend: service: name: nginx-demo port: number: 80执行之后在客户端机器的 hosts 文件里把demo.local解析到任意工作节点的 IP访问http://demo.local就能看到 Nginx 默认页面。这里一个容易踩的坑不同 Ingress Controller 对pathType的行为有细微差异Prefix和Exact的匹配规则要提前测试。另外如果多个 Ingress 规则配置了相同的域名但路径不同,小心别把路由规则写重叠了Nginx 会按规则顺序匹配容易跑到错误的 service 上。5.4 让数据活起来PV 和 PVC 的配合Pod 是临时性的容器内的数据随 Pod 删除而消失。想让数据持久化需要引入 PVPersistentVolume和 PVCPersistentVolumeClaim。PV 相当于一块已经准备好的存储资源PVC 则是使用方提出的存储申请两者匹配成功后Pod 才能挂载使用。我用本地目录模拟一个 PVapiVersion: v1 kind: PersistentVolume metadata: name: local-pv spec: capacity: storage: 5Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain local: path: /data/k8s-local注意local类型的 PV 需要配合节点亲和性使用否则调度器不知道这个目录在哪台节点上。这里不展开 HostPath 或者 local 的完整用法但原理要清楚PV 是静态资源PVC 是动态申请StorageClass 是动态供给的桥梁。学习阶段可以先手动创建 PV 体验一下真正做持久化存储时一般会部署一个 StorageClass 提供方比如 NFS 或者云硬盘驱动来动态创建 PV。6. 高频问题与排查思路实录6.1 kubeadm init 失败kubelet 一直报错这是我身边同学遇到最多的一个问题。初始化失败后第一反看 kubelet 状态systemctl status kubelet journalctl -u kubelet -f --no-pager日志里常见报错之一是failed to run Kubelet: failed to create kubelet: misconfiguration: kubelet cgroup driver: systemd is different from docker cgroup driver: cgroupfs这个就是我之前提到过的 cgroup 驱动不一致问题。解决方法就是把 containerd 的SystemdCgroup改成 true。改完之后先别急着重新 init最好清掉之前的残留状态kubeadm reset -f rm -rf /etc/kubernetes /var/lib/etcd /var/lib/kubelet然后再重新初始化。这个干净的环境非常重要很多奇怪问题都是因为上一次初始化残留导致的。6.2 Pod 一直 Pending调度器说资源不足kubectl describe pod name是排查 Pending 的第一命令。如果事件里出现0/3 nodes are available一般分两种情况一种是节点资源不足CPU、内存不够满足 request另一种是节点有污点taintPod 没有对应的容忍度。资源问题的话可以把 Deployment 里的replicas先调小或者检查节点上是不是堆积了一堆不用的 Pod用kubectl top nodes查看资源使用率。污点问题则要先看节点上的污点kubectl describe node k8s-node1 | grep Taints控制面节点默认带node-role.kubernetes.io/control-plane:NoSchedule污点普通 Pod 不会调度上去。如果你只有一个单节点集群想在控制面节点上跑业务 Pod需要给 Pod 加对应的 toleration或者把污点去掉kubectl taint nodes --all node-role.kubernetes.io/control-plane-注意这个操作在生产环境要谨慎去掉控制面污点等于允许业务负载和系统组件混部。6.3 跨节点 Pod 网络不通但同节点正常这个问题几乎都出在 CNI 插件配置上。先看几个关键状态kubectl get pods -n kube-system | grep calico kubectl logs -n kube-system calico-node-pod我在实际操作中遇到过一个很隐蔽的问题Calico 要求的net.ipv4.ip_forward没有在某个节点上开启导致该节点上的 Pod 能接收数据却无法转发出去。重新执行 sysctl 配置并重启网络服务就能解决。如果你之前换过 CNI 插件比如从 Flannel 切到 Calico还要注意清理残留的 Flannel 配置和 iptables 规则。这种情况下最省心的做法是kubeadm reset整个节点重新加入否则旧路由表会一直干扰新插件正常工作。6.4 证书过期集群跑久了的人都会遇到K8s 集群里的组件证书默认有效期是一年。很多集群跑着跑着突然 apiserver 不可用kubectl 报证书过期错误就是这个原因。检查证书有效期kubeadm certs check-expiration如果剩余时间不长就提前续期kubeadm certs renew all续期之后要重启 kubelet 和静态 Pod控制平面组件以静态 Pod 方式运行重启 kubelet 会自动拉起systemctl restart kubelet如果 kubeconfig 文件引用了旧证书也要一并更新把/etc/kubernetes/admin.conf重新复制到~/.kube/config。证书这块属于“你永远不会注意到直到它出问题”的冷知识建议大家建好集群以后就把检查证书过期加进监控告警里。6.5 镜像拉取失败一个容易被忽视的部署前置问题国内环境部署 K8s新手遇到最多的就是镜像拉不了卡在ContainerCreating。我用的是 containerd它的镜像配置跟 Docker 不太一样配置文件是/etc/containerd/config.toml里面没有 Docker daemon 那样的 registry mirror 概念但可以通过配置为 containerd 设置镜像加速。实际操作起来比较简单的方案是在有网络条件的机器上先拉好镜像导出成 tar 包再导入到目标节点。但这只适合解决单个镜像问题。如果是批量镜像更建议在 containerd 配置里配置镜像仓库registry的 mirror 或用中转镜像源然后重启 containerd。我用的方式是先把镜像 pull 下来打 tag 改成能推送到本地镜像仓库的地址再从仓库拉取虽然多了一步但胜在可控、可重复。7. 关于多集群、监控和后续扩展的几句补充集群搭好、业务跑通这只是第一步。后续的运维才是决定集群能不能长期稳定跑下去的关键。我建议下一步做三件事。第一部署一个监控告警体系。最经典的是 Prometheus Grafana 组合kube-prometheus-stack 这个 Helm chart 已经打包好了大部分组件装上就能看到节点资源、Pod 状态、容器指标。硬盘告警一定要配K8s 节点磁盘满会导致很多诡异行为比如 Pod 无法创建、etcd 写入失败。第二资源配额一定要从一开始就配上。用 ResourceQuota 限制每个命名空间的 CPU 和内存总量用 LimitRange 限制单个 Pod 的最小和最大资源申请。否则团队里有人创建了一个没有 limit 的 Pod内存占用无限上涨会拖垮整个节点。第三日志收集提前规划。K8s 里 Pod 是可以随时漂移的日志分散在各个节点上排查问题如果不集中收集日志会非常痛苦。轻量方案是 Loki重一点用 Elasticsearch。无论选哪种都建议在集群规模变大之前把链路设计好。扩展高可用的话控制平面可以拆成三节点etcd 也要做三副本这需要额外的机器和配置成本。但在做这步之前先想清楚你现阶段是否真的需要高可用很多团队在业务量还没上来时单控制面完全够用盲目追求高可用反而增加运维复杂度。把基础监控做好比多配几台机器更有价值。我个人在实际操作中的体会是K8s 的难点不在“搭起来”而在“出了问题怎么办”。如果你只是照着教程敲一遍命令那学到的东西很有限真正让你理解 K8s 架构的是它报错时你去翻阅日志、查资料、对照组件关系定位问题的那段时间。你试着把集群搞烂两台节点再一条命令一条命令把它救回来你会发现对控制面、kubelet、网络插件之间的协作关系突然就通了。最后再分享一个小技巧给常用命令设置好别名比如alias kkubectl然后熟练使用kubectl get events --sort-by.lastTimestamp来看集群事件流。很多时候你以为的问题根本不是问题只是排错了方向多看看事件能少走很多弯路。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。