主题
第5章 port / targetPort / NodePort 深度解析
本章用大白话 + 图示 + 实例,彻底讲清楚 Service 里的三个端口到底什么意思、怎么配、常见坑有哪些。
5.1 一句话总结
| 字段 | 作用位置 | 一句话解释 |
|---|---|---|
port | Service 自己身上 | Service 对外暴露的端口,集群内其他服务访问它时用 |
targetPort | 后端 Pod 的容器上 | 容器真正监听的端口,流量最终要打到这个端口 |
nodePort | 集群每个节点上 | 节点对外开放的端口,外部用户通过 <节点IP>:<NodePort> 访问 |
关系链
text
外部用户/客户端
│
▼
NodePort ← 节点上的端口(外部入口)
│
▼
Service:port ← Service 的虚拟 IP + 端口(集群内入口)
│
▼
Pod:targetPort ← 容器实际监听的端口(最终目的地)5.2 用快递物流来类比
想象你要寄一个快递到某公司的“客服部”:
port= 公司总机号码(比如400-1234)- 你不需要知道客服部每个人的分机号,只要打总机。
targetPort= 客服小张办公桌上的分机号(比如8080)- 总机会把电话转接给真正接听的人。
NodePort= 公司园区门口的一个公共收件窗口(比如30080)- 外部快递员不用进园区,把包裹放到门口窗口,园区内部再转送到总机。
Service 就是那个“总机系统”:外部/内部调用方只需要记住 port,由 Service 根据 Endpoints 把流量转发到 Pod 的 targetPort。
5.3 ClusterIP 场景:只有 port + targetPort
yaml
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
type: ClusterIP
selector:
app: user
ports:
- name: http
port: 80 # 其他服务访问 user-service 时用的端口
targetPort: 8080 # user 容器里 Spring Boot / Gin / Express 实际监听的端口访问方式
bash
# 访问的是 Service 的 port(80),不是 Pod 的 8080
curl http://user-service:80
# 也可以写成
curl http://user-service.default.svc.cluster.local:80流量路径
text
调用方 Pod
│
▼
user-service:80 (ClusterIP: 10.96.123.10)
│
│ kube-proxy 做 DNAT
▼
user Pod IP:8080 (10.244.1.15:8080)为什么要分开?
解耦 + 标准化:
- 业务代码里统一写
http://user-service:80,不用关心后端容器跑的是 8080、3000 还是 5000。 - 容器端口改了,只要改 Service 的
targetPort,调用方不用改。 - 多个服务都用
80对外暴露,内部互不冲突(因为每个 Service 有独立 ClusterIP)。
5.4 NodePort 场景:port + targetPort + nodePort
yaml
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
type: NodePort
selector:
app: user
ports:
- name: http
port: 80
targetPort: 8080
nodePort: 30080 # 不指定则系统从 30000-32767 自动分配访问方式
bash
# 集群内部访问(仍可用 Service port)
curl http://user-service:80
# 集群外部访问(任一节点的 IP + NodePort)
curl http://<Node-IP>:30080流量路径
text
外部客户端
│
▼
<Node-IP>:30080 ← 节点网卡上的 NodePort
│
▼
user-service:80 ← 还是 Service 的 port
│
▼
user Pod:8080 ← 最终打到容器研发需要注意
nodePort是全局稀缺资源,范围默认30000-32767。- 不写
nodePort时,Kubernetes 自动分配,每次重建可能变;如果要固定,请显式指定。 - 生产环境一般不直接用 NodePort 暴露给公网,而是配合 LoadBalancer / Ingress。
5.5 LoadBalancer 场景:云厂商给你的外部 IP
yaml
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
# LoadBalancer 不需要你写 nodePort,但底层仍会创建 NodePort访问方式
bash
# 通过云厂商分配的外部 IP 访问
curl http://<EXTERNAL-IP>:80路径
text
用户
│
▼
云 LB(如阿里云 SLB / AWS ELB)
│
▼
<Node-IP>:<NodePort>
│
▼
Service:80
│
▼
Pod:8080研发团队重点:对外接口文档写 http://<EXTERNAL-IP>:80 或域名,不要写 NodePort。
5.6 常见配置组合速查
| 场景 | type | port | targetPort | nodePort | 给谁用 |
|---|---|---|---|---|---|
| 内部微服务调用 | ClusterIP | 80 | 8080 | 不写 | 集群内 Pod |
| 临时测试外部访问 | NodePort | 80 | 8080 | 30080 | 外部通过节点 IP |
| 生产公网访问 | LoadBalancer | 80/443 | 8080 | 不写 | 外部通过云 LB IP |
| 七层域名路由 | ClusterIP/NodePort | 80 | 8080 | 视 Ingress 而定 | Ingress Controller |
5.7 研发最容易踩的坑
坑1:只改容器端口,忘了改 targetPort
yaml
# 错误示例:容器监听 8080,但 targetPort 写的 80
ports:
- port: 80
targetPort: 80 # ❌ 应该是 8080现象:Service 能解析,请求无响应或 Connection refused。
排查:
bash
kubectl get endpoints <svc-name>
# 有 endpoints,但 telnet PodIP:targetPort 不通坑2:调用方写死了 Pod IP
python
# 错误示例
requests.get("http://10.244.1.15:8080")Pod IP 会变,应该通过 Service 域名访问:
python
requests.get("http://user-service:80")坑3:以为 port 必须等于 targetPort
port 和 targetPort 可以不一样,而且通常建议不一样:
- Service
port: 80对外友好、标准。 - Pod
targetPort: 8080容器启动参数决定。
坑4:NodePort 和生产域名混用
直接让前端调用 http://192.168.1.10:30080 这种节点 IP + NodePort,一旦节点挂了、IP 变了、NodePort 变了就出问题。
正确做法:NodePort 只作为 Ingress Controller 或 LoadBalancer 的入口,业务通过域名/外部 IP 访问。
坑5:多端口服务命名不一致
yaml
ports:
- name: http
port: 80
targetPort: 8080
- name: grpc
port: 50051
targetPort: 50051建议:每个端口都命名,便于 Ingress、监控、日志区分。
5.8 给研发的速记口诀
text
Service 的 port 是门面,
Pod 的 targetPort 是真身,
NodePort 是园区大门,
LoadBalancer 是云端前台。
调用方永远记门面(port),
容器应用守真身(targetPort),
外部流量走大门(NodePort)或前台(LB),
千万别把 Pod IP 当门牌。5.9 实操验证小实验
部署一个 Nginx
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-demo
spec:
replicas: 2
selector:
matchLabels:
app: nginx-demo
template:
metadata:
labels:
app: nginx-demo
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-demo
spec:
type: NodePort
selector:
app: nginx-demo
ports:
- port: 8080 # Service 暴露的端口
targetPort: 80 # Nginx 容器监听端口
nodePort: 30080 # 节点上的端口验证三种访问方式
bash
# 1. 集群内访问 Service port
kubectl run -it --rm debug --image=busybox --restart=Never -- wget -O- http://nginx-demo:8080
# 2. 查看 NodePort
kubectl get svc nginx-demo
# 输出示例:
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# nginx-demo NodePort 10.96.10.20 <none> 8080:30080/TCP 1m
# 3. 集群外访问 NodePort
curl http://<任意节点IP>:30080观察 EndpointSlice
bash
kubectl get endpointslices -l kubernetes.io/service-name=nginx-demo -o yaml可以看到 endpoints 里记录的是 Pod IP + targetPort(80),而不是 Service port(8080)或 NodePort(30080)。
5.10 本章小结
port是 Service 的对外端口,调用方使用。targetPort是容器的真实端口,流量最终到达这里。nodePort是节点对外暴露的端口,外部访问的入口。- 三者共同作用,实现了稳定入口 → 负载均衡 → 真实后端的流量转发。
- 研发写代码时,只需关心
port;配置容器和 YAML 时,要正确对应targetPort;外部对接时,使用 NodePort 或 LoadBalancer,不要写死 Pod IP。
本章练习
- 创建一个 Deployment,容器监听 5000,Service
port用 80,targetPort用 5000。 - 验证:从集群内访问
http://<service>:80是否能到达容器的 5000 端口。 - 故意把
targetPort写错,观察会出现什么现象,并学会用kubectl get endpoints排查。