K8s Lab 把当前仓库文档整理成一个可阅读的网页站点

Repository Reading Site

本轮操作记录:Ingress-nginx、NodePort 与北南向流量实验

这一轮我要把下面这些事做成事实: 1. 这套集群当前入口层到底是不是 `ingress-nginx` 2. Kubernetes Gateway API 在这套集群里到底有没有安装 3. IngressClass 怎样决定某个 Ingress 会不会生效 4. 外部流量怎样从 NodePort 进入 ingress controller 5. 为什么同样是打

Markdown14-操作记录-Ingress-nginx-NodePort与北南向流量实验.md2026年4月10日 06:56

本轮操作记录:Ingress-nginx、NodePort 与北南向流量实验

本轮目标

这一轮我要把下面这些事做成事实:

  1. 这套集群当前入口层到底是不是 ingress-nginx
  2. Kubernetes Gateway API 在这套集群里到底有没有安装
  3. IngressClass 怎样决定某个 Ingress 会不会生效
  4. 外部流量怎样从 NodePort 进入 ingress controller
  5. 为什么同样是打 30080,不同 Host 会得到 200 / 404 / 503
  6. 为什么 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/TCP
  • 443: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 里只有:

  • IngressClass
  • Ingress

没有:

  • GatewayClass
  • Gateway
  • HTTPRoute

原理解释

这一点非常重要。

它说明:

  1. 这套集群的真实北南向入口能力是 Ingress
  2. 当前并没有安装 Kubernetes Gateway API CRD
  3. 文档里某个“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: nginx
  • spec.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.10
  • access_log /var/log/nginx/access.log
  • server { 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.local
  • ingressClassName = ghost

目的:

  • 演示“对象创建成功,但 controller 不认,所以不会生效”

ingress-client

作用:

  • 作为集群内 HTTP 客户端

原理解释

这组实验的设计故意包含:

  • 成功样本
  • 路由命中但上游失败样本
  • 对象存在但 controller 忽略样本

因为只有把这三类现象并排放,你才能真正理解:

  • 404
  • 503
  • “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.102
  • api-6897b4fd7-hhx6d -> 10.244.147.103
  • ingress-client -> 10.244.147.104

三个 Pod 都在:

  • wk-1

Service

  • portal-svc -> ClusterIP 10.110.217.109
  • api-svc -> ClusterIP 10.109.19.60
  • broken-svc -> ClusterIP 10.106.225.55

Endpoints

  • portal-svc -> 10.244.147.102:8080
  • api-svc -> 10.244.147.103:8080
  • broken-svc -> <none>

Ingress

  • app-ingress
    • CLASS: nginx
    • ADDRESS: 10.109.198.120
  • ignored-ingress
    • CLASS: ghost
    • ADDRESS:

原理解释

这一组事实已经足够说明:

  1. app-ingress 被 controller 认领并回写了地址
  2. ignored-ingress 没被任何 controller 接手
  3. 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.local
  • server_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

原理解释

这一步是本课最能打通“控制器模式”的证据之一。

它证明:

  1. app-ingress 已经被 controller 翻译成 Nginx server block
  2. 不同路径真的被翻译成不同 location
  3. 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

原理解释

这一步说明:

  1. 带正确 Host 时,ingress-nginx 能正确命中 server block
  2. //api/ 已经按路径分别路由到两个后端
  3. /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-ingress
  • Ignoring ingress ... ignored-ingress ... no object matching key "ghost"
  • Service "ingress-lab/broken-svc" does not have any active Endpoint.

同时访问日志还记录了:

  • GET /broken -> 503 -> upstream name ingress-lab-broken-svc-80
  • GET /api -> 302 -> upstream 10.244.147.103:8080
  • GET / -> 200 -> upstream 10.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: NodePort
  • clusterIP: 10.109.198.120
  • nodePort: 30080
  • externalTrafficPolicy: Cluster

iptables

我看到了链路:

  • KUBE-NODEPORTS --dport 30080 -> KUBE-EXT-CG5I4G2RS3ZVWGLK
  • KUBE-EXT-CG5I4G2RS3ZVWGLK -> KUBE-SVC-CG5I4G2RS3ZVWGLK
  • KUBE-SVC-CG5I4G2RS3ZVWGLK -> 10.244.119.205:80

以及 ClusterIP 规则:

  • 10.109.198.120:80 -> KUBE-SVC-CG5I4G2RS3ZVWGLK

原理解释

这说明外部打到节点 30080 后:

  1. 先命中 NodePort 规则
  2. 进入 ingress controller 对应的 Service 链
  3. 最终 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:7890
  • HTTPS_PROXY=http://127.0.0.1:7890
  • NO_PROXY=localhost,127.0.0.1

而直接从这台工作机打公网 NodePort 时,一度出现了:

  • 502 Bad Gateway
  • curl: (52) Empty reply from server

原理解释

这一步很有实战意义。

因为它提醒你:

入口异常不一定都在 Kubernetes 内部,客户端本机网络环境也可能干扰结果。

所以我在本课里采用了更稳妥的方法:

  1. 先从集群内访问 controller Service
  2. 再从节点本机访问 NodePort
  3. 最后才看工作机公网访问

这就是把问题边界一层层缩小的标准工程方法。


Step 13:整理本轮最重要的结论

结论 1:这套集群当前用的是 Ingress,不是 Kubernetes Gateway API

这是事实验证得出的,不是名称猜测。

结论 2:Ingress 对象不会自己转发流量,controller 才会

ignored-ingress 已经把这一点证明得很清楚。

结论 3:404503 代表的层次不同

  • 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 正确认领的能力
  • 区分 404503 故障层级的能力
  • 下沉到 nginx.confiptables 做实证排障的能力

这已经是平台工程、架构设计和生产事故排查里非常核心的一块基本功。