K8s 里 Service 和 Ingress 到底啥区别

Service 是个什么东西

Service 在 K8s 里本质上就是一组 Pod 的负载均衡器。你想啊,Pod 的 IP 是会变的,今天调度到这个节点是这个 IP,明天可能就飘到别的节点去了,IP 也就跟着变了。

如果你的应用直接连 Pod IP,那每次 Pod 重建你就得改配置,这谁受得了?

Service 就是来解决这个问题的。它会持续追踪一组 Pod 的变化,然后给它们分配一个固定的虚拟 IP(也叫 Cluster IP)。不管后面的 Pod 怎么折腾,外面的请求只要认识 Service 这个固定地址就行。

最常见的写法就是这样:

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: my-webapp
spec:
  selector:
    app: my-webapp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  type: ClusterIP

这个配置说的是:创建一个叫 my-webapp 的 Service,它通过标签选择器找到所有 app=my-webapp 的 Pod,然后把外部 80 端口的请求转发到这些 Pod 的 8080 端口上。type 是 ClusterIP,意味着这个 Service 只在集群内部可见。

Service 有几种类型,这个必须搞清楚:

ClusterIP 是默认类型,只给集群内部用,分配一个内部 IP,外面的请求进不来。

NodePort 会在每个节点上开放一个端口,比如 30080,这样外部就能通过 节点IP:30080 访问。这个方式简单粗暴,但端口范围限制在 30000-32767,而且没有域名支持,用起来局限性挺大的。

LoadBalancer 常见于云环境,会创建一个外部负载均衡器,比如在阿里云或 AWS 上自动生成一个 SLB,外部请求先到 SLB,再转发到你的 Service。这个很方便,但你得在云上跑才行。

ExternalName 比较特殊,它把 Service 映射到一个外部域名,相当于给外部服务起了个别名。这个用得不多,但有些场景挺有用的,比如你想给内部服务一个统一访问入口,但实际服务跑在集群外面。

还有 ExternalName,我之前用它解决过一个问题。公司的旧系统跑在虚拟机上,新做的微服务要调用它,但不想改现有的配置。我就建了个 ExternalName 类型的 Service,指向旧系统的地址,这样新服务的代码只需要访问 Service 名字就行了,不用记住 IP 和端口。后来迁移的时候特别方便,改个 Service 配置就行,代码一层没动。

我之前维护一个项目,用的是 NodePort 方式,配置完 nginx 之后发现访问老是不稳定。后来才发现是 kube-proxy 的 iptables 模式在高并发下有性能问题,换成 IPVS 模式才解决。这个坑我专门记录过,改天单独写一篇讲讲。

Ingress 又是干嘛的

好,Service 我大概整明白了,那 Ingress 呢?

简单说,Ingress 就是给集群提供 HTTP/HTTPS 路由规则的东西。你可以把它理解为"HTTP 层面的七层负载均衡器+路由控制器"。

Service 只负责四层转发,它不认识 HTTP 协议里的东西,比如域名、路径、请求头这些。Ingress 可以,它可以基于这些信息做更精细的路由。

举个例子,你想实现这样的需求:

用 Ingress 就能很轻松地配置这些规则。

不过要注意,Ingress 本身只是个规则定义,真正处理流量的还得是 Ingress Controller。K8s 本身不带 Ingress Controller,常用的有 Nginx Ingress Controller、Trafik、Ambassador 等等。

安装 Nginx Ingress Controller 的一般方式:

bash 复制代码
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/cloud/deploy.yaml

装完之后,集群里会跑起一个 nginx-ingress-controller 的 Pod,它会监听 Ingress 资源的变化,然后自动生成 nginx 配置。

然后你就可以写 Ingress 规则了:

yaml 复制代码
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
  - host: foo.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80
  - host: bar.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: backend-service
            port:
              number: 80

这个 Ingress 定义了两个主机规则,foo.example.com 走前台服务,bar.example.com 走后台服务。

核心区别在哪里

说到这里,可能有人要问了:Service 不也能对外暴露服务吗?NodePort 和 LoadBalancer 不也能让外部访问?

没错,这是最容易混淆的地方。我总结了几个关键区别:

协议层级不同。 Service 工作在四层(TCP/UDP),它只管把请求转发给后端 Pod,不关心包里的内容是什么。Ingress 工作在七层(HTTP/HTTPS),它能读取请求的 URL、Host 头、路径等信息,然后做更智能的路由决策。

