事情的起因是創建了一個 NodePort 類型 Service,其端口映射關系為 8000:30948/TCP。既然30948是在每個node開的端口,那8000是開在哪的呢?出于好奇回顧了一下K8s的Cluster IP和Service
端口映射關系解析
在 Kubernetes 的 NodePort Service 中,端口配置遵循以下格式:
<ClusterIP 端口>:<NodePort 端口>/<協議>
- 8000:Service 的 ClusterIP 端口(集群內部訪問端口)
- 30948:NodePort 端口(節點外部訪問端口)
訪問測試結果
1. 在宿主機上訪問 IP:8000
curl http://<節點IP>:8000
結果:連接失敗
原因:
- 8000 端口僅在集群內部監聽(通過 Service 的 ClusterIP)
- 節點操作系統沒有在 8000 端口監聽請求
2. 在宿主機上訪問 IP:30948
curl http://<節點IP>:30948
結果:成功訪問服務
原因:
- kube-proxy 在所有節點上監聽了 30948 端口
- 流量會被轉發到 Service 的后端 Pod
技術原理詳解
1. NodePort 工作原理
當創建 NodePort Service 時:
-
kube-proxy 在所有節點上打開指定端口(30948)
-
創建 iptables/IPVS 規則:
-A KUBE-NODEPORTS -p tcp --dport 30948 -j KUBE-SVC-XXXXXX
-
流量轉發路徑:
外部用戶 → 節點IP:30948 → kube-proxy → Service → Pod
2. ClusterIP 端口用途
-
集群內部訪問入口:
# 在集群內部Pod中訪問 curl http://my-dep.default.svc.cluster.local:8000
-
服務發現的基礎端口
實際驗證步驟
1. 查看 Service 完整定義
kubectl describe svc my-dep
輸出關鍵部分:
Port: 8000/TCP
TargetPort: 80/TCP # 后端Pod實際端口
NodePort: 30948/TCP
Endpoints: 10.244.1.2:80,10.244.2.3:80 # 后端Pod IP
2. 測試端口訪問
# 1. 訪問NodePort (應成功)
curl http://<任一節點IP>:30948# 2. 訪問ClusterIP端口 (應失敗)
curl http://<節點IP>:8000# 3. 在集群內部訪問 (在Pod中執行)
kubectl run test --image=busybox -it --rm --restart=Never -- \wget -qO- http://my-dep:8000
3. 檢查節點端口監聽
# 在K8s節點上執行
sudo netstat -tuln | grep 30948
# 應輸出: tcp6 0 0 :::30948 :::* LISTEN
那么回到最開始的問題,8000端口開在哪呢?
如果你采用的是原生搭建k8s,那么你一定會記得初始化的時候有這樣一個命令
kubeadm init \
--apiserver-advertise-address=172.31.0.4 \
--control-plane-endpoint=cluster-endpoint \
--image-repository registry.cn-hangzhou.aliyuncs.com/k8s_images \
--kubernetes-version v1.20.9 \
--service-cidr=10.96.0.0/16 \
--pod-network-cidr=192.168.0.0/16
在這里就指定了創建svc和pod的網段
集群內部訪問端口的本質
ClusterIP 端口是 Kubernetes 服務抽象層的核心設計。
ClusterIP 端口的三層抽象
1. 虛擬 IP 層 (Service ClusterIP)
- 非真實接口:ClusterIP (如
10.96.91.238
) 是 kube-proxy 創建的虛擬 IP - 無端口監聽:節點操作系統上沒有進程真正監聽 8000 端口
- 內核級攔截:通過 Linux 內核的 netfilter 框架實現流量攔截
2. 規則轉發層 (kube-proxy)
kube-proxy 創建轉發規則(以 iptables 為例):
# 查看 Service 規則鏈
sudo iptables -t nat -L KUBE-SERVICES# 示例輸出
KUBE-SVC-XYZ tcp -- anywhere 10.96.91.238 tcp dpt:8000
具體規則細節:
# DNAT 規則
-A KUBE-SVC-XYZ -m statistic --mode random --probability 0.333 -j KUBE-SEP-111
-A KUBE-SVC-XYZ -m statistic --mode random --probability 0.5 -j KUBE-SEP-222
-A KUBE-SVC-XYZ -j KUBE-SEP-333# 終結點規則
-A KUBE-SEP-111 -p tcp -m tcp -j DNAT --to-destination 10.244.1.2:80
-A KUBE-SEP-222 -p tcp -m tcp -j DNAT --to-destination 10.244.1.3:80
-A KUBE-SEP-333 -p tcp -m tcp -j DNAT --to-destination 10.244.2.4:80
3. 真實端點層 (Pod)
-
實際端口監聽:在 Pod 內部的容器端口(如配置的 80 端口)
-
Endpoint 對象管理:
apiVersion: v1 kind: Endpoints metadata:name: my-dep subsets: - addresses:- ip: 10.244.1.2- ip: 10.244.1.3- ip: 10.244.2.4ports:- port: 80protocol: TCP
流量轉發全路徑
當集群內部客戶端訪問 10.96.91.238:8000
時:
-
客戶端發起請求:
resp, err := http.Get("http://10.96.91.238:8000")
-
內核網絡棧攔截:
- 目標 IP 匹配 Service CIDR (如 10.96.0.0/16)
- 進入
KUBE-SERVICES
鏈
-
DNAT 轉換:
- 根據 iptables 規則
- 目標 IP:Port 被替換為 Pod IP:Port (如 10.244.1.2:80)
-
路由到目標 Pod:
- 通過 CNI 插件創建的網絡路由
- 流量進入 Pod 網絡命名空間
-
容器接收請求:
- 容器內進程監聽 80 端口
- 處理請求并返回響應
與 NodePort 的關鍵區別
特性 | ClusterIP 端口 (8000) | NodePort 端口 (30948) |
---|---|---|
可見性 | 僅集群內部可見 | 可從集群外部訪問 |
實現層級 | 內核網絡棧 (L3/L4) | 用戶空間監聽 (L4) |
監聽位置 | 無真實監聽,僅規則 | kube-proxy 進程真實監聽 |
訪問控制 | 受網絡策略控制 | 受節點防火墻控制 |
性能開銷 | 低(內核轉發) | 中(用戶態轉發) |
數據包變化 | 目標地址被修改 | 目標地址不變 |
查看內核規則
在任意節點執行:
# 查看NAT表規則
sudo iptables -t nat -L KUBE-SERVICES -n --line-numbers# 查找Service規則
sudo iptables -t nat -L KUBE-SVC-$(kubectl get svc my-dep -o jsonpath='{.spec.ports[0].name}') -n
為什么需要 ClusterIP
-
穩定訪問端點
Pod 可能隨時重建,但 Service IP 保持不變 -
負載均衡
自動將流量分發到多個后端 Pod -
服務發現
通過 DNS 名稱解耦服務位置 -
流量策略
支持會話保持、流量權重等高級特性 -
安全隔離
默認僅集群內部可訪問,減少攻擊面
生產環境實踐
-
避免直接使用 NodePort 配合 Ingress 或 LoadBalancer 使用:
apiVersion: networking.k8s.io/v1 kind: Ingress metadata:name: my-ingress spec:rules:- http:paths:- path: /pathType: Prefixbackend:service:name: my-depport:number: 8000 # 使用ClusterIP端口
-
自定義 NodePort 范圍 修改 apiserver 配置:
apiServer:extraArgs:service-node-port-range: "30000-35000"
-
防火墻規則 僅開放必要的 NodePort 端口:
sudo ufw allow 30948/tcp