12

使用Ingress暴露服务

本章涵盖

  创建Ingress对象

  部署理解Ingress控制器

  使用TLS保护入口

  向Ingress添加额外配置

•  在安装多个控制器时使用IngressClasses

•  在非服务后端中使用 Ingress

在上一章中,你学习了如何使用 Service 对象在一个稳定的 IP 地址上暴露一组 Pod。如果你使用 LoadBalancer 类型的服务,该服务可以通过负载均衡器向集群外的客户端提供。这种方法在你只需要对外暴露单个服务时是可行的,但当服务数量众多时就会出现问题,因为每个服务都需要一个自己的公共 IP 地址。幸运的是,通过使用 Ingress 对象来暴露这些服务,你只需要一个 IP 地址。此外,Ingress 还提供了其他功能,例如 HTTP 认证、基于 Cookie 的会话亲和性、URL 重写,以及 Service 对象无法实现的其他功能。

注意  你可以在以下网址找到本章的代码文件:https://github.com/luksa/kubernetes-in-action-2ndedition/tree/master/Chapter12

12.1 介绍 Ingress

在我解释在 Kubernetes 环境中 Ingress 是什么之前,对于那些英语不是母语的读者,先定义一下 ingress 这个词的含义可能会有所帮助。

定义 Ingress(名词)——进入或进入的行为;进入的权利;进入的方式或地点;入口。

在 Kubernetes 中,Ingress 是外部客户端访问集群中运行的应用服务的一种方式。Ingress 功能包括以下三个组件:

  • Ingress API 对象,用于定义和配置 ingress。
  • L7 负载均衡器或反向代理,将流量路由到后端服务。
  • Ingress 控制器,监控 Kubernetes API 中的 Ingress 对象,并部署和配置负载均衡器或反向代理。

注意  L4 和 L7 分别指开放系统互连模型 (OSI 模型) 的第 4 层(传输层;TCP、UDP)和第 7 层(应用层;HTTP)。

注意与正向代理不同,正向代理负责路由和过滤外发流量,通常位于其所服务的客户端所在的位置,而反向代理处理传入流量并将其路由到一个或多个后端服务器。反向代理位于这些服务器附近。在大多数在线内容中,术语 ingress controller(入口控制器)通常用来将负载均衡器/反向代理和实际控制器作为一个整体,但它们实际上是两个不同的组件。因此,本章中我会分别提到它们。我还使用 proxy(代理)一词来指代 L7 负载均衡器,以免将其与处理 LoadBalancer 类型服务流量的 L4 负载均衡器混淆。

12.1.1 介绍 Ingress 对象类型

当你希望将一组服务对外暴露时,可以创建一个 Ingress 对象,并在其中引用 Service 对象。Kubernetes 使用这个 Ingress 对象来配置 L7 负载均衡器(HTTP 反向代理),通过一个统一入口点使外部客户端可以访问这些服务。

注意  如果通过 Ingress 暴露 Service,通常可以将 Service 类型保留为 ClusterIP。然而,一些 Ingress 实现要求 Service 类型为 NodePort。请参考 Ingress 控制器的文档,确认是否如此。

通过 Ingress 对象暴露服务

虽然可以使用 Ingress 对象来暴露单个 Service,但它通常与多个 Service 对象结合使用,如下图所示。图中展示了单个 Ingress 对象如何使 Kiada 套件中的三个服务对外部客户端可访问。

图 12.1 Ingress 将外部流量转发到多个服务

Ingress 对象包含用于根据 HTTP 请求中的信息将流量路由到三个服务的规则。三个服务的公共 DNS 条目都指向同一个 Ingress。Ingress 会根据请求本身确定应该将请求发送到哪个服务。如果客户端请求指定主机为 kiada.example.com,Ingress 会将其转发到属于 kiada 服务的 Pod,而指定主机为 api.example.com 的请求则会根据请求的路径转发到 quote 或 quiz 服务。

在集群中使用多个 Ingress 对象

一个 Ingress 对象通常处理特定 Kubernetes 命名空间中所有 Service 对象的流量,但也可以选择使用多个 Ingress。通常,每个 Ingress 对象都会有自己的 IP 地址,但有些 Ingress 实现会为集群中创建的所有 Ingress 对象使用共享入口。

12.1.2 介绍 Ingress

控制器和反向代理并非所有 Kubernetes 集群开箱即可支持 Ingress 功能。这一功能由集群附加组件称为 Ingress 控制器提供。该控制器是 Ingress 对象与实际物理入口(反向代理)之间的桥梁。通常,控制器和代理会以两个进程在同一容器中运行,或者以两个容器在同一个 Pod 中运行。这也是为什么人们使用“Ingress 控制器”这个术语来指代两者。有时,控制器或代理位于集群之外。例如,Google Kubernetes Engine 提供其自己的 Ingress 控制器,利用 Google Cloud Platform 的 L7 负载均衡器为集群提供 Ingress 功能。如果您的集群部署在多个可用区,单个 Ingress 可以处理所有可用区的流量。它会根据客户端的位置,将每个 HTTP 请求转发到最合适的可用区。

