ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Kubernetes从入门到实战:从单机容器到多节点集群编排指南

Kubernetes从入门到实战:从单机容器到多节点集群编排指南 说实话我最早接触Kubernetes是被逼的。2019年团队容器化之后几十个服务散落在几台机器上环境不一致、IP乱跳、磁盘吹弹可破每次发版都像拆弹。后来咬牙上了Kubernetes前后折腾了两个多月踩了无数坑才算把一套多节点集群稳定跑起来。回头看最难的不是安装和命令而是思维方式的转变——从我操作容器变成我声明想要的最终状态。这篇指南不会去抄官方文档而是按我自己的实操路径来整理先聊为什么单机编排不够用了再扫一遍核心概念然后直接上手搭一套3节点集群最后用部署nginx这个经典场景走一遍完整流程。文章覆盖从单机容器到分布式集群的完整演进路线适合用过Docker、但还没系统上手Kubernetes的运维和开发同学。照着走一遍你对Pod、Deployment、Service这些东西就会有真实体感而不是停留在概念层面。1. 从单机到集群为什么Kubernetes成了必选项1.1 单机容器编排的舒适区与天花板单机模式下Docker和Docker Compose确实够爽。docker run一条命令就能起一个容器Compose用几个配置就能把一套应用栈拉起来。我自己刚用Docker那一年幸福感极高——环境一致了、交付变快了、再也不用跟在我机器上能跑这句话较劲。但爽归爽天花板很快就撞到了。首先是单点故障。一台机器挂了上面所有容器一起完蛋数据和服务瞬间不可用恢复全看运气和备份。其次是资源上限。单机的CPU、内存、磁盘总有瓶颈业务一涨再好的Compose配置也无能为力。最后是扩展性。想加机器Compose默认根本不支持跨主机编排两台机器上的容器互相访问都得手动处理IP和端口。我拿一个很生活化的类比来帮理解单机Docker就像一家只有一个厨师的小面馆生意好时大厨效率再高接客量也有上限而且大厨一病面馆直接停业。你开了分店就需要一个老板来协调各门店的人手、供应和客流。这个老板就是我们要讲的Kubernetes。1.2 多机部署撞上的三堵墙真正把应用拆到多台机器上之后你会立刻撞上三个以前压根不用想的问题。第一服务发现。容器被调度到哪台机器是随机的IP也是动态分配的。今天服务A在192.168.1.11明天重启就跑到192.168.1.15去了。如果服务B要通过固定IP访问服务A那代码根本没法写。解决思路无非两个要么自己维护一个主机列表要么引入注册中心。但服务几十个、上百个之后人工维护就是灾难。第二负载均衡。为了扛住流量同一个服务会部署多个副本。那么请求来了该发给哪个副本如何保证不同副本间的压力均匀这需要一台反向代理或者负载均衡器而且这个均衡器自身也得高可用。第三故障自愈。某台节点宕机了原本运行在上面的容器怎么办单机时代靠人肉重启分布式时代靠谁来自动发现故障、自动在其他健康节点上拉起新副本并且让上层无感知这三堵墙正是容器编排器存在的全部理由。1.3 核心解题思路声明式API与控制循环Kubernetes解决上述问题的核心哲学就两句话声明式API加上控制循环。声明式API的意思是说你不告诉系统先创建容器A、再配置网络B、再启动服务C这一串动作你只告诉它我需要3个nginx副本监听80端口暴露到集群外部。剩下的怎么做、做多做少、出故障怎么调整Kubernetes自己负责。控制循环则是这套系统的心脏。它的工作方式是不断观察集群的当前状态和你在YAML里声明的期望状态做对比如果发现不一致就调用各种组件把实际状态往期望状态拉。今天你手动删掉一个Pod控制循环发现副本数少了立刻重新创建一个你把镜像版本从1.25改成1.26它会触发一轮滚动更新。这种管目标、不管过程的模式好处非常明显系统是自愈的配置是代码化的环境是可复现的。你从操作机器的工匠变成了定义规则的架构师。我实际用下来最大的感触就是团队协作变了——大家不再为谁动过那台服务器扯皮一切变更都有YAML记录可审阅、可回滚。2. 核心概念速览这些抽象层到底在干什么2.1 Pod最小调度单元Kubernetes里最小的管理单位不是容器而是Pod。一个Pod里可以装一个或多个容器这些容器共享同一个网络命名空间和存储卷可以通过localhost互相访问也可以通过共享卷直接交换文件。为什么要多包一层Pod因为有些容器天生需要形影不离。最典型的是sidecar模式主业务容器只跑应用逻辑旁边的sidecar容器负责日志采集、流量代理或配置热更新。这两种容器要访问同一个网卡、读同一份文件只有放进同一个Pod才能做到。你可以把Pod想象成一台微型虚拟机里面跑的进程容器共享一切对外则像一个整体。实际使用中最常见的误区是把毫无关系的多个服务硬塞进同一个Pod。我见过有人把redis和mysql塞一个Pod里理由是省资源。这就是典型的反面教材——Pod内的容器会一起调度、一起重启你任何一次更新都会同时影响多个服务扩缩容粒度也变粗了。记住原则Pod就像一房同住的室友只有真正需要共享网络和存储、且生命周期完全一致的进程才适合塞进同一个Pod。2.2 Deployment管副本、管升级、管回滚Pod是生产车间里的工人但工人会累、会倒你不能一个一个手动管理。Deployment就是那个负责管理一组同类Pod的车间主任。Deployment通过ReplicaSet来保证存活副本数始终等于你设定的数量。你把replicas设为3它会确保任何时刻都有3个Pod在运行。当某个Pod挂了ReplicaSet会立刻新建一个顶上当你更新镜像版本Deployment会按你设定的策略做滚动更新——通常是先起一个新的、等下就绪后再杀一个旧的整个过程对用户无感知。更实用的是版本回滚能力。有一次我把一个旧版本Java应用的镜像推错了tag上线后发现接口异常一条kubectl rollout undo deployment/xxx直接回滚到上一个版本前后不到一分钟比传统的重新发布流程快了一个数量级。生产环境里这个能一键回去的能力有时比稳定向前更重要。2.3 Service稳定的访问入口Pod是有寿命的随时可能被替换所以Pod的IP绝对不能直接用来做服务访问。Service就是为这层不稳定因素打的补丁。Service会给它背后的Pod组分配一个稳定的虚拟IPClusterIP并利用标签选择器selector动态绑定当前所有符合条件的Pod。举个例子你给所有nginx Pod打了app: nginx标签Service只要selector里写app: nginx它就会自动把这些Pod的IP加进自己的转发列表。Pod换了一茬又一茬Service的虚拟IP和域名保持不变客户端无需感知任何变化。Service的三种类型要记牢ClusterIP只在集群内部可访问适合服务间调用NodePort把端口映射到每个节点的IP上适合测试和外部简单接入LoadBalancer则对接云平台的负载均衡器是生产环境对外暴露的主流方式。我自己的经验是内网微服务间用ClusterIP就够了千万别给每个内部服务都开NodePort——端口管理会让你崩溃。2.4 ConfigMap、Secret与Namespace配置不能写死在镜像里否则改一个数据库地址就要重新打包发版。ConfigMap负责存储非敏感的配置项比如环境变量、配置文件内容Secret负责存储敏感信息比如密码、证书、API Key它只以内存方式挂载不会写入磁盘。Namespace则是集群内部的空间隔离方案。每个团队、每个环境dev/staging/prod建议各占一个Namespace避免互相干扰。配合RBAC权限控制可以做到A团队只能看到和操作自己的Namespace这对多人共用一个集群的团队尤其重要。3. 手把手实操从零搭建3节点Kubernetes集群3.1 节点规划与方案选型先明确目标搭一个1个控制平面 2个工作节点的最小集群。这个规模足够你跑通所有核心实验也符合你未来加节点的真实路径。我建议的节点配置控制平面节点2核4GB起步工作节点2核4GB起步磁盘40GB以上。生产环境要求高很多但学习阶段别在硬件上较劲虚拟机也行。操作系统选Ubuntu 22.04 LTS或Debian 12这两个系统的内核和软件源都比较友好。有两个选型需要提前说明白。第一是容器运行时Kubernetes从1.24版本起移除了对Docker的直接支持现在的标配是containerd。别慌你照样用Docker来构建镜像只是运行时不再通过dockershim去对接Docker引擎而已。第二是集群网络插件我建议直接用Calico或者Flannel。Calico功能更全支持NetworkPolicy网络策略Flannel更简单适合快速跑通。学习阶段你选哪个都行关键是要在初始化控制平面之后尽快装上。3.2 环境初始化关swap、加载内核模块三台节点都要做同样的基础配置。先关闭swap这一步是硬性要求——kubelet的QoS和资源管理依赖cgroupswap会干扰调度判断。然后加载overlay和br_netfilter内核模块这是容器网络和iptables转发的基础。# 关闭swap同时修改fstab防止重启后自动挂载 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.ipv4.ip_forward 1 net.bridge.bridge-nf-call-ip6tables 1 EOF sysctl --system这里我不建议跳过net.ipv4.ip_forward。自测过一次漏配后Pod网络一直不通排查了整整一下午才发现是转发被关掉了。设置net.bridge.bridge-nf-call-iptables是为了让桥接流量也能被iptables规则过滤Calico和Flannel都依赖这个。3.3 安装containerd并修改核心配置三台节点都要装containerd。装完之后最关键的一步是修改配置把SystemdCgroup从false改为true。containerd默认使用的cgroup驱动是cgroupfs而kubelet 1.28以后默认用systemd驱动两者不一致会导致节点状态一直NotReady。# 安装依赖与containerd apt-get update apt-get install -y containerd # 生成默认配置 containerd config default | tee /etc/containerd/config.toml # 然后修改SystemdCgroup为true # 通常在 [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc] # 找到 SystemdCgroup false 改成 true改完配置记得重启containerdsystemctl restart containerd。另外如果在国内环境建议把config.toml里的sandbox_image改成合适镜像源否则k8s.gcr.io的pause镜像可能拉不下来初始化控制平面会卡住。这一步看着小实际踩坑率极高。3.4 安装kubeadm、kubelet、kubectl三个组件版本必须保持一致我写这篇时用1.28系列做演示。用系统的包管理器装上之后记得用apt-mark hold锁住版本防止意外升级把集群搞崩。apt-get update apt-get install -y kubeadm1.28.2-00 kubelet1.28.2-00 kubectl1.28.2-00 apt-mark hold kubeadm kubelet kubectl3.5 初始化控制平面与加入工作节点在控制平面节点上执行kubeadm init。两个参数很关键--apiserver-advertise-address填控制节点IP--pod-network-cidr填Pod网段。Pod网段取决于你选的网络插件Flannel固定用10.244.0.0/16Calico用192.168.0.0/16。我这次用Flannel所以按10.244来。kubeadm init \ --apiserver-advertise-address192.168.1.10 \ --pod-network-cidr10.244.0.0/16 \ --kubernetes-versionv1.28.2初始化成功后会输出一段kubeadm join命令务必存好这是工作节点加入集群的唯一凭证。接着按提示配置kubectlmkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config然后立刻安装网络插件。这步别拖控制平面初始化完成后CoreDNS会一直Pending直到网络插件就绪才恢复Running。kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml等个两三分钟kubectl get pods -n kube-system看到所有组件都是Running控制平面就稳了。最后在工作节点上执行之前保存的join命令到控制节点上跑kubectl get nodes三台节点都是Ready状态集群搭建完成。4. 实战演练一个nginx部署走完集群全流程4.1 编写Deployment清单文件我们先创建一个nginx工作负载。新建文件nginx-deployment.yaml内容如下apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80这个文件的核心逻辑replicas声明要3个副本selector.matchLabels告诉Deployment管哪些Pod必须匹配app: nginxtemplate定义了Pod模板里面的labels也必须带app: nginx否则Deployment找不到自己人。镜像用nginx:1.25是常见稳定版本。执行kubectl apply -f nginx-deployment.yaml然后用kubectl get pods -o wide观察你会看到3个Pod分布在不同的工作节点上——这正是调度器根据资源情况做的均匀分布。集群的价值在这一刻体现出来了你只写了要3个它自动给你安排到了两台机器上。4.2 用Service把nginx暴露出去Pod有了但我们现在还不能从集群外部访问它。需要创建Service。新建nginx-service.yamlapiVersion: v1 kind: Service metadata: name: nginx-service spec: type: NodePort selector: app: nginx ports: - port: 80 targetPort: 80 nodePort: 30080这里的关键配对是selector和ports。Service通过selector选中所有打了app: nginx标签的Podport 80是Service自己的虚拟端口targetPort 80是Pod上nginx监听的端口nodePort 30080则是每个节点上开出的外部访问端口。kubectl apply -f nginx-service.yaml后在任意一台节点上访问http://节点IP:30080看到nginx欢迎页说明整个集群链路已经通了。4.3 滚动更新、扩容与回滚实操接下来把Deployment的副本数扩到5个并升级镜像到1.26观察滚动更新过程# 扩容到5个副本 kubectl scale deployment/nginx-deployment --replicas5 # 滚动更新镜像 kubectl set image deployment/nginx-deployment nginxnginx:1.26 # 实时查看更新进度 kubectl rollout status deployment/nginx-deployment更新过程中kubectl get pods会看到新旧Pod并存——每新建一个就绪的Pod才会杀掉一个旧的保证服务不中断。升级完了如果发现异常回滚也是一条命令kubectl rollout undo deployment/nginx-deployment这个回滚操作我在生产环境用过不止一次比传统的备份-恢复流程高效太多。Deployment的更新历史默认保留10个版本足够应对绝大多数回滚场景。4.4 高可用验证节点宕机后Pod如何自愈用kubectl cordon模拟节点维护或者直接关掉一台工作节点然后观察kubectl get nodes——节点状态会变成NotReady几分钟后运行在上面Pod会被标记为Terminating并在其他健康节点上重新创建。这里有个细节值得强调Pod的重新调度需要kube-controller-manager发现副本数不足并且要等待etcd中的节点心跳超时默认约40秒所以不会瞬间完成。如果你想快速看到效果可以把关掉的节点直接shutdown或者用kubectl drain主动排空。无论如何最终副本数会被拉回3个这就是Kubernetes自愈能力的直观体现。5. 高频问题与排查实录这些坑我替你踩过了5.1 节点一直NotReady这是新集群最常见的故障。90%是网络插件没装好或者内核模块有问题。先看kubelet日志确认方向journalctl -u kubelet -f如果在日志里看到Container runtime network not ready、cni config uninitialized这类字样基本就是Flannel/Calico没就绪。重新apply一遍网络插件再用kubectl get pods -n kube-system确认flannel或calico的Pod是Running状态。还有一个小概率原因是hostname冲突两个节点用了相同的主机名kubelet注册时会互相覆盖。确保每台节点hostname都不一样。5.2 Pod一直PendingPending说明调度器找不到合适的节点。最常见的原因有几个节点资源不足、有污点taint不允许调度、或者声明了PVC但存储卷没绑定。用kubectl describe pod pod名称查看最下面的Events里面会写清楚原因。比如看到0/3 nodes are available: 1 Insufficient cpu, 2 node(s) didnt match node selector就说明CPU不够或者节点选择约束不满足。我碰到过一次Pod一直Pending查了半天发现是Deployment里写了nodeSelector指向一个不存在标签的节点把标签加上就好了。5.3 CrashLoopBackOff与ImagePullBackOffCrashLoopBackOff表示Pod启动后立即崩溃又被反复拉起。这时候一定要用kubectl logs pod名称看容器日志绝大多数是应用自身报错比如数据库连接不上、端口被占用、配置文件读不到。ImagePullBackOff则是镜像拉取失败可能是tag写错、镜像不存在或仓库未认证。用kubectl describe pod能看到具体的拉取报错。5.4 忘记join token怎么办集群搭建后token默认24小时过期。如果某台新节点要加入在控制平面节点上重新生成一条kubeadm token create --print-join-command同时如果工作节点的证书过期用kubeadm token create --print-join-command生成的命令里会带着新的discovery-token-ca-cert-hash不需要手动重新计算。5.5 Service服务间访问不通路由不对时优先级最高的检查项是selector。GrantedYAML里随便改动一个标签Service的后端就会变成空列表。用kubectl describe svc 服务名看Endpoints如果Endpoints为空基本全是标签不匹配。把Deployment里Pod的labels和Service的selector对齐马上就能通。另一个常见坑是Service端口写错port和targetPort的方向别搞反。5.6 生产环境一定要kubectl apply而不是kubectl create用kubectl create创建的资源如果想改YAML再执行一次create会报已存在。kubectl apply则实现了声明式管理——每次执行都会跟集群内的当前状态做diff自动补齐或更新字段。从第一天起养成apply的习惯编排类模板尤其重要。两者的区别就相当于手工写命令和按配置管理。6. 写在最后的一些个人体会这套流程我从头到尾完整搭过很多遍最后想给大家几个实操层面的建议。第一先让小集群跑起来别一上来就设计高可用。很多人问我控制平面怎么搞多副本、etcd怎么堆三节点但对学习来讲复杂度是最大的敌人。一个控制平面加两三个工作节点的集群已经足够体会Kubernetes的全部核心机制。生产环境的高可用和容灾等你理解了Pod生命周期、调度和存储这些基础之后再做加法会轻松得多。第二遇到故障别慌养成看日志的先入为主习惯。Pod状态异常kubectl describe pod永远比瞎猜快节点状态异常journalctl -u kubelet -f是最直接的证据来源。我见过太多人浪费几小时在直觉上结果最后都是日志里明明白白写了的简单原因。第三也是我最想强调的一点从单机切换到集群真正的挑战不是技术而是思维。你必须接受我不管具体怎么做我只描述要达到的状态这种理念。刚开始很别扭但一旦你习惯了用声明式YAML管理基础设施你会发现它不仅更省力而且更可靠——因为每一处变动都有迹可循、可回滚、可审阅。最后分享一个小技巧在集群里多折腾、多制造故障。手动删几个Pod看它怎么自愈断掉一个节点看它怎么转移把镜像改错看它怎么报错。这些主动破坏实验比任何文档都让人涨经验。折腾坏了删掉重建就行这正是Kubernetes最大的底气——基础设施本身也是代码坏了恢复起来比传统环境快太多。
返回列表