☰
K8s Ingress Controller 核心YAML逐段拆解:部署、配置与排障实践
2026/10/20 15:16:57 网站建设 项目流程

说实话,很多人第一次接触 Kubernetes 里的 Ingress,第一反应就是去网上抄一份“官方 Ingress 控制器 YAML”,然后在集群里kubectl apply -f一把梭。跑起来倒是简单,但后面一旦出现 404、证书报错、Ingress 状态一直空着,就完全不知道从哪查起。原因很简单:官方那份 YAML 不是给你无脑照着执行的,它更像一张带注释的原理图——Namespace、ServiceAccount、ClusterRole、ConfigMap、Deployment、Service、IngressClass、Webhook 配置,每一个对象都有存在的理由,而且彼此之间通过命名约定和启动参数联动。这篇文章就以官方 Ingress 控制器的部署清单为线索,把核心 YAML 逐个拆开讲清楚:每段资源是干什么的,为什么必须有,改哪个参数会导致什么后果,以及怎么把它改成生产可用的形态。适合正在做集群运维的工程师,也适合那些第一次接触 Ingress 但想把原理搞明白的同学。

1. 官方 YAML 的整体设计与资源总览

1.1 一套清单里到底包含哪些资源

官方发布的管理清单通常以一个ingress-nginxNamespace 为中心,把控制器、Service、配置、权限、校验组件打包在一起。虽然不同版本文件组织不一样,有的提供单文件部署、有的按 Provider 拆了多个目录,也有一些以模板化打包方式发布的版本,但核心对象是一致的。下面这张表是我个人习惯的“对照速查”,你拿到任何一份清单都可以按这张表去对号入座:

资源类型名称(示例)作用域职责
Namespaceingress-nginx集群级隔离控制器的所有组件
ServiceAccountingress-nginx命名空间级给控制器 Pod 提供身份
ConfigMapingress-nginx-controller命名空间级保存全局 Nginx 配置参数
ClusterRole / ClusterRoleBindingingress-nginx集群级让控制器能跨命名空间监听 Ingress、Service、Secret 等
Role / RoleBindingingress-nginx-admission命名空间级给 Webhook 创建和补丁 Job 使用
Deployment(或 DaemonSet)ingress-nginx-controller命名空间级运行控制器进程
Serviceingress-nginx-controller命名空间级把 80/443 端口暴露出去
IngressClassnginx集群级声明“哪种 Ingress 归这个控制器管”
ValidatingWebhookConfiguration + Secret + Jobingress-nginx-admission集群级 + 命名空间级校验 Ingress 注解,防止非法配置进入集群

这里有个关键点:一定要分清“集群级资源”和“命名空间级资源”。Namespace、ClusterRole、ClusterRoleBinding、IngressClass、ValidatingWebhookConfiguration 是集群范围的,你删掉任何一个,整个集群里所有使用该控制器的命名空间都会受影响;而 ServiceAccount、ConfigMap、Deployment、Service、Role、RoleBinding 只存在于ingress-nginx这个 Namespace 里,误删后会连累所有 Ingress 转发。所以生产环境做清理的时候,别随手kubectl delete ns ingress-nginx,那会连集群级资源一起带走,后果比想象中严重。

1.2 Deployment 与 DaemonSet:两种形态背后的取舍

如果你去翻官方在不同部署场景下提供的清单,会发现控制器的负载类型并不统一。云环境下的推荐形态是 Deployment + LoadBalancer Service,裸金属或自建集群则是 DaemonSet + NodePort / hostNetwork。要理解这个区别,得先搞清楚控制器的本质:它本质上就是一个“配置生成器 + Nginx 进程”,负责监听 Ingress API 的变化,然后把路由规则渲染成 Nginx 配置文件并热加载。它不需要运行在每一台节点上,只需要保证对外有一个稳定的入口。