有多种 ingress 控制器可供选择。Kubernetes 社区在 https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/ 上维护了一份清单。其中最受欢迎的有 Nginx ingress 控制器、Ambassador、Contour 和 Traefik。大多数这些 ingress 控制器使用 Nginx、HAProxy 或 Envoy 作为反向代理,但也有一些使用自己的代理实现。

理解 ingress 控制器的作用

Ingress 控制器是使 Ingress 对象生效的软件组件。如下面的图所示,控制器连接到 Kubernetes API 服务器并监控 Ingress、Service 和 Endpoints 或 EndpointSlice 对象。每当你创建、修改或删除这些对象时,控制器都会收到通知。它使用这些对象中的信息来为 ingress 配置和设置反向代理,如下图所示。

图 12.2 ingress 控制器的作用

当你创建 Ingress 对象时,控制器会读取其 spec 部分,并将其与它所引用的 Service 和 EndpointSlice 对象中的信息结合起来。控制器将这些信息转换为反向代理的配置。然后,它会使用该配置设置一个新的代理,并执行额外的步骤以确保该代理可以从集群外部访问。如果代理在集群内的某个 Pod 中运行,这通常意味着会创建一个 LoadBalancer 类型的服务来将代理对外公开。当你对 Ingress 对象进行更改时,控制器会更新代理的配置;当你删除该对象时,控制器会停止并移除代理,以及它同时创建的任何其他对象。

了解代理如何将流量转发到服务反向代理(或 L7 负载均衡器)是处理传入 HTTP 请求并将其转发到服务的组件。代理配置通常包含虚拟主机列表,以及每个虚拟主机对应的端点 IP 列表。这些信息是从 Ingress、Service 和 Endpoints/EndpointSlice 对象中获得的。当客户端连接到代理时,代理会使用这些信息根据请求路径和头信息将请求路由到某个端点,例如某个 Pod。下图展示了客户端如何通过代理访问 Kiada 服务。客户端首先执行对 kiada.example.com 的 DNS 查询。

DNS 服务器返回反向代理的公网 IP 地址。然后客户端向代理发送 HTTP 请求,其中 Host 头包含值 kiada.example.com。代理将此主机映射到其中一个 Kiada Pod 的 IP 地址,并将 HTTP 请求转发给它。请注意,代理不会将请求发送到服务 IP,而是直接发送到 Pod。这就是大多数 Ingress 实现的工作方式。

图 12.3 通过 Ingress 访问 Pods

12.1.3 安装 Ingress 控制器

在开始创建 Ingress 之前,你需要确保集群中运行着一个 Ingress 控制器。如同你在上一节中学到的,并非所有 Kubernetes 集群都自带 Ingress 控制器。如果你使用的是主要云提供商的托管集群,那么通常已经配置好了 Ingress 控制器。在 Google Kubernetes Engine 中,Ingress 控制器是 GLBC(GCE L7 负载均衡器);在 AWS 中,Ingress 功能由 AWS 负载均衡器控制器提供;而 Azure 提供 AGIC(应用网关 Ingress 控制器)。请查阅你的云提供商文档,确认是否提供了 Ingress 控制器以及是否需要启用它。或者,你也可以自行安装 Ingress 控制器。

正如你已经知道的,有很多不同的 Ingress 实现可供选择。它们都提供了上一节中解释的流量路由类型,但每种实现还提供了不同的附加功能。在本章的所有示例中,我使用的是 Nginx Ingress 控制器。除非你的集群提供了其他的 Ingress 控制器,否则我建议你也使用它。要在你的集群中安装 Nginx Ingress 控制器,请参阅侧边栏。

注意  Nginx Ingress 控制器有两种实现。一种由 Kubernetes 维护者提供,另一种由 Nginx 本身的作者提供。如果你是 Kubernetes 新手,应该从前者开始。这也是我使用的版本。

安装 Nginx Ingress 控制器

无论你如何运行你的 Kubernetes 集群,都应该可以按照 https://kubernetes.github.io/ingress-nginx/deploy/ 上的说明安装 Nginx Ingress 控制器。

如果使用kind工具创建集群,可以运行以下命令安装控制器:

如果你用Minikube运行集群,你可以像下面这样安装控制器:

12.2 创建和使用 Ingress 对象

上一节解释了 Ingress 对象和控制器的基础知识,以及如何安装 Nginx Ingress 控制器。在本节中,您将学习如何使用 Ingress 来暴露 Kiada 套件的服务。在创建第一个 Ingress 对象之前,您必须先部署 Kiada 套件的 Pods 和服务。如果您按照上一章的练习进行操作,它们应该已经存在。如果没有,您可以通过创建 kiada 命名空间,然后使用以下命令应用 Chapter12/SETUP/ 目录中的所有清单来创建它们:

$ kubectl apply -f SETUP/ --recursive

12.2.1 通过 Ingress 暴露服务

Ingress 对象引用一个或多个 Service 对象。你的第一个 Ingress 对象会暴露你在上一章创建的 kiada 服务。在创建 Ingress 之前,请通过查看以下清单中的服务清单来回顾一下。

列表 12.1 kiada 服务清单

