第三章:应用访问与服务发现:Service与Ingress

在第二章中,我们成功地使用Deployment部署了一个高可用的Nginx应用。然而,我们留下了一个关键问题:这些运行在Pod中的应用,如何被其他应用发现和访问?用户又如何从集群外部访问到我们的Nginx欢迎页面?

答案就在于Kubernetes的网络模型核心:ServiceIngress。它们共同构成了K8s中强大而灵活的服务发现和流量路由机制。本章将彻底剖析这两个概念,并带领你通过实践,将我们的Nginx应用真正地暴露给世界。

3.1 Service:集群内部的“负载均衡器”

我们知道,Pod是短暂的、有生命周期的。当一个Pod因为节点故障、应用更新或水平缩容而被销毁并重建时,它的IP地址会发生改变。如果我们的应用(比如一个前端Pod)直接通过IP地址去调用另一个应用(比如一个后端Pod),那么这种依赖关系将是极其脆弱和不可靠的。

Service正是为了解决这个核心痛点而设计的。它为一组功能相同的Pod提供了一个稳定、统一的访问入口

你可以将Service理解为一个抽象层,它定义了一个逻辑上的Pod集合以及访问这个集合的策略。当一个Service被创建时,它会被分配一个虚拟的、在集群内部唯一的IP地址(称为ClusterIP)一个DNS名称。这两个标识符在Service的整个生命周期内都是固定不变的

当集群内的任何客户端(例如另一个Pod)向这个Service的ClusterIP或DNS名称发起请求时,Kubernetes的网络代理(kube-proxy)会拦截这些流量,并以负载均衡的方式将其转发到该Service后端所关联的某个健康的Pod上。

Service是如何找到它应该代理的Pod的?
答案是我们在上一章学习的Label和Selector。Service通过其定义中的selector字段来匹配Pod的label。凡是标签与selector匹配的Pod,都会被自动加入到Service的端点(Endpoints)列表中。这是一个动态的过程:当一个新的、标签匹配的Pod被创建时,它会自动成为Service的后端;当一个Pod被删除或变得不健康时,它会自动从端点列表中移除。

Service的三种主要类型

Kubernetes提供了不同类型的Service,以满足不同的暴露需求。你在创建Service时,通过spec.type字段来指定。

Service类型 描述 主要使用场景
ClusterIP 默认类型。Service只在集群内部可见,它会被分配一个内部的ClusterIP。从集群外部无法直接访问。 集群内部服务间通信。这是最常用的类型,用于微服务架构中后端服务之间的相互调用(例如,订单服务调用用户服务)。
NodePort 在ClusterIP的基础上,额外在每个工作节点(Node)的物理IP上开放一个静态的、固定的端口(NodePort)。端口范围通常是30000-32767。任何发送到<NodeIP>:<NodePort>的流量都会被转发到该Service。
  • 开发和测试环境:快速暴露一个服务进行调试。
  • 作为更高级负载均衡器(如Ingress)的流量入口。
  • 不推荐直接用于生产环境的Web服务,因为端口管理复杂且不安全。
LoadBalancer 在NodePort的基础上,进一步与云服务提供商(如AWS, GCP, Azure)的负载均衡器集成。当你创建此类型的Service时,K8s会向云平台请求一个外部负载均衡器,该负载均衡器会被分配一个公网IP地址,并将流量路由到集群中所有节点的NodePort上。 将服务暴露给公网的标准方式。当你需要在公有云上为你的应用提供一个稳定的公网访问入口时,应使用此类型。这也是将Ingress Controller暴露给外部的常用方法。

重要说明:

  • NodePortClusterIP的超集。创建一个NodePort类型的Service时,K8s会自动创建一个ClusterIP
  • LoadBalancerNodePort的超集。创建一个LoadBalancer类型的Service时,K8s会自动创建一个NodePort和一个ClusterIP。流量路径为:外部客户端 -> 云负载均衡器IP -> 任意节点的NodePort -> Service的ClusterIP -> 目标Pod

3.2 服务发现: K8s如何通过DNS和环境变量实现服务间的通信

现在我们知道Service提供了一个稳定的入口,但一个Pod如何“发现”这个入口的地址呢?Kubernetes主要提供了两种内建的服务发现机制。

1. DNS-based Service Discovery (基于DNS的服务发现) - 推荐方式

这是Kubernetes中最常用也是最推荐的服务发现方式。每个Kubernetes集群内部都会运行一个DNS服务(例如CoreDNS),它负责解析集群内部的服务名称。

