第28篇 k8s之Service:为 Pod 提供稳定的访问入口

IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。


在前几篇中,我们通过 Deployment 管理了 Flask 应用的多个副本,实现了滚动更新和自愈。但有一个问题一直没解决:Pod 的 IP 是临时的。

每次 Pod 被重建(更新、扩容、节点故障),它的 IP 都会变化。如果你用 kubectl get pods -o wide 观察,会发现同一个 Deployment 下的 Pod IP 各不相同,且随时可能改变。那么问题来了:其他服务怎么找到这些 Pod?在前端 Nginx 的配置里写死 IP?每次 Pod 重建后手动更新配置?

显然不现实。Kubernetes 的解决方案是 Service------一个为 Pod 提供稳定访问入口的抽象层。今天这篇,我们从 Service 的核心原理讲起,到三种 Service 类型的对比,再到落地我们的贯穿案例,把 Pod 之间的通信问题彻底解决。

一、为什么需要 Service?

回顾第 25 篇,我们创建了一个 Deployment 管理 3 个 Flask Pod:

bash 复制代码
kubectl get pods -l app=flask-counter -o wide

输出:

bash 复制代码
NAME                                READY   STATUS    IP            NODE
flask-deployment-8f9a0b1c2d3-abcde  1/1     Running   10.244.1.10   minikube
flask-deployment-8f9a0b1c2d3-def34  1/1     Running   10.244.1.11   minikube
flask-deployment-8f9a0b1c2d3-ghi56  1/1     Running   10.244.1.12   minikube

如果 Redis 需要连接 Flask,它该连哪个 IP?三个 Pod 都可以处理请求,但每个 Pod 的 IP 都不同。一个 Pod 被重建后,新 Pod 的 IP 完全不同。你需要的是一组 Pod 的稳定入口 和一个自动分发流量的负载均衡器。

这正是 Service 的两大核心能力:

  1. 稳定的虚拟 IP(ClusterIP):Service 一旦创建,它的 ClusterIP 在整个生命周期中保持不变(除非手动删除 Service 并重新创建)。无论后端 Pod 如何创建、销毁、IP 变更,Client 始终通过同一个 ClusterIP 访问服务。

  2. 自动负载均衡:Service 通过标签选择器找到后端的 Pod,将请求均匀分发给所有健康的 Pod。

回想第 9 篇,我们在 Docker 中通过 --network-alias 和 DNS 轮询实现了类似能力。但 Compose 的 DNS 只能在同一台机器上工作,且没有健康检查驱动的流量摘除。K8s 的 Service 将这套机制提升到了集群级别,并与 readiness probe 深度集成。

二、Service 的工作原理

2.1 标签选择器:找到 Pod

Service 通过 selector 字段指定一组标签,K8s 自动找到所有匹配这些标签的 Pod,将它们作为流量的后端目标。这和你之前学到的 Deployment 用 matchLabels 找到自己管理的 Pod 是完全一样的机制。

bash 复制代码
Service (selector: app=flask-counter)
    │
    ├── Pod A (app=flask-counter, IP: 10.244.1.10) ← 匹配
    ├── Pod B (app=flask-counter, IP: 10.244.1.11) ← 匹配
    └── Pod C (app=flask-counter, IP: 10.244.1.12) ← 匹配

标签选择器是 K8s 中实现松耦合的核心机制。Service 不需要知道 Pod 叫什么名字、IP 是多少,只需要知道"我要找带 app=flask-counter 标签的 Pod"。

2.2 kube-proxy 与 iptables:实现负载均衡

Service 的虚拟 IP 并非真实存在的网络接口,而是由每个节点上的 kube-proxy 组件通过 iptables 规则实现的。这一点与第 8 篇学到的 Docker 端口映射本质相同------都是用 iptables 做流量转发。

当你访问 Service 的 ClusterIP 时,数据包被 iptables 规则捕获,随机转发给后端某个 Pod 的 IP。kube-proxy 会持续监控 Service 和 Pod 的变化,自动更新 iptables 规则。当一个 Pod 的 readiness probe 失败时,kube-proxy 会从 iptables 规则中移除该 Pod 的条目,确保流量不再发给不健康的实例。

三、三种 Service 类型