服务类型是 ClusterIP,因为该服务本身不需要直接向集群外的客户端开放访问,Ingress 会负责处理这些访问。尽管该服务暴露了端口 80 和 443,但 Ingress 只会将流量转发到端口 80。创建 Ingress 对象Ingress 对象清单如下所示。你可以在本书代码仓库的文件 Chapter12/ing.kiada-example-com.yaml 中找到它。

清单 12.2 在 kiada.example.com 上暴露 kiada 服务的 Ingress 对象

清单中的清单文件定义了一个名为 kiada-examplecom 的 Ingress 对象。虽然您可以给该对象取任何名称,但建议名称能够反映 Ingress 规则中指定的主机和/或路径。

警告  在Google Kubernetes Engine 中,Ingress 名称不能包含点,否则在与该 Ingress 对象相关的事件中会显示以下错误信息:Error syncing to GCP: error running load balancer syncing routine: invalid loadbalancer name。

清单中的 Ingress 对象定义了一个规则。该规则说明,对于主机 kiada.example.com 的所有请求都应转发到 kiada 服务的 80 端口,无论请求的路径是什么(由 path 和 pathType 字段指示)。如下图所示。

图 12.4 kiada-example-com Ingress 对象如何配置外部流量路由

在使用kubectl apply创建了Ingress对象之后,你可以通过kubectl get Ingress在当前命名空间中列出Ingress对象的基本信息,如下所示:

注意  您可以使用ing作为入口的简写。

要详细查看Ingress对象,请使用kubectl describe命令,如下所示:

如您所见,kubectl describe 命令会列出 Ingress 对象中的所有规则。对于每条规则,不仅会显示目标服务的名称,还会显示其端点。如果您看到与默认后端相关的错误信息,请暂时忽略。稍后您会修复它。kubectl get 和 kubectl describe 都会显示 ingress 的 IP 地址。这个 IP 地址是客户端应发送请求的 L7 负载均衡器或反向代理的地址。在示例输出中,IP 地址是 11.22.33.44,端口是 80。

注意 地址可能不会立即显示。当集群运行在云端时,这种情况非常常见。如果几分钟后地址仍未显示,说明没有任何Ingress控制器处理该Ingress对象。检查控制器是否正在运行。由于集群可以运行多个Ingress控制器,如果你没有指定由哪一个处理,你的Ingress对象可能会被全部忽略。查阅所选Ingress控制器的文档,以了解是否需要添加kubernetes.io/ingress.class注解或在Ingress对象中设置spec.ingressClassName字段。你将在后续学习中更多了解该字段。

你也可以在Ingress对象的status字段中找到IP地址,如下所示:

注意  有时显示的地址可能会产生误导。例如,如果你使用 Minikube 并在虚拟机中启动集群,ingress 地址会显示为 localhost,但这只是从虚拟机的角度来看才成立。实际的 ingress 地址是虚拟机的 IP 地址,你可以使用 `minikube ip` 命令获取该地址。

将 Ingress IP 添加到 DNS

在将 Ingress 添加到生产集群后,下一步是向您的互联网域名 DNS 服务器添加一条记录。在这些示例中,我们假设您拥有域名 example.com。为了允许外部客户端通过 Ingress 访问您的服务,您需要配置 DNS 服务器,将域名 kiada.example.com 解析到 Ingress IP 11.22.33.44。在本地开发集群中,您无需处理 DNS 服务器。由于您只是从自己的计算机访问服务,可以通过其他方式让其解析地址。接下来将对此进行说明,并提供通过 Ingress 访问服务的操作步骤。

通过 Ingress 访问服务

由于 Ingress 使用虚拟主机来确定请求的转发位置,因此仅向 Ingress 的 IP 地址和端口发送 HTTP 请求不会得到期望的结果。您需要确保 HTTP 请求中的 Host 头与 Ingress 对象中的某个规则匹配。为此,必须告诉 HTTP 客户端将请求发送到主机 kiada.example.com。然而,这需要将主机解析到 Ingress 的 IP 地址。如果使用 curl,可以在无需配置 DNS 服务器或本地 /etc/hosts 文件的情况下完成此操作。假设 Ingress 的 IP 为 11.22.33.44,您可以使用以下命令通过 Ingress 访问 kiada 服务:

--resolve 选项将主机名 kiada.example.com 添加到 DNS 缓存中。这确保 kiada.example.com 能解析到 ingress IP。然后,Curl 会打开与 ingress 的连接并发送 HTTP 请求。请求中的 Host 头被设置为 kiada.example.com,这允许 ingress 将请求转发到正确的服务。当然,如果你想改用网页浏览器,则不能使用 --resolve 选项。相反,你可以将以下条目添加到你的 /etc/hosts 文件中。

注意  在 Windows 上,hosts 文件通常位于C:WindowsSystem32Driversetchosts。

现在,您可以通过网页浏览器或 curl 访问 http://kiada.example.com 服务,而无需使用 --resolve 选项将主机名映射到 IP。

12.2.2 基于路径的Ingress流量路由

一个Ingress对象可以包含许多规则,因此可以将多个主机和路径映射到多个服务。您已经为kiada服务创建了一个Ingress。现在,您将为quote和quiz服务创建一个Ingress对象。这两个服务的Ingress对象通过相同的主机api.example.com提供服务。HTTP请求中的路径决定了每个请求被发送到哪个服务。如以下图所示,所有路径为/quote的请求都会转发到quote服务,而所有路径以/questions开头的请求都会转发到quiz服务。图12.5 基于路径的Ingress流量路由

