1. 先搞懂一件事为什么有了Service还不够还要Ingress我们从上手K8s第一天起就一直跟Service打交道。一开始用ClusterIP集群内部访问服务后来想从外面访问打上NodePort把端口暴露到节点上再往后上云了整一个LoadBalancer云厂商给一公网IP流量走进来。按三部曲走好像什么场景都能应付。但实际做项目问题就来了。一个服务端口暴露三四个外部访问要记一堆IP和随机的NodePort端口。服务稍微多一点整个端口规划乱成一团。最尴尬的是NodePort凭端口号区分服务生产环境几乎没法用——你不能让用户访问的时候还要带上奇怪的三万端口吧。这时候Ingress就出来了。Ingress干的是一件很本质的事情把原本一个服务一个入口IPPort的模型升级成一个入口按域名和路径分发到多个服务的模型。你可以把它理解成K8s集群的前置网关或者更直白一点——K8s里的反向代理规则管理器。它让外部流量先打到一个统一入口再由入口按照URL规则分流到各个Service整套链路就走通了。这篇文章是我学习K8s的第十二篇笔记聊的就是Service体系里的Ingress部分。我会从原理讲到实战包含坑和排错手段把Ingress Controller安装、Ingress规则编写、路径重写、TLS、灰度发布这些都过一遍。适合已经会用Service、想进一步把对外暴露方式做规范化的朋友如果你连Service的三种类型还没搞明白建议先把前面那篇翻出来读一遍再回来。2. Ingress的运作机制拆解——Controller和Ingress资源各管什么事2.1 你创建的不是流量入口而是一个“声明”刚接触Ingress的人最容易产生一个误解以为创建了一个Ingress资源集群入口就自动出现了。实际上Ingress这个对象本身不干活它只是一份流量规则的声明——声明了哪个域名、哪个路径转发到哪个Service的哪个端口。真正落地这份声明、承担流量转发工作的是Ingress Controller。这点要分得非常清楚否则后面排查问题的时候你会疯掉。打个比方Ingress资源相当于你给快递公司贴了一张配送单写明这个包裹送到哪栋楼哪个房间而Ingress Controller才是那个真正的配送员他跑到这张单子把包裹送到位。Ingress Controller是一个独立运行在集群里的工作负载一般是Deployment形式部署的它会持续监听API Server上Ingress资源的变化。一旦有新的Ingress被创建Controller就把它翻译成自己的配置比如Nginx风格的upstream和server配置然后重新加载配置让流量开始按规则转发。2.2 一个Ingress对象里到底能写什么看一个Ingress的YAML是最直观的。拿一个典型例子apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: demo-ingress namespace: default annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: ingressClassName: nginx tls: - hosts: - demo.example.com secretName: demo-tls-secret rules: - host: demo.example.com http: paths: - path: /api pathType: Prefix backend: service: name: api-service port: number: 8080 - path: /web pathType: Prefix backend: service: name: web-service port: number: 80核心就三块。第一块是ingressClassName。它声明这份规则要交给哪个Controller来处理。集群里可以装多套不同的Ingress Controller比如社区常用的nginx-ingress泛指一类基于Nginx内核的解决方案下文统称为Nginx Ingress、还有集群内部自带的那种默认Controller。通过这个字段指定用哪套互不干扰。第二块是rules。每条rule包含一个可选host域名以及一组HTTP路径映射。每个path对应一个backendbackend就是后端的Service名称和端口。如果rule里没有写host就代表该规则匹配所有未指定域名的流量也就是默认规则。第三块是tls。它声明了哪些域名走HTTPS证书秘钥放在哪个Secret对象里。这个Secret的类型是kubernetes.io/tls里面需要按标准格式包含tls.crt证书和tls.key私钥。2.3 流量完整链路从域名到Pod要经过几道弯从用户请求到Pod整条链路的顺序是用户请求某个域名 -- DNS解析到Ingress Controller的负载均衡器/节点IP -- 经过Ingress Controller按Ingress规则做路由匹配 -- 转发到对应的Service ClusterIP -- 由Service的Endpoints转发到某个后端Pod注意Ingress Controller转发到后端时默认走的是Service的ClusterIP。这意味着即使你后端Service选的类型是ClusterIP只要Ingress规则正确解析到Service名流量就能从外部到达Pod。中间每一环都有可能出现断点这也是后面排查问题的主线逻辑DNS是否解析到了Controller - 流量是否命中了Ingress Controller的监听端口 - Controller规则是否匹配了正确的Service - Service后端Pod是否健康。3. 环境准备装一套可用的Ingress Controller3.1 怎么选Controller——不是只有Nginx一种在开始动手之前先把Controller的选型讲清楚。K8s社区的生态里Ingress Controller的常见方案有这么几类基于Nginx内核的Ingress Controller社区最流行资料最多功能覆盖最全。我们日常说的ingress-nginx一般就是它。企业内部自建集群绝大多数默认选它。基于HAProxy的方案性能很好但社区资料和配置示例相对少一些。云厂商自带的原生Ingress比如在托管的集群里直接用厂商提供的入口网关好处是跟云负载均衡器深度集成但规则字段和社区Nginx的方案不完全一致。我自己平时的选择很简单自建集群一律用基于Nginx核心的方案因为资料多、排错有人问、踩坑经验满地都是。如果你用的是托管集群那直接用厂商自带的Ingress更省事毕竟它已经跟云上的LB、证书服务打好了配合。3.2 从零部署一套Nginx Ingress Controller部署方式有两种主流路线一种是用Helm一种是用官方发布的YAML文件。我实际操作下来YAML方式更适合学习阶段——你能看清楚它到底创建了哪些资源也更方便排查问题。以社区版本为例最基础的部署命令长这样下面这条命令中的版本号请以官方仓库发布的最新稳定版为准kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.1/deploy/static/provider/baremetal/deploy.yaml这个YAML文件会一次性创建一系列资源包括一个独立的命名空间Controller的Deployment给Controller使用的Service还有对应的RBAC权限、配置字典、服务账号等等。部署完成后观察一下Controller的运行状态kubectl get pod -n ingress-nginx正常情况下你会看到一个Running状态的Pod。如果是CrashLoopBackOff优先看日志kubectl logs -n ingress-nginx -l app.kubernetes.io/nameingress-nginx在我实际部署中最常见的启动失败原因有两类。一类是镜像拉不下来这个跟网络环境关系很大建议提前把镜像拉到节点上或者配置好节点的镜像加速。另一类是端口冲突Controller默认要占用节点的80和443端口如果节点上已经有别的进程占用了这两个端口Pod会一直起不来。3.3 让外部流量真正打到Controller上针对自建集群或者说裸金属环境Controller的Service一般推荐用NodePort方式暴露或者直接在Controller Pod上挂hostNetwork让Pod直接复用节点IP和80/443端口。我个人的实践是如果在自建环境里直接给Controller的Service设置externalTrafficPolicy: Cluster然后用NodePort暴露如果是在云上直接把Service类型改成LoadBalancer云平台会给它分配一个公网IP。有一点需要注意如果你的公司还在用比较老的K8s版本比如1.18及以前的集群而Controller版本已经用了最新的那你可能会碰到兼容性报错——Ingress资源的apiVersion还是networking.k8s.io/v1beta1而新的Controller要求是v1。这种问题多数出现在公司里集群版本不统一参考网上过时资料的情况下。我建议先确认集群版本再选对应的Controller版本不要盲目上最新版。4. 第一次用Ingress从部署测试服务到写规则4.1 准备一个最简单的后端服务为了做实验先部署一个简单的测试服务。你可以在本地写一个极简的HTTP服务镜像也好直接用现成的demo镜像也好。这里以我自己常用的方式举例随便起个回显服务请求什么路径它都把访问的路径和Host信息原样返回这样调试路由规则时你能一眼看出来请求到底到了谁那儿。先创建命名空间和Servicekubectl create ns demo kubectl create deployment demo-app --imagenginxdemos/hello --port80 -n demo kubectl expose deployment demo-app --port80 --target-port80 --namedemo-svc -n demo实际上我更喜欢用一个更简单的临时sleep镜像配合curl来做但从演示效果来说返回页面能直接显示我访问的是哪个路径更直观。所以上面的nginxdemos/hello就很好用。确认Service正常工作kubectl get svc -n demo到这里你已经拥有了一个后端服务。接下来要做的就是让外部流量通过Ingress到这个服务来。4.2 写一份最基础的Ingress规则在当前集群里创建一个Ingress规则把demo.example.com这个域名下所有路径都转发给demo-svcapiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: demo-ingress namespace: demo spec: ingressClassName: nginx rules: - host: demo.example.com http: paths: - path: / pathType: Prefix backend: service: name: demo-svc port: number: 80应用它kubectl apply -f ingress-demo.yaml然后看一下规则是否生效kubectl get ingress -n demo输出的ADDRESS字段如果显示的是Controller的地址或节点IP说明规则已经被Controller接收了。4.3 测试流量不改DNS也能验证因为我们是在实验环境没有实际把demo.example.com解析到Controller的IP上所以直接用curl加Host头的方式模拟域名访问curl -H Host: demo.example.com http://Controller的IP或节点IP/这里的Controller IP怎么找如果是NodePort方式暴露的就用节点的IP加上Service的NodePort端口kubectl get svc -n ingress-nginx ingress-nginx-controller这条命令能看到对外端口假设80端口映射成了31826那么访问命令就变成curl -H Host: demo.example.com http://节点IP:31826/如果一切配置正常你会看到demo-app返回的页面说明请求已经通过Ingress规则打到了后端的Pod里。4.4 多路径路由一个入口分流两个服务上面只是最简单的全量转发实际项目里更常见的需求是同一个域名不同路径分给不同后端。举个例子apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: multi-path-ingress namespace: demo spec: ingressClassName: nginx rules: - host: demo.example.com http: paths: - path: /api pathType: Prefix backend: service: name: api-svc port: number: 8080 - path: / pathType: Prefix backend: service: name: web-svc port: number: 80这一段逻辑跟前面的区别在于同一份规则里配置了多个path流量进入后Controller按最长路径优先匹配原则把/api开头的请求转给api-svc其余的走web-svc。这里有一个非常常见的坑很多人把路径和rewrite-target搞混。上面这个配置请求打的是/api/user后端web-svc收到的是/api/user路径不变。如果你后端服务里路由根本不识别/api前缀比如你的API服务只认/user那就需要动用重写机制也就是接下来要讲的内容。4.5 路径重写为什么明明看到404但调试感觉啥都对rewrite-target是Ingress使用率最高的一个注解。它的作用是把匹配到的路径在转发到后端之前重写掉。最常见的用法是metadata: annotations: nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - host: demo.example.com http: paths: - path: /api(/|$)(.*) pathType: ImplementationSpecific backend: service: name: api-svc port: number: 8080这里($2)代表正则中第二个括号匹配到的部分。请求/api/user经过重写后后端的service收到的是/user。为什么需要这个因为很多后端应用内置的路由是不带前缀的。你在前端通过/api/user访问网关网关把请求转发给后端微服务时后端只认识/user。如果后端不做前缀剥离就会一直报404。这个重写就用在这里。如果你用的是Prefix路径类型并希望把所有前缀都剥掉、统一重写到根路径可以用nginx.ingress.kubernetes.io/rewrite-target: /这样一切到达的路径都会变成根路径/转发到后端。这种配置在只跑一个前端静态页面、且页面里全部用相对路径引资源时比较实用但也容易引起页面静态资源404——因为请求/assets/main.css被改成根路径/之后后端找不到对应文件所以实际生产里我建议按需用正则精确重写不要一刀切重写根。5. 大杀器TLS配置与多环境灰度发布5.1 给Ingress挂上HTTPS从自签证书到Secret生产环境中所有到达集群的流量都应该走HTTPS。Ingress的TLS配置并不复杂第一步是准备证书。平台或运维一般会给你一套符合内部域名规范的证书实验环境可以用openssl自签一张openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout tls.key -out tls.crt -subj /CNdemo.example.com -addext subjectAltNameDNS:demo.example.com然后把证书导入K8s的Secretkubectl create secret tls demo-tls --keytls.key --certtls.crt -n demo接着在Ingress的spec里挂上tls字段spec: tls: - hosts: - demo.example.com secretName: demo-tls rules: - host: demo.example.com http: paths: - path: / pathType: Prefix backend: service: name: demo-svc port: number: 80应用后Controller会自动监听443端口。用curl验证curl -k -H Host: demo.example.com https://节点IP:443端口/-k是因为自签证书不受系统信任实验时可以跳过校验。我踩过一个坑是TLS配置后访问居然还是HTTP响应没有跳转HTTPS。原因是Nginx Ingress默认不会强制跳转需要加上注解nginx.ingress.kubernetes.io/ssl-redirect: true加了之后HTTP请求访问会返回301自动跳转到HTTPS。5.2 给多个域名分别挂不同证书如果你的集群需要服务多个团队或产品线比如app-a.example.com和app-b.example.com并且每个域名都有自己的证书可以分别创建Secret然后在不同的Ingress里分别引用对应的Secret。也可以在一个Ingress里同时声明多个host、多个Secretspec: tls: - hosts: - app-a.example.com secretName: app-a-cert - hosts: - app-b.example.com secretName: app-b-cert rules: - host: app-a.example.com http: ... - host: app-b.example.com http: ...证书选择按host来区分Controller在建立TLS握手时根据SNI匹配对应的证书。实际运维中证书过期是一个很容易被忽略的点建议为证书过期时间加监控告警服务上线一年后突然所有人打不开页面那种体验真的很酸爽。5.3 用Ingress做蓝绿发布和灰度发布Ingress不只是对外暴露入口这么简单它还能做流量的精细化控制。Nginx Ingress Controller原生支持基于权重或基于请求头的灰度发布。先看基于权重的方式metadata: annotations: nginx.ingress.kubernetes.io/canary: true nginx.ingress.kubernetes.io/canary-weight: 10 spec: ingressClassName: nginx rules: - host: demo.example.com http: paths: - path: / pathType: Prefix backend: service: name: demo-app-v2 port: number: 80这个叫canary的Ingress和主Ingress指向同一个域名但带上了canary注解。canary-weight为10表示有10%的流量被转发给v2版本的服务主Ingress继续处理剩余90%。这样可以小流量验证新版本稳定后再逐步抬高权重最后把主Ingress的backend切到新版本再删掉canary规则。还有基于Header的方式比如内部测试人员带一个自定义请求头才能命中新版metadata: annotations: nginx.ingress.kubernetes.io/canary: true nginx.ingress.kubernetes.io/canary-by-header: x-canary nginx.ingress.kubernetes.io/canary-by-header-value: true这个玩法在做内部验收、特定用户灰度测试时非常香。有一次我们做全链路联调前端团队要拿正式域名测新版本同时又不想影响真实用户就用这个Header灰度方式联调人员在浏览器装一个改请求头的插件爽爽地测了一整天用户侧完全无感知。5.4 限流、白名单、超时——日常用得上的几个注解Ingress还有一些实用注解值得记下来。限流nginx.ingress.kubernetes.io/limit-rps: 100 nginx.ingress.kubernetes.io/limit-connections: 20IP白名单nginx.ingress.kubernetes.io/whitelist-source-range: 10.0.0.0/8, 172.16.0.0/12连接超时nginx.ingress.kubernetes.io/proxy-connect-timeout: 30 nginx.ingress.kubernetes.io/proxy-read-timeout: 60我的经验是限流和连接数这两个几乎每个生产Ingress都要加不加的后果就是上游一个慢Query就能把整个Controller拖垮。白名单字段注意逗号分隔不要用竖线。6. 踩坑实录Ingress相关常见故障排查手册6.1 症状1访问报404规则未生效/路径不匹配排查路线先确认Ingress规则有没有被Controller加载kubectl get ingress -n demo。如果ADDRESS字段为空说明Controller没有接收规则继续往下面查。查看Controller日志kubectl logs -n ingress-nginx controller-pod名称看有没有关于Ingress同步的错误。确认ingressClassName写的是不是Controller实际监听的名字。很多人升级后把这个字段漏了导致Controller根本不处理这个Ingress。检查路径匹配规则。如果你写的是Exact类型请求路径只要比规则多一个字符就会不匹配。生产环境建议用Prefix注意它匹配的是路径前缀/api会匹配/api/v1/user但不会匹配/apixxx。6.2 症状2访问报502后端服务不通502是Controller和后端之间的通信出了问题。核心排查方向kubectl get ep -n demo kubectl get po -n demo如果Endpoints为空说明Service的selector没有匹配到任何Pod或者Pod没就绪。这里最常见的坑是selector写错比如Deployment里的label是app: demo但Service里的selector写成了app: demo-app永远匹配不上。其次确认Pod的端口和Service的targetPort对得上。如果Pod监听的端口是8080Service里写的是80那流量转发必然失败。还有一个容易忽略的Pod的readinessProbe返回失败会踢出Endpoints。如果你配置了健康检查先确认探针接口返回200否则即使Pod是Running状态流量也进不去。6.3 症状3证书配置了但还是HTTP/TLS告警排查思路确认Secret存在且类型正确kubectl get secret -n demoTYPE必须显示为kubernetes.io/tls。用kubectl get secret demo-tls -o yaml检查data字段里必须要有tls.crt和tls.key两个key。确认ingress里的secretName和Secret实际名字大小写一致。访问时带上域名curl -H Host: demo.example.com https://IP/不要用IP去访问否则SNI不匹配Controller选不到证书。6.4 症状4配置了rewrite但页面资源还是404我之前讲过rewrite-target如果直接重写到根路径静态资源全挂。推荐的方案是这样的nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: paths: - path: /app(/|$)(.*)把/app/xxx重写成/xxx而不是全部变成根路径。如果你用的是Prefix路径类型加上rewrite到根的配置基本等于自废武功。优先改用正则路径配合组引用这是处理带前缀前端应用的最稳妥姿势。另外要注意如果你的页面里写的是绝对路径/static/xxx那不管你怎么重写请求都会落到根路径下找static资源这时候后端必须有对应的静态目录或者你需要给静态资源单独配一条规则而不是硬扛在一条重写规则里。6.5 症状5Ingress规则变更后流量没变化Controller的配置不是实时的它有一层缓存。如果你改了Ingress但感觉流量没变通常等几秒就好。如果等了一段时间还是老样子可以强制触发一次reloadkubectl exec -n ingress-nginx controller-pod -- nginx -s reload另外确认一下你有没有多套Controller混用。如果集群里有多个Ingress Controller而你的Ingress没有指定ingressClassName新版本默认会有一个默认Controller来接管旧版本则可能随机被某一个Controller捡起来造成越改越乱。强烈建议在集群里只保留一套生产用Controller并给每个Ingress都明确写上ingressClassName。6.6 症状6日志提示upstream timed out请求进了Controller但后端响应太慢触发了上游超时。常规处理是放大概率超时参数nginx.ingress.kubernetes.io/proxy-read-timeout: 300 nginx.ingress.kubernetes.io/proxy-send-timeout: 300但更重要的是判断后端为什么慢。有可能是某个Pod的数据库连接慢某个接口本身耗时长或者连接数被打满排队。只看现象改超时只是治标。7. 实践经验总结Ingress最值钱的价值是“统一”聊到这里Ingress的原理和实操就基本覆盖完了。如果让我提炼一个核心观点我会说Ingress最值钱的地方在于它把集群外部流量的接入标准统一了。以前每接一个新服务都要跟网络组要端口、跟DNS组加记录防火墙开一堆口子。有了Ingress之后就固定一个入口按域名和路径灵活分发后端Service全跑ClusterIP就行整个暴露面收得非常干净。同时因为入口集中了限流、鉴权、访问日志、证书管理都可以在这个统一的入口层面上做文章省掉大量重复配置。最后再分享两个实操中过来的细节建议。第一所有生产环境的Ingress一定要加ingressClassName不要依赖默认Controller的自动接管。这能让配置在多人协作出问题的时候少很多无谓的纷争。第二建议给Ingress资源统一配置一个公用的annotations模板放团队规范里把全局限流、连接数、超时这些基础参数都写进去新服务加进来时不用再一个个讨论该配多少减少很大的沟通成本。我和Ingress的渊源不算浅一开始踩过404也被证书问题坑到半夜拉群处理。但系统玩明白之后它带来的便利性是真的高。希望这篇笔记能帮你少走一点弯路把Ingress真正地用起来而不是停留在看过文档、建过资源的阶段。