云环境有现成的负载均衡器,所以用 Deployment 部署一两个副本,前面挂一个 LoadBalancer Service,负载均衡器把流量转发到任意一个控制器 Pod 就行。裸金属集群没有云 LB 的便利,最常见做法是 DaemonSet 让每个节点都跑一个控制器 Pod,再配合 hostNetwork 直接占用节点的 80/443 端口,或者在每个节点上用 NodePort 映射出来。

对比项Deployment + LoadBalancer ServiceDaemonSet + NodePort/hostNetwork
高可用需要多副本 + 选主,依赖 LB 健康检查天然每节点一个,节点故障自动转移
端口占用只占用 Pod 内端口,节点上干净hostNetwork 时直接占用节点 80/443
客户端 IP需要正确配置 externalTrafficPolicyhostNetwork 模式天然保留源 IP
依赖组件必须要有可用的 LoadBalancer无需云 LB,但需要自己管端口规划
运维复杂度相对简单,扩容靠副本数扩缩容颗粒度到节点,升级面更大

我个人在生产环境的建议是:有云 LB 就用 Deployment + LoadBalancer,没有就用 DaemonSet,少去折腾“Deployment + NodePort”这种半吊子方案。NodePort 本身只有 30000-32767 的高位端口,配合 Ingress 控制器的 80/443 语义并不自然,后面前端切流量会很别扭。

2. 核心对象逐个拆解:权限、参数与配置

2.1 RBAC 那一段到底在授权什么

打开官方 YAML,最容易被跳过的大概就是 RBAC 那一大段,看起来又长又枯燥。但它其实是控制器能否正常工作的重要前提。控制器不是一个只读组件,它干的活包括:监听整个集群的 Ingress 和 IngressClass、读后端 Service 和 EndpointSlice、看 Secret 做 TLS 终结、改 Ingress 的 status 字段、写事件、多副本选主。每一项都需要对应的 RBAC 权限,少一个都会在日志里以“permission denied”或者“Ingress 状态一直空白”的形式暴露出来。

我这里列一份典型的授权规则速查表(不同版本可能略有出入,以你下载的清单为准):

apiGroupresourcesverbs为什么需要
空(核心组)configmaps、endpoints、nodes、pods、secrets、namespaceslist、watch监听证书、后端 endpoints、节点信息
空(核心组)servicesget、list、watch读取后端 Service 和发布用的入口 Service
空(核心组)eventscreate、patch写入事件,方便排错
networking.k8s.ioingresses、ingressclassesget、list、watch监听路由规则和控制器类
networking.k8s.ioingresses/statusupdate把负载均衡地址回写到 Ingress 的 status 字段
discovery.k8s.ioendpointslicesget、list、watch获取后端 Pod 的最新 endpoint
coordination.k8s.ioleasesget/list/watch/create/update/patch/delete多副本选主,确定谁是 Leader

注意最后一行,coordination.k8s.io/leases的权限经常被忽略。有些同学为了“最小权限”把 rules 精简到只剩 Ingress 相关,结果多副本跑起来后,两个 Pod 无法正常协调,日志里会出现 “error acquiring leader lease” 或者某个副本一直处于等待状态。其实有选主权不仅无害,反而是控制器高可用的基础。

2.2 控制器启动参数:每一行都值得读一遍

Deployment 里最值得研究的是容器 args,因为控制器的行为几乎全靠这些参数控制。官方默认清单里大致是长这样的:

containers: - name: controller image: registry.k8s.io/ingress-nginx/controller:v1.10.1 args: - /nginx-ingress-controller - --election-id=ingress-nginx-leader - --controller-class=k8s.io/ingress-nginx - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller - --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller - --validating-webhook=<webhook名称> - --validating-webhook-secret=$(POD_NAMESPACE)/ingress-nginx-admission - --watch-ingress-without-class=true env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace

