devops系列(六) Kubernetes 入门实战:容器多了怎么管

张开发
2026/4/17 17:59:33 15 分钟阅读

分享文章

devops系列(六) Kubernetes 入门实战:容器多了怎么管
Kubernetes 入门实战容器多了怎么管从在我电脑上能跑到半夜爬起来重启容器再到让 K8s 替我值班这篇文章聊聊我是怎么被 Kubernetes 拯救的。一个让人崩溃的夜晚去年夏天我负责的一个项目上线没多久就出事了。那天晚上十一点我刚洗完澡准备躺平钉钉电话响了。运维老哥的声音带着一丝疲惫“用户服务那个容器又挂了你赶紧上服务器重启一下。”我心里一万只羊驼奔腾而过。这已经不是第一次了。咱们当时的部署方式特别原始用 Docker 把 Spring Boot 应用打成镜像然后在几台服务器上手动docker run启动。容器跑起来了皆大欢喜容器挂了半夜爬起来docker restart。要是流量大了需要扩容手动再 run 几个然后在前面的 Nginx 里改配置加 upstream。说白了全靠人肉运维。那段时间我的手机闹钟是这样的凌晨 1 点检查订单服务容器状态凌晨 3 点检查库存服务容器状态早上 7 点检查所有服务日志开玩笑的但真的差不多。问题很明显容器越跑越多手动启停、扩容、故障恢复根本扛不住。我开始认真思考一个问题有没有一个大管家能帮我盯着这些容器挂了自动重启流量大了自动扩容升级的时候不要一刀切全部停掉答案就是Kubernetes江湖人称 K8s。K8s 是什么先别慌咱们用公寓楼来理解我第一次看 K8s 官方文档的时候整个人是懵的。Pod、Deployment、Service、Ingress、ConfigMap……概念一大堆感觉像在背天书。后来我想了个办法把 K8s 集群想象成一栋公寓楼。一旦用了这个类比很多概念突然就通了。Pod 房间Pod 是 K8s 里最小的调度单位一个 Pod 里可以跑一个或多个容器。你可以把 Pod 理解为公寓楼里的一个房间。房间里住着一个租户通常就是一个 Docker 容器比如你的 Java 应用。房间有独立的门牌号IP 地址有自己的水电配置CPU、内存资源。注意一个房间里也可以住多个租户多容器 Pod但咱们初学者先理解一个房间住一个人就够了。Deployment 楼栋管理员如果你只手动启动一个 Pod那跟直接用 Docker 没啥区别。K8s 的厉害之处在于它能帮你批量管理这些房间。Deployment 就是这栋楼的管理员。你告诉它“我这栋楼要有 3 个房间每个房间里都住这个 Java 应用。” 管理员就会去安排确保始终有 3 个房间在运行。如果某个房间的租户病倒了容器挂了管理员会立刻再开一个房间把新租户搬进去。这就是 K8s 的自愈能力。Service 前台现在你有 3 个房间每个房间都有自己的 IP。但问题是房间的 IP 可能会变Pod 重启后 IP 就变了。外面的人怎么找到这些房间呢Service 相当于公寓楼的前台。你不需要记每个房间的门牌号你只需要记住前台电话。前台会根据你找的是谁帮你转接到具体的房间。而且前台还会轮流转接实现负载均衡。Ingress 大门Service 解决了集群内部的访问问题但外部用户怎么进来呢Ingress 就是公寓楼的大门和门禁系统。它负责把外部流量按规则分发到不同的前台。比如访问/api/user的转给用户服务的前台访问/api/order的转给订单服务的前台还可以做 SSL 证书、域名绑定、限流等等ConfigMap 公告栏你的 Java 应用总有一些配置比如数据库连接地址、日志级别、端口号。如果把这些配置硬编码在镜像里每次改配置都要重新打镜像太麻烦了。ConfigMap 相当于公寓楼的公告栏。你把配置贴上去房间里的租户自己来看。这样配置和代码就解耦了改公告栏不用重新装修房间。方案分析为什么选 K8s说到这里你可能会问容器编排工具又不是只有 K8sDocker Swarm 不行吗直接用云厂商的 ECS/虚拟机不行吗咱们来简单对比一下方案优点缺点手动 Docker简单学习成本低无法自动恢复、无法自动扩缩容Docker Swarm比 K8s 轻量上手快生态弱很多高级功能没有云厂商托管服务不用自己搭集群贵 vendor lock-in被云厂商绑定Kubernetes功能最全生态最丰富已成事实标准学习曲线陡峭我的结论是如果你只是个人小项目手动 Docker 或者云厂商一键部署就够了但如果你想在团队里做生产级别的容器化K8s 几乎是必选项。它就像学 Java 里的 Spring——一开始觉得重但越用越香因为整个行业都在围绕它建设生态。实战部署一个 Spring Boot 应用到 K8s好了概念聊完了咱们动手。下面我会 Step by Step 带你部署一个 Java/Spring Boot 应用到 K8s 集群。前置准备假设你已经有一个可用的 K8s 集群本地可以用 Minikube云上有各种托管版安装好了kubectl命令行工具你的 Spring Boot 应用已经打成了 Docker 镜像推到了镜像仓库比如 Docker Hub 或私有 Harbor第一步写 Deployment YAML咱们先定义 Deployment告诉 K8s 要跑什么应用、跑几个副本。# user-service-deployment.yamlapiVersion:apps/v1kind:Deploymentmetadata:name:user-service# 这个 Deployment 的名字labels:app:user-servicespec:replicas:3# 关键我要跑 3 个副本selector:matchLabels:app:user-service# 关键通过标签找到我管理的 Podtemplate:metadata:labels:app:user-service# Pod 的标签必须跟 selector 对上spec:containers:-name:user-serviceimage:your-registry/user-service:1.0.0# 你的镜像地址ports:-containerPort:8080resources:requests:memory:512Micpu:500mlimits:memory:1Gicpu:1000menv:-name:SPRING_PROFILES_ACTIVEvalue:prod这段要干嘛就是告诉 K8s“给我启动 3 个 Pod每个 Pod 里跑user-service:1.0.0这个镜像暴露 8080 端口分配 512M 内存和 0.5 核 CPU。”关键点在哪replicas: 3副本数K8s 会保证始终有 3 个 Pod 在运行。selector和labels这是 K8s 的配对机制Deployment 通过标签找到自己要管理的 Pod这两处必须一致。resources建议一定要写。如果不写某个 Pod 可能会把节点资源吃光导致其他 Pod 饿死。执行部署kubectl apply-fuser-service-deployment.yaml然后看看 Pod 起来没kubectl get pods-lappuser-service如果看到 3 个 Pod 都是Running状态恭喜你第一步成了第二步写 Service YAMLPod 有了但它们的 IP 是动态分配的外部没法直接访问。咱们需要给它们配一个稳定的前台——Service。# user-service-svc.yamlapiVersion:v1kind:Servicemetadata:name:user-servicespec:selector:app:user-service# 关键把流量转发给带这个标签的 Podports:-protocol:TCPport:80# Service 对外暴露的端口targetPort:8080# 转发到 Pod 的 8080 端口type:ClusterIP# 只在集群内部可访问这段要干嘛创建一个 Service名字也叫user-service。集群内其他服务访问http://user-service:80时Service 会把请求轮询转发给那 3 个 Pod 的 8080 端口。关键点在哪selector这里必须跟 Deployment 里 Pod 的labels对上否则 Service 找不到 Pod流量就发不出去了。type: ClusterIP这是默认类型只在集群内部可用。如果直接想让外部访问可以改成NodePort或LoadBalancer但生产环境一般配合 Ingress 使用。执行kubectl apply-fuser-service-svc.yaml第三步写 Ingress YAML现在集群内部可以互相访问了但外部用户怎么进来咱们用 Ingress 配一个大门。# user-service-ingress.yamlapiVersion:networking.k8s.io/v1kind:Ingressmetadata:name:user-service-ingressannotations:nginx.ingress.kubernetes.io/rewrite-target:/spec:rules:-host:api.example.com# 你的域名http:paths:-path:/api/userspathType:Prefixbackend:service:name:user-service# 转发到刚才创建的 Serviceport:number:80这段要干嘛告诉 Ingress 控制器“当外部用户访问api.example.com/api/users时把请求转给user-service这个 Service 的 80 端口。”执行kubectl apply-fuser-service-ingress.yaml到这一步一个完整的 K8s 部署链路就通了外部用户 ↓ Ingress大门 api.example.com/api/users ↓ Service前台 user-service:80 ↓ Pod房间x3 ← Deployment 管理保证始终有 3 个 ↓ Spring Boot 应用 :8080你可以用浏览器或者 Postman 访问http://api.example.com/api/users如果看到返回了数据那就说明整条链路跑通了滚动更新与回滚升级不再心惊胆战以前咱们升级应用的时候做法是停掉所有老版本容器启动新版本容器。中间有一段时间服务是完全不可用的这叫停机发布。K8s 的 Deployment 支持滚动更新Rolling Update它的逻辑是先启动一个新版本的 Pod等它健康检查通过了再停掉一个老版本的 Pod以此类推直到全部替换完成整个过程中服务的副本数始终不会低于你设定的数量用户几乎无感知。怎么更新假设你打了一个新镜像user-service:1.1.0有两种方式升级方式一直接改 YAML 再 apply把 Deployment YAML 里的image: your-registry/user-service:1.0.0改成1.1.0然后kubectl apply-fuser-service-deployment.yaml方式二用 kubectl set image更快kubectlsetimage deployment/user-service user-serviceyour-registry/user-service:1.1.0然后你可以实时观察更新过程kubectl rollout status deployment/user-serviceK8s 会告诉你“正在创建新 Pod……正在终止旧 Pod……完成。”升级翻车了怎么办回滚咱们都懂新版本不一定靠谱。如果升级后发现 BugK8s 可以一键回滚到上一个版本kubectl rollout undo deployment/user-service如果你想看历史版本记录kubectl rollouthistorydeployment/user-service甚至还能回滚到指定版本kubectl rollout undo deployment/user-service --to-revision2这功能真的太香了。以前升级前我手心都冒汗现在淡定多了。服务发现、负载均衡与存储简要带过服务发现在 K8s 里服务发现是自动的。你的订单服务想要调用用户服务不需要知道用户服务跑在哪台机器上、IP 是多少直接访问http://user-service就行。K8s 内部的 DNS 会自动解析这个域名到对应的 Service。负载均衡Service 默认会把请求**轮询Round Robin**分发给后端的 Pod。如果你的 3 个 Pod 分别跑在 3 个节点上流量就会自然分散开。这比手动维护 Nginx 的 upstream 省心太多了。持久化存储PersistentVolume前面说的 Pod 是临时的重启后里面的数据就没了。如果你的应用需要保存文件比如上传的图片、日志就需要用到PersistentVolumePV和PersistentVolumeClaimPVC。简单理解PV 公寓楼的公共储物柜一块实际的存储资源PVC 租户向物业申请的我要一个 10G 的储物柜Pod 通过挂载 PVC就能像用本地磁盘一样用这块持久化存储了。即使 Pod 重启数据还在储物柜里。这块内容展开能写一篇文章今天咱们先知道有这个东西用到的时候再深入研究。踩坑记录这些坑我替你踩过了学习 K8s 的过程中我踩过不少坑。这里分享两个印象最深的希望能帮你少走弯路。坑一Pod 一直 Pending原来是资源不足有一次我 apply 了一个 Deployment结果 Pod 死活起不来状态一直是Pending。我查了半天日志啥错误都没有。后来用kubectl describe pod pod-name一看Events 里赫然写着0/3 nodes are available: 3 Insufficient cpu.原因我给 Pod 配的 CPU request 是1000m1 核但集群里的节点每台只剩 0.5 核可用。K8s 调度器找不到能满足资源要求的节点所以 Pod 只能一直 Pending。解决方案降低 Pod 的 resources requests/limits或者给集群扩容加节点经验教训一定要养成kubectl describe的习惯很多问题的答案都在 Events 里。写 YAML 时资源限制要合理别一拍脑袋写个 8 核 16G。坑二Service selector 写错了访问一直 502有一次我部署完应用Ingress、Service、Pod 看起来都正常但一访问就是 502 Bad Gateway。我排查了 Nginx Ingress 日志、Service endpoints最后发现Deployment 里 Pod 的 label 是app: user-service但 Service 的 selector 写成了app: userservice少了一个连字符。因为标签对不上Service 根本找不到后端的 Pod流量发不出去Ingress 自然报 502。解决方案把 selector 改对重新 apply。经验教训K8s 里很多资源是通过labels和selector配对的拼写错误是新手最常见的问题。可以用kubectl get endpoints user-service检查 Service 是否成功关联到了 Pod。如果 endpoints 是空的大概率是 selector 不对。写在最后从手动docker run到用 K8s 编排容器我的感受可以用一句话总结K8s 不是让你少写几行命令而是让你晚上能睡个好觉。它帮你解决了容器时代最核心的几个问题故障自愈Pod 挂了自动重启弹性伸缩流量大了自动扩容配合 HPA平滑升级滚动更新用户无感知服务发现不用硬编码 IP内部调用走 DNS配置解耦ConfigMap/Secret 让配置和镜像分离当然K8s 的学习曲线确实不友好。我第一次接触的时候也被各种概念绕晕了。但只要你记住公寓楼这个类比多动手实践慢慢就会有一种豁然开朗的感觉。这篇文章咱们主要聊了 K8s 的核心概念和基础部署。如果你已经入门了下一步可以研究HelmK8s 的包管理器简化 YAML 管理HPAHorizontal Pod Autoscaler根据 CPU/内存自动扩缩容Prometheus Grafana监控 K8s 集群和应用Istio服务网格做更细粒度的流量治理你在用 K8s 的过程中踩过哪些坑或者你觉得 Docker Swarm 也挺好用的欢迎在评论区交流咱们一起进步

更多文章