Minikube手动安装Ingress-nginx实战:从部署到排错全解析
在Kubernetes的入门阶段Minikube几乎是所有人绕不开的第一个实验场。它把“一套集群”压缩成一句minikube start几分钟内就能在你本地机器上跑起一个单节点Kubernetes环境。但集群跑起来之后很多人会卡在同一个地方我该怎么把外部流量真正送进集群里的业务PodService类型有ClusterIP、NodePort、LoadBalancer选了NodePort却发现端口不好记、还要手动拼接IP和端口实在不够优雅。这时候就轮到Ingress出场了。标题里写的是“手动安装Ingress-nginx”这一点很关键。因为Minikube其实提供了一个一行命令的捷径minikube addons enable ingress它会在集群里自动部署好一套nginx Ingress Controller。既然有捷径为什么还要手动装我的答案是手动安装能让你把这个组件从头到尾看个明白知道它到底创建了哪些资源、以什么方式监听流量、遇到问题该从哪里下手排查。而且手动安装的流程和你在真实云环境、生产环境里做的几乎一致将来换到真正的Kubernetes集群这套经验能直接迁移。这篇文章我就把完整的安装、验证、排错过程一步步拆开讲清楚每个环节背后的逻辑给后面还要踩坑的朋友做个参考。1. 为什么选择手动安装Ingress-nginx1.1 快速方案的问题在哪Minikube自带的addons方案确实非常省事执行minikube addons enable ingress之后它会自动跑到ingress-nginx的发布页拉取对应的资源清单然后在你集群里一次性铺开所有组件。表面上看起来是“一键完成”但这个黑盒也会带来几个实际问题。第一个问题是版本不透明。addons内置的ingress-nginx版本往往跟随Minikube的发布节奏你想知道自己集群里跑的Controller到底是哪个版本必须去看镜像标签或者发布日志不可能由你来指定一个特定版本。可实际生产环境中Ingress Controller版本和Kubernetes集群版本是强相关的官方发布页会对每个Kubernetes版本列出兼容的Controller版本范围装错版本轻则功能异常重则启动失败。addons是没法让你做这种精细控版的。第二个问题是故障排查困难。用addons装完之后你如果想理解“为什么流量没有转发到我的服务”会发现资源清单被Minikube封装了起来想改配置得绕过它的管理逻辑有时候还要去翻minikube的源码才能搞清楚它是怎么拼装这些资源的。这对学习阶段的人来说并不友好。1.2 手动安装的不可替代价值手动安装本质上是执行kubectl apply -f deploy.yaml把一份由官方维护、充满注释和结构化定义的资源清单应用进集群。整个过程中你会亲眼看到Namespace、ServiceAccount、ClusterRole、ConfigMap、Deployment、Service、ValidatingWebhookConfiguration这些资源一个个被创建出来。这一步做完你对Ingress Controller的资源组成就有了具体认知而不是停留在“它是一个会拦截流量的东西”这种模糊印象上。另一个关键原因是这套操作和生产高度一致。你在云厂商的托管Kubernetes集群里、在裸机集群里如果想要部署ingress-nginx官方提供的同样是一份或多份部署清单操作路径和本地手动安装几乎相同。先在这个小集群里把流程跑通后续遇到生产环境需要部署同类型组件时可以直接复用这套经验。还有一点手动安装之后你还可以顺手把ingress-nginx的Service从LoadBalancer类型改成NodePort或者接入MetalLB理解不同流量入口方案的差异。这些实验性操作很难在addons封闭管理下自由进行但手动安装可以让你随便折腾。2. 动手前的环境准备与版本匹配2.1 Minikube集群的启动配置安装ingress-nginx之前先要有一个状态健康的Minikube集群。这个步骤看似基础但有几个参数会直接影响后面Controller的运行值得提前交代清楚。建议启动命令是这样的minikube start --driverdocker --cpus2 --memory4096 --kubernetes-versionv1.26.0解释一下这里的几个关键参数。--driverdocker是当前最省事的驱动方式需要本机装好Docker它会创建一个容器作为Minikube的节点。--memory4096比较重要ingress-nginx Controller自身占用的内存不算高但如果后面还要部署测试用的业务Pod、做各种验证内存太小很容易出现节点资源不足导致Pod被驱逐Evicted。我第一次用默认的2G内存跑结果连着起了好几个Pod之后Controller就被Evicted了Pod状态一直显示Eviction那个排查过程挺折磨人的。--kubernetes-version指定集群版本这里我选了1.26.0作为示例。选择这个版本是因为对应版本的ingress-nginx很好找后续演示中使用的Controller版本v1.9.x对1.26的支持非常成熟。如果你不确定自己该选哪个版本可以去ingress-nginx的GitHub Release页面查看每个Controller版本对应的“Supported Kubernetes versions”说明那里有一个明确的兼容性表格。启动完成后先确认两个信息kubectl cluster-info kubectl get nodes确保控制平面正常、节点处于Ready状态。2.2 镜像拉取与网络前置条件ingress-nginx的Controller镜像默认从registry.k8s.io拉取这个地址从Kubernetes早期就一直是官方镜像仓库但在部分网络环境下访问可能比较慢甚至失败。如果遇到ImagePullBackOff的报错常规做法是提前把镜像拉到本地然后通过加载本地镜像的方式喂给Minikube。minikube image load registry.k8s.io/ingress-nginx/controller:v1.9.6 minikube image load registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.1这里要注意的是你从deploy.yaml里看到的镜像地址是什么本地加载时就必须保持一致如果deploy.yaml里写的是带sha256摘要的地址比如registry.k8s.io/ingress-nginx/controllersha256:xxx那么光load镜像还不够还需要把deploy.yaml里的镜像地址改成你本地load好的标签形式。实际中更省事的方式是直接修改deploy.yaml中的镜像地址替换为你本地可访问的镜像仓库域名这个我们在下一步安装时具体说。2.3 版本匹配原则很多新手在安装ingress-nginx时遇到诡异问题最后发现根源是版本不匹配。我整理一下基础原则Kubernetes 1.26到1.29的集群用ingress-nginx v1.8.x到v1.9.x基本没问题Kubernetes 1.28以上的集群可以尝试v1.10.x。每次发布新版本时官方release页面都会给出类似这样的兼容表Controller版本支持的最低K8s版本建议的K8s版本范围v1.8.11.251.25 - 1.28v1.9.61.261.26 - 1.29v1.10.11.281.28 - 1.30版本匹配不是我严谨而是这个组件跟Kubernetes API的耦合度确实比较高。比如旧版本的Controller使用networking.k8s.io/v1beta1的Ingress API在Kubernetes 1.22之后就没有了如果装得太老Controller根本起不来。反过来新版本Controller放在旧版本集群上可能引用了旧集群不存在的API或字段。这点在动手前必须确认清楚。注意不要光看Controller镜像版本还要关注它对应的Helm chart或deploy清单版本。镜像和清单经常不是同一个版本号直接拿新版镜像套旧版清单非常容易出问题。3. 安装从下载清单到资源生效3.1 获取官方部署清单接下来进入正题。打开ingress-nginx的GitHub仓库在release页面找到controller-v1.9.6它的资源清单路径一般是https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.9.6/deploy/static/provider/cloud/deploy.yaml下载下来curl -LO https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.9.6/deploy/static/provider/cloud/deploy.yaml这里选择的provider是cloud因为这个版本默认会创建一个类型为LoadBalancer的Service。在Minikube里LoadBalancer类型不会自动分配外部IP除非你后面启动了minikube tunnel或者把这类型改成NodePort手动接管。所以cloud版本反而是最适合“手动”安装场景的因为多出来的一步操作——改Service类型——正好能把Service的运作机制看明白。建议先看看这个文件里有哪些内容别急着applygrep -E ^kind: deploy.yaml | sort | uniq -c看到的结果大概有Namespace、ServiceAccount、ClusterRole、ClusterRoleBinding、ConfigMap、Service、Deployment、ValidatingWebhookConfiguration这些类型。它们的作用可以简单理解为Controller需要一套RBAC权限去监听Ingress资源和Service/Endpoint的变化需要一份ConfigMap保存配置参数需要Deployment保证自己以多副本或单副本方式运行需要ValidatingWebhook在用户提交Ingress规则时做校验。3.2 调整Service类型与镜像地址在apply之前有两处需要提前调整如果不调也不是不能用但要么体验不佳要么可能拉不到镜像。第一处把Service的类型改为NodePort。用编辑器打开deploy.yaml找到这一段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把spec.type的值改成NodePort。这样改完之后Service会分配一个30000到32767之间的端口我们后续就是通过这个端口访问Ingress Controller。第二处如果你所在的网络环境拉取registry.k8s.io镜像不畅把Deployment里containers.image和initContainers里的镜像地址都改成本地可用的镜像仓库地址。比如image: your-local-registry.example.com/ingress-nginx/controller:v1.9.6 image: your-local-registry.example.com/ingress-nginx/kube-webhook-certgen:v1.4.1initContainers里的kube-webhook-certgen负责为Admission Webhook生成证书它只在启动时跑一下用完即退但也必须能被拉取到。注意修改镜像地址时只能改域名部分版本号必须保留原样否则Controller版本和集群API的兼容性就失控了。3.3 应用清单并等待Controller就绪确认改动无误后执行kubectl apply -f deploy.yaml这一步理论上不会有任何报错因为它只是把清单交给API Server处理。真正值得等待的是Pod状态的变化。执行kubectl get pods -n ingress-nginx -w正常情况下会看到几个initContainer依次跑完然后主容器进入Running状态。其中initContainer运行时间一般几秒钟只要不出错接下来controller容器就正常启动了。用一条更容易判断成功的命令kubectl wait --namespace ingress-nginx \ --forconditionready pod \ --selectorapp.kubernetes.io/componentcontroller \ --timeout180s这条命令会一直阻塞到Controller Pod变成Ready超时时间为180秒。如果成功后面该做什么都很清楚了。如果一直Pending或ImagePullBackOff那就按照第2.2节的方法处理镜像问题。3.4 确认Controller的对外端口Service类型改成NodePort后检查一下端口分配情况kubectl get svc -n ingress-nginx输出中可以看到ingress-nginx-controller这一行有两个端口80端口映射到了类似30567这样的随机高位端口443端口映射到另一个端口。整个Ingress链路中这个NodePort就是我们所有HTTP请求的入口。注意Ingress Controller自己运行在Pod里监听的是Pod网络上的80和443端口它并不关心NodePort到底分配了多少。NodePort只是Kubernetes把宿主机的某个端口转发到Service再把Service的流量定向到Pod的手段。提示NodePort端口是随机分配的如果你希望固定端口可以改Service配置在ports下显式声明nodePort字段取值范围30000到32767。虽然固定端口方便记忆但也要小心与宿主机上其他进程的端口冲突。4. 功能验证装完不等于能用4.1 准备一个最小的测试服务Controller安装完先别急着欢呼不验证一下你不能确定流量链路是否真的通了。验证Ingress需要准备一个后端服务并用Ingress规则把域名和路径映射到它上面。为了简单可控我用了nginx镜像把它的首页替换成一段固定文字kubectl create deployment hello-app --imagenginx:latest kubectl expose deployment hello-app --port80 --target-port80 --namehello-svc如果你更喜欢用现成的echo服务也可以用gcr.io/google-containers/echoserver镜像它会返回请求的Header、路径等信息调试时更方便。但nginx更常见方便你后续做一个自定义页面来测试不同路径。确认一下Service确实关联到了Podkubectl get endpoints hello-svcEndpoints里应该有Pod的IP。如果Endpoints为空说明Service的selector没有匹配到任何Pod这种低级错误会导致Ingress转发时502这里提前看到能省很多排查时间。4.2 创建Ingress规则并理解匹配逻辑创建一个Ingress资源kubectl apply -f - EOF apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: hello-ingress spec: ingressClassName: nginx rules: - host: hello.test http: paths: - path: / pathType: Prefix backend: service: name: hello-svc port: number: 80 EOF这里有两个容易踩坑的字段需要讲清楚。第一个是spec.ingressClassName从Kubernetes 1.22开始这个字段替代了旧版注解kubernetes.io/ingress.class值必须和Controller启动参数里的--ingress-class一致。ingress-nginx默认的ingressClass就是nginx所以这里填nginx。如果没有填这个字段新版的Ingress Controller很可能不会接管这条规则规则会一直停留在“没有关联Controller”的状态。第二个是pathType我用了Prefix。它的含义是所有以/开头的路径都匹配这也意味着hello.test/任意路径都能转发到hello-svc。如果你只想匹配精确路径就改成Exact。4.3 通过Host头模拟域名访问因为Minikube集群没有外部DNS我们也懒得去改本机的/etc/hosts临时实验尽量不留系统级配置所以直接通过curl的resolve参数把域名解析到Minikube IP即可。先拿到Minikube节点的IPminikube ip比如输出是192.168.49.2。然后找到NodePort端口假设是30567访问命令长这样curl --resolve hello.test:80:192.168.49.2 http://hello.test/ -H Host: hello.test http://192.168.49.2:30567/前面的--resolve其实已经指定了域名和IP的映射后面-H Host可以省略但写出来能让你更直观理解Ingress匹配的是HTTP请求中的Host字段而不是域名背后的IP。curl实际访问的是192.168.49.2的30567端口但HTTP请求头里带着Host: hello.testIngress Controller看到这个Host之后就会去寻找Host为hello.test的Ingress规则找到后把流量转发给hello-svc再到Pod里的nginx。如果一切正常你会看到nginx默认欢迎页的HTML内容。如果你设置了自定义首页则会看到自己写的那段文字。4.4 加上路径重写验证更多规则确认基本链路通了之后我建议再实验一个带路径的情况这是以后实际业务中最常见的需求。比如创建一个带rewrite的Ingress规则kubectl apply -f - EOF apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: hello-ingress-rewrite annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: ingressClassName: nginx rules: - host: hello.test http: paths: - path: /app pathType: Prefix backend: service: name: hello-svc port: number: 80 EOF这条规则下访问http://hello.test/app时的请求会被转发到后端服务时重写成/。注解rewrite-target: /的意思是进入Ingress Controller时路径/app会在转发前被替换成/后端nginx收到的请求路径就是/而不是/app。如果你不加这个注解后端Pod收到的还是/app路径如果你的后端服务路由里没有定义/app就会404。遇到302跳转、静态资源加载不出来这类问题十有八九就是重写规则没写对。5. 常见问题与排查思路5.1 Pod一直Pending或ImagePullBackOff解决思路分两步走。先看Pending的原因用kubectl describe pod查看Events如果是Insufficient memory或者Insufficient cpu那就是Minikube节点资源不够可能需要先删除部分测试负载或者重新创建资源更大的集群。如果是ImagePullBackOff看具体报错拉取超时或者404都很常见按前面说的方法处理镜像地址。还有一个隐藏问题如果你之前用addons装过ingress-nginx又手动安装一遍新老资源的镜像拉取策略会互相干扰。建议手动安装前先minikube addons disable ingress把旧的清理干净或者干脆重建一个干净的Minikube集群。5.2 404 Not Found但Controller看起来正常这种情况最让人头大。Pod是RunningService也正常但访问域名就是404。排查顺序很重要我一般从下面几个方向入手。第一查Ingress规则是否被你创建的Controller接管。执行kubectl get ingress -A如果ADDRESS列是空的很可能ingressClassName没对上或者有多个Ingress Controller并存导致规则没有被pick up。第二确认请求路径是否真的匹配到了后端。404很可能是Ingress规则确实生效了但路径匹配规则太严格导致找不到对应的path。比如你访问的是/app但Ingress里只写了/规则理论上Prefix匹配应该能转发但如果后端没有处理/app路径的handler默认就是404。这时候用rewrite-target注解把路径重写为/会有效。第三看看Controller日志里有没有线索kubectl logs -n ingress-nginx -l app.kubernetes.io/nameingress-nginx日志中会打印出每个请求的匹配信息包括它匹配到了哪条Ingress规则、转发的上游IP、响应状态码。有这份日志排查效率和摸黑完全不一样。5.3 502 Bad Gateway的排查思路502通常表示Ingress Controller已经找到了Ingress规则但转发到后端时失败。最常见的原因是Service的后端Endpoints为空。检查方法前面提到过就是kubectl get endpoints hello-svc。如果Endpoints为空就看Service的selector是否匹配到了Pod的标签然后检查Pod自身是否健康比如CRASHED或Ready为False也会导致Endpoint被剔除。5.4 Webhook校验导致的Ingress提交失败提交Ingress规则时报这样的错Internal error occurred: failed calling webhook validate.nginx.ingress.kubernetes.io这说明ValidatingWebhookConfiguration已经注册但Controller的Webhook服务还没就绪或者证书有问题。一个常见场景是刚apply完deploy.yaml就立刻创建Ingress此时Webhook后端还没起来自然校验失败。解决方式很简单等到Controller Pod Ready之后再创建Ingress。另一个常见原因是改用自定义镜像或自定义namespace之后Webhook配置里指向的Service名称/命名空间路径没同步更新导致Kubernetes API Server无法访问到Webhook服务。这时候检查ValidatingWebhookConfiguration里的clientConfig.service确认namespace和name与实际的Controller Service一致。小技巧修改Webhook配置出问题后如果不想等待API Server超时可以把ValidatingWebhookConfiguration里的failurePolicy改成Ignore临时绕过校验等Controller正常后再改回Fail。但这个方法只适合本地实验生产环境不建议用。5.5 本地环境访问不到NodePort端口如果你在Minikube所在宿主机上curl NodePort不通先确认Service类型确实是NodePortkubectl get svc -n ingress-nginx。如果类型显示LoadBalancer说明你apply的清单没有被修改重新改配置再apply一遍或者直接编辑Servicekubectl edit svc ingress-nginx-controller -n ingress-nginx把type改成NodePort保存后立即生效。还有一类情况是防火墙或Docker网络隔离导致的尤其是使用Docker驱动时宿主机和容器节点之间偶尔会有网络端口映射的延迟多等几秒再试或者用minikube service ingress-nginx-controller -n ingress-nginx这个命令让Minikube自动帮你打开浏览器访问。6. 手动安装后的扩展方向安装好Ingress Controller只是第一步它的配置能力非常强建议顺着下面几个方向继续实验会收获更多。TLS证书配置是最优先要做的。给域名配上自签名证书或者接入cert-manager自动签发证书核心是创建包含证书内容的Secret然后在Ingress规则中指定tls字段和secretName。完成这一步之后你的本地集群就具备了和线上一致的HTTPS访问能力。其次是配置Controller自带的可观测性。ingress-nginx会暴露一个10254端口的/metrics接口默认会抓取连接数、请求数、延迟等指标。配合Prometheus和Grafana你能看到一个请求从进入Ingress到转发给后端Pod的完整数据链路。在本地集群搭一套Prometheus堆栈并不复杂但收获很大。还可以试试多副本和优雅升级。把Deployment的副本数从1改成2Controller会自动在多副本间共享配置滚动更新时无感知。这样可以直观理解Controller无状态部署的特性也能发现和多副本相关的资源约束问题。7. 写在最后的经验手动安装过一次ingress-nginx之后我最大的感受是很多技巧都无法靠“一行命令”获得。比如我从addons方案切到手动安装时一度遇到规则不生效但Controller日志毫无异常的怪事后来发现是旧的addons Controller和手动安装的Controller同时在集群里解剖同样的Ingress资源。因为两个Controller都监听了同一个Ingress流量被旧实例截走新实例却干瞪眼。这种情况除非你理解Ingress Controller的注册与监听原理否则排查起来完全没有头绪。所以我的建议是即便你用addons或Helm把ingress-nginx装好了也值得找一台干净环境手动应用一遍官方deploy.yaml把所有资源过一遍脑子。等你亲手把Service类型改来改去、亲手修改Webhook配置、亲手验证过一条完整的Host和Path转发规则下次遇到生产环境的Ingress问题你就知道该看Pod日志、该查Endpoints、该翻Webhook配置而不是对着一个404页面发呆。手动安装不是目的理解整个资源模型和请求链路才是。这篇记录里提到的所有命令和排查步骤都是我自己在Minikube上反复操作后沉淀下来的方法。如果你也正在本地学习Kubernetes建议照着走一遍然后拆掉重来再走一遍。Ingress这套东西装一遍会排一次错才算真正掌握。