K8s 集群外部访问 Pod 的几种方式
记录时间:2026-08-20
环境:K8s 1.28 测试集群(Calico 网络插件);生产 K8s 1.28 arm64 集群(MetalLB v0.14.5 L2 模式 + ingress-nginx)
一、背景
集群内访问 Pod 很简单,直接走 Pod IP 或者对应 Service 的 ClusterIP 就行。但从集群外面访问就是另一回事了:在 Flannel、Calico 这类方案下,Pod IP 是容器网段的内部地址,集群外的机器根本路由不到。最近看了一篇讲这个问题的文章,把文章里列的方式挨个和我们自己集群的实际情况对了一遍,整理下来。
我们集群的情况是:测试和生产现在都是 1.28。测试集群网络插件用的 Calico(没跟上联交换机开 BGP 对等);生产集群对外服务主要走 ingress-nginx,另外部署过 MetalLB 给需要直连 IP 的服务用。所以下面每种方式我都会带一句”我们环境里用没用、怎么用的”。
二、几种方式
2.1 hostNetwork:Pod 直接用宿主机网络
Pod 加 hostNetwork: true 后,容器直接用宿主机节点的网络命名空间,Pod IP 就是 node IP,集群外拿 node IP + 端口就能访问。
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
hostNetwork: true
containers:
- name: nginx
image: nginx
验证方法就是对比两个 IP 是否一致:
kubectl get pods -o wide nginx
kubectl get nodes -o wide
curl http://<node-ip> # 应该返回 nginx 欢迎页
它的问题:Pod 漂移到别的 node 后 IP 跟着变;多个 Pod 抢同一个宿主机端口会调度失败;Pod 里能看到宿主机的全部网络,安全上不干净。
文章里给的变通是配合 keepalived 漂 VIP,再绑固定 node。容器里这么搞太绕,一般不这么干。
2.2 hostPort:只映射一个端口到宿主机
hostPort 和 hostNetwork 效果类似,都是拿 Pod 所在 node 的 IP 加端口访问,但 Pod 自己的 IP 还是容器网段的内部地址。
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
hostPort: 80
原理上 hostPort 是 CNI 在宿主机上写的一套 iptables 规则做 fullnat(完整 NAT),文章里贴的规则核心就三条:
-A CNI-DN-1652b489c7cf2eeec2243 -p tcp -m tcp --dport 80 -j DNAT --to-destination 10.244.0.156:80
-A CNI-HOSTPORT-MASQ -m mark --mark 0x2000/0x2000 -j MASQUERADE
-A CNI-HOSTPORT-SETMARK -m comment --comment "CNI portfwd masquerade mark" -j MARK --set-xmark 0x2000/0x2000
访问流量先打标(0x2000),DNAT 到 Pod IP 的容器端口,回包再 MASQUERADE 回 node 地址。比 hostNetwork 好的地方是不用把整个宿主机网络交给 Pod,差的地方是多了一层 iptables 转发,性能略低。
hostPort 最典型的用途是暴露 ingress-controller:ingress 本身也要有入口,常见做法就是给 controller 配 hostPort 或 hostNetwork 挂在固定节点上,外面再指域名过去。
2.3 NodePort:最常用的 Service 暴露方式
前两种改的是 Pod,NodePort 改的是 Service。配一个 type: NodePort 的 Service,集群里任意一个 node 的 IP 加 nodePort 都能访问到服务,哪怕这个 node 上根本没跑目标 Pod。
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
name: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
---
kind: Service
apiVersion: v1
metadata:
name: nginx
spec:
type: NodePort
ports:
- name: nginx
port: 80
nodePort: 30018
selector:
name: nginx
kubectl get svc -o wide nginx # 输出里是 80:30018/TCP
curl http://<任意node-ip>:30018 # 能访问到
任意 node 都能转发的原因是 kube-proxy 把相同的 iptables 规则下发到了所有 node 上。nodePort 不显式指定的话 K8s 从 30000-32767 自动分配,显式指定要保证不和其他服务冲突。
| 参数 | 值 | 说明 |
|---|---|---|
| port | 80 | Service 对集群内暴露的端口 |
| nodePort | 30018 | 集群外通过 node IP 访问的端口,30000-32767 |
| targetPort | 默认同 port | 转发到 Pod 的容器端口 |
我们环境早期没有 LoadBalancer 方案的时候,对外服务基本靠 NodePort。
2.4 LoadBalancer:需要外部 LB 或者 MetalLB
LoadBalancer 和 NodePort 的唯一配置区别就是把 type 换了,但它需要外部负载均衡设备配合,公有云有现成的,自建机房就得自己解决。文章里用的 MetalLB,我们集群里也部署过一套。
kind: Service
apiVersion: v1
metadata:
name: nginx
spec:
type: LoadBalancer
ports:
- name: nginx
port: 80
selector:
name: nginx
kubectl get svc nginx -o wide
# TYPE=LoadBalancer,EXTERNAL-IP 是 MetalLB 从地址池分配的地址
curl http://<EXTERNAL-IP>
我们部署的 MetalLB 是 v0.14.5,L2 模式,地址池 192.168.120.150-192.168.120.155,部署细节我单独记过一篇(MetalLB LoadBalancer 部署记录)。L2 模式不要求路由器支持 BGP,内网够用,所以选 L2 而不是 BGP 模式。
2.5 Ingress:七层转发
NodePort 和 LoadBalancer 都是四层,Ingress 管的是七层,按域名和路径把流量路由到不同 Service。
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: nginx
spec:
rules:
- host: nginx.example.com
http:
paths:
- backend:
serviceName: nginx
servicePort: 80
流程:Ingress 对象绑定域名到 Service,ingress-controller 监听 Service 拿 endpoints 动态刷新自己的配置,外部请求按 host 匹配转发。K8s 本身不提供 Ingress 实现,controller 要自己部署(nginx、traefik 都行)。
注意一点:ingress-controller 自己接收集群外流量,还是得靠 2.2 的 hostPort/hostNetwork 或者 NodePort 暴露。我们生产对外就是走 ingress-nginx,真实 IP 透传的问题我之前单独记过一篇(Ingress 获取真实 IP)。
2.6 Pod IP 全局可达:Calico BGP 发布路由
如果网络插件是 Calico 或 Contiv,可以让宿主机和上联交换机建 BGP 邻居,宿主机把 Pod 网段路由发布出去,交换机之间再交换路由,外面的机器就能直接路由到 Pod IP,什么 Service、端口映射都不用配。
我们测试集群虽然是 Calico,但默认没有跟上联交换机做 BGP 对等,Pod IP 只到宿主机为止,这条路我们没走,集群外访问还是靠上面几种方式。要做的话网络设备要配合改配置,动起来牵涉面大。
三、怎么选
| 方式 | 层级 | 依赖 | 主要代价 |
|---|---|---|---|
| hostNetwork | 网络命名空间 | 无 | Pod 漂移 IP 变、端口冲突、能看到宿主机网络 |
| hostPort | iptables NAT | 无 | 类似 hostNetwork,性能略低 |
| NodePort | 四层 Service | kube-proxy | 占用各 node 端口(30000-32767) |
| LoadBalancer | 四层 Service | 外部 LB / MetalLB | 需要额外组件支持 |
| Ingress | 七层 | ingress-controller | 要部署 controller,controller 自身也要暴露 |
| Pod IP 全局可达 | 网络层 | Calico/Contiv + BGP | 依赖 CNI 和交换机 BGP 支持 |
我的选择顺序:对外 HTTP 服务一律 Ingress(域名管理方便);需要固定 IP 直连的服务用 LoadBalancer + MetalLB;什么都没有的环境 NodePort 兜底;hostNetwork 和 hostPort 只留给 ingress-controller 这类基础组件。
四、注意事项
- hostNetwork 和 hostPort 都占用宿主机端口,端口规划要提前做,冲突了 Pod 会一直调度失败。
- NodePort 的端口范围是 30000-32767,显式指定 nodePort 时先查一遍集群现有 Service,别撞了。
- LoadBalancer 在自建机房没公有云的情况下,MetalLB 是主流方案;L2 模式和 BGP 模式的选择取决于路由器支不支持 BGP。
- Ingress 对象只是规则声明,controller 不部署的话规则不生效。
- 我们环境的真实情况:测试集群 Calico 默认没跟上联交换机做 BGP 对等,Pod IP 不可达外部;MetalLB 地址池是 192.168.120.150-192.168.120.155。