下面的列表显示了Ingress清单。

列表12.3将请求路径映射到不同的服务

在列表中显示的 Ingress 对象中,定义了一个包含两个路径的单一规则。该规则匹配主机为 api.example.com 的 HTTP 请求。在此规则中,paths 数组包含两个条目。第一个条目匹配请求 /quote 路径的请求,并将其转发到 quote 服务对象中名为 http 的端口。第二个条目匹配第一个路径元素为 /questions 的所有请求,并将其转发到 quiz 服务的 http 端口。

注意  默认情况下,入口代理不会执行任何 URL 重写。如果客户端请求路径 /quote,代理向后端服务发出的请求路径也将是 /quote。在某些入口实现中,你可以通过在 Ingress 对象中指定 URL 重写规则来更改此行为。

在你从前一示例清单中创建 Ingress 对象后,你可以按如下方式访问它公开的两个服务(将 IP 替换为你的入口 IP):

$ curl --resolve api.example.com:80:11.22.33.44 api.example.com/quote #A

$ curl --resolve api.example.com:80:11.22.33.44 api.example.com/questions/r如果你想用网页浏览器访问这些服务,请将 api.example.com 添加到你之前在 /etc/hosts 文件中添加的行中。它现在应该如下所示:

11.22.33.44 kiada.example.com api.example.com #A

了解路径是如何匹配的你是否注意到前一个列表中两个条目中的pathType字段的差异?pathType字段指定请求中的路径如何与Ingress规则中的路径匹配。支持的三个值在下表中进行了总结。

表12.1 pathType字段支持的值

路径类型

描述

Exact

请求的URL路径必须精确匹配指定的入口路由规则

Prefix

请求的URL路径必须以入口规则中指定的路径开始,逐个元素。

ImplementataionSpecific

路径匹配取决于入口控制器的实现。

如果在入口规则中指定了多个路径,并且请求中的路径匹配规则中的多个路径,则优先使用路径类型为 Exact 的路径。使用 Exact 路径类型匹配路径下表展示了当 pathType 设置为 Exact 时匹配的示例。

规则中的路径

匹配请求路径

未匹配

/

/

/foo

/boo

/foo

/foo

/foo/

/bar

/foo/

/foo/

/foo

/foo/bar

/bar

/FOO

/Foo

/foo

表 12.2 当 pathType 为 Exact 时请求路径的匹配情况

正如您从表格中的示例中所看到的,匹配按预期工作。它区分大小写,并且请求中的路径必须与入口规则中指定的路径完全匹配。使用前缀路径类型进行路径匹配当 pathType 设置为 Prefix 时,情况可能不像您预期的那样。请考虑下表中的示例。

表 12.3 当 pathType 为 Prefix 时匹配的请求路径

规则中的路径

匹配请求路径

未匹配

/

全路径;比如

/

/foo

/foo/

/foo

Or

/foo/

/foo

/foo/

/foo/bar

/foobar

/bar

/FOO

/FOO

/foo

请求路径不会被视为字符串来检查它是否以指定的前缀开头。相反,规则中的路径和请求路径都会按 / 进行拆分,然后将请求路径的每个元素与前缀的对应元素进行比较。例如,路径 /foo 会匹配请求路径 /foo/bar,但不会匹配 /foobar。同时,它也不会匹配请求路径 /fooxyz/bar。在匹配时,无论规则中的路径还是请求路径是否以斜杠结尾,都没有影响。与 Exact 路径类型一样,匹配是区分大小写的。

使用 ImplementationSpecific 路径类型进行路径匹配