逐个说:

  • --controller-class=k8s.io/ingress-nginx:这是控制器给自己贴的“类标签”。只有当 IngressClass 的spec.controller字段与这个值一致时,这个控制器才会接管对应的 Ingress。如果集群里有多个 Ingress 控制器,靠这个字段做隔离,互不抢资源。
  • --configmap=$(POD_NAMESPACE)/ingress-nginx-controller:指定全局配置 ConfigMap 的位置。控制器会监听这个 ConfigMap 的变化,改配置不需要重启 Pod,它会自动重新加载 Nginx。
  • --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller:告诉控制器把自己对应的 Service 地址写进每个 Ingress 的status.loadBalancer字段。很多自动发现组件、DNS 同步工具都依赖这个字段,如果你发现 Ingress 的 Address 一直是空的,先查这个参数。
  • --election-id=ingress-nginx-leader:多副本时选主用的 Lease 名称。副本间通过它选举出唯一的 Leader,由 Leader 负责写 status 和一致性相关的工作,避免多个副本同时操作同一份数据。
  • --validating-webhook和--validating-webhook-secret:控制器的 Webhook 校验能力。它会在 Ingress 创建/更新时做注解校验,配合 ValidatingWebhookConfiguration 使用。如果这两个参数和实际部署的 Secret、Service 名称对不上,最常见的就是创建 Ingress 时卡在校验阶段。
  • --watch-ingress-without-class=true:允许控制器接管不带ingressClassName的旧式 Ingress。单控制器集群默认打开没毛病;但如果集群有多个 Ingress 控制器,建议改成false,强迫新 Ingress 显式声明归属,避免多个控制器都去抢同一个 Ingress。

2.3 ConfigMap:控制器的“总控制台”

ConfigMap 是另一个和控制器“长在一起”的对象。官方清单里通常会先创建一个 ConfigMap,等你在里面填 key-value 后,控制器再把它们应用到 Nginx 配置里。常见的配置项比如:

kind: ConfigMap apiVersion: v1 metadata: name: ingress-nginx-controller namespace: ingress-nginx data: proxy-body-size: "20m" use-forwarded-headers: "false" ssl-redirect: "false" keep-alive: "32" allow-snippet-annotations: "false"

我的习惯是把它当成“全局默认值”而不是“每一条 Ingress 定制项”。比如proxy-body-size控制请求体大小限制,默认 1m 对上传场景显然太小;use-forwarded-headers控制是否信任X-Forwarded-*头,如果你前面还有一层反向代理,需要结合实际打开;ssl-redirect决定是否强制 HTTP 跳 HTTPS。最重要的是allow-snippet-annotations,这个从控制器 v1.6 起默认就是false,因为允许在注解里写 Nginx 片段等于开放了任意指令注入,没有强隔离机制的话风险很高。如果你的团队确实依赖 nginx snippet 注解做定制,至少要意识到这是安全边界上的妥协。

顺带提醒一句:ConfigMap 的 key 不是随便写的,控制器版本不同,支持的配置项也会不同。写完配置后如果发现没生效,先去看控制器日志里有没有 “unknown configuration” 之类的告警,而不是怀疑集群坏了。

3. 流量入口链路:Service、IngressClass 与探针

3.1 Service 暴露方式对流量路径的影响

官方典型的控制器 Service 长这样(云环境版本):

apiVersion: v1 kind: Service metadata: name: ingress-nginx-controller namespace: ingress-nginx spec: type: LoadBalancer ports: - name: http port: 80 targetPort: http protocol: TCP - name: https port: 443 targetPort: https protocol: TCP selector: app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/component: controller

这里有两个容易被忽略的细节。第一,targetPort指向的是容器端口的名字http/https,而不是数字 80 / 443。你看 Deployment 里容器端口定义的是name: http, containerPort: 80,Service 通过端口名关联。如果你自定义 Deployment 时改乱了端口名,Service 就会找不到后端。第二,externalTrafficPolicy的取值。官方示例默认不显式设置,集群会用 Cluster 模式;如果你希望保留客户端真实 IP,把externalTrafficPolicy改成Local,但代价是入口流量只会转发到“本节点上有控制器 Pod”的节点。如果负载均衡器恰好把流量打到没有控制器 Pod 的节点,可能直接丢包。Cluster 模式允许跨节点转发,转发路径更宽容,但源 IP 会被 SNAT 掉。

