Skip to content

第5章 port / targetPort / NodePort 深度解析

本章用大白话 + 图示 + 实例,彻底讲清楚 Service 里的三个端口到底什么意思、怎么配、常见坑有哪些。


5.1 一句话总结

字段作用位置一句话解释
portService 自己身上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 常见配置组合速查

场景typeporttargetPortnodePort给谁用
内部微服务调用ClusterIP808080不写集群内 Pod
临时测试外部访问NodePort80808030080外部通过节点 IP
生产公网访问LoadBalancer80/4438080不写外部通过云 LB IP
七层域名路由ClusterIP/NodePort808080视 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。

本章练习

  1. 创建一个 Deployment,容器监听 5000,Service port 用 80,targetPort 用 5000。
  2. 验证:从集群内访问 http://<service>:80 是否能到达容器的 5000 端口。
  3. 故意把 targetPort 写错,观察会出现什么现象,并学会用 kubectl get endpoints 排查。