路由能力不同。 Service 只能做简单的轮询或会话保持,而 Ingress 可以基于域名、路径、正则表达式、请求头、Cookie 等条件做路由。举个例子,你想让 /api 开头的路径访问后端服务,其他路径访问前端服务,用 Service 做不到,用 Ingress 就是几行配置的事。

使用场景不同。 Service 更像是微服务之间的内部通信桥梁,或者是那种不需要域名、IP+端口就能访问的场景。Ingress 主要用于从外部用域名访问集群内的 Web 服务,是用户请求进入集群的第一站。

依赖不同。 Service 是 K8s 的核心资源类型,任何集群都有。Ingress 只是个规范定义,必须配合 Ingress Controller 使用,没有 Controller 的话 Ingress 规则就是废纸一张。

再打个比方吧,Service 就像公司内部的分机系统,你知道分机号就能打通,但外人不知道这个分机号,而且分机只能按房间分配,不能按业务分配。Ingress 就像是前台接待,根据来访者的身份和需求,把他们引导到不同的部门去。

实际项目怎么选

我建议这么考虑:

如果你在云上跑,Ingress 是首选。现在各大云厂商的 Kubernetes 服务都自带或者很容易集成负载均衡器,配合 Ingress 管理域名和路由非常方便。而且云上的 Ingress Controller 通常还有免费的 SSL 证书功能,省去你手动管理证书的麻烦。

如果是本地测试或者内部系统,NodePort 也不是不能用。简单场景下,NodePort 够用,而且好排查问题。Ingress Controller 跑起来也有资源开销,小集群可能觉得浪费。

这里有个坑我必须提醒一下。很多新手以为用了 Ingress 就万事大吉了,其实不对。Ingress 最终还是要把请求转发给 Service 的。所以 Service 本身的配置不能错,Pod 的健康检查也要做好,不然 Ingress 转发过去都是烂请求,用户那边看就是服务不可用。

我之前遇到过一个奇怪的问题,Ingress 配置了,规则也写对了,浏览器访问就是 502。排查了半天发现是 Service 的 selector 写错了,标签对不上,Service 根本找不到后端 Pod,白白转发了半天。这经验告诉我,K8s 的资源之间是有关联的,任何一个环节出问题都可能导致不可用。

TLS 那些事

HTTPS 是现代 Web 的标配了,这块 Ingress 支持得怎么样?

挺方便的。Ingress 可以直接配置 TLS 证书,Nginx Ingress Controller 会自动处理 SSL termination:

yaml 复制代码
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tls-ingress
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - foo.example.com
    secretName: foo-tls-secret
  rules:
  - host: foo.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80

证书存在 Secret 里,创建方式:

bash 复制代码
kubectl create secret tls foo-tls-secret \
  --cert=path/to/cert.pem \
  --key=path/to/key.pem

如果你用 Let's Encrypt,cert-manager 这个组件可以自动帮你申请和续期证书,特别适合懒人。不过 cert-manager 的配置比较复杂,建议单独研究,这里就不展开了。

路径重写也很常用

有时候后端服务的路径和用户访问的路径不一致,比如用户访问 /api/users,但后端服务期望的是 /users。这时候就需要路径重写。

Nginx Ingress Controller 提供了 rewrite-target 注解来实现这个功能:

yaml 复制代码
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rewrite-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  ingressClassName: nginx
  rules:
  - host: example.com
    http:
      paths:
      - path: /api(/|$)(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: backend-service
            port:
              number: 80

这个配置会把 /api/xxx 的请求重写为 /xxx 再转发给后端服务。正则 (/|$)(.*) 捕获了斜杠后面的部分,$2 就是这个捕获组的值。

基于请求头的灰度发布

Ingress 还能做灰度发布,这个在实际项目中很有用。比如你想让带特定 header 的请求走新版本,其他请求走老版本:

yaml 复制代码
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: canary-ingress
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-by-header: "X-Canary"
    nginx.ingress.kubernetes.io/canary-by-header-value: "always"
spec:
  ingressClassName: nginx
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: new-version-service
            port:
              number: 80

加上 canary: "true" 注解之后,这条规则就变成了灰度规则。带 X-Canary: always header 的请求会走 new-version-service,其他请求继续走原来的服务。

限流配置别忘了

公网服务最好加上限流,不然被人恶意刷接口就麻烦了。Nginx Ingress Controller 支持基于连接数和请求速率的限流:

yaml 复制代码
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rate-limit-ingress
  annotations:
    nginx.ingress.kubernetes.io/limit-rps: "100"
    nginx.ingress.kubernetes.io/limit-connections: "50"
    nginx.ingress.kubernetes.io/limit-rpm: "1000"
spec:
  ingressClassName: nginx
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-service
            port:
              number: 80

配置了每秒最多 100 个请求,每个 IP 最多 50 个并发连接,每分钟最多 1000 个请求。超过限制会返回 503 或者 429,取决于具体配置。

性能方面的考虑

说实话,对于大多数中小型应用,Service 和 Ingress 的性能都绰绰有余,不用太纠结。

但如果你的系统流量特别大,那还是有优化空间的。Service 默认用 iptables 模式,规则多了之后查询效率会下降。如果集群里 Service 超过 1000 个,建议考虑 kube-proxy 的 IPVS 模式,性能会好很多。切换方法也很简单,改个配置就行:

yaml 复制代码
kind: ConfigMap
apiVersion: v1
metadata:
  name: kube-proxy
  namespace: kube-system
data:
  mode: "ipvs"

Ingress Controller 这边,Nginx 本身性能就很强,扛住几万 QPS 问题不大。关键是要合理配置 worker 进程数、连接数这些参数,还有做好 Pod 的资源限制,避免单点故障。建议给 Ingress Controller 部署多副本,再配合 Pod 反亲和性分散到不同节点上。

yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-ingress-controller
spec:
  replicas: 3
  template:
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - nginx-ingress-controller
              topologyKey: kubernetes.io/hostname

这样配置之后,三个副本会尽量分布在不同的节点上,一个节点挂了也不会全军覆没。

常见问题汇总

工作里遇到的高频问题我也总结了一下:

问:Ingress 能不能不做七层转发,直接四层透传?

答:技术上可以,Nginx Ingress Controller 支持 TCP/UDP 透传模式,但这时候就不叫 Ingress 了,叫 IngressClass 的 TCP 功能,本质上更像是 Service 的 NodePort 扩展版。

问:Service 和 Ingress 能配合着用吗?

答:必须配合。Ingress 的后端就是 Service,Ingress 收到请求后还是要转发给具体的 Service,再由 Service 分发到 Pod。这是一整条链路,哪个环节断了都不行。

问:一个 Ingress 能不能绑定多个域名?

答:可以。一个 Ingress 可以定义多个 host,每个 host 对应不同的服务。同一 host 下也可以定义多个 path 匹配不同前缀。

问:生产环境用 Ingress 是选 Nginx 还是 Traefik?

答:Nginx Ingress Controller 社区更成熟,文档更全,踩坑的人多,有问题容易找到解决方案。Trafik 配置更简洁,但生态稍弱。如果是公司用,我建议选 Nginx Ingress Controller。

问:Ingress Class 是什么?有什么用?

答:K8s 1.18 之前用注解 kubernetes.io/ingress.class 来指定用哪个 Ingress Controller,之后改成了 IngressClass 资源。更规范,也更灵活。如果你集群里跑了多个 Ingress Controller(比如一个内部用,一个外部用),那就需要通过 IngressClass 来区分它们。

问:Service 的 sessionAffinity 是什么?

答:就是会话保持。默认是 None,请求会被轮询分发到不同的 Pod。改成 ClientIP 之后,同一个来源 IP 的请求会被固定发送到同一个 Pod。这个在有些场景下很有用,比如购物车服务。

总结一下

Service 和 Ingress 各有各的用处,别把它们对立起来。Service 是集群内部的服务发现和负载均衡基础,不管你用不用 Ingress,Service 都跑不掉。Ingress 是 Service 的上一层,专门负责外部 HTTP/HTTPS 流量的精细化路由。

简单理解就是:Service 解决的是 Pod 怎么被访问的问题,Ingress 解决的是外部请求怎么智能地找到正确的 Service 的问题。

实际项目中,Ingress 基本上是现代 K8s 部署 Web 应用的标配,配合云厂商的负载均衡和证书管理,能省不少事。但 Service 的基础地位也不能忽视,Ingress 下面的流量最终还是要靠 Service 来承接。

好了,这篇文章就到这里。如果觉得有用,欢迎转发给有需要的朋友。有问题也可以在评论区留言,咱们一起探讨。