Repository Reading Site
本轮操作记录:Ingress-nginx、NodePort 与北南向流量实验
这一轮我要把下面这些事做成事实: 1. 这套集群当前入口层到底是不是 `ingress-nginx` 2. Kubernetes Gateway API 在这套集群里到底有没有安装 3. IngressClass 怎样决定某个 Ingress 会不会生效 4. 外部流量怎样从 NodePort 进入 ingress controller 5. 为什么同样是打
本轮操作记录:Ingress-nginx、NodePort 与北南向流量实验
本轮目标
这一轮我要把下面这些事做成事实:
- 这套集群当前入口层到底是不是
ingress-nginx - Kubernetes Gateway API 在这套集群里到底有没有安装
- IngressClass 怎样决定某个 Ingress 会不会生效
- 外部流量怎样从 NodePort 进入 ingress controller
- 为什么同样是打
30080,不同Host会得到200 / 404 / 503 - 为什么
503常常表示“后端没活着”,不是“入口没命中”
Step 1:先核对入口底座,不做想当然假设
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n ingress-nginx get all -o wide
KUBECONFIG=~/.kube/config-k8s-lab kubectl get ingress -A
KUBECONFIG=~/.kube/config-k8s-lab kubectl get gatewayclass,gateway,httproute -A
KUBECONFIG=~/.kube/config-k8s-lab kubectl api-resources | grep -E 'Ingress|Gateway|HTTPRoute|GatewayClass'
sed -n '1,140p' SERVICE-ACCESS.md
为什么先做这一步
因为仓库里的名字和真实集群能力不一定完全等价。
例如 SERVICE-ACCESS.md 里写了:
Gateway API
但这不代表 Kubernetes 集群里真的装了 Gateway API CRD。
专家的第一反应应该是:
- 先验证资源类型是否存在
- 再验证对应 controller 是否存在
我看到的结果
ingress-nginx
- controller Deployment 存在
- Pod 正常运行
- Service
ingress-nginx-controller类型是NodePort 80:30080/TCP443:30443/TCP
集群里的 Ingress
已有一个:
dev/demo-ingress
Gateway API
实际执行 kubectl get gatewayclass,gateway,httproute -A 返回:
error: the server doesn't have a resource type "gatewayclass"
而 api-resources 里只有:
IngressClassIngress
没有:
GatewayClassGatewayHTTPRoute
原理解释
这一点非常重要。
它说明:
- 这套集群的真实北南向入口能力是
Ingress - 当前并没有安装 Kubernetes Gateway API CRD
- 文档里某个“Gateway API”名字,可能是产品入口、系统名、业务名,不等于 K8s API 一定存在
这类“先验名字”和“真实能力”分离的意识,是专家必须具备的。
Step 2:核对 IngressClass、controller 参数和现有样本
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl get ingressclass -A -o yaml
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n ingress-nginx get deploy ingress-nginx-controller -o yaml | sed -n '1,240p'
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n dev get ingress demo-ingress -o yaml
我看到的关键结果
IngressClass
只有一个:
name: nginxspec.controller: k8s.io/ingress-nginx
controller 启动参数
我看到关键参数:
--publish-service=$(POD_NAMESPACE)/ingress-nginx-controller--controller-class=k8s.io/ingress-nginx--ingress-class=nginx
现有样本
dev/demo-ingress 里明确写了:
ingressClassName: nginx
原理解释
这一步讲透了两个核心点。
第一:IngressClass 是“认领关系”的桥
controller 不是看见所有 Ingress 都处理。
它会先判断:
ingressClassName是不是自己认的- 对应 IngressClass 的
spec.controller是不是自己
第二:--publish-service 会影响 Ingress status.address
这就是为什么很多 Ingress 的 ADDRESS 字段会显示成:
- controller Service 的 ClusterIP
- 或者 LB 地址
不是凭空冒出来的。
Step 3:进入 controller 容器,确认它确实是“控制器 + 反向代理”
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n ingress-nginx exec deploy/ingress-nginx-controller -- \
sh -c 'sed -n "1,260p" /etc/nginx/nginx.conf'
为什么这一步很重要
因为我要把这件事从抽象概念变成你亲眼可见的事实:
Ingress controller 会监听 Kubernetes 对象,然后生成真正的 Nginx 代理配置。
我看到的结果
可以看到全局配置例如:
resolver 10.96.0.10access_log /var/log/nginx/access.logserver { server_name "_" ... }
原理解释
这说明:
- controller 不是一个只会调用 API 的“逻辑程序”
- 它内部真的跑着 Nginx 数据平面
- 并且会把 Kubernetes 资源翻译成 Nginx 配置
后面我还会进一步验证具体 server block。
Step 4:创建本课实验对象
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl apply -f manifests/14-ingress
创建了什么
ingress-lab 命名空间
用来隔离本课对象。
portal Deployment
作用:
- 模拟根路径
/后端
api Deployment
作用:
- 模拟
/api后端
portal-svc / api-svc
作用:
- 为两个正常后端提供 Service
broken-svc
作用:
- selector 指向不存在的
app=ghost - 故意制造“Service 存在但没有 Endpoint”的样本
app-ingress
规则:
Host = apps.k8s-lab.local/api->api-svc/broken->broken-svc/->portal-svc
ignored-ingress
规则:
Host = ignored.apps.k8s-lab.localingressClassName = ghost
目的:
- 演示“对象创建成功,但 controller 不认,所以不会生效”
ingress-client
作用:
- 作为集群内 HTTP 客户端
原理解释
这组实验的设计故意包含:
- 成功样本
- 路由命中但上游失败样本
- 对象存在但 controller 忽略样本
因为只有把这三类现象并排放,你才能真正理解:
404503- “Ingress 看起来存在但其实没生效”
到底分别对应哪一层。
Step 5:观察对象就绪后的真实状态
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n ingress-lab rollout status deployment/portal --timeout=180s
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n ingress-lab rollout status deployment/api --timeout=180s
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n ingress-lab wait --for=condition=Ready pod/ingress-client --timeout=180s
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n ingress-lab get pod,svc,ingress,endpoints,endpointslices -o wide
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n ingress-lab describe ingress app-ingress
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n ingress-lab describe ingress ignored-ingress
我看到的关键结果
Pod
portal-8fdcd8777-9mbxd->10.244.147.102api-6897b4fd7-hhx6d->10.244.147.103ingress-client->10.244.147.104
三个 Pod 都在:
wk-1
Service
portal-svc-> ClusterIP10.110.217.109api-svc-> ClusterIP10.109.19.60broken-svc-> ClusterIP10.106.225.55
Endpoints
portal-svc->10.244.147.102:8080api-svc->10.244.147.103:8080broken-svc-><none>
Ingress
app-ingressCLASS: nginxADDRESS: 10.109.198.120
ignored-ingressCLASS: ghostADDRESS:空
原理解释
这一组事实已经足够说明:
app-ingress被 controller 认领并回写了地址ignored-ingress没被任何 controller 接手broken-svc作为 Ingress 后端时,天然会成为后面503的候选样本
Step 6:验证 controller 是否真的渲染了这条 Ingress
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n ingress-nginx exec deploy/ingress-nginx-controller -- \
sh -c 'grep -n "apps.k8s-lab.local\|ignored.apps.k8s-lab.local\|broken-svc\|api-svc\|portal-svc" /etc/nginx/nginx.conf'
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n ingress-nginx exec deploy/ingress-nginx-controller -- \
sh -c 'sed -n "548,1010p" /etc/nginx/nginx.conf'
我看到的结果
配置里明确出现:
## start server apps.k8s-lab.localserver_name "apps.k8s-lab.local"service_name "broken-svc"service_name "api-svc"service_name "portal-svc"
并且具体 location 包含:
location "/broken/"location = "/broken"location "/api/"location = "/api"location "/"
但是没有看到:
ignored.apps.k8s-lab.local
原理解释
这一步是本课最能打通“控制器模式”的证据之一。
它证明:
app-ingress已经被 controller 翻译成 Nginx server block- 不同路径真的被翻译成不同 location
ignored-ingress因 class 不匹配,连配置文件都进不去
以后如果你怀疑 Ingress 规则没生效,进 controller 看生成配置是非常强的一招。
Step 7:先从集群内访问 ingress controller Service
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n ingress-lab exec ingress-client -- \
sh -c "wget -qO- --header='Host: apps.k8s-lab.local' http://ingress-nginx-controller.ingress-nginx.svc.cluster.local/"
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n ingress-lab exec ingress-client -- \
sh -c "wget -S -O- --header='Host: apps.k8s-lab.local' http://ingress-nginx-controller.ingress-nginx.svc.cluster.local/api/ 2>&1"
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n ingress-lab exec ingress-client -- \
sh -c "wget -S -O- --header='Host: apps.k8s-lab.local' http://ingress-nginx-controller.ingress-nginx.svc.cluster.local/broken 2>&1"
为什么先从集群内打
因为我要先排除公网链路、运营商、代理软件等外部干扰。
只要在集群内打 ingress controller Service 正常,我们就能先确认:
- Ingress 规则本身
- controller 本身
- 后端 Service
这一整段逻辑是成立的。
我看到的结果
/
返回:
app=portal
pod=portal-8fdcd8777-9mbxd
pod_ip=10.244.147.102
node=wk-1
route=/
/api/
返回:
HTTP/1.1 200 OK
...
app=api
pod=api-6897b4fd7-hhx6d
pod_ip=10.244.147.103
node=wk-1
route=/api
/broken
返回:
HTTP/1.1 503 Service Temporarily Unavailable
原理解释
这一步说明:
- 带正确
Host时,ingress-nginx 能正确命中 server block /和/api/已经按路径分别路由到两个后端/broken命中规则后,由于上游broken-svc没 Endpoint,所以返回503
这里非常重要的一点是:
503不是路由没命中,而是路由命中了,但 upstream 不可用。
Step 8:从 control-plane 节点本机验证 NodePort
实际命令
ssh root@107.148.176.193 \
"curl -sS -D - http://127.0.0.1:30080/ -H 'Host: apps.k8s-lab.local'"
ssh root@107.148.176.193 \
"curl -sS -D - http://127.0.0.1:30080/api -H 'Host: apps.k8s-lab.local'"
ssh root@107.148.176.193 \
"curl -sS -D - http://127.0.0.1:30080/api/ -H 'Host: apps.k8s-lab.local'"
ssh root@107.148.176.193 \
"curl -sS -D - http://127.0.0.1:30080/broken -H 'Host: apps.k8s-lab.local'"
ssh root@107.148.176.193 \
"curl -sS -D - http://127.0.0.1:30080/"
ssh root@107.148.176.193 \
"curl -sS -D - http://127.0.0.1:30080/ -H 'Host: ignored.apps.k8s-lab.local'"
ssh root@107.148.176.193 \
"curl -sS -D - http://10.10.0.1:30080/ -H 'Host: apps.k8s-lab.local'"
我看到的关键结果
正确 Host + /
返回 200 OK,正文是:
app=portal
...
正确 Host + /api
返回:
HTTP/1.1 302 Found
Location: /api/
正确 Host + /api/
返回 200 OK,正文是:
app=api
...
正确 Host + /broken
返回:
HTTP/1.1 503 Service Temporarily Unavailable
没有 Host
返回:
HTTP/1.1 404 Not Found
Host 指向 ignored-ingress
仍然:
HTTP/1.1 404 Not Found
打 10.10.0.1:30080
也能返回正常 portal 内容。
原理解释
这一组现象把入口排障最重要的几个状态全部做实了。
200
说明:
- 命中了有效 Host / Path
- upstream 存在
302
这次不是 ingress controller 自己的路由变化,而是 backend busybox httpd 对目录 /api 做了标准化跳转到 /api/
503
说明:
- 命中了
app-ingress - 命中了
/broken - 但上游
broken-svc没活跃 Endpoint
404
说明:
- 请求到了 ingress-nginx
- 但没命中任何有效虚拟主机
ignored.apps.k8s-lab.local 也返回 404,正好反证:
ignored-ingress没被 controller 接手
Step 9:核对 controller 日志,把“认领 / 忽略 / 无后端 / 请求结果”串起来
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n ingress-nginx logs deploy/ingress-nginx-controller --tail=60
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n ingress-nginx logs deploy/ingress-nginx-controller --tail=60 | grep 'ingress-lab/'
我看到的关键结果
日志里明确出现:
Found valid IngressClass ... app-ingressIgnoring ingress ... ignored-ingress ... no object matching key "ghost"Service "ingress-lab/broken-svc" does not have any active Endpoint.
同时访问日志还记录了:
GET /broken->503-> upstream nameingress-lab-broken-svc-80GET /api->302-> upstream10.244.147.103:8080GET /->200-> upstream10.244.147.102:8080
原理解释
这一层证据价值非常高,因为它把:
- 控制器认领逻辑
- 配置重载
- 后端可用性
- 最终请求结果
全部放在一条观测线上。
以后你排 Ingress 问题时,controller 日志是必查项,不是可选项。
Step 10:下沉到节点,看 NodePort 与 Service 规则
实际命令
ssh root@107.148.176.193 \
'sudo iptables-save | grep -n -C 3 -E "30080|ingress-nginx-controller:http"'
ssh root@107.148.176.193 \
'sudo iptables-save | grep -n -C 2 10.109.198.120'
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n ingress-nginx get svc ingress-nginx-controller -o yaml | sed -n '1,220p'
我看到的关键结果
Service 定义
type: NodePortclusterIP: 10.109.198.120nodePort: 30080externalTrafficPolicy: Cluster
iptables
我看到了链路:
KUBE-NODEPORTS --dport 30080 -> KUBE-EXT-CG5I4G2RS3ZVWGLKKUBE-EXT-CG5I4G2RS3ZVWGLK -> KUBE-SVC-CG5I4G2RS3ZVWGLKKUBE-SVC-CG5I4G2RS3ZVWGLK -> 10.244.119.205:80
以及 ClusterIP 规则:
10.109.198.120:80 -> KUBE-SVC-CG5I4G2RS3ZVWGLK
原理解释
这说明外部打到节点 30080 后:
- 先命中 NodePort 规则
- 进入 ingress controller 对应的 Service 链
- 最终 DNAT 到 ingress controller Pod
10.244.119.205:80
也就是说,Ingress 入口虽然表现成“七层反向代理”,但在它之前仍然有一段典型的 Kubernetes Service / kube-proxy 数据平面。
Step 11:解释为什么 127.0.0.1:30080 也能访问
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n kube-system logs ds/kube-proxy --tail=80 | grep -n 'route_localnet\|localhost'
我看到的结果
日志明确写了:
Setting route_localnet=1 to allow node-ports on localhost
原理解释
这说明当前这套 kube-proxy iptables 配置允许:
- NodePort 命中 localhost
所以:
127.0.0.1:30080
能够正常进入 NodePort 链路,并不是偶然现象。
Step 12:处理一个“工作机公网访问异常”的干扰样本
实际命令
env | grep -i proxy
curl -sS -D - http://107.148.176.193:30080/
curl --noproxy '*' -sS -D - http://107.148.176.193:30080/ -H 'Host: apps.k8s-lab.local'
我看到的结果
当前工作机里存在:
HTTP_PROXY=http://127.0.0.1:7890HTTPS_PROXY=http://127.0.0.1:7890NO_PROXY=localhost,127.0.0.1
而直接从这台工作机打公网 NodePort 时,一度出现了:
502 Bad Gatewaycurl: (52) Empty reply from server
原理解释
这一步很有实战意义。
因为它提醒你:
入口异常不一定都在 Kubernetes 内部,客户端本机网络环境也可能干扰结果。
所以我在本课里采用了更稳妥的方法:
- 先从集群内访问 controller Service
- 再从节点本机访问 NodePort
- 最后才看工作机公网访问
这就是把问题边界一层层缩小的标准工程方法。
Step 13:整理本轮最重要的结论
结论 1:这套集群当前用的是 Ingress,不是 Kubernetes Gateway API
这是事实验证得出的,不是名称猜测。
结论 2:Ingress 对象不会自己转发流量,controller 才会
ignored-ingress 已经把这一点证明得很清楚。
结论 3:404 和 503 代表的层次不同
404更偏“没命中有效路由”503更偏“命中了路由,但后端不可用”
结论 4:NodePort 只是入口引流层,真正的 HTTP 路由发生在 ingress controller
这是四层与七层分工的典型体现。
结论 5:入口问题必须分层排查
不能把所有错误都笼统叫做:
- “Ingress 不通”
本轮最值得记住的命令与用途
kubectl get ingressclass
看 controller 能认哪些 IngressClass。
kubectl describe ingress
看 Ingress 是否被认领、是否有地址、是否有同步事件。
kubectl logs deploy/ingress-nginx-controller
看 controller 是否忽略对象、是否发现后端没 Endpoint、是否重载配置。
kubectl exec ... /etc/nginx/nginx.conf
看 Ingress 规则是否真的被渲染进 Nginx。
curl -H 'Host: ...'
验证虚拟主机和路径路由。
iptables-save
看 NodePort 和 Service 在节点上的真实转发表。
这一课给你的专家能力提升点
如果你把这轮实验真正吃透,你已经具备了:
- 把入口问题拆成四层引流和七层路由两段来分析的能力
- 判断 Ingress 有没有被 controller 正确认领的能力
- 区分
404与503故障层级的能力 - 下沉到
nginx.conf和iptables做实证排障的能力
这已经是平台工程、架构设计和生产事故排查里非常核心的一块基本功。