前言
上一篇把 Pod、ReplicaSet、Deployment 的关系讲了一遍。但一个应用只是在集群里跑起来还不够,其他服务要能访问它,外部用户也要能访问它。
这就会遇到 Service 和 Ingress。
很多 k8s 初学问题都卡在这里:Deployment 明明启动成功了,为什么浏览器访问不到?Pod IP 明明能通,为什么重启后又不行?Service 和 Ingress 到底谁负责什么?
这篇就按一次普通 Web 服务发布的路径,把 Deployment、Service、Ingress 串起来看。
Pod IP 不适合直接依赖
Pod 启动后会有自己的 IP,但这个 IP 不稳定。
Pod 被重建、调度到其他节点、滚动发布时,IP 都可能变化。如果其他服务直接记 Pod IP,就会很脆弱。
这和传统机器部署不一样。传统部署里我们可能会固定几台机器,然后在 Nginx 或注册中心里配置它们。k8s 里 Pod 是可替换的,应用实例随时可能被销毁再创建。
所以访问后端时,不应该直接依赖 Pod IP,而是通过 Service。
Service 提供稳定入口
Service 的作用是给一组 Pod 提供稳定访问入口。
它通过 selector 找到后端 Pod,然后给调用方暴露一个稳定的服务名和虚拟 IP。即使后端 Pod 发生变化,调用方仍然访问同一个 Service。
一个最常见的 ClusterIP Service:
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
type: ClusterIP
selector:
app: order-service
ports:
- name: http
port: 80
targetPort: 8080
这里的 port 是 Service 暴露的端口,targetPort 是 Pod 容器实际监听的端口。
如果另一个服务也在集群内,就可以访问:
http://order-service
在同一个 namespace 下,DNS 会把 order-service 解析到 Service。跨 namespace 时,可以用更完整的域名,比如 order-service.mall.svc.cluster.local。
selector 一定要对齐
Service 能不能转发到 Pod,核心看 selector 是否能匹配到 Pod label。
Deployment 里通常会写:
template:
metadata:
labels:
app: order-service
Service 里要对应:
selector:
app: order-service
如果这里写错,Service 本身可能是正常创建的,但没有任何后端端点。排查时可以看 endpoints:
kubectl get endpoints order-service
如果结果为空,优先查 label 和 selector,而不是一上来就怀疑网络插件。
Service 类型不要乱用
Service 有几种类型,刚开始容易混。
ClusterIP 是默认类型,只在集群内部暴露服务。微服务之间互相调用,大多数用它就够了。
NodePort 会在每个节点上开一个端口,把流量转到 Service。它可以用于简单测试,但生产里直接暴露 NodePort 并不优雅,端口管理也容易混乱。
LoadBalancer 通常依赖云厂商或负载均衡实现,会给 Service 分配一个外部负载均衡入口。云上场景比较常见。
ExternalName 则更像 DNS 别名,用来把集群内服务名指向外部域名。
我的习惯是:内部服务先用 ClusterIP;外部 HTTP 流量优先考虑 Ingress;确实需要四层负载均衡时再看 LoadBalancer。
Ingress 负责 HTTP 入口
如果一个 Web 应用要从集群外访问,通常会引入 Ingress。
Ingress 本身是一种路由规则,真正转发流量的是 Ingress Controller,比如 Nginx Ingress Controller、Traefik、HAProxy 或云厂商提供的实现。
一个简单 Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: mall-ingress
spec:
rules:
- host: mall.example.com
http:
paths:
- path: /api/order
pathType: Prefix
backend:
service:
name: order-service
port:
number: 80
这条规则表达的是:访问 mall.example.com/api/order 的请求,转发给 order-service 这个 Service 的 80 端口。
所以链路是这样的:
Client -> Ingress Controller -> Service -> Pod
Ingress 不直接找 Pod,它找 Service。Service 再通过 selector 找 Pod。
先把一条链路跑通
排查访问问题时,我比较喜欢从内到外确认。
第一步,确认 Pod 正常:
kubectl get pods -l app=order-service
kubectl logs deploy/order-service
第二步,确认 Service 有后端:
kubectl get service order-service
kubectl get endpoints order-service
第三步,从集群内访问 Service:
kubectl run curl-test --rm -it --image=curlimages/curl -- sh
curl http://order-service
第四步,再看 Ingress 规则和 Controller 日志。
这样排查比直接在浏览器里刷新要有效。外部访问失败可能是域名、证书、Ingress、Service、Pod、应用端口任何一层的问题。逐层收窄,才不会乱猜。
Java 服务要注意端口和健康检查
很多 Spring Boot 服务默认监听 8080。Service 里 targetPort 要对应容器真实端口,Ingress 后端端口要对应 Service 端口。
如果应用启动慢,还要配合 readinessProbe。否则 Pod 刚创建,Service 就把流量打进去,用户可能遇到启动阶段的错误。
一个简单 readinessProbe:
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 20
periodSeconds: 10
这不是形式主义。滚动发布时,readiness 决定一个新 Pod 什么时候可以接流量。如果这个探针不准,发布稳定性会直接受影响。
小结
Deployment 负责让应用副本跑起来,Service 负责给一组 Pod 提供稳定入口,Ingress 负责把外部 HTTP 请求路由到 Service。
理解这条链路以后,k8s 里的“能不能访问”问题就会清晰很多。先看 Pod,再看 Service endpoints,再看 Ingress Controller,不要一开始就把所有问题混在一起。
对于普通后端服务,最常见的组合就是 Deployment + ClusterIP Service + Ingress。把 label、端口、健康检查写稳,后面的发布和排障会轻松很多。
気に入ったならばコメントを残してくださいね~