当一个Service被创建时,DNS服务会自动为其创建一条DNS记录。这条记录的格式通常是:
my-svc.my-namespace.svc.cluster.local

  • my-svc: Service的名称。
  • my-namespace: Service所在的命名空间。
  • svc.cluster.local: 集群内部的域名后缀,可以自定义。

这个机制的美妙之处在于:

  • 同命名空间内: 如果一个Pod和它想访问的Service在同一个namespace下,它只需要直接使用Service的名称 (my-svc)作为域名即可。Pod内的DNS解析器会自动补全后缀,将其解析为Service的ClusterIP。
  • 跨命名空间: 如果需要访问其他namespace下的服务,则需要使用更完整的域名,如my-svc.my-namespace

这种方式是动态的解耦的。你的应用程序代码中只需要硬编码一个稳定的服务名称,而不需要关心其IP地址,这完全符合微服务的设计理念。

2. Environment Variable-based Service Discovery (基于环境变量的服务发现)

这是一种较早的机制。当一个Pod被创建时,kubelet会为在该Pod创建之前就已经存在的所有Service注入一系列环境变量。

例如,如果有一个名为redis-master的Service,其ClusterIP为10.0.0.11,端口为6379,那么在新创建的Pod中,你会看到类似以下的环境变量:

REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

主要缺点:
这种方式有一个致命的缺陷:强依赖于启动顺序。如果你的Pod A依赖于Service B,但Pod A在Service B创建之前就已经启动了,那么Pod A中将不会包含Service B的环境变量。这在复杂的、动态的微服务环境中会造成严重问题。因此,强烈建议优先使用DNS进行服务发现

3.3 Ingress:集群外部流量的入口

我们已经讨论了如何通过NodePortLoadBalancer类型的Service将服务暴露给外部。但这两种方式存在一些局限性:

  • NodePort: 管理和记忆端口号很麻烦,而且通常不允许使用80/443等标准端口,不适合生产环境。
  • LoadBalancer: 每暴露一个服务就需要一个公网IP和一台云负载均衡器,这在有大量服务的场景下成本非常高昂
  • 功能局限: Service工作在OSI模型的第四层(传输层),它只关心IP和端口,进行TCP/UDP流量的转发。它无法理解HTTP/HTTPS协议,因此无法实现更高级的路由功能,比如:
    • 根据域名进行路由(api.example.com转发到API服务,shop.example.com转发到商店服务)。
    • 根据URL路径进行路由(example.com/api转发到API服务,example.com/ui转发到前端服务)。
    • SSL/TLS证书的终结和管理。

为了解决这些问题,Kubernetes引入了一个更高层次的抽象:Ingress

Ingress是Kubernetes中一个API对象,它定义了从集群外部访问内部服务的HTTP和HTTPS路由规则。你可以把它看作是集群流量的“总入口”或“API网关”。它工作在OSI模型的第七层(应用层)

Ingress Controller的重要性

一个非常关键的点是:仅仅在集群中创建一个Ingress资源对象是没有任何效果的!

Ingress资源本身只是一份静态的路由规则声明。你必须在集群中运行一个Ingress Controller(Ingress控制器),它才是真正实现这些规则的“大脑”和“执行者”。

Ingress Controller是一个持续运行的Pod,它会通过Kubernetes API监视集群中所有的Ingress资源。当检测到Ingress资源的创建、更新或删除时,它会动态地更新其内部的负载均衡器配置(例如,修改Nginx的nginx.conf文件),使Ingress规则生效。

常见的Ingress Controller实现包括:

  • NGINX Ingress Controller (由Kubernetes社区维护)
  • Traefik
  • HAProxy Ingress
  • 以及各大云厂商提供的专有实现(如GKE Ingress Controller)

工作流程:

  1. 管理员部署一个Ingress Controller到集群中。这个Controller本身通常通过一个LoadBalancerNodePort类型的Service暴露到公网。
  2. 用户创建一个或多个Service,用于暴露内部的应用(通常是ClusterIP类型)。
  3. 用户创建一个Ingress资源,定义路由规则,例如:“所有访问app.example.com/path的HTTP请求,都应转发到名为my-app-service的服务的80端口”。
  4. Ingress Controller监听到这个新的Ingress资源,并更新自身的路由配置。
  5. 外部用户的DNS将app.example.com解析到Ingress Controller暴露的公网IP。
  6. 流量到达Ingress Controller,Controller检查请求的Host和Path,根据Ingress规则,将请求代理到后端的my-app-service,最终到达应用Pod。