ImplementationSpecific 路径类型,如其名称所示,依赖于 ingress 控制器的实现。使用此路径类型时,每个控制器可以设置自己的请求路径匹配规则。例如,在 GKE 中,你可以在路径中使用通配符。与其使用 Prefix 类型并将路径设置为 /foo,不如将类型设置为 ImplementationSpecific,并将路径设置为 /foo/*。

12.2.3 在 Ingress 对象中使用多个规则

在前面的章节中,你创建了两个 Ingress 对象来访问 Kiada 套件服务。在大多数 Ingress 实现中,每个 Ingress 对象都需要自己的公共 IP 地址,因此你现在可能在使用两个公共 IP 地址。由于这可能会产生额外成本,因此最好将 Ingress 对象合并为一个。创建具有多个规则的 Ingress 对象因为一个 Ingress 对象可以包含多个规则,所以将多个对象合并为一个非常简单。你只需要将这些规则放入同一个 Ingress 对象中,如下列清单所示。你可以在文件 ing.kiada.yaml 中找到该清单。

清单 12.4 在不同主机上公开多个服务的 Ingress

这个单一的 Ingress 对象处理 Kiada 套件中所有服务的所有流量,但只需要一个公共 IP 地址。Ingress 对象使用虚拟主机将流量路由到后端服务。如果请求中的 Host 头的值是 kiada.example.com,请求将被转发到 kiada 服务。如果头部值是 api.example.com,请求将根据请求的路径路由到其他两个服务之一。Ingress 及其关联的 Service 对象显示在下图中。

图 12.6 包含 Kiada 套件所有服务的 Ingress 对象

您可以删除之前创建的两个 Ingress 对象,然后用前面列表中的那个替换它们。然后,您可以尝试通过这个 ingress 访问所有三个服务。由于这是一个新的 Ingress 对象,其 IP 地址很可能与之前不同。因此,您需要更新 DNS、/etc/hosts 文件,或者在再次运行 curl 命令时使用 --resolve 选项。

在 host 字段中使用通配符

Ingress 规则中的 host 字段支持使用通配符。这允许您捕获发送到与 *.example.com 匹配的主机的所有请求,并将它们转发到您的服务。下表展示了通配符匹配的工作原理。

表 12.4 ingress 规则中 host 字段使用通配符的示例

主机

匹配的主机请求

未匹配

Kiada.example.com

Kiada.example.com

example.com
api.example.com

foo.kiada.example.com

*.example.com

kiada.example.com
api.example.com
foo.example.com

            

example.com
foo.kiada.example.com

看看带通配符的示例。正如你所见,*.example.com 可以匹配 kiada.example.com,但它不匹配 foo.kiada.example.com 或 example.com。这是因为通配符仅覆盖 DNS 名称的单个部分。与规则路径一样,精确匹配请求中主机的规则优先于带有主机通配符的规则。注意你也可以完全省略主机字段,使规则匹配任何主机。

12.2.4 设置默认后端

如果客户端的请求不符合 Ingress 对象中定义的任何规则,通常会返回 404 Not Found 响应。不过,你也可以定义一个默认后端,当没有规则匹配时,Ingress 会将请求转发到该后端。默认后端充当一个通配规则。下图显示了在 Ingress 对象中其他规则的上下文中默认后端的作用。

图 12.7 默认后端处理不符合任何 Ingress 规则的请求

如图所示,一个名为 fun404 的服务被用作默认后端。让我们将其添加到 kiada 的 Ingress 对象中。

在 Ingress 对象中指定默认后端

您可以在 spec.defaultBackend 字段中指定默认后端,如下清单所示(完整清单可以在 ing.kiada.defaultBackend.yaml 文件中找到)。

清单 12.5 在 Ingress 对象中指定默认后端

在列表中,你可以看到设置默认后端与在规则中设置后端没有太大区别。就像你在每条规则中指定后端服务的名称和端口一样,你也可以在 spec.defaultBackend 下的 service 字段中指定默认后端服务的名称和端口。

为默认后端创建服务和Pod

Kiada Ingress 对象配置为将不匹配任何规则的请求转发到名为 fun404 的服务。你需要创建此服务及其底层的 Pod。你可以在文件 all.my-default-backend.yaml 中找到同时包含这两个对象定义的对象清单。该文件的内容如下列表所示。

列表 12.6 默认 Ingress 后端的 Pod 和服务对象清单

在同时应用 Ingress 对象清单以及 Pod 和 Service 对象清单后,你可以通过发送不符合 Ingress 中任何规则的请求来测试默认后端。例如:

正如预期的那样,响应文本与您在 fun404 pod 中配置的内容匹配。当然,您也可以不使用默认后端来返回自定义的 404 状态,而是将其用于将所有请求转发到您选择的服务。您甚至可以创建一个仅具有默认后端且没有规则的 Ingress 对象,将所有外部流量转发到单个服务。如果您想知道为什么要使用 Ingress 对象而不是简单地将服务类型设置为 LoadBalancer,那是因为 Ingress 可以提供服务无法提供的额外 HTTP 功能。一个例子是使用传输层安全性 (TLS) 来保护客户端与服务之间的通信,这将在下一部分进行说明。

12.3 为 Ingress 配置 TLS

到目前为止,本章中,你已经使用 Ingress 对象允许外部 HTTP 流量访问你的服务。然而,如今你通常希望至少对所有外部流量进行 SSL/TLS 加密。你可能还记得 kiada 服务同时提供 HTTP 和 HTTPS 端口。当你创建 Ingress 时,你只配置了它将 HTTP 流量转发到服务,而没有配置 HTTPS。现在你将进行此配置。添加 HTTPS 支持有两种方式。你可以允许 HTTPS 流量通过 Ingress 代理,并由后端 Pod 终止 TLS 连接;或者让代理终止 TLS 连接,再通过 HTTP 与后端 Pod 连接。

12.3.1 为 Ingress 配置 TLS 直通

你可能会惊讶地发现,Kubernetes 并没有提供在 Ingress 对象中配置 TLS 透传的标准方法。如果 ingress 控制器支持 TLS 透传,通常可以通过向 Ingress 对象添加注解来进行配置。在 Nginx ingress 控制器的情况下,可以通过添加以下清单中显示的注解来实现。

清单 12.7 在使用 Nginx Ingress 控制器时启用 Ingress 的 SSL 透传

Nginx Ingress 控制器默认情况下不支持 SSL 直通(SSL passthrough)。要启用此功能,必须使用 --enable-sslpassthrough 标志启动控制器。由于这是一个非标准功能,并且高度依赖于你使用的具体 Ingress 控制器,因此我们不再深入探讨。有关如何在你的情况下启用直通功能的更多信息,请参阅你所使用的控制器的文档。 相反,我们先关注在 Ingress 代理处终止 TLS 连接。这是大多数 Ingress 控制器提供的标准功能,因此值得仔细研究。

12.3.2 在入口处终止 TLS大多数(如果不是全部的话)入口控制器实现都支持在入口代理处终止 TLS。代理终止客户端与自身之间的 TLS 连接,并将 HTTP 请求以未加密形式转发到后端 Pod,如下图所示。

图 12.8 使用 TLS 保护到入口的连接

要终止 TLS 连接,代理需要一个 TLS 证书和一个私钥。您可以通过在 Ingress 对象中引用的 Secret 来提供它们。

为 Ingress 创建 TLS 密钥

对于 kiada Ingress,您可以从书籍代码仓库中的清单文件 secret.tls-example-com.yaml 创建 Secret,或者使用以下命令生成私钥、证书和 Secret:

将 TLS 密钥添加到 Ingress要将 Secret 添加到 Ingress 对象,可以使用 kubectl edit 编辑对象,并添加下一个清单中突出显示的行,或者使用 kubectl apply 应用 ing.kiada.tls.yaml 文件。

清单 12.8 将 TLS 密钥添加到 Ingress

如您在清单中所见,tls 字段可以包含一个或多个条目。每个条目指定存储 TLS 证书/密钥对的 secretName,以及该密钥对适用的主机列表。警告tls.hosts 中指定的主机必须与密钥中证书使用的名称匹配。通过 TLS 访问 Ingress在您更新 Ingress 对象后,可以通过 HTTPS 访问该服务,方法如下:

命令的输出显示服务器证书与您配置 Ingress 时使用的证书匹配。通过将 TLS 密钥添加到 Ingress,您不仅保护了 kiada 服务,还保护了 quote 和 quiz 服务,因为它们都包含在 Ingress 对象中。尝试通过 Ingress 使用 HTTPS 访问它们。请记住,提供这两个服务的 pods 本身不提供 HTTPS。Ingress 为它们提供了 HTTPS。

12.4 额外的 Ingress 配置选项

希望您没有忘记,可以使用 kubectl explain 命令了解特定 API 对象类型的更多信息,并且您也会经常使用它。如果还没有,现在是使用它查看 Ingress 对象 spec 字段中可以配置哪些内容的好时机。查看以下命令的输出:

$ kubectl explain ingress.spec

请查看此命令显示的字段列表。您可能会惊讶地发现,除了前几节中解释的 defaultBackend、rules 和 tls 字段外,仅支持一个其他字段,即 ingressClassName。该字段用于指定应处理该 Ingress 对象的 ingress 控制器。稍后您将了解更多相关内容。目前,我想强调的是,HTTP 代理通常提供的附加配置选项在这里缺失。之所以看不到用于指定这些选项的其他字段,是因为几乎不可能在 Ingress 对象的模式中包含每种可能的 ingress 实现的所有配置选项。相反,这些自定义选项是通过注解或在单独的自定义 Kubernetes API 对象中进行配置的。

每个 ingress 控制器实现都支持其自己的注解或对象集合。我之前提到过,Nginx ingress 控制器使用注解来配置 TLS 透传。注解还用于配置 HTTP 认证、会话亲和性、URL 重写、重定向、跨域资源共享(CORS)等。支持的注解列表可以在 https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/ 查到。我不想详细介绍每一个注解,因为它们是实现特定的,但我想给你展示一个如何使用它们的示例。

12.4.1 使用注解配置 Ingress

在上一章中,你已经了解到 Kubernetes 服务仅支持基于客户端 IP 的会话亲和性。服务不支持基于 Cookie 的会话亲和性,因为服务工作在 OSI 网络模型的第 4 层,而 Cookie 是第 7 层(HTTP)的组成部分。然而,因为 Ingress 工作在 L7(第 7 层),它们可以支持基于 Cookie 的会话亲和性。在下面的示例中,我使用的 Nginx Ingress 控制器就是这种情况。

使用注解在 Nginx Ingress 中启用基于 Cookie 的会话亲和性

下面的清单展示了一个使用 Nginx Ingress 特定注解来启用基于 Cookie 的会话亲和性并配置会话 Cookie 名称的示例。清单中展示的清单文件可以在 ing.kiada.nginx-affinity.yaml 文件中找到。

清单 12.9 使用注解在 Nginx Ingress 中配置会话亲和性

在列表中,您可以看到注释 nginx.ingress.kubernetes.io/affinity 和 nginx.ingress.kubernetes.io/session-cookie-name。第一个注释启用基于 Cookie 的会话亲和性,第二个设置 Cookie 名称。注释键前缀表明这些注释是 Nginx ingress 控制器特有的,其他实现会忽略它们。

测试基于 Cookie 的会话亲和性

如果您想查看会话亲和性的实际效果,首先应用清单文件,等待 Nginx 配置更新,然后通过以下方式获取 Cookie:

你现在可以在你的请求中包含这个cookie,通过指定cookie头:

12.4.2 使用额外的 API 对象配置 Ingress

某些 Ingress 实现不使用注解来进行额外的 Ingress 配置,而是提供自己的对象类型。在前一节中,你已经学习了如何在使用 Nginx Ingress 控制器时,通过注解来配置会话亲和性。在本节中,你将学习如何在 Google Kubernetes Engine 中实现相同的操作。

注意  你将在第 29 章中学习如何通过 CustomResourceDefinition 对象创建自己的自定义对象类型。

使用 BackendConfig 对象类型在 GKE 中启用基于 Cookie 的会话亲和性

在运行 GKE 的集群中,Kubernetes API 中可以找到一种类型为 BackendConfig 的自定义对象。您可以创建该对象的实例,并在希望应用该对象的 Service 对象中按名称引用它。您可以使用 cloud.google.com/backendconfig 注释来引用该对象,如以下清单所示。清单 12.10 在 GKE 的 Service 对象中引用 BackendConfig

您可以使用 BackendConfig 对象来配置许多内容。由于此对象超出了本书的范围,请使用 `kubectl explain backendconfig.spec` 来了解更多信息,或参阅 GKE 文档。作为一个快速示例,说明如何使用自定义对象来配置 Ingress,我将向您展示如何使用 BackendConfig 对象配置基于 Cookie 的会话亲和性。您可以在以下清单中看到对象清单。

清单 12.11 使用 GKE 特定的 BackendConfig 对象来配置会话亲和性

在列表中,会话亲和类型设置为 GENERATED_COOKIE。由于这个对象在 kiada 服务中被引用,所以每当客户端通过 Ingress 访问该服务时,请求总是会路由到同一个后端 Pod。在本节和上一节中,您看到了向 Ingress 对象添加自定义配置的两种方法。由于方法取决于您使用的 Ingress 控制器,请参阅其文档以获取更多信息。

12.5 使用多个入口控制器

由于不同的入口实现提供不同的附加功能,你可能希望在集群中安装多个入口控制器。在这种情况下,每个 Ingress 对象都需要指明哪个入口控制器应处理它。最初,这是通过在 Ingress 对象的 kubernetes.io/ingress.class 注解中指定控制器名称来实现的。该方法现已被弃用,但仍有一些控制器使用它。正确指定要使用的控制器的方法是通过 IngressClass 对象。在安装入口控制器时,通常会创建一个或多个 IngressClass 对象。

当您创建一个 Ingress 对象时,可以通过在 Ingress 对象的 spec 字段中指定 IngressClass 对象的名称来指定 ingress 类。每个 IngressClass 都指定控制器的名称和可选参数。因此,您在 Ingress 对象中引用的类决定了提供哪个 ingress 代理以及如何配置它。如下一图所示,不同的 Ingress 对象可以引用不同的 IngressClass,而这些 IngressClass 又引用不同的 ingress 控制器。

图 12.9 Ingress、IngressClass 与 Ingress 控制器之间的关系

12.5.1 介绍 IngressClass 对象类型

如果 Nginx ingress 控制器正在您的集群中运行,当您安装该控制器时会创建一个名为 nginx 的 IngressClass 对象。如果在您的集群中部署了其他 ingress 控制器,您可能还会发现其他 IngressClass。

在集群中查找 IngressClass

要查看您的集群提供哪些 ingress 类,您可以使用以下命令列出它们:kubectl get:

命令的输出显示集群中存在一个名为 nginx 的单一 IngressClass。使用此类的 Ingress 将由 k8s.io/ingress-nginx 控制器处理。您还可以看到该类未指定任何控制器参数。

查看 IngressClass 对象的 YAML 清单

让我们通过检查其 YAML 定义来仔细查看 nginx IngressClass 对象:

如您所见,这个 IngressClass 对象仅指定了控制器的名称。稍后,您还将看到如何向对象添加控制器的参数。

12.5.2 在 Ingress 对象中指定 IngressClass

当您创建 Ingress 对象时,可以使用 Ingress 对象 spec 部分的 ingressClassName 字段来指定 ingress 的类别,如下列清单所示。

清单 12.12 引用特定 IngressClass 的 Ingress 对象

列表中的 Ingress 对象表明其类应为 nginx。由于该 IngressClass 指定 k8s.io/ingress-nginx 作为控制器,因此此列表中的 Ingress 由 Nginx Ingress 控制器处理。设置默认 IngressClass如果集群中安装了多个 Ingress 控制器,则应该有多个 IngressClass 对象。如果一个 Ingress 对象没有指定类,Kubernetes 将应用默认的 IngressClass,该默认类通过将 annotation ingressclass.kubernetes.io/is-default-class 设置为 "true" 来标记。

12.5.3 向 IngressClass 添加参数

除了使用 IngressClass 来指定某个特定 Ingress 对象所使用的 Ingress 控制器之外,如果单个 Ingress 控制器能够提供不同的 ingress 类型,IngressClass 也可以与之配合使用。这可以通过在每个 IngressClass 中指定不同的参数来实现。

在 IngressClass 对象中指定参数

IngressClass 对象本身不提供用于在对象内设置参数的字段,因为每个 ingress 控制器都有其特定要求,并且需要不同的字段集。相反,IngressClass 的自定义配置通常存储在一个单独的、针对每个 ingress 控制器实现的自定义 Kubernetes 对象类型中。您创建该自定义对象类型的实例,并在 IngressClass 对象中引用它。

例如,AWS 在 API 组 elbv2.k8s.aws、版本 v1beta1 中提供了一个类型为 IngressClassParams 的对象。要在 IngressClass 对象中配置这些参数,可以按下列清单所示引用 IngressClassParams 对象实例。

清单 12.13 在 IngressClass 中引用自定义参数对象

在列表中,包含此 IngressClass 参数的 IngressClassParams 对象实例名为 custom-ingress-params。对象类型(kind)和 apiGroup 也已指定。

使用定制 API 对象类型存储 IngressClass 参数的示例

下面的列表展示了一个 IngressClassParams 对象的示例。

列表 12.14 IngressClassParams 对象清单示例

有了 IngressClass 和 IngressClassParams 对象之后,集群用户可以创建将 ingressClassName 设置为 customingress-class 的 Ingress 对象。这些对象由 ingress.k8s.aws/alb 控制器(AWS 负载均衡控制器)处理。控制器会从 IngressClassParams 对象中读取参数,并使用这些参数来配置负载均衡器。Kubernetes 并不关心 IngressClassParams 对象的内容。它们仅由 ingress 控制器使用。由于每种实现使用自己的对象类型,您应该参考控制器的文档或使用 kubectl explain 来了解每种类型的更多信息。

12.6 使用自定义资源而非服务作为后端

本章中,Ingress 中引用的后端一直是 Service 对象。然而,一些 Ingress 控制器允许你使用其他资源作为后端。理论上,Ingress 控制器可以允许使用 Ingress 对象来暴露 ConfigMap 或 PersistentVolume 的内容,但更常见的是,控制器使用资源后端来提供通过自定义资源配置高级 Ingress 路由规则的选项。

12.6.1 使用自定义对象配置 Ingress 路由

Citrix Ingress 控制器提供了 HTTPRoute 自定义对象类型,它允许您配置 Ingress 应该将 HTTP 请求路由到哪里。如以下清单所示,您不是指定一个 Service 对象作为后端,而是指定包含路由规则的 HTTPRoute 对象的类型、apiGroup 和名称。

清单 12.15 使用资源后端的示例 Ingress 对象

列表中的 Ingress 对象指定了单条规则。它说明 ingress 控制器应该根据名为 my-example-route 的 HTTPRoute(来自 API 组 citrix.com)对象中指定的配置,转发发往主机 example.com 的流量。由于 HTTPRoute 对象不是 Kubernetes API 的一部分,其内容超出了本书的范围,但你大概可以猜到,它包含类似于 Ingress 对象的规则,只是指定方式不同,并且有额外的配置选项。在撰写本文时,支持自定义资源后端的 ingress 控制器非常少,但也许你会想自己实现一个。当你读完本书时,你将会知道如何去做。

12.7 总结

在本章中,您学习了如何创建 Ingress 对象,使一个或多个服务可以被外部客户端访问。您了解到:

  • Ingress 控制器根据 Ingress 对象中的配置来配置 L7 负载均衡器或反向代理。
  • 虽然 Service 是对一组 Pods 的抽象,但 Ingress 是对一组 Services 的抽象。
  • 无论它暴露多少个服务,Ingress 都只需要一个公共 IP,而每个 LoadBalancer 类型的 Service 都需要自己的公共 IP。
  • 外部客户端必须将 Ingress 对象中指定的主机名解析到 Ingress 代理的 IP 地址。为此,必须向负责该主机所属域的 DNS 服务器添加必要的记录。或者,为了开发目的,可以修改本地机器上的 /etc/hosts 文件。
  • Ingress 在 OSI 模型的第 7 层(应用层)运行,因此可以提供在第 4 层(传输层)运行的 Service 无法提供的 HTTP 相关功能。
  • Ingress 代理通常会将 HTTP 请求直接转发到后端 Pod,而不经过服务 IP,但这取决于具体的 Ingress 实现。
  • Ingress 对象包含规则,根据请求中的主机和路径,指定 Ingress 代理接收到的 HTTP 请求应转发到哪个服务。每条规则可以指定一个精确的主机或带通配符的主机,以及精确路径或路径前缀。
  • 默认后端是一个全局规则,用于决定应该由哪个服务处理不匹配任何规则的请求。可以将 Ingress 配置为通过 TLS 暴露服务。
  • Ingress 代理可以终止 TLS 连接,并将 HTTP 请求以未加密的方式转发到后端 Pod。有些 Ingress 实现支持 TLS 透传。
  • 特定于某个入口实现的入口配置选项通过 Ingress 对象的注解或控制器提供的自定义 Kubernetes 对象类型进行设置。
  • 一个 Kubernetes 集群可以同时运行多个入口控制器实现。当你创建一个 Ingress 对象时,你需要指定 IngressClass。IngressClass 对象指定哪个控制器应处理该 Ingress 对象。可选地,IngressClass 还可以为控制器指定参数。

你现在已经了解了如何在内部和外部公开一组 Pod。在下一章中,你将学习如何将这些 Pod 作为一个整体进行管理,并通过 Deployment 对象进行复制。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