裸金属场景下把 Service 改成 NodePort 时,我建议干脆配合 DaemonSet 用hostNetwork: true,这样既省掉一层 SNAT,又保留真实源 IP,只是要提前做好端口规划,别和节点上的其他服务撞 80/443。

3.2 IngressClass 与 controller-class 的匹配逻辑

控制器接管哪些 Ingress,不是按命名空间分的,而是严格按照 IngressClass 匹配。官方清单里有一个集群级对象:

apiVersion: networking.k8s.io/v1 kind: IngressClass metadata: name: nginx spec: controller: k8s.io/ingress-nginx

而真正创建 Ingress 时,需要在spec里写:

apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: demo spec: ingressClassName: nginx rules: - host: demo.example.com http: paths: - path: / pathType: Prefix backend: service: name: demo-svc port: number: 80

匹配过程是:Ingress 的ingressClassName指到名为nginx的 IngressClass,IngressClass 的controller字段等于控制器的--controller-class参数。三个环节任何一个对不上,这个 Ingress 就不会被控制器生成路由。最典型的 404 场景就是:你已经把 Ingress 创建到了集群里,kubectl get ingress也能看到它,但访问域名返回 404——大概率是 Ingress 没有声明ingressClassName: nginx,而控制器的--watch-ingress-without-class又是false,两边失联。

如果你的集群只有一个控制器,把--watch-ingress-without-class=true打开确实省心;但集群里有多个控制器时,建议把不带 class 的 Ingress 视为“无人认领”,让团队在创建 Ingress 时显式写明 class,否则多个控制器可能同时生成路由规则,表现让你难以排查。

3.3 健康检查探针与端口规划

Deployment 里控制器 Pod 暴露的端口一般有 80、443、8443 和 10254。前三个好理解,10254 常被忽略。它是控制器自带的管理端口,/healthz健康检查和监控指标都在这上面。官方的存活探针和就绪探针默认是这样:

livenessProbe: httpGet: path: /healthz port: 10254 scheme: HTTP initialDelaySeconds: 10 periodSeconds: 10 readinessProbe: httpGet: path: /healthz port: 10254 scheme: HTTP initialDelaySeconds: 10 periodSeconds: 10

为什么探针不检查 80 端口而是 10254?因为 80/443 是 Nginx 的数据面端口,偶尔出现短暂的连接抖动不一定代表控制器进程挂掉了;而 10254 是控制器自己维护的健康端点,只有当控制器本身无法提供配置生成服务时才告警。在监控面板里建议同时关注 10254 的指标和 Nginx 数据面的连接状态,两者结合才能判断到底是谁的问题。

4. 生产化改造:从“能跑”到“好用”

4.1 先把官方默认配置原样跑通

很多同学一上来就改镜像 tag、加资源限制、改副本数,结果出了问题时连“官方默认是什么行为”都不知道。我的建议永远是:先用官方清单原样跑一遍,验证链路通了再动手改。

具体顺序是这样的。第一步,锁定版本去下载对应 tag 的部署清单,并把清单存进自己的代码仓库。这个动作能避免后续“版本漂移”的坑——控制器的镜像 tag 和 Webhook Job 的镜像 tag 必须保持一致,否则一个版本跑 Nginx 逻辑、另一个版本跑校验逻辑,很容易出现莫名奇妙的兼容问题。第二步,kubectl apply -f之后,先看 Pod 和 Service:

kubectl -n ingress-nginx get pods kubectl -n ingress-nginx get svc kubectl -n ingress-nginx logs deployment/ingress-nginx-controller -f