通过这种方式,我们可以使用一个公网IP和负载均衡器,来为集群中成百上千个服务提供统一的、基于HTTP/HTTPS的外部访问入口,极大地节约了成本并简化了管理。

3.4 实战: 为Nginx应用创建Service和Ingress,并从外部访问

让我们回到上一章创建的nginx-deployment。现在,我们将一步步为它创建Service和Ingress,最终通过浏览器访问它。

(前提:确保你有一个运行中的K8s集群,并且nginx-deployment已成功部署并运行着3个副本。如果你使用Minikube,请先执行 minikube addons enable ingress 来安装Ingress Controller。)

步骤1:创建一个ClusterIP类型的Service

首先,我们为Nginx Pod创建一个ClusterIP类型的Service,让它们在集群内部可以被稳定地访问。

创建一个名为 nginx-service.yaml 的文件:

# nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-service  # Service的名称
spec:
  type: ClusterIP      # 指定Service类型为ClusterIP
  selector:
    app: nginx         # 这个selector必须与Deployment管理的Pod的label完全匹配
  ports:
    - protocol: TCP
      port: 80         # Service暴露的端口
      targetPort: 80   # 流量将被转发到Pod内容器的这个端口

应用该文件:

kubectl apply -f nginx-service.yaml

查看Service状态:

kubectl get service nginx-service
# 或者简写为 kubectl get svc nginx-service

你会看到如下输出,其中CLUSTER-IP就是这个Service的虚拟IP:

NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
nginx-service   ClusterIP   10.108.112.55   <none>        80/TCP    30s

此时,你还无法从外部访问它。但你可以从集群内部的另一个Pod来测试它:

# 启动一个临时的busybox Pod并进入其shell
kubectl run busybox --rm -it --image=busybox -- sh

# 在busybox的shell中,使用Service的DNS名称来访问Nginx
/ # wget -q -O - http://nginx-service
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</html>

/ # exit

这证明了我们的Service和DNS服务发现机制工作正常。

步骤2:创建一个Ingress规则

现在,我们来定义Ingress规则,将外部流量路由到我们刚刚创建的nginx-service

创建一个名为 nginx-ingress.yaml 的文件:

# nginx-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
spec:
  rules:
  - host: nginx.example.com  # 我们希望通过这个域名来访问
    http:
      paths:
      - path: /              # 访问根路径
        pathType: Prefix
        backend:
          service:
            name: nginx-service # 将流量转发到这个Service
            port:
              number: 80       # Service的端口

应用该文件:

kubectl apply -f nginx-ingress.yaml

查看Ingress状态:

kubectl get ingress

输出应该类似这样,ADDRESS列可能需要一小段时间才会分配好IP地址:

NAME            CLASS    HOSTS              ADDRESS        PORTS   AGE
nginx-ingress   <none>   nginx.example.com  192.168.49.2   80      15s

这里的 ADDRESS 就是你的Ingress Controller的入口IP。在Minikube中,它就是Minikube虚拟机的IP。

步骤3:配置本地DNS解析并访问

由于nginx.example.com是一个虚构的域名,你需要告诉你的电脑如何找到它。我们需要修改本地的hosts文件,将域名映射到Ingress Controller的IP地址。

  • 获取Ingress IP: kubectl get ingress 命令的 ADDRESS 列就是。如果是Minikube,也可以用minikube ip命令获取。

  • 修改hosts文件:

    • Linux/macOS: sudo nano /etc/hosts
    • Windows: 以管理员身份打开记事本,编辑 C:\Windows\System32\drivers\etc\hosts

    在文件末尾添加一行 (将192.168.49.2替换为你的实际IP):

    192.168.49.2   nginx.example.com
    

    保存文件。

步骤4:验证

现在,打开你的浏览器,访问 http://nginx.example.com
你应该能看到熟悉的Nginx欢迎页面!

祝贺你!你已经成功地通过Service和Ingress,将一个部署在Kubernetes集群深处的应用,以一种健壮、可扩展且符合生产标准的方式暴露给了外部世界。

【重难点】 辨析Service和Ingress的区别与联系,理解不同Service类型的使用场景

这是面试和实际工作中经常被问到和混淆的地方。掌握它们的本质区别至关重要。

