K8s 集群流量入口:Ingress-nginx 七层与 TCP/UDP 四层代理实战
TL;DR:在 K8s 里 Pod IP / ClusterIP 只能在集群内访问。要把服务暴露到集群外,常见链路是 ingress-nginx(七层 HTTP)→ Service(ClusterIP)→ Pod;非 HTTP 的 TCP/UDP 流量(数据库、MQTT、WebSocket、SSH)走 ingress-nginx 的 四层 stream-snippet / ConfigMap 或 hostNetwork + DaemonSet 模式。本文是一份基于生产二进制集群踩坑的实战笔记。
一、为什么需要 Ingress(When to use)
K8s 提供了 4 种把集群内服务暴露到外部的方式:
| 方式 | 适用场景 | 优点 | 缺点 |
|---|
NodePort | 开发测试、单端口临时调试 | 简单,配置最少 | 端口范围受限(30000-32767),要逐个开防火墙 |
LoadBalancer | 云厂商环境 | 自动化分配公网 IP | 依赖云厂商 LB,每个 Service 一个 LB 贵 |
Ingress | HTTP/HTTPS 七层路由,多域名多路径 | 一台 LB 服务多套系统,支持 TLS 终止、灰度 | 只能代理 HTTP/HTTPS |
hostNetwork | 四层流量 + 极致性能 | 走宿主机网络,无 kube-proxy 转发损耗 | 一个 Node 只能跑一个 Pod,要配 nodeSelector |
核心架构:
1
2
3
4
5
6
7
8
9
10
11
12
13
| 外网 (Client)
│
▼
[ NodePort / LoadBalancer / hostNetwork ]
│
▼
[ ingress-nginx-controller ] ← 七层:解析 Host / Path
│
▼
[ Service (ClusterIP) ] ← 集群内虚拟 IP
│
▼
[ Pod (业务容器) ]
|
原则:ingress 代理 service,service 代理 pod。Ingress 本身不直接连 Pod,一定要过 Service 这一层(service mesh 也是同理)。
API 版本注意:本文示例全部使用 apiVersion: networking.k8s.io/v1(K8s 1.19 GA,1.22 起 extensions/v1beta1 被彻底移除)。kubernetes.io/ingress.class 注解在 1.18 后也已被 spec.ingressClassName 取代,请直接使用新写法。
二、安装 ingress-nginx
2.1 部署清单来源
⚠️ 版本匹配:K8s 1.19+ 务必使用 ingress-nginx v0.46+(首个 networking.k8s.io/v1 稳定支持的 controller),2020 年下半年该分支已 GA。
2.2 应用与查看
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # 部署
kubectl apply -f /etc/k8s/ingress-nginx/deploy.yaml
# 删除
kubectl delete -f /etc/k8s/ingress-nginx/deploy.yaml
# 部署后产生的核心资源
namespace/ingress-nginx created
serviceaccount/ingress-nginx created
serviceaccount/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
configmap/ingress-nginx-controller created
service/ingress-nginx-controller created
deployment.apps/ingress-nginx-controller created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created
ingressclass.networking.k8s.io/nginx created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created
|
2.3 资源清单
1
| kubectl get all -n ingress-nginx
|
1
2
3
4
5
6
7
8
9
10
11
| NAME READY STATUS RESTARTS AGE
pod/ingress-nginx-admission-create-cf9w5 0/1 Completed 0 7m36s
pod/ingress-nginx-admission-patch-zm7m5 0/1 Completed 0 7m36s
pod/ingress-nginx-controller-766bcbf897-fhvrb 1/1 Running 0 7m36s
NAME TYPE CLUSTER-IP PORT(S)
service/ingress-nginx-controller NodePort 10.x.x.x 80:30080/TCP,443:30443/TCP
service/ingress-nginx-controller-admission ClusterIP 10.x.x.x 443/TCP
NAME READY UP-TO-DATE AVAILABLE
deployment.apps/ingress-nginx-controller 1/1 1 1
|
这里的 ingressclass.networking.k8s.io/nginx 是 v1 引入的 IngressClass 对象,下游 Ingress 资源靠 spec.ingressClassName: nginx 引用它。
三、最简七层反代测试
3.1 测试 Deployment / Service / Ingress
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| # deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: test
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
namespace: test
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # ingress.yaml(networking.k8s.io/v1)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: test-ingress
namespace: test
spec:
ingressClassName: nginx
rules:
- host: nginx.example.com
http:
paths:
- pathType: Prefix
path: /
backend:
service:
name: nginx-service
port:
number: 80
|
1
2
3
| kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f ingress.yaml
|
3.2 查看 Ingress 解析结果
1
| kubectl describe ingress test-ingress -n test
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| Name: test-ingress
Namespace: test
Address: 10.x.x.x
Ingress Class: nginx
Default backend: <default>
Rules:
Host Path Backends
---- ---- --------
nginx.example.com
/ nginx-service:80 (10.244.1.10:80, 10.244.2.71:80, 10.244.3.8:80)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 110s (x2 over 2m22s) nginx-ingress-controller Scheduled for sync
|
3.3 本地 hosts + curl 测试
1
2
3
4
5
6
7
8
| # /etc/hosts(开发机)
# 内网IP-A nginx.example.com tomcat.example.com
# 直接访问 NodePort
curl -H "Host: nginx.example.com" http://内网IP-A:30080/
# 或先 ping 测连通
ping 内网IP-A
|
v1 与 v1beta1 的关键差异(写错会 apply 失败):
| 字段 | extensions/v1beta1(已弃用) | networking.k8s.io/v1(本文用) |
|---|
| apiVersion | extensions/v1beta1 | networking.k8s.io/v1 |
| 路径匹配 | path: /foo(隐式 Prefix) | path: /foo + pathType: Prefix | Exact | ImplementationSpecific(必填) |
| 后端 | backend.serviceName + servicePort | backend.service.name + backend.service.port.number/name |
| Ingress 类 | 注解 kubernetes.io/ingress.class: nginx | spec.ingressClassName: nginx |
四、关键注解(Annotations)速查
完整注解列表:https://github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/nginx-configuration/annotations.md
4.1 不配置 host,用路径区分服务(Nacos 场景)
把 Nacos 控制台通过 /nacos 路径前缀反代出去,不绑定 host:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nacos-ingress
namespace: example-corp
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
spec:
ingressClassName: nginx
rules:
- http:
paths:
- pathType: Prefix
path: /nacos
backend:
service:
name: nacos-headless
port:
name: server
|
访问地址:http://内网IP-A/nacos
注意:当 host 字段省略,Ingress 会响应所有到达 controller 的请求,生产环境强烈建议绑定 host 防止冲突。如果一定要多服务复用 host,请用 pathType: Prefix 拆分路径。
4.2 app-root:访问根路径自动重定向
1
2
3
| metadata:
annotations:
nginx.ingress.kubernetes.io/app-root: /corp
|
访问 https://example-corp.com/ 会 302 到 https://example-corp.com/corp,适合老门户的默认页迁移。
五、hostNetwork 模式:性能 + 四层必备
默认 ingress-nginx-controller 是 Deployment + Service(NodePort),但生产环境有两类需求让它不够用:
- 直接监听 80/443 端口——不想用 30080/30443 这种高位端口
- 代理 TCP/UDP 四层流量——NodePort 不支持 stream
解决方案:hostNetwork: true + DaemonSet + nodeSelector,让 controller 在每个打了 ingress=true 标签的 Node 上独占监听宿主机网络。
5.1 修改清单的关键点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| spec:
# 1. 改成 DaemonSet:一个 Node 只能跑一个 controller
kind: DaemonSet
template:
spec:
# 2. 与宿主机共享网络栈
hostNetwork: true
# 3. 节点选择器
nodeSelector:
kubernetes.io/os: linux
ingress: "true"
# 4. hostNetwork 模式下要保留 K8s DNS
dnsPolicy: ClusterFirstWithHostNet
|
删除 replicas、strategy.rollingUpdate 等 Deployment 专属字段。
5.2 给 Node 打标签
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 打标签
kubectl label node k8s-master-1 ingress=true
kubectl label node k8s-master-2 ingress=true
kubectl label node k8s-master-3 ingress=true
# 移除标签
kubectl label node k8s-master-3 ingress=true-
# 容忍 NoSchedule 污点(master 节点常有)
kubectl taint nodes k8s-master-1 node-role.kubernetes.io/control-plane:NoSchedule-
# 查看所有节点标签
kubectl get node --show-labels
|
5.3 部署与验证
1
2
3
4
5
6
7
8
| # 先删旧 Deployment
kubectl delete -f /etc/k8s/ingress-nginx/deploy.yaml
# 部署新清单
kubectl apply -f /etc/k8s/ingress-nginx/hostNetwork.yaml
# 查看 controller 是否在每个 Node 跑一个
kubectl get pods -n ingress-nginx -o wide
|
1
2
3
4
| NAME READY STATUS IP NODE
ingress-nginx-controller-cd58b 1/1 Running 内网IP-B k8s-master-2
ingress-nginx-controller-cgzk7 1/1 Running 内网IP-C k8s-master-1
ingress-nginx-controller-p2fdt 1/1 Running 内网IP-D k8s-master-3
|
5.4 验证四层与七层
1
2
3
4
5
| # 七层:80 端口直接访问
curl http://nginx.example.com
# 四层:可以走 NodePort 也可以走宿主机 IP 直连
curl -H "Host: nginx.example.com" http://内网IP-A
|
六、四层代理:代理集群外的 TCP / WS / UDP
ingress-nginx 默认只代理 HTTP / HTTPS。要让 ingress-nginx 转发 TCP / UDP 流量(如 MySQL 3306、MQTT 1883、自定义 WebSocket),有三种方案:
6.1 方案对比
| 方案 | 适用场景 | 优点 | 缺点 |
|---|
① stream-snippet 注解 | 单个 Ingress 临时使用 | 配置最简单 | 不易复用 |
② ConfigMap 集中管理 | 多服务、需统一管理 | 一个 CM 管多个端口 | 要修改 DaemonSet args |
③ ExternalName + Service | 集群内 Pod 访问集群外 | 应用侧零配置 | 集群外无法反向访问 |
6.2 方案一:使用 Endpoints + Service + Ingress + stream-snippet
核心思路:用 Endpoints 直接指向集群外的 IP:PORT,再通过 Ingress 的 stream-snippet 注解把流量引过去。
⚠️ Endpoints.name 必须和 Service.name 完全一致——这是手写四层 Service 的铁律,写错 Service 永远 no endpoints available。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| # tcp-outside-3306.yaml
apiVersion: v1
kind: Endpoints
metadata:
name: external-mysql
namespace: example-corp
subsets:
- addresses:
- ip: 内网IP-E # 集群外的 MySQL 真实地址
ports:
- port: 3306
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: external-mysql
namespace: example-corp
spec:
clusterIP: None # Headless Service,由 Endpoints 决定后端
type: ClusterIP
ports:
- port: 7306 # 集群内访问用的端口
protocol: TCP
targetPort: 3306 # 转发到 Endpoints 里的 3306
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| # tcp-outside-3306-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: external-mysql-ingress
namespace: example-corp
annotations:
nginx.ingress.kubernetes.io/stream-snippet: |
server {
listen 8000;
proxy_pass 内网IP-E:3306;
}
spec:
ingressClassName: nginx
rules:
- host: mysql.example-corp.com
http:
paths:
- pathType: Prefix
path: /
backend:
service:
name: external-mysql
port:
number: 7306
|
TCP 流量不要用 HTTP 测:四层流量没有 Host 头,curl http://内网IP-A:8000 会一直 400。正确姿势:nc -vz 内网IP-A 8000 或 telnet 内网IP-A 8000。
6.3 方案二:ConfigMap 集中管理(推荐生产用)
第一步:创建 ConfigMap 占位
1
| kubectl -n ingress-nginx create configmap tcp-services
|
第二步:编辑 ConfigMap
1
| kubectl edit -n ingress-nginx cm tcp-services
|
1
2
3
4
5
6
7
8
9
| apiVersion: v1
kind: ConfigMap
metadata:
name: tcp-services
namespace: ingress-nginx
data:
# 格式:"<对外端口>": "<namespace>/<service>:<service-port>"
"7306": "example-corp/external-mysql:7306"
"1883": "example-corp/mqtt-service:1883"
|
第三步:让 controller 加载 ConfigMap
1
| kubectl edit -n ingress-nginx DaemonSet/ingress-nginx-controller
|
1
2
3
4
5
6
7
8
| spec:
template:
spec:
containers:
- args:
- /nginx-ingress-controller
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
|
重要:$(POD_NAMESPACE) 是 controller 自带的 downward API 变量,会自动展开为 ingress-nginx,不要手写死。
第四步:补充 Service 端口暴露
1
| kubectl edit -n ingress-nginx Service/ingress-nginx-controller
|
1
2
3
4
5
6
7
8
9
10
11
12
| spec:
ports:
- name: http
port: 80
targetPort: 80
- name: https
port: 443
targetPort: 443
- name: external-mysql
port: 7306
targetPort: 7306
protocol: TCP
|
第五步:调整 Service 类型与 externalIPs
1
2
3
| # 类型改为 LoadBalancer(云厂商自动分配 IP)
kubectl patch svc ingress-nginx-controller -n ingress-nginx \
-p '{"spec": {"type": "LoadBalancer", "externalIPs":["内网IP-A"]}}'
|
6.4 集群内测试连通
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| # 用 busybox 测连通
kubectl exec -it busybox -- sh
ping external-mysql.example-corp.svc.cluster.local
telnet external-mysql.example-corp.svc.cluster.local 3306
# 用 ubuntu + mycli 工具测
cat << "EOF" > ubuntu.yml
apiVersion: v1
kind: Pod
metadata:
name: diag-pod
spec:
containers:
- name: ubuntu
image: ubuntu:22.04
command: [ "/bin/bash", "-c", "--" ]
args: [ "while true; do sleep 3600; done;" ]
EOF
kubectl apply -f ubuntu.yml
kubectl exec -it diag-pod -- bash
apt update && apt install -y python3-pip
pip3 install mycli
# 用 K8s Service DNS 名连接(注意改密码)
mycli -h external-mysql.example-corp.svc.cluster.local -uroot -p'<REDACTED-MYSQL-PWD>'
|
七、TLS 终端
7.1 创建 Secret
1
2
3
4
5
6
7
8
| # 先建命名空间
kubectl create namespace example-corp
# 从证书文件创建 TLS Secret
kubectl create secret generic www-example-corp-secret \
--from-file=tls.key=<CERT_ID>__example-corp.com.key \
--from-file=tls.crt=<CERT_ID>__example-corp.com.pem \
-n example-corp
|
7.2 开启 configuration-snippet
ingress-nginx v0.46+ 默认禁用 configuration-snippet(安全考量),要自定义 nginx 片段必须先开:
1
| kubectl edit -n ingress-nginx configmap/ingress-nginx-controller
|
1
2
| data:
allow-snippet-annotations: "true" # 允许使用 configuration-snippet 注解
|
7.3 启用 TLS 的 Ingress
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
| apiVersion: v1
kind: Endpoints
metadata:
name: www-service
namespace: example-corp
subsets:
- addresses:
- ip: 内网IP-F
ports:
- port: 81
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: www-service
namespace: example-corp
spec:
ports:
- port: 81
targetPort: 81
protocol: TCP
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: www-ingress
namespace: example-corp
annotations:
nginx.ingress.kubernetes.io/app-root: /corp
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/configuration-snippet: |
access_log /var/log/nginx/www.example-corp.com.access.log upstreaminfo if=$loggable;
error_log /var/log/nginx/www.example-corp.com.error.log;
spec:
ingressClassName: nginx
tls:
- hosts:
- example-corp.com
- www.example-corp.com
secretName: www-example-corp-secret
rules:
- host: www.example-corp.com
http:
paths:
- pathType: Prefix
path: /
backend:
service:
name: www-service
port:
number: 81
- host: example-corp.com
http:
paths:
- pathType: Prefix
path: /
backend:
service:
name: www-service
port:
number: 81
|
1
| kubectl apply -f /etc/k8s/ingress/www-ingress.yaml
|
访问:https://example-corp.com https://www.example-corp.com
八、WebSocket 代理
ingress-nginx 默认支持 WebSocket,但要在 location 里显式设置:
1
2
3
4
5
6
7
8
9
| location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 3600s; # 长连接超时
}
|
在 ingress-nginx 里用注解实现:
1
2
3
4
5
| metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
# WebSocket 自动识别 Connection: Upgrade
|
默认 60s 不发包就断,WebSocket / 长轮询场景必须把 read/send timeout 拉到 3600s 以上,否则客户端会频繁断连。
九、Nacos 反代实战
Nacos Headless Service(clusterIP: None)+ Ingress 是社区最常见的"在 K8s 外面访问 Nacos 控制台"姿势:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # 假设 Nacos 已经用 Helm 或 Operator 部署好
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nacos-console
namespace: nacos
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
spec:
ingressClassName: nginx
rules:
- host: nacos.example-corp.com
http:
paths:
- pathType: Prefix
path: /
backend:
service:
name: nacos-headless
port:
name: server # 8848,对应 service.port.name
|
同样适用 Apollo、Consul、Sentinel Dashboard 等"控制台类服务"。use-regex: "true" 是因为 Nacos 前端路由里有 /#/xxx 这种 hash 路径,不开 regex 会被当成物理路径处理。
十、常见 8 个坑
| # | 坑 | 现象 | 解决 |
|---|
| 1 | 用了 extensions/v1beta1 | K8s 1.22+ 直接 apply 失败 no matches for kind | 改用 networking.k8s.io/v1,pathType 必填 |
| 2 | 忘写 pathType | networking.k8s.io/v1 apply 报 pathType Required value | 路径下必须显式 pathType: Prefix | Exact | ImplementationSpecific |
| 3 | Windows 能 ping 通但浏览器打不开 | hosts 配了域名,ping 通,浏览器 502 | 关代理、关防火墙;netsh winsock reset 重启 |
| 4 | 客户端开了代理工具后无法访问 Ingress | 浏览器挂了科学上网工具,所有流量走代理,Ingress 不通 | 代理工具"Bypass Domain/IP"里加业务域名;或选"全球直连" |
| 5 | Endpoints.name 与 Service.name 不一致 | Service 一直无 Endpoints,curl 报 no endpoints available | 严格保持两个 name 一致;这是 K8s 老坑 |
| 6 | hostNetwork 模式下 DNS 解析错乱 | Pod 用宿主机 /etc/resolv.conf,找不到集群内服务 | dnsPolicy: ClusterFirstWithHostNet |
| 7 | DaemonSet 配了 replicas 字段 | apply 时报 validation error | 删掉 replicas 和 strategy.rollingUpdate |
| 8 | configuration-snippet 不生效 | 自定义 nginx 片段被忽略 | ConfigMap 里设 allow-snippet-annotations: "true" |
| 9 | TCP 走 8000 端口但用 HTTP 测 | telnet 通,curl 失败 | 四层不能用 HTTP 探测;用 nc -vz 或 telnet |
十一、核心 6 点速记
- 链路:外网 → NodePort/hostNetwork → ingress-nginx → Service → Pod
- 七层(HTTP)用 Ingress;四层(TCP/UDP)用
stream-snippet 或 ConfigMap + DaemonSet args - 生产必开
hostNetwork:直连 80/443 + 支持四层;必须配 DaemonSet + nodeSelector + dnsPolicy: ClusterFirstWithHostNet Endpoints.name == Service.name 是手写四层 Service 的铁律- TLS 终端走 Ingress
tls.hosts + secretName;自定义 nginx 片段需先开 allow-snippet-annotations - Ingress 不直接连 Pod:中间一定要过 Service(service mesh 同样),不要图省事
十二、排错命令速查
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| # 查看 controller 配置
kubectl exec -it <pod> -n ingress-nginx -- cat /etc/nginx/nginx.conf
# 实时日志
kubectl logs -f -n ingress-nginx <pod>
# 集群内连通性
kubectl exec -it busybox -- sh
ping <svc>.<ns>.svc.cluster.local
# Service 详情
kubectl describe svc <svc-name> -n <ns>
kubectl describe endpoints <svc-name> -n <ns>
# Ingress 状态
kubectl get ingress -A
kubectl describe ingress <name> -n <ns>
# IngressClass 引用关系
kubectl get ingressclass
kubectl get ingress -A -o jsonpath='{range .items[?(@.spec.ingressClassName)]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}'
# hostNetwork 模式查看端口监听
ss -tlnp | grep -E ":(80|443|3306|1883)"
# 检查 controller 加载的 TCP/UDP ConfigMap
kubectl logs -n ingress-nginx <pod> | grep -E "tcp-services|stream-snippet"
|
十三、参考资料