第三步,创建一个最小测试 Ingress,访问一下,同时kubectl get ingress -o wide看ADDRESS字段有没有被回写。地址字段能写出来,说明控制器与集群的联动是通的;如果不能,先回到前面的--publish-service参数检查。

4.2 多副本、资源限制与 Pod 调度

官方默认清单通常只有一个副本,生产环境至少要跑到 2 个副本才谈得上高可用。但加副本不是简单把replicas改成 2 就完事,还要注意几个点。

第一,控制器有 Leader 选举机制,多副本时只有一个 Leader 在写 Ingress status 和维护一致性,这是正常行为,别看到有一个副本日志里没有“leader”相关输出就以为它坏了。第二,建议加 PodDisruptionBudget,比如minAvailable: 1,这样集群节点维护时不会把控制器全部赶下线。第三,官方镜像默认以非 root 用户运行,并依赖NET_BIND_SERVICE能力绑定 80/443 端口,如果你自定义 securityContext,一定不要把那个能力删掉,否则 Pod 能起来但端口监听失败。

资源限制方面,我给一套比较稳妥的起步值(具体按业务压测调整):

resources: requests: cpu: 100m memory: 256Mi limits: cpu: "1" memory: 512Mi

控制器转发量上来之后,CPU 和内存都会跟着上涨,尤其是开启大量注解、复杂正则需要更多内存。建议把监控接上之后,以 7 天均值再反推合适的 limits,而不是拍脑袋写死。

4.3 裸金属与多租户场景的改动清单

裸金属环境最常见的改造是把 Deployment 换成 DaemonSet,并打开 hostNetwork。一共要改的就这么几处:负载类型改成 DaemonSet;spec.template.spec里加hostNetwork: true和dnsPolicy: ClusterFirstWithHostNet;把 Service 改成 NodePort,或者干脆用节点 IP 加端口直接访问;--publish-service照旧指向自己的 Service,这样 Ingress 的 status 字段还能显示成节点 IP。

多租户场景反过来,你可能不想让控制器看到整个集群所有命名空间的 Ingress。这时可以给控制器加--watch-namespace=某个命名空间参数,并相应收紧 RBAC 的权限范围。不过要提醒一句:Ingress 控制器在设计上更贴近“集群级共享入口”的模型,强行按命名空间拆分控制器的做法会增加维护成本,如果租户隔离要求很高,建议直接上更重的网关方案,而不是在 Ingress 控制器层面硬切。

5. 常见问题与排障记录

5.1 控制器正常但访问 404

这是出现频率最高的问题。现象是kubectl get ingress能看到 Ingress,控制器 Pod 也在 running,但 curl 域名返回 404。排查顺序可以固定成以下几步。

先看 IngressClass 和 Ingress 的匹配关系,kubectl get ingressclass看controller字段,再看 Ingress 的ingressClassName,最后确认控制器启动参数里的--controller-class,三者必须一致。再看控制器日志里有没有处理过这条 Ingress,kubectl -n ingress-nginx logs deployment/ingress-nginx-controller --tail=200 | grep 域名,如果日志里根本没有这个域名,说明控制器压根没接管到这条路由;如果日志里显示生成了配置但访问仍 404,那通常要怀疑后端 Service 的 selector 写的对不对,或者 endpoint 是否为空。很多人一看到 404 就怀疑业务 YAML 写错,其实从“谁接管了这条路由”开始查,会快很多。

5.2 Webhook 校验失败

创建 Ingress 时常见的报错长这样:failed calling webhook "validate.nginx.ingress.kubernetes.io"。大多数情况下,是 ValidatingWebhookConfiguration 里的 Service 指向和实际部署不一致,或者 CA Bundle 过期。最简单的修复方式是删掉 Webhook 相关资源,让官方 Job 重新生成一遍:

kubectl delete ValidatingWebhookConfiguration ingress-nginx-admission kubectl delete job -n ingress-nginx ingress-nginx-admission-create ingress-nginx-admission-patch

然后重新 apply 清单,让 Job 重新生成 Secret 和 CA。如果你的集群因为安全策略禁止在命名空间里创建 Secret,或者禁止 Job 访问 API,那就要手动提供证书供 Webhook 使用。这类问题在把官方清单拆成多文件、修改过 Namespace 名称时尤其容易出现——Webhook 配置里的 Service 是全限定名,Namespace 不一致就会定位失败。

5.3 Ingress 的 ADDRESS 字段为空

很多自动同步 DNS 的组件是靠 Ingress 的status.loadBalancer字段来获取入口地址的,如果这个字段一直空着,DNS 同步就会失败。原因通常只有三类:一是控制器没加--publish-service参数;二是 Service 类型是 NodePort 且没有外部 IP;三是控制器还没把状态回写成功,多副本时 Leader 选举没完成。裸金属场景下如果确实没有 LB 地址可写,可以给控制器加--publish-status-address=节点IP手动指定地址列表,让 status 字段先跳出来。

5.4 配置改了但没生效

如果修改 ConfigMap 后路由行为没有变化,先检查 ConfigMap 的 name 和 namespace 是否与--configmap参数完全一致——这里经常因为复制 YAML 时把名字写错,控制器盯着一个根本没人改的 ConfigMap 看了半天。另一个可能是配置项名字打错了,控制器日志会出现类似unknown configuration的提示,它会忽略不认识的 key,而不是报错让你知道。所以每次改完 ConfigMap,建议立刻看一遍控制器日志,确认有没有 reload 的记录。

5.5 问题速查表

现象可能原因优先排查点
访问返回 404IngressClass 未匹配 / 后端 Service 无 endpoint检查 ingressClassName 与 controller-class 链路
创建 Ingress 报 webhook 失败Webhook 配置过期或 Service 不一致重建 ValidatingWebhookConfiguration 和 Secret
Ingress status 为空缺 --publish-service / 无外部 IP查看控制器启动参数
多个副本日志报 leader 冲突Lease 权限缺失检查 coordination.k8s.io/leases 授权
改 ConfigMap 无效果key 不支持或名字不匹配看控制器日志的 reload 和 unknown configuration
后端返回 503Service 没有 ready endpointkubectl get endpoints 检查数量

6. 最后再分享几个实操习惯

6.1 版本锁定与变更记录

做 Ingress 控制器运维这几年,我个人最大的体会是:不要用“能跑就行”的心态面对控制器 YAML,也不要一上来就追求“全部自定义”。官方清单里的默认值之所以是默认值,是因为它们在绝大多数场景下是安全的理性选择;真正该自定义的是业务相关的参数——副本数、资源限制、ConfigMap 里的业务尺寸、节点调度策略。

控制器版本升级前,把新旧清单完整diff一遍是必须的动作。重点关注 RBAC 和 Webhook 相关部分的变更——这两个地方往往是升级后最先出问题的地方。另外建议把部署清单固化到自己的代码仓库里,而不是每次从网上临时拉取,这样你能知道当前集群到底跑的哪一套配置,出问题时也能快速回溯。

6.2 新集群上线的一套固定顺序

我现在接手一个新集群,流程基本固定:先把官方清单按版本 tag 下载并提交到代码仓库,记录当时集群的运行时版本;然后原样部署、跑通测试 Ingress、确认 status 回写;再做多副本、资源限制、PodDisruptionBudget 和监控;最后才谈 ConfigMap 里的业务参数调整。

这套流程看起来很慢,但踩过的坑告诉我,Ingress 控制器这种基础设施,求快反而会花更多时间去填坑。保持“默认配置先跑通,业务参数后调整”的顺序,绝大多数诡异问题都能在早期暴露出来,而不是等到业务流量上来之后才手忙脚乱。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询