核心区别与联系:一个生动的比喻

想象一个大型的、现代化的办公园区(Kubernetes集群)

  • Pods: 园区里的各个办公室,员工(容器)在里面工作。办公室的门牌号(Pod IP)可能会因为部门调整而改变。
  • Service (ClusterIP): 园区的内部电话分机系统。你想找“财务部”,只需要拨打分机号“8001”(Service的DNS名 finance-service),电话总机(kube-proxy)就会自动帮你转接到当前有人在的财务部办公室(健康的Pod)。这个系统只能在园区内部使用。
  • Service (NodePort): 园区为每个部门开设的一个指定的后勤通道(例如,30080号门)。外部的快递员(外部请求)可以直接通过这个门找到对应的部门。但这种方式不正式,也不方便管理,不适合重要客户来访。
  • Ingress Controller: 园区的总前台和接待大厅。它有园区唯一的、气派的正门地址(Ingress Controller的公网IP)。所有访客都先到这里。
  • Ingress 规则: 摆放在接待大厅里的访客指南/公司名录。它告诉前台接待员(Ingress Controller)如何引导访客:
    • “找 Acme 公司的 (Host: acme.example.com),请去A座(acme-service)。”
    • “找 Stark 工业的 (Host: stark.example.com),并且是去研发部的 (/api),请去B座(stark-api-service);如果是去产品展示厅的 (/showcase),请去C座(stark-ui-service)。”

总结这个比喻:

  • Service 主要负责集群内部的“南北向”(从Service到Pod)和“东西向”(Pod到Pod)的4层网络通信,提供稳定的内部端点和负载均衡。
  • Ingress 主要负责集群外部到内部的“南北向”7层流量路由,是HTTP/HTTPS流量的“智能网关”。
  • 联系: Ingress依赖于Service。Ingress本身不直接与Pod通信,它的路由规则的最终目的地是一个Service。Ingress将流量转发给匹配的Service,再由Service将流量分发给后端的Pod。
对比表格
特性 Service Ingress
OSI模型层级 第4层 (传输层) - TCP/UDP 第7层 (应用层) - HTTP/HTTPS
核心功能
  • 提供稳定的虚拟IP (ClusterIP)
  • 内部服务发现
  • Pod负载均衡
  • 外部流量的统一入口
  • 基于Host和Path的路由
  • SSL/TLS终止
作用域 集群内部 (主要) 从集群外部到内部
通信协议 任意基于TCP/UDP的协议 主要是HTTP和HTTPS
实现方式 kube-proxy组件在每个节点上通过iptables/IPVS实现 需要部署一个单独的Ingress Controller Pod来实现
成本 (云环境) LoadBalancer类型为每个服务创建一个云负载均衡器,成本高 可通过一个Ingress Controller(对应一个云负载均衡器)管理多个服务,成本效益高
不同Service类型的使用场景选择
  • ClusterIP: 默认和首选。当你不需要从集群外部访问服务时,都应该使用它。这是90%的微服务间通信场景的选择。

  • NodePort:

    • 临时调试: 当你想快速、临时地从你的笔记本电脑访问集群里的某个服务时,这是一个简单的方法。
    • 特殊协议: 当你需要暴露一个非HTTP的TCP/UDP服务,且不想使用昂贵的LoadBalancer时(例如,一个临时的数据库连接)。
    • Ingress Controller的入口: Ingress Controller本身也需要被外部访问,通常会使用NodePortLoadBalancer类型的Service来暴露自己。
  • LoadBalancer:

    • 暴露非HTTP服务: 当你需要为一个非HTTP/HTTPS的服务(如数据库、消息队列、游戏服务器)提供一个稳定的、面向公网的IP地址时,这是最直接、最标准的方式。
    • 简单场景: 如果你只有一个服务需要暴露,并且不需要复杂的路由规则,使用LoadBalancer比部署和管理Ingunress更简单。
  • Ingress: 暴露HTTP/HTTPS服务的标准生产方案

    • 当你需要将多个服务聚合在同一个公网IP下时。
    • 当你需要根据域名或URL路径进行智能路由时。
    • 当你需要集中管理SSL证书,实现HTTPS时。

掌握了Service和Ingress,你就掌握了Kubernetes中服务网络通信的任督二脉,能够自如地构建、连接和暴露你的微服务应用。

K8s 详细学习笔记 目录

以下是整个系列的10章目录,点击章节标题即可跳转阅读:

更多推荐