K8s 提供了三种主要的 Service 类型,适应不同的访问场景:

3.1 ClusterIP(默认类型)

ClusterIP 是默认的 Service 类型,也是最常用的类型。它只分配一个集群内部可访问的虚拟 IP,外部流量无法直接到达。

适用场景:集群内微服务之间的调用。例如,Flask 应用访问 Redis、后端 API 访问数据库。

bash 复制代码
apiVersion: v1
kind: Service
metadata:
  name: redis-service
spec:
  type: ClusterIP
  selector:
    app: redis
  ports:
    - port: 6379
      targetPort: 6379
  • port: 6379:Service 监听的端口(其他服务通过 <服务名>:6379 访问)

  • targetPort: 6379:后端 Pod 的容器端口(流量最终到达的端口)

3.2 NodePort

NodePort 在 ClusterIP 的基础上,额外在每个节点上开放一个固定端口(默认范围 30000-32767)。外部客户端可以通过 <任意节点IP>:<NodePort> 访问服务。

适用场景:开发调试、临时暴露服务,或没有云负载均衡器的自建集群。

bash 复制代码
apiVersion: v1
kind: Service
metadata:
  name: flask-service-nodeport
spec:
  type: NodePort
  selector:
    app: flask-counter
  ports:
    - port: 5000
      targetPort: 5000
      nodePort: 30080
  • nodePort: 30080:在每个节点上开放的端口。如果不指定,K8s 会从 30000-32767 范围内自动分配一个

3.3 LoadBalancer

LoadBalancer 在 NodePort 的基础上,自动向云平台申请一个外部负载均衡器(如 AWS ELB、GCP Cloud LB),并分配一个公网可访问的 IP。在 Minikube 中,可以通过 minikube tunnel 模拟 LoadBalancer 行为。

适用场景:生产环境中需要对外暴露 HTTP/HTTPS 服务。

bash 复制代码
apiVersion: v1
kind: Service
metadata:
  name: flask-service-lb
spec:
  type: LoadBalancer
  selector:
    app: flask-counter
  ports:
    - port: 80
      targetPort: 5000

在 Minikube 环境中,运行 minikube tunnel 后,EXTERNAL-IP 会从 <pending> 变为可访问的 IP 地址。

四、实战:为贯穿案例创建 Service

现在将 Service 落地到我们的 Flask + Redis 应用中。以下是完整的 Service 配置,为 Redis 和 Flask 分别创建 ClusterIP 和 NodePort 类型的 Service:

bash 复制代码
# redis-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: redis-service
spec:
  type: ClusterIP
  selector:
    app: redis
  ports:
    - port: 6379
      targetPort: 6379
---
# flask-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: flask-service
spec:
  type: NodePort
  selector:
    app: flask-counter
  ports:
    - port: 5000
      targetPort: 5000
      nodePort: 30080
bash 复制代码
# 部署 Redis(如果尚未部署)
kubectl create deployment redis --image=redis:alpine
kubectl expose deployment redis --port=6379 --target-port=6379 --name=redis-service

# 应用 Flask Service
kubectl apply -f flask-service.yaml

4.1 验证 Service

输出:

bash 复制代码
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
kubernetes      ClusterIP   10.96.0.1       <none>        443/TCP          1d
redis-service   ClusterIP   10.96.100.50    <none>        6379/TCP         1m
flask-service   NodePort    10.96.200.80    <none>        5000:30080/TCP   30s
  • kubernetes 是 K8s API Server 的 Service,由集群自动创建

  • redis-service 是 ClusterIP 类型,只能在集群内部访问

  • flask-service 是 NodePort 类型,可以通过节点 IP:30080 访问

4.2 验证 ClusterIP 的稳定性

bash 复制代码
# 删除所有 Flask Pod,让 Deployment 重建它们
kubectl delete pods -l app=flask-counter

# 观察 Pod IP 变化
kubectl get pods -l app=flask-counter -o wide
# Pod 重建后 IP 全部变化,但 Service ClusterIP 不变

# 再次通过 Service 访问
kubectl exec deploy/redis -- redis-cli -h flask-service -p 5000
# 仍能正常连接

4.3 验证负载均衡

