首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
Kubernetes镜像
Kubernetes容器环境
Kubernetes容器运行时类
Kubernetes容器生命周期回调
KubernetesPod 的生命周期
Kubernetes中的Init 容器
Kubernetes干扰(Disruptions)
Kubernetes临时容器
Kubernetes中的Downward API
Kubernetes中的Deployments
Kubernetes中的ReplicaSet
Kubernetes中的StatefulSet
Kubernetes中的DaemonSet
Kubernetes中的Job
Kubernetes中的Job 的自动清理
Kubernetes中的CronJob
Kubernetes中的ReplicationController
Kubernetes服务
Kubernetes拓扑感知的流量路由
Kubernetes中的Ingress
Kubernetes中的Ingress 控制器
Kubernetes中的Service连接到应用
Kubernetes中的EndpointSlice
Kubernetes网络策略
Kubernetes拓扑感知提示
Kubernetes中的Windows网络
Kubernetes服务内部流量策略
Kubernetes中的Service与Pod的DNS
Kubernetes中的IPv4/IPv6双协议栈
当前位置:
首页>>
技术小册>>
Kubernetes中文教程(二)
小册名称:Kubernetes中文教程(二)
Kubernetes 中 Service 的一个关键目标是让你无需修改现有应用程序就能使用不熟悉的服务发现机制。 你可以在 Pod 中运行代码,无需顾虑这是为云原生世界设计的代码,还是为已容器化的老应用程序设计的代码。 你可以使用 Service 让一组 Pod 在网络上可用,让客户端能够与其交互。 如果你使用 来运行你的应用, Deployment 可以动态地创建和销毁 Pod。不管是这一刻还是下一刻, 你不知道有多少个这样的 Pod 正在工作以及健康与否;你可能甚至不知道那些健康的 Pod 是如何命名的。 Kubernetes 被创建和销毁以匹配集群的预期状态。 Pod 是临时资源(你不应该期待单个 Pod 既可靠又耐用)。 每个 Pod 获取其自己的 IP 地址(Kubernetes 期待网络插件确保 IP 地址分配)。 对于集群中给定的 Deployment,这一刻运行的这组 Pod 可能不同于下一刻运行应用程序的那组 Pod。 这导致了一个问题: 如果一组 Pod(称为“后端”)为集群内的其他 Pod(称为“前端”)提供功能, 那么前端如何找出并跟踪要连接的 IP 地址,以便前端可以使用提供工作负载的后端部分? ## Kubernetes 中的 Service Service API 是 Kubernetes 的组成部分,它是一种抽象,帮助你通过网络暴露 Pod 组合。 每个 Service 对象定义一个逻辑组的端点(通常这些端点是 Pod)以及如何才能访问这些 Pod 的策略。 举个例子,考虑一个图片处理后端,它运行了 3 个副本。这些副本是可互换的 —— 前端不需要关心它们调用了哪个后端副本。 然而组成这一组后端程序的 Pod 实际上可能会发生变化, 前端客户端不应该也没必要知道,而且也不需要跟踪这一组后端的状态。 Service 定义的抽象能够解耦这种关联。 Service 针对的这组 Pod 通常由你定义的来确定。 若想了解定义 Service 端点的其他方式,可以查阅[**不带**选择算符的 Service]。 如果你的工作负载以 HTTP 通信,你可能会选择使用 [Ingress] 来控制 Web 流量如何到达该工作负载。Ingress 不是一种 Service,但它可用作集群的入口点。 Ingress 能让你将路由规则整合到单个资源,这样你就能在单个侦听器之后暴露工作负载的多个组件,在集群中分别运行这些组件。 Kubernetes 所用的 [Gateway] API 提供了除 Ingress 和 Service 之外的更多功能。你可以添加 Gateway 到你的集群。Gateway 是使用 实现的一系列扩展 API。将 Gateway 添加到你的集群后,就可以使用这些 Gateway 配置如何访问集群中正运行的网络服务。 ### 云原生服务发现 如果你想要在应用程序中使用 Kubernetes API 进行服务发现,则可以查询 用于匹配 EndpointSlices。 只要服务中的这组 Pod 发生变化,Kubernetes 就会为服务更新 EndpointSlices。 对于非本机应用程序,Kubernetes 提供了在应用程序和后端 Pod 之间放置网络端口或负载均衡器的方法。 ## 定义 Service Service 在 Kubernetes 中是一个 (与 Pod 或 ConfigMap 类似的对象)。你可以使用 Kubernetes API 创建、查看或修改 Service 定义。 通常你使用 `kubectl` 这类工具来进行这些 API 调用。 例如,假定有一组 Pod,每个 Pod 都在侦听 TCP 端口 9376,同时还被打上 `app.kubernetes.io/name=MyApp` 标签。 你可以定义一个 Service 来发布 TCP 侦听器。 ```yaml apiVersion: v1 kind: Service metadata: name: my-service spec: selector: app.kubernetes.io/name: MyApp ports: - protocol: TCP port: 80 targetPort: 9376 ``` 应用上述清单将创建一个名称为 "my-service" 的新 Service,它在所有 Pod 上指向 TCP 端口 9376,并且具有标签 `app.kubernetes.io/name: MyApp`。 Kubernetes 为该服务分配一个 IP 地址(有时称为 “集群 IP”),该 IP 地址由虚拟 IP 地址机制使用。 有关该机制的更多详情,请阅读[虚拟 IP 和服务代理]。 Service 的控制器不断扫描与其选择算符匹配的 Pod,然后对 Service 的 EndpointSlices 集合执行所有必要的更新。 Service 对象的名称必须是有效的 [RFC 1035 标签名称]。 需要注意的是,Service 能够将一个接收 `port` 映射到任意的 `targetPort`。 默认情况下,`targetPort` 将被设置为与 `port` 字段相同的值。 ### 端口定义 Pod 中的端口定义是有名字的,你可以在 Service 的 `targetPort` 属性中引用这些名称。 例如,我们可以通过以下方式将 Service 的 `targetPort` 绑定到 Pod 端口: ```yaml apiVersion: v1 kind: Pod metadata: name: nginx labels: app.kubernetes.io/name: proxy spec: containers: - name: nginx image: nginx:stable ports: - containerPort: 80 name: http-web-svc --- apiVersion: v1 kind: Service metadata: name: nginx-service spec: selector: app.kubernetes.io/name: proxy ports: - name: name-of-service-port protocol: TCP port: 80 targetPort: http-web-svc ``` 即使 Service 中使用同一配置名称混合使用多个 Pod,各 Pod 通过不同的端口号支持相同的网络协议, 此功能也可以使用。这为 Service 的部署和演化提供了很大的灵活性。 例如,你可以在新版本中更改 Pod 中后端软件公开的端口号,而不会破坏客户端。 服务的默认协议是 [TCP]; 你还可以使用任何其他[受支持的协议]。 由于许多服务需要公开多个端口,所以 Kubernetes 针对单个服务支持[多个端口定义]。 每个端口定义可以具有相同的 `protocol`,也可以具有不同的协议。 ### 没有选择算符的 Service 由于选择算符的存在,服务最常见的用法是为 Kubernetes Pod 的访问提供抽象, 但是当与相应的 对象一起使用且没有选择算符时, 服务也可以为其他类型的后端提供抽象,包括在集群外运行的后端。 例如: * 希望在生产环境中使用外部的数据库集群,但测试环境使用自己的数据库。 * 希望服务指向另一个 中或其它集群中的服务。 * 你正在将工作负载迁移到 Kubernetes。在评估该方法时,你仅在 Kubernetes 中运行一部分后端。 在任何这些场景中,都能够定义**未**指定与 Pod 匹配的选择算符的 Service。例如: 实例: ```yaml apiVersion: v1 kind: Service metadata: name: my-service spec: ports: - protocol: TCP port: 80 targetPort: 9376 ``` 由于此服务没有选择算符,因此不会自动创建相应的 EndpointSlice(和旧版 Endpoint)对象。 你可以通过手动添加 EndpointSlice 对象,将服务映射到运行该服务的网络地址和端口: ```yaml apiVersion: discovery.k8s.io/v1 kind: EndpointSlice metadata: name: my-service-1 # 按惯例将服务的名称用作 EndpointSlice 名称的前缀 labels: # 你应设置 "kubernetes.io/service-name" 标签。 # 设置其值以匹配服务的名称 kubernetes.io/service-name: my-service addressType: IPv4 ports: - name: '' # 留空,因为 port 9376 未被 IANA 分配为已注册端口 appProtocol: http protocol: TCP port: 9376 endpoints: - addresses: - "10.4.5.6" # 此列表中的 IP 地址可以按任何顺序显示 - "10.1.2.3" ``` #### 自定义 EndpointSlices 当为服务创建 [EndpointSlice] 对象时,可以为 EndpointSlice 使用任何名称。 命名空间中的每个 EndpointSlice 必须有一个唯一的名称。通过在 EndpointSlice 上设置 `kubernetes.io/service-name` 可以将 EndpointSlice 链接到服务。 端点 IP 地址**必须不是** :本地回路地址(IPv4 的 127.0.0.0/8、IPv6 的 ::1/128) 或链路本地地址(IPv4 的 169.254.0.0/16 和 224.0.0.0/24、IPv6 的 fe80::/64)。 端点 IP 地址不能是其他 Kubernetes 服务的集群 IP,因为 不支持将虚拟 IP 作为目标。 对于你自己或在你自己代码中创建的 EndpointSlice,你还应该为 [`endpointslice.kubernetes.io/managed-by`] 标签拣选一个值。如果你创建自己的控制器代码来管理 EndpointSlice, 请考虑使用类似于 `"my-domain.example/name-of-controller"` 的值。 如果你使用的是第三方工具,请使用全小写的工具名称,并将空格和其他标点符号更改为短划线 。 如果人们直接使用 `kubectl` 之类的工具来管理 EndpointSlices,请使用描述这种手动管理的名称, 例如 `"staff"` 或 `"cluster-admins"`。你应该避免使用保留值 `"controller"`, 该值标识由 Kubernetes 自己的控制平面管理的 EndpointSlices。 #### 访问没有选择算符的 Service 访问没有选择算符的 Service,与有选择算符的 Service 的原理相同。 在没有选择算符的 Service [示例]中, 流量被路由到 EndpointSlice 清单中定义的两个端点之一: 通过 TCP 协议连接到 10.1.2.3 或 10.4.5.6 的端口 9376。 Kubernetes API 服务器不允许代理到未被映射至 Pod 上的端点。由于此约束,当 Service 没有选择算符时,诸如 `kubectl proxy <service-name>` 之类的操作将会失败。这可以防止 Kubernetes API 服务器被用作调用者可能无权访问的端点的代理。 `ExternalName` Service 是 Service 的特例,它没有选择算符,而是使用 DNS 名称。 有关更多信息,请参阅 [ExternalName] 一节。 ### EndpointSlices [EndpointSlices] 这些对象表示针对服务的后备网络端点的子集(**切片**)。 你的 Kubernetes 集群会跟踪每个 EndpointSlice 表示的端点数量。 如果服务的端点太多以至于达到阈值,Kubernetes 会添加另一个空的 EndpointSlice 并在其中存储新的端点信息。 默认情况下,一旦现有 EndpointSlice 都包含至少 100 个端点,Kubernetes 就会创建一个新的 EndpointSlice。 在需要添加额外的端点之前,Kubernetes 不会创建新的 EndpointSlice。 参阅 [EndpointSlices] 了解有关该 API 的更多信息。 ### Endpoints 在 Kubernetes API 中,[Endpoints] (该资源类别为复数)定义了网络端点的列表,通常由 Service 引用,以定义可以将流量发送到哪些 Pod。 推荐用 EndpointSlice API 替换 Endpoints。 #### 超出容量的端点 Kubernetes 限制单个 Endpoints 对象中可以容纳的端点数量。 当一个服务有超过 1000 个后备端点时,Kubernetes 会截断 Endpoints 对象中的数据。 由于一个服务可以链接多个 EndpointSlice,所以 1000 个后备端点的限制仅影响旧版的 Endpoints API。 这种情况下,Kubernetes 选择最多 1000 个可能的后端端点来存储到 Endpoints 对象中,并在 Endpoints: [`endpoints.kubernetes.io/over-capacity: truncated`] 上设置。 如果后端 Pod 的数量低于 1000,控制平面也会移除该注解。 流量仍会发送到后端,但任何依赖旧版 Endpoints API 的负载均衡机制最多只能将流量发送到 1000 个可用的后备端点。 相同的 API 限制意味着你不能手动将 Endpoints 更新为拥有超过 1000 个端点。 ### 应用协议 `appProtocol` 字段提供了一种为每个 Service 端口指定应用协议的方式。 此字段的取值会被映射到对应的 Endpoints 和 EndpointSlices 对象。 该字段遵循标准的 Kubernetes 标签语法。 其值可以是 [IANA 标准服务名称] 或以域名为前缀的名称,如 `mycompany.com/my-custom-protocol`。 ### 多端口 Service 对于某些服务,你需要公开多个端口。 Kubernetes 允许你在 Service 对象上配置多个端口定义。 为服务使用多个端口时,必须提供所有端口名称,以使它们无歧义。 例如: ```yaml apiVersion: v1 kind: Service metadata: name: my-service spec: selector: app.kubernetes.io/name: MyApp ports: - name: http protocol: TCP port: 80 targetPort: 9376 - name: https protocol: TCP port: 443 targetPort: 9377 ``` 与一般的 Kubernetes 名称一样,端口名称只能包含小写字母数字字符 和 `-`。 端口名称还必须以字母数字字符开头和结尾。 例如,名称 `123-abc` 和 `web` 有效,但是 `123_abc` 和 `-web` 无效。 ## 选择自己的 IP 地址 在 `Service` 创建的请求中,可以通过设置 `spec.clusterIP` 字段来指定自己的集群 IP 地址。 比如,希望替换一个已经已存在的 DNS 条目,或者遗留系统已经配置了一个固定的 IP 且很难重新配置。 用户选择的 IP 地址必须合法,并且这个 IP 地址在 `service-cluster-ip-range` CIDR 范围内, 这对 API 服务器来说是通过一个标识来指定的。 如果 IP 地址不合法,API 服务器会返回 HTTP 状态码 422,表示值不合法。 ## 服务发现 Kubernetes 支持两种基本的服务发现模式 —— 环境变量和 DNS。 ### 环境变量 当 Pod 运行在 `Node` 上,kubelet 会为每个活跃的 Service 添加一组环境变量。 kubelet 为 Pod 添加环境变量 `{SVCNAME}_SERVICE_HOST` 和 `{SVCNAME}_SERVICE_PORT`。 这里 Service 的名称需大写,横线被转换成下划线。 它还支持与 Docker Engine 的 "**[legacy container links]**" 特性兼容的变量 (参阅 [makeLinkVariables]) 。 举个例子,一个名称为 `redis-primary` 的 Service 暴露了 TCP 端口 6379, 同时给它分配了 Cluster IP 地址 10.0.0.11,这个 Service 生成了如下环境变量: ```shell REDIS_PRIMARY_SERVICE_HOST=10.0.0.11 REDIS_PRIMARY_SERVICE_PORT=6379 REDIS_PRIMARY_PORT=tcp://10.0.0.11:6379 REDIS_PRIMARY_PORT_6379_TCP=tcp://10.0.0.11:6379 REDIS_PRIMARY_PORT_6379_TCP_PROTO=tcp REDIS_PRIMARY_PORT_6379_TCP_PORT=6379 REDIS_PRIMARY_PORT_6379_TCP_ADDR=10.0.0.11 ``` 当你具有需要访问服务的 Pod 时,并且你正在使用环境变量方法将端口和集群 IP 发布到客户端 Pod 时,必须在客户端 Pod 出现 **之前** 创建服务。 否则,这些客户端 Pod 将不会设定其环境变量。 如果仅使用 DNS 查找服务的集群 IP,则无需担心此设定问题。 ### DNS 你可以(几乎总是应该)使用[附加组件] 为 Kubernetes 集群设置 DNS 服务。 支持集群的 DNS 服务器(例如 CoreDNS)监视 Kubernetes API 中的新服务,并为每个服务创建一组 DNS 记录。 如果在整个集群中都启用了 DNS,则所有 Pod 都应该能够通过其 DNS 名称自动解析服务。 例如,如果你在 Kubernetes 命名空间 `my-ns` 中有一个名为 `my-service` 的服务, 则控制平面和 DNS 服务共同为 `my-service.my-ns` 创建 DNS 记录。 `my-ns` 命名空间中的 Pod 应该能够通过按名检索 `my-service` 来找到服务 (`my-service.my-ns` 也可以工作)。 其他命名空间中的 Pod 必须将名称限定为 `my-service.my-ns`。 这些名称将解析为为服务分配的集群 IP。 Kubernetes 还支持命名端口的 DNS SRV(服务)记录。 如果 `my-service.my-ns` 服务具有名为 `http` 的端口,且协议设置为 TCP, 则可以对 `_http._tcp.my-service.my-ns` 执行 DNS SRV 查询以发现该端口号、`"http"` 以及 IP 地址。 Kubernetes DNS 服务器是唯一的一种能够访问 `ExternalName` 类型的 Service 的方式。 更多关于 `ExternalName` 解析的信息可以查看 [Service 与 Pod 的 DNS]。 ## 无头服务(Headless Services) 有时不需要或不想要负载均衡,以及单独的 Service IP。 遇到这种情况,可以通过指定 Cluster IP(`spec.clusterIP`)的值为 `"None"` 来创建 `Headless` Service。 你可以使用一个无头 Service 与其他服务发现机制进行接口,而不必与 Kubernetes 的实现捆绑在一起。 对于无头 `Services` 并不会分配 Cluster IP,kube-proxy 不会处理它们, 而且平台也不会为它们进行负载均衡和路由。 DNS 如何实现自动配置,依赖于 Service 是否定义了选择算符。 ### 带选择算符的服务 对定义了选择算符的无头服务,Kubernetes 控制平面在 Kubernetes API 中创建 EndpointSlice 对象, 并且修改 DNS 配置返回 A 或 AAA 条记录(IPv4 或 IPv6 地址),通过这个地址直接到达 `Service` 的后端 Pod 上。 ### 无选择算符的服务 对没有定义选择算符的无头服务,控制平面不会创建 EndpointSlice 对象。 然而 DNS 系统会查找和配置以下之一: * 对于 [`type: ExternalName`] 服务,查找和配置其 CNAME 记录 * 对所有其他类型的服务,针对 Service 的就绪端点的所有 IP 地址,查找和配置 DNS A / AAAA 条记录 * 对于 IPv4 端点,DNS 系统创建 A 条记录。 * 对于 IPv6 端点,DNS 系统创建 AAAA 条记录。 ## 发布服务(服务类型) 对一些应用的某些部分(如前端),可能希望将其暴露给 Kubernetes 集群外部的 IP 地址。 Kubernetes `ServiceTypes` 允许指定你所需要的 Service 类型。 `Type` 的取值以及行为如下: * `ClusterIP`:通过集群的内部 IP 暴露服务,选择该值时服务只能够在集群内部访问。 这也是你没有为服务显式指定 `type` 时使用的默认值。 你可以使用 [Ingress] 或者 [Gateway API] 向公众暴露服务。 * [`NodePort`]:通过每个节点上的 IP 和静态端口(`NodePort`)暴露服务。 为了让节点端口可用,Kubernetes 设置了集群 IP 地址,这等同于你请求 `type: ClusterIP` 的服务。 * [`LoadBalancer`]:使用云提供商的负载均衡器向外部暴露服务。 外部负载均衡器可以将流量路由到自动创建的 `NodePort` 服务和 `ClusterIP` 服务上。 * [`ExternalName`]:通过返回 `CNAME` 记录和对应值,可以将服务映射到 `externalName` 字段的内容(例如,`foo.bar.example.com`)。 无需创建任何类型代理。 你需要使用 kube-dns 1.7 及以上版本或者 CoreDNS 0.0.8 及以上版本才能使用 `ExternalName` 类型。 `type` 字段被设计为嵌套功能 - 每个级别都添加到前一个级别。 这并不是所有云提供商都严格要求的(例如:Google Compute Engine 不需要分配节点端口来使 `type: LoadBalancer` 工作,但另一个云提供商集成可能会这样做)。 虽然不需要严格的嵌套,但是 Service 的 Kubernetes API 设计无论如何都需要它。 你也可以使用 [Ingress] 来暴露自己的服务。 Ingress 不是一种服务类型,但它充当集群的入口点。 它可以将路由规则整合到一个资源中,因为它可以在同一 IP 地址下公开多个服务。 ### NodePort 类型 如果你将 `type` 字段设置为 `NodePort`,则 Kubernetes 控制平面将在 `--service-node-port-range` 标志指定的范围内分配端口(默认值:30000-32767)。 每个节点将那个端口(每个节点上的相同端口号)代理到你的服务中。 你的服务在其 `.spec.ports[*].nodePort` 字段中报告已分配的端口。 使用 NodePort 可以让你自由设置自己的负载均衡解决方案, 配置 Kubernetes 不完全支持的环境, 甚至直接暴露一个或多个节点的 IP 地址。 对于 NodePort 服务,Kubernetes 额外分配一个端口(TCP、UDP 或 SCTP 以匹配服务的协议)。 集群中的每个节点都将自己配置为监听分配的端口并将流量转发到与该服务关联的某个就绪端点。 通过使用适当的协议(例如 TCP)和适当的端口(分配给该服务)连接到所有节点, 你将能够从集群外部使用 `type: NodePort` 服务。 #### 选择你自己的端口 如果需要特定的端口号,你可以在 `nodePort` 字段中指定一个值。 控制平面将为你分配该端口或报告 API 事务失败。 这意味着你需要自己注意可能发生的端口冲突。 你还必须使用有效的端口号,该端口号在配置用于 NodePort 的范围内。 以下是 `type: NodePort` 服务的一个示例清单,它指定了一个 NodePort 值(在本例中为 30007)。 ```yaml apiVersion: v1 kind: Service metadata: name: my-service spec: type: NodePort selector: app.kubernetes.io/name: MyApp ports: # 默认情况下,为了方便起见,`targetPort` 被设置为与 `port` 字段相同的值。 - port: 80 targetPort: 80 # 可选字段 # 默认情况下,为了方便起见,Kubernetes 控制平面会从某个范围内分配一个端口号(默认:30000-32767) nodePort: 30007 ``` #### 为 `type: NodePort` 服务自定义 IP 地址配置 你可以在集群中设置节点以使用特定 IP 地址来提供 NodePort 服务。 如果每个节点都连接到多个网络(例如:一个网络用于应用程序流量,另一个网络用于节点和控制平面之间的流量), 你可能需要执行此操作。 如果你要指定特定的 IP 地址来代理端口,可以将 kube-proxy 的 `--nodeport-addresses` 标志或 [kube-proxy 配置文件]的等效 `nodePortAddresses` 字段设置为特定的 IP 段。 此标志采用逗号分隔的 IP 段列表(例如 `10.0.0.0/8`、`192.0.2.0/25`)来指定 kube-proxy 应视为该节点本地的 IP 地址范围。 例如,如果你使用 `--nodeport-addresses=127.0.0.0/8` 标志启动 kube-proxy, 则 kube-proxy 仅选择 NodePort 服务的环回接口。 `--nodeport-addresses` 的默认值是一个空列表。 这意味着 kube-proxy 应考虑 NodePort 的所有可用网络接口。 (这也与早期的 Kubernetes 版本兼容。) 此服务呈现为 `<NodeIP>:spec.ports[*].nodePort` 和 `.spec.clusterIP:spec.ports[*].port`。 如果设置了 kube-proxy 的 `--nodeport-addresses` 标志或 kube-proxy 配置文件中的等效字段, 则 `<NodeIP>` 将是过滤的节点 IP 地址(或可能的 IP 地址)。 ### LoadBalancer 类型 在使用支持外部负载均衡器的云提供商的服务时,设置 `type` 的值为 `"LoadBalancer"`, 将为 Service 提供负载均衡器。 负载均衡器是异步创建的,关于被提供的负载均衡器的信息将会通过 Service 的 `status.loadBalancer` 字段发布出去。 实例: ```yaml apiVersion: v1 kind: Service metadata: name: my-service spec: selector: app.kubernetes.io/name: MyApp ports: - protocol: TCP port: 80 targetPort: 9376 clusterIP: 10.0.171.239 type: LoadBalancer status: loadBalancer: ingress: - ip: 192.0.2.127 ``` 来自外部负载均衡器的流量将直接重定向到后端 Pod 上,不过实际它们是如何工作的,这要依赖于云提供商。 要实现 `type: LoadBalancer` 的服务,Kubernetes 通常首先进行与请求 `type: NodePort` 服务等效的更改。 cloud-controller-manager 组件随后配置外部负载均衡器以将流量转发到已分配的节点端口。 你可以将负载均衡服务配置为[忽略]分配节点端口, 前提是云提供商实现支持这点。 某些云提供商允许设置 `loadBalancerIP`。 在这些情况下,将根据用户设置的 `loadBalancerIP` 来创建负载均衡器。 如果没有设置 `loadBalancerIP` 字段,将会给负载均衡器指派一个临时 IP。 如果设置了 `loadBalancerIP`,但云提供商并不支持这种特性,那么设置的 `loadBalancerIP` 值将会被忽略掉。 针对 Service 的 `.spec.loadBalancerIP` 字段已在 Kubernetes v1.24 中被弃用。 此字段的定义模糊,其含义因实现而异。它也不支持双协议栈联网。 此字段可能会在未来的 API 版本中被移除。 如果你正在集成某云平台,该平台通过(特定于提供商的)注解为 Service 指定负载均衡器 IP 地址, 你应该切换到这样做。 如果你正在为集成到 Kubernetes 的负载均衡器编写代码,请避免使用此字段。 你可以与 [Gateway] 而不是 Service 集成, 或者你可以在 Service 上定义自己的(特定于提供商的)注解,以指定等效的细节。 #### 混合协议类型的负载均衡器 默认情况下,对于 LoadBalancer 类型的服务,当定义了多个端口时, 所有端口必须具有相同的协议,并且该协议必须是受云提供商支持的协议。 当服务中定义了多个端口时,特性门控 `MixedProtocolLBService` (在 kube-apiserver 1.24 版本默认为启用) 允许 LoadBalancer 类型的服务使用不同的协议。 可用于 LoadBalancer 类型服务的协议集仍然由云提供商决定。 如果云提供商不支持混合协议,他们将只提供单一协议。 ### 禁用负载均衡器节点端口分配 你可以通过设置 `spec.allocateLoadBalancerNodePorts` 为 `false` 对类型为 LoadBalancer 的服务禁用节点端口分配。 这仅适用于直接将流量路由到 Pod 而不是使用节点端口的负载均衡器实现。 默认情况下,`spec.allocateLoadBalancerNodePorts` 为 `true`, LoadBalancer 类型的服务继续分配节点端口。 如果现有服务已被分配节点端口,将参数 `spec.allocateLoadBalancerNodePorts` 设置为 `false` 时,这些服务上已分配置的节点端口**不会**被自动释放。 你必须显式地在每个服务端口中删除 `nodePorts` 项以释放对应端口。 #### 设置负载均衡器实现的类别 `spec.loadBalancerClass` 允许你不使用云提供商的默认负载均衡器实现,转而使用指定的负载均衡器实现。 默认情况下,`.spec.loadBalancerClass` 的取值是 `nil`,如果集群使用 `--cloud-provider` 配置了云提供商, `LoadBalancer` 类型服务会使用云提供商的默认负载均衡器实现。 如果设置了 `.spec.loadBalancerClass`,则假定存在某个与所指定的类相匹配的负载均衡器实现在监视服务变化。 所有默认的负载均衡器实现(例如,由云提供商所提供的)都会忽略设置了此字段的服务。`.spec.loadBalancerClass` 只能设置到类型为 `LoadBalancer` 的 Service 之上,而且一旦设置之后不可变更。 `.spec.loadBalancerClass` 的值必须是一个标签风格的标识符, 可以有选择地带有类似 "`internal-vip`" 或 "`example.com/internal-vip`" 这类前缀。 没有前缀的名字是保留给最终用户的。 #### 内部负载均衡器 在混合环境中,有时有必要在同一网络地址块内路由来自服务的流量。 在水平分割 DNS 环境中,你需要两个服务才能将内部和外部流量都路由到你的端点(Endpoints)。 如要设置内部负载均衡器,请根据你所使用的云运营商,为服务添加以下注解之一: 选择一个标签。 ```yaml [...] metadata: name: my-service annotations: cloud.google.com/load-balancer-type: "Internal" [...] ``` ```yaml [...] metadata: name: my-service annotations: service.beta.kubernetes.io/aws-load-balancer-internal: "true" [...] ``` ```yaml [...] metadata: name: my-service annotations: service.beta.kubernetes.io/azure-load-balancer-internal: "true" [...] ``` ```yaml [...] metadata: name: my-service annotations: service.kubernetes.io/ibm-load-balancer-cloud-provider-ip-type: "private" [...] ``` ```yaml [...] metadata: name: my-service annotations: service.beta.kubernetes.io/openstack-internal-load-balancer: "true" [...] ``` ```yaml [...] metadata: name: my-service annotations: service.beta.kubernetes.io/cce-load-balancer-internal-vpc: "true" [...] ``` ```yaml [...] metadata: annotations: service.kubernetes.io/qcloud-loadbalancer-internal-subnetid: subnet-xxxxx [...] ``` ```yaml [...] metadata: annotations: service.beta.kubernetes.io/alibaba-cloud-loadbalancer-address-type: "intranet" [...] ``` ```yaml [...] metadata: name: my-service annotations: service.beta.kubernetes.io/oci-load-balancer-internal: true [...] ``` ### AWS TLS 支持 为了对在 AWS 上运行的集群提供 TLS/SSL 部分支持,你可以向 `LoadBalancer` 服务添加三个注解: ```yaml metadata: name: my-service annotations: service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012 ``` 第一个指定要使用的证书的 ARN。 它可以是已上载到 IAM 的第三方颁发者的证书, 也可以是在 AWS Certificate Manager 中创建的证书。 ```yaml metadata: name: my-service annotations: service.beta.kubernetes.io/aws-load-balancer-backend-protocol: ``` 第二个注解指定 Pod 使用哪种协议。对于 HTTPS 和 SSL,ELB 希望 Pod 使用证书通过加密连接对自己进行身份验证。 HTTP 和 HTTPS 选择第 7 层代理:ELB 终止与用户的连接,解析标头,并在转发请求时向 `X-Forwarded-For` 标头注入用户的 IP 地址(Pod 仅在连接的另一端看到 ELB 的 IP 地址)。 TCP 和 SSL 选择第 4 层代理:ELB 转发流量而不修改报头。 在某些端口处于安全状态而其他端口未加密的混合使用环境中,可以使用以下注解: ```yaml metadata: name: my-service annotations: service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443,8443" ``` 在上例中,如果服务包含 `80`、`443` 和 `8443` 三个端口, 那么 `443` 和 `8443` 将使用 SSL 证书, 而 `80` 端口将转发 HTTP 数据包。 从 Kubernetes v1.9 起可以使用 [预定义的 AWS SSL 策略] 为你的服务使用 HTTPS 或 SSL 侦听器。 要查看可以使用哪些策略,可以使用 `aws` 命令行工具: ```bash aws elb describe-load-balancer-policies --query 'PolicyDescriptions[].PolicyName' ``` 然后,你可以使用 "`service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy`" 注解;例如: ```yaml metadata: name: my-service annotations: service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy: "ELBSecurityPolicy-TLS-1-2-2017-01" ``` #### AWS 上的 PROXY 协议支持 为了支持在 AWS 上运行的集群,启用 [PROXY 协议]。 你可以使用以下服务注解: ```yaml metadata: name: my-service annotations: service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*" ``` 从 1.3.0 版开始,此注解的使用适用于 ELB 代理的所有端口,并且不能进行其他配置。 #### AWS 上的 ELB 访问日志 有几个注解可用于管理 AWS 上 ELB 服务的访问日志。 注解 `service.beta.kubernetes.io/aws-load-balancer-access-log-enabled` 控制是否启用访问日志。 注解 `service.beta.kubernetes.io/aws-load-balancer-access-log-emit-interval` 控制发布访问日志的时间间隔(以分钟为单位)。你可以指定 5 分钟或 60 分钟的间隔。 注解 `service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name` 控制存储负载均衡器访问日志的 Amazon S3 存储桶的名称。 注解 `service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix` 指定为 Amazon S3 存储桶创建的逻辑层次结构。 ```yaml metadata: name: my-service annotations: # 指定是否为负载均衡器启用访问日志 service.beta.kubernetes.io/aws-load-balancer-access-log-enabled: "true" # 发布访问日志的时间间隔。你可以将其设置为 5 分钟或 60 分钟。 service.beta.kubernetes.io/aws-load-balancer-access-log-emit-interval: "60" # 用来存放访问日志的 Amazon S3 Bucket 名称 service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name: "my-bucket" # 你为 Amazon S3 Bucket 所创建的逻辑层次结构,例如 `my-bucket-prefix/prod` service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix: "my-bucket-prefix/prod" ``` #### AWS 上的连接排空 可以将注解 `service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled` 设置为 `"true"` 来管理 ELB 的连接排空。 注解 `service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout` 也可以用于设置最大时间(以秒为单位),以保持现有连接在注销实例之前保持打开状态。 ```yaml metadata: name: my-service annotations: service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled: "true" service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout: "60" ``` #### 其他 ELB 注解 还有其他一些注解,用于管理经典弹性负载均衡器,如下所述。 ```yaml metadata: name: my-service annotations: # 按秒计的时间,表示负载均衡器关闭连接之前连接可以保持空闲 # (连接上无数据传输)的时间长度 service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "60" # 指定该负载均衡器上是否启用跨区的负载均衡能力 service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true" # 逗号分隔列表值,每一项都是一个键-值耦对,会作为额外的标签记录于 ELB 中 service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags: "environment=prod,owner=devops" # 将某后端视为健康、可接收请求之前需要达到的连续成功健康检查次数。 # 默认为 2,必须介于 2 和 10 之间 service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold: "" # 将某后端视为不健康、不可接收请求之前需要达到的连续不成功健康检查次数。 # 默认为 6,必须介于 2 和 10 之间 service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold: "3" # 对每个实例进行健康检查时,连续两次检查之间的大致间隔秒数 # 默认为 10,必须介于 5 和 300 之间 service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "20" # 时长秒数,在此期间没有响应意味着健康检查失败 # 此值必须小于 service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval # 默认值为 5,必须介于 2 和 60 之间 service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "5" # 由已有的安全组所构成的列表,可以配置到所创建的 ELB 之上。 # 与注解 service.beta.kubernetes.io/aws-load-balancer-extra-security-groups 不同, # 这一设置会替代掉之前指定给该 ELB 的所有其他安全组,也会覆盖掉为此 # ELB 所唯一创建的安全组。 # 此列表中的第一个安全组 ID 被用来作为决策源,以允许入站流量流入目标工作节点 # (包括服务流量和健康检查)。 # 如果多个 ELB 配置了相同的安全组 ID,为工作节点安全组添加的允许规则行只有一个, # 这意味着如果你删除了这些 ELB 中的任何一个,都会导致该规则记录被删除, # 以至于所有共享该安全组 ID 的其他 ELB 都无法访问该节点。 # 此注解如果使用不当,会导致跨服务的不可用状况。 service.beta.kubernetes.io/aws-load-balancer-security-groups: "sg-53fae93f" # 额外的安全组列表,将被添加到所创建的 ELB 之上。 # 添加时,会保留为 ELB 所专门创建的安全组。 # 这样会确保每个 ELB 都有一个唯一的安全组 ID 和与之对应的允许规则记录, # 允许请求(服务流量和健康检查)发送到目标工作节点。 # 这里顶一个安全组可以被多个服务共享。 service.beta.kubernetes.io/aws-load-balancer-extra-security-groups: "sg-53fae93f,sg-42efd82e" # 用逗号分隔的一个键-值偶对列表,用来为负载均衡器选择目标节点 service.beta.kubernetes.io/aws-load-balancer-target-node-labels: "ingress-gw,gw-name=public-api" ``` #### AWS 上网络负载均衡器支持 要在 AWS 上使用网络负载均衡器,可以使用注解 `service.beta.kubernetes.io/aws-load-balancer-type`,将其取值设为 `nlb`。 ```yaml metadata: name: my-service annotations: service.beta.kubernetes.io/aws-load-balancer-type: "nlb" ``` NLB 仅适用于某些实例类。有关受支持的实例类型的列表, 请参见 [AWS 文档] 中关于所支持的实例类型的 Elastic Load Balancing 说明。 与经典弹性负载均衡器不同,网络负载均衡器(NLB)将客户端的 IP 地址转发到该节点。 如果服务的 `.spec.externalTrafficPolicy` 设置为 `Cluster` ,则客户端的 IP 地址不会传达到最终的 Pod。 通过将 `.spec.externalTrafficPolicy` 设置为 `Local`,客户端 IP 地址将传播到最终的 Pod, 但这可能导致流量分配不均。 没有针对特定 LoadBalancer 服务的任何 Pod 的节点将无法通过自动分配的 `.spec.healthCheckNodePort` 进行 NLB 目标组的运行状况检查,并且不会收到任何流量。 为了获得均衡流量,请使用 DaemonSet 或指定 [Pod 反亲和性] 使其不在同一节点上。 你还可以将 NLB 服务与[内部负载均衡器] 注解一起使用。 为了使客户端流量能够到达 NLB 后面的实例,使用以下 IP 规则修改了节点安全组: | 规则 | 协议 | 端口 | IpRange | IpRange 描述 | |------|----------|---------|------------|---------------------| | Health Check | TCP | NodePort | Subnet CIDR | kubernetes.io/rule/nlb/health=\<loadBalancerName\> | | Client Traffic | TCP | NodePort | `.spec.loadBalancerSourceRanges` | kubernetes.io/rule/nlb/client=\<loadBalancerName\> | | MTU Discovery | ICMP | 3,4 | `.spec.loadBalancerSourceRanges` | kubernetes.io/rule/nlb/mtu=\<loadBalancerName\> | 为了限制哪些客户端 IP 可以访问网络负载均衡器,请指定 `loadBalancerSourceRanges`。 ```yaml spec: loadBalancerSourceRanges: - "143.231.0.0/16" ``` 如果未设置 `.spec.loadBalancerSourceRanges` ,则 Kubernetes 允许从 `0.0.0.0/0` 到节点安全组的流量。 如果节点具有公共 IP 地址,请注意,非 NLB 流量也可以到达那些修改后的安全组中的所有实例。 有关弹性 IP 注解和更多其他常见用例, 请参阅[AWS 负载均衡控制器文档]。 ### ExternalName 类型 类型为 ExternalName 的服务将服务映射到 DNS 名称,而不是典型的选择算符,例如 `my-service` 或者 `cassandra`。 你可以使用 `spec.externalName` 参数指定这些服务。 例如,以下 Service 定义将 `prod` 名称空间中的 `my-service` 服务映射到 `my.database.example.com`: ```yaml apiVersion: v1 kind: Service metadata: name: my-service namespace: prod spec: type: ExternalName externalName: my.database.example.com ``` ExternalName 服务接受 IPv4 地址字符串,但作为包含数字的 DNS 名称,而不是 IP 地址。 类似于 IPv4 地址的外部名称不能由 CoreDNS 或 ingress-nginx 解析,因为外部名称旨在指定规范的 DNS 名称。 要对 IP 地址进行硬编码,请考虑使用[无头 Services]。 当查找主机 `my-service.prod.svc.cluster.local` 时,集群 DNS 服务返回 `CNAME` 记录, 其值为 `my.database.example.com`。 访问 `my-service` 的方式与其他服务的方式相同,但主要区别在于重定向发生在 DNS 级别,而不是通过代理或转发。 如果以后你决定将数据库移到集群中,则可以启动其 Pod,添加适当的选择算符或端点以及更改服务的 `type`。 对于一些常见的协议,包括 HTTP 和 HTTPS,你使用 ExternalName 可能会遇到问题。 如果你使用 ExternalName,那么集群内客户端使用的主机名与 ExternalName 引用的名称不同。 对于使用主机名的协议,此差异可能会导致错误或意外响应。 HTTP 请求将具有源服务器无法识别的 `Host:` 标头; TLS 服务器将无法提供与客户端连接的主机名匹配的证书。 有关这部分内容,我们要感谢 [Alen Komljen] 刊登的 [Kubernetes Tips - Part1] 这篇博文。 ### 外部 IP 如果外部的 IP 路由到集群中一个或多个 Node 上,Kubernetes Service 会被暴露给这些 `externalIPs`。 通过外部 IP(作为目的 IP 地址)进入到集群,打到 Service 的端口上的流量, 将会被路由到 Service 的 Endpoint 上。 `externalIPs` 不会被 Kubernetes 管理,它属于集群管理员的职责范畴。 根据 Service 的规定,`externalIPs` 可以同任意的 `ServiceType` 来一起指定。 在上面的例子中,`my-service` 可以在 "`198.51.100.32:80`" 上被客户端访问。 ```yaml apiVersion: v1 kind: Service metadata: name: my-service spec: selector: app.kubernetes.io/name: MyApp ports: - name: http protocol: TCP port: 80 targetPort: 49152 externalIPs: - 198.51.100.32 ``` ## 粘性会话 如果你想确保来自特定客户端的连接每次都传递到同一个 Pod,你可以配置根据客户端 IP 地址来执行的会话亲和性。 阅读[会话亲和性]了解更多。 ## API 对象 Service 是 Kubernetes REST API 中的顶级资源。你可以找到有关 [Service 对象 API] 的更多详细信息。 <a id="shortcomings" /><a id="#the-gory-details-of-virtual-ips" /> ## 虚拟 IP 寻址机制 阅读[虚拟 IP 和 Service 代理]以了解 Kubernetes 提供的使用虚拟 IP 地址公开服务的机制。 ## 进一步学习 Service 及其在 Kubernetes 中所发挥的作用: * 遵循[使用 Service 连接到应用]教程。 * 阅读 [Ingress] 将来自集群外部的 HTTP 和 HTTPS 请求路由暴露给集群内的服务。 * 阅读 [Gateway] 作为 Kubernetes 的扩展提供比 Ingress 更大的灵活性。 更多上下文,可以阅读以下内容: * [虚拟 IP 和 Service 代理] * [EndpointSlices] * Service API 的 [API 参考] * EndpointSlice API 的 [API 参考] * Endpoint API 的 [API 参考]
上一篇:
Kubernetes中的ReplicationController
下一篇:
Kubernetes拓扑感知的流量路由
该分类下的相关小册推荐:
Kubernetes中文教程(六)
Kubernets合辑7-存储
Kubernets合辑9-资源约束
Kubernetes中文教程(一)
Kubernets合辑6-服务发现
Kubernets合辑13-集群监控
Kubernets合辑10-网络
Kubernets合辑11-持续集成
Kubernets合辑14-日志收集
Kubernetes中文教程(四)
Kubernets合辑15-持续部署
Kubernetes中文教程(三)