在 Flask 应用中,/health 端点返回 {"status":"ok"}。我们可以临时修改它,让每个 Pod 返回自己的主机名,以直观验证负载均衡效果:

bash 复制代码
# 进入一个临时 Pod,多次访问 flask-service
kubectl run -it --rm debug --image=alpine -- sh
# 在容器内执行:
apk add curl
while true; do curl -s flask-service:5000 && sleep 0.5; done

你会看到请求被轮询分发到不同的 Pod------这就是 Service 的负载均衡在起作用。

4.4 验证 NodePort(外部访问)

在 Minikube 环境中获取节点 IP:

bash 复制代码
minikube ip
# 192.168.49.2

在浏览器中访问 http://192.168.49.2:30080,或者使用 curl:

bash 复制代码
curl http://192.168.49.2:30080
# Hello World! I have been seen 42 times.

4.5 验证 DNS 解析

K8s 内置了 CoreDNS,为每个 Service 自动创建 DNS 记录。格式为 <服务名>.<命名空间>.svc.cluster.local。在默认命名空间下,可以直接用服务名访问:

bash 复制代码
kubectl exec deploy/redis -- redis-cli -h flask-service -p 5000 PING
# 如果 Redis CLI 支持 TCP PING,或使用其他方式验证
kubectl exec deploy/redis -- sh -c "nslookup flask-service"
# Name:      flask-service
# Address 1: 10.96.200.80 flask-service.default.svc.cluster.local

这就是为什么在 app.py 中我们可以写 REDIS_HOST=redis-service------CoreDNS 会将 redis-service 解析为 Redis Service 的 ClusterIP。

五、Service 与 Compose 网络对比

这是理解 K8s 网络的关键一步:

在 Compose 中,DNS 轮询是服务发现的主要方式,但没有与健康检查联动------即使容器 healthcheck 失败,DNS 仍会返回该容器的 IP,导致请求被路由到不健康的实例。K8s 的 Service 通过与 readiness probe 深度集成解决了这个问题:只有 readiness probe 通过的 Pod 才会被加入 Service 的后端列表,确保流量永远不会发送到未就绪的 Pod。

六、命令速查表

七、本篇总结

  • Service 的本质:为动态变化的 Pod 提供稳定的虚拟 IP 和 DNS 名称,实现服务发现和负载均衡。

  • 三种类型:ClusterIP(内部通信)、NodePort(节点端口暴露)、LoadBalancer(云负载均衡器外部入口)。

  • 工作原理:kube-proxy 通过 iptables/IPVS 规则将 Service ClusterIP 的流量转发到后端 Pod,并自动感知 Pod 的增删和健康状态变化。

  • 与 Compose 的关键差异:Service 是集群级抽象,与 readiness probe 联动实现健康检查驱动的流量管理,DNS 解析跨节点有效。

这篇让你掌握了 K8s 内部的服务发现和负载均衡。但 Service 只能提供四层(TCP/UDP)的负载均衡------无法基于 HTTP 路径或域名做路由。下一篇------第 29 篇:Service 与 Endpoints 深入:服务发现原理,我们将深入 Endpoints 对象和 CoreDNS 的工作机制,彻底理解 K8s 服务发现的底层原理。

想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维 !

相关推荐
用户2181697049301 小时前
Gin (三) 中间件 并发测试
后端
fliter1 小时前
你想在 Rust 中实现动态库热重载?
后端
用户467245132231 小时前
分布式唯一序列号:万亿级订单不重复的奥秘
后端
未秃头的程序猿1 小时前
别再让大模型单打独斗了!Java 多 Agent 协作实战:任务拆解+结果聚合
java·后端·ai编程
XovH1 小时前
第29篇 k8s之Service 与 Endpoints 深入:服务发现原理
后端
人道领域1 小时前
【LeetCode刷题日记】538.把二叉搜索树转换为累加树
java·开发语言·后端·算法·leetcode
西凉的悲伤1 小时前
Spring Boot + ShardingSphere 介绍
java·spring boot·后端·shardingsphere·分库分表
不爱编程的小陈1 小时前
Go内存模型与GC机制:高性能编程的核心
开发语言·后端·golang
日月云棠1 小时前
12 Enum —— 枚举类型的底层实现
java·后端