Service
在 Kubernetes 中,Service(服务)是一种暴露运行在集群中一个或多个 Pods 上的网络应用程序的方法。
Kubernetes 中 Service 的一个核心目标是,你无需修改现有应用即可使用陌生的服务发现机制。你可以在 Pod 中运行代码,无论这是为云原生世界设计的代码,还是你已经容器化的旧应用。你使用 Service 使这组 Pod 在网络上可用,以便客户端可以与之交互。
如果你使用 Deployment 来运行应用,该 Deployment 可以动态地创建和销毁 Pod。在任何时刻,你都不知道有多少 Pod 正在正常运行;你甚至可能不知道这些健康 Pod 的名称。Kubernetes Pods 的创建和销毁是为了匹配集群的期望状态。Pod 是临时资源(你不应该期望单个 Pod 是可靠且持久的)。
每个 Pod 都会获得自己的 IP 地址(Kubernetes 要求网络插件确保这一点)。对于集群中的给定 Deployment,某一时刻运行的 Pod 集合可能与片刻后运行该应用的 Pod 集合不同。
这导致了一个问题:如果某组 Pod(称之为“后端”)为集群内的其他 Pod(称之为“前端”)提供功能,前端如何发现并跟踪要连接的 IP 地址,以便前端可以使用工作负载的后端部分?
于是 Services 出现了。
Kubernetes 中的 Service
Service API 是 Kubernetes 的一部分,它是一种抽象,帮助你在网络上暴露一组 Pod。每个 Service 对象定义了一组逻辑端点(通常这些端点是 Pod)以及如何使这些 Pod 可访问的策略。
例如,考虑一个运行 3 个副本的无状态图像处理后端。这些副本是可互换的——前端不在乎它们使用哪个后端。虽然构成后端集合的实际 Pod 可能会发生变化,但前端客户端不需要意识到这一点,也不需要亲自跟踪后端集合。
Service 抽象实现了这种解耦。
Service 所针对的 Pod 集合通常由你定义的 选择算子 (selector) 确定。要了解定义 Service 端点的其他方式,请参阅 不带选择算子的 Service。
如果你的工作负载使用 HTTP 协议,你可以选择使用 Ingress 来控制 Web 流量如何到达该工作负载。Ingress 不是一种 Service 类型,但它作为集群的入口点。Ingress 允许你将路由规则合并到单个资源中,从而使你能够将集群中分别运行的工作负载的多个组件暴露在单个监听器之后。
Kubernetes 的 Gateway API 提供了超越 Ingress 和 Service 的额外功能。你可以将 Gateway 添加到集群中——它是一系列扩展 API,使用 CustomResourceDefinitions 实现——然后使用这些 API 来配置对集群中运行的网络服务的访问。
云原生服务发现
如果你能够在应用程序中使用 Kubernetes API 进行服务发现,你可以查询 API 服务器 以获取匹配的 EndpointSlice。每当 Service 中的 Pod 集合发生变化时,Kubernetes 都会更新该 Service 的 EndpointSlice。
对于非原生应用,Kubernetes 提供了在你的应用和后端 Pod 之间放置网络端口或负载均衡器的方法。
无论哪种方式,你的工作负载都可以使用这些 服务发现 机制来找到它想要连接的目标。
定义 Service
Service 是一个 对象(就像 Pod 或 ConfigMap 是对象一样)。你可以使用 Kubernetes API 创建、查看或修改 Service 定义。通常,你会使用 kubectl 之类的工具来为你执行这些 API 调用。
例如,假设你有一组 Pod,每个 Pod 都在 TCP 端口 9376 上监听,并带有标签 app.kubernetes.io/name=MyApp。你可以定义一个 Service 来发布该 TCP 监听器:
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,其默认服务类型为 ClusterIP。该 Service 以任何带有 app.kubernetes.io/name: MyApp 标签的 Pod 的 TCP 端口 9376 为目标。
Kubernetes 会为该 Service 分配一个 IP 地址(即 集群 IP),虚拟 IP 地址机制会使用该地址。有关该机制的更多详情,请阅读 虚拟 IP 和服务代理。
该 Service 的控制器会不断扫描匹配其选择算子的 Pod,然后对该 Service 的 EndpointSlice 集合进行任何必要的更新。
Service 对象的名称必须是有效的 RFC 1035 标签名称。
说明
Service 可以将 任何 入站port 映射到 targetPort。默认情况下,为了方便起见,targetPort 被设置为与 port 字段相同的值。放宽对 Service 对象的命名要求
Kubernetes v1.34 [alpha](默认禁用)RelaxedServiceNameValidation 特性门控允许 Service 对象名称以数字开头。启用此特性门控后,Service 对象名称必须是有效的 RFC 1123 标签名称。
端口定义
Pod 中的端口定义可以有名称,你可以在 Service 的 targetPort 属性中引用这些名称。例如,我们可以通过以下方式将 Service 的 targetPort 绑定到 Pod 端口:
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
---
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
即使 Service 中的 Pod 混合使用单个配置名称,并通过不同的端口号提供相同的网络协议,这也同样有效。这为部署和演进你的 Service 提供了极大的灵活性。例如,你可以在下一个版本的后端软件中更改 Pod 暴露的端口号,而不会破坏客户端。
Service 的默认协议是 TCP;你也可以使用任何其他 支持的协议。
由于许多 Service 需要暴露多个端口,Kubernetes 支持为单个 Service 定义 多个端口。每个端口定义可以具有相同的 protocol,也可以具有不同的协议。
不带选择算子的 Service
由于有了选择算子,Service 最常用于抽象对 Kubernetes Pod 的访问。但是,当与相应的 EndpointSlice 对象配合使用且不带选择算子时,Service 可以抽象其他类型的后端,包括在集群外部运行的后端。
例如
- 你想在生产环境中使用外部数据库集群,但在测试环境中使用你自己的数据库。
- 你想将你的 Service 指向不同 命名空间 或另一个集群上的 Service。
- 你正在将工作负载迁移到 Kubernetes。在评估方案时,你仅在 Kubernetes 中运行一部分后端。
在任何这些场景中,你都可以定义一个 Service 而不 指定匹配 Pod 的选择算子。例如:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
由于此 Service 没有选择算子,因此不会自动创建相应的 EndpointSlice 对象。你可以通过手动添加 EndpointSlice 对象,将 Service 映射到其运行的网络地址和端口。例如:
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: my-service-1 # by convention, use the name of the Service
# as a prefix for the name of the EndpointSlice
labels:
# You should set the "kubernetes.io/service-name" label.
# Set its value to match the name of the Service
kubernetes.io/service-name: my-service
addressType: IPv4
ports:
- name: http # should match with the name of the service port defined above
appProtocol: http
protocol: TCP
port: 9376
endpoints:
- addresses:
- "10.4.5.6"
- addresses:
- "10.1.2.3"
自定义 EndpointSlice
当你为 Service 创建 EndpointSlice 对象时,可以使用任何名称。命名空间中的每个 EndpointSlice 必须具有唯一的名称。你通过在该 EndpointSlice 上设置 kubernetes.io/service-name 标签 将其链接到 Service。
说明
端点 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 Service 的集群 IP,因为 kube-proxy 不支持将虚拟 IP 作为目标地址。
对于你自己创建或在代码中创建的 EndpointSlice,你还应该为标签 endpointslice.kubernetes.io/managed-by 选择一个值。如果你编写自己的控制器代码来管理 EndpointSlice,请考虑使用类似 "my-domain.example/name-of-controller" 的值。如果你使用第三方工具,请使用该工具的全小写名称,并将空格和其他标点符号更改为连字符 (-)。如果人们直接使用 kubectl 之类的工具来管理 EndpointSlice,请使用描述这种手动管理方式的名称,例如 "staff" 或 "cluster-admins"。你应该避免使用保留值 "controller",它标识由 Kubernetes 自身控制平面管理的 EndpointSlice。
访问不带选择算子的 Service
访问不带选择算子的 Service 与访问带有选择算子的 Service 方式相同。在不带选择算子的 Service 示例 中,流量被路由到 EndpointSlice 清单中定义的两个端点之一:在端口 9376 上连接到 10.1.2.3 或 10.4.5.6 的 TCP 连接。
说明
Kubernetes API 服务器不允许代理到未映射到 Pod 的端点。由于此限制,在 Service 没有选择算子的情况下,执行kubectl port-forward service/<service-name> forwardedPort:servicePort 等操作将会失败。这可以防止 Kubernetes API 服务器被用作代理去访问调用者可能无权访问的端点。ExternalName Service 是 Service 的一种特殊情况,它没有选择算子,而是使用 DNS 名称。有关更多信息,请参阅 ExternalName 部分。
EndpointSlices
Kubernetes v1.21 [稳定]EndpointSlices 是代表 Service 后端网络端点子集(slice,分片)的对象。
你的 Kubernetes 集群会跟踪每个 EndpointSlice 代表多少个端点。如果一个 Service 的端点数量多到达到阈值,Kubernetes 就会添加另一个空的 EndpointSlice 并在那里存储新的端点信息。默认情况下,一旦现有的 EndpointSlice 都包含至少 100 个端点,Kubernetes 就会创建一个新的 EndpointSlice。直到需要添加额外的端点时,Kubernetes 才会创建新的 EndpointSlice。
有关此 API 的更多信息,请参阅 EndpointSlices。
Endpoints (已弃用)
Kubernetes v1.33 [deprecated]EndpointSlice API 是旧版 Endpoints API 的进化版。弃用的 Endpoints API 相对于 EndpointSlice 存在几个问题:
- 它不支持双栈集群。
- 它不包含支持新特性所需的信息,例如 trafficDistribution(流量分配)。
- 如果端点列表太长,无法放入单个对象中,它将截断列表。
因此,建议所有客户端使用 EndpointSlice API 而不是 Endpoints。
超量端点
Kubernetes 限制了单个 Endpoints 对象中可容纳的端点数量。当 Service 的后端端点超过 1000 个时,Kubernetes 会截断 Endpoints 对象中的数据。由于一个 Service 可以链接多个 EndpointSlice,因此 1000 个后端端点的限制仅影响旧版 Endpoints API。
在这种情况下,Kubernetes 会选择最多 1000 个可能的后端端点存储到 Endpoints 对象中,并在 Endpoints 上设置一个 注解:endpoints.kubernetes.io/over-capacity: truncated。如果后端 Pod 数量降至 1000 以下,控制平面也会移除该注解。
流量仍会发送到后端,但任何依赖于旧版 Endpoints API 的负载均衡机制最多只能将流量发送到 1000 个可用的后端端点。
同样的 API 限制意味着你无法手动更新 Endpoints 以拥有超过 1000 个端点。
应用协议
Kubernetes v1.20 [稳定]appProtocol 字段提供了一种为每个 Service 端口指定应用协议的方法。这被用作实现的提示,以便为它们理解的协议提供更丰富的行为。该字段的值由相应的 Endpoints 和 EndpointSlice 对象镜像。
该字段遵循标准的 Kubernetes 标签语法。有效值可以是以下之一:
实现定义的带前缀名称,例如
mycompany.com/my-custom-protocol。Kubernetes 定义的前缀名称
| 协议 | 描述 |
|---|---|
kubernetes.io/h2c | 如 RFC 7540 中所述的明文 HTTP/2 |
kubernetes.io/ws | 如 RFC 6455 中所述的明文 WebSocket |
kubernetes.io/wss | 如 RFC 6455 中所述的 TLS 上的 WebSocket |
多端口 Service
对于某些 Service,你需要暴露多个端口。Kubernetes 允许你在 Service 对象上配置多个端口定义。当为 Service 使用多个端口时,必须为所有端口命名,以免产生歧义。例如:
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 是无效的。
服务类型
对于应用程序的某些部分(例如前端),你可能希望将 Service 暴露给外部 IP 地址,即可以从集群外部访问的地址。
Kubernetes 的 Service 类型允许你指定所需的 Service 种类。
可用的 type 值及其行为如下:
ClusterIP- 在集群内部 IP 上暴露 Service。选择此值将使 Service 仅能从集群内部访问。如果你未显式指定 Service 的
type,则这是默认使用的类型。你可以使用 Ingress 或 Gateway 将 Service 暴露给公共互联网。 NodePort- 在每个节点的 IP 上的静态端口(即
NodePort)上暴露 Service。为了使节点端口可用,Kubernetes 会设置一个集群 IP 地址,就像你请求了type: ClusterIP的 Service 一样。 LoadBalancer- 使用外部负载均衡器在外部暴露 Service。Kubernetes 不直接提供负载均衡组件;你必须提供一个,或者你可以将你的 Kubernetes 集群与云提供商集成。
ExternalName- 将 Service 映射到
externalName字段的内容(例如映射到主机名api.foo.bar.example)。该映射配置集群的 DNS 服务器以返回带有该外部主机名值的CNAME记录。不设置任何形式的代理。
Service API 中的 type 字段被设计为嵌套功能——每一层都在前一层的基础上增加。但这种嵌套设计有一个例外:你可以通过 禁用负载均衡器 NodePort 分配 来定义 LoadBalancer Service。
type: ClusterIP
这种默认的 Service 类型会从集群为此目的预留的 IP 地址池中分配一个 IP 地址。
Service 的其他几种类型都是以 ClusterIP 类型为基础构建的。
如果你定义的 Service 将 .spec.clusterIP 设置为 "None",那么 Kubernetes 就不会分配 IP 地址。有关更多信息,请参阅 无头 (Headless) Service。
选择你自己的 IP 地址
你可以在创建 Service 的请求中指定自己的集群 IP 地址。为此,请设置 .spec.clusterIP 字段。例如,如果你已经有一个想要重用的现有 DNS 条目,或者旧系统配置了特定 IP 地址且难以重新配置。
你选择的 IP 地址必须是为 API 服务器配置的 service-cluster-ip-range CIDR 范围内的有效 IPv4 或 IPv6 地址。如果你尝试创建带有无效 clusterIP 地址值的 Service,API 服务器将返回 422 HTTP 状态码,表示存在问题。
阅读 避免冲突,了解 Kubernetes 如何帮助降低两个不同 Service 尝试使用相同 IP 地址的风险和影响。
type: NodePort
如果你将 type 字段设置为 NodePort,Kubernetes 控制平面会从 --service-node-port-range 标志指定的范围内分配一个端口(默认:30000-32767)。每个节点都会将该端口(每个节点上的端口号相同)代理到你的 Service 中。你的 Service 会在其 .spec.ports[*].nodePort 字段中报告分配的端口。
使用 NodePort 让你能够自由地建立自己的负载均衡解决方案,配置 Kubernetes 不完全支持的环境,甚至直接暴露一个或多个节点的 IP 地址。
对于 NodePort Service,Kubernetes 还会分配一个端口(TCP、UDP 或 SCTP,以匹配 Service 的协议)。集群中的每个节点都会配置自身监听该分配的端口,并将流量转发到与该 Service 关联的就绪端点之一。通过使用适当的协议(例如:TCP)和适当的端口(分配给该 Service 的端口)连接到任何节点,你将能够从集群外部访问 type: NodePort Service。
选择你自己的端口
如果你想要一个特定的端口号,可以在 nodePort 字段中指定一个值。控制平面要么为你分配该端口,要么报告 API 事务失败。这意味着你需要自己处理可能的端口冲突。你还必须使用有效的端口号,即在为 NodePort 使用配置的范围内。
这是一个 type: NodePort Service 的示例清单,其中指定了 NodePort 值(在此示例中为 30007):
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
selector:
app.kubernetes.io/name: MyApp
ports:
- port: 80
# By default and for convenience, the `targetPort` is set to
# the same value as the `port` field.
targetPort: 80
# Optional field
# By default and for convenience, the Kubernetes control plane
# will allocate a port from a range (default: 30000-32767)
nodePort: 30007
保留 NodePort 范围以避免冲突
为 NodePort 服务分配端口的策略同时适用于自动分配和手动分配场景。当用户想要创建一个使用特定端口的 NodePort 服务时,目标端口可能会与已分配的另一个端口冲突。
为了避免这个问题,NodePort 服务的端口范围被分为两个波段。默认情况下,动态端口分配使用高位波段,一旦高位波段耗尽,它可能会使用低位波段。用户随后可以从低位波段进行分配,从而降低端口冲突的风险。
使用默认 NodePort 范围 30000-32767 时,波段划分如下:
- 静态波段:30000-30085
- 动态波段:30086-32767
有关如何计算静态和动态波段的更多详情,请参阅 避免为 NodePort 服务分配端口时发生冲突。
type: NodePort Service 的自定义 IP 地址配置
你可以将集群中的节点配置为使用特定的 IP 地址来提供节点端口服务。如果每个节点连接到多个网络(例如:一个网络用于应用流量,另一个网络用于节点与控制平面之间的流量),你可能希望这样做。
如果你想指定特定的 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 Service 选择回环接口。--nodeport-addresses 的默认值为空列表。这意味着 kube-proxy 应考虑 NodePort 的所有可用网络接口。(这也与早期的 Kubernetes 版本兼容。)
说明
该 Service 以<NodeIP>:spec.ports[*].nodePort 和 .spec.clusterIP:spec.ports[*].port 的形式可见。如果设置了 kube-proxy 的 --nodeport-addresses 标志或 kube-proxy 配置文件中的等效字段,则 <NodeIP> 将是过滤后的节点 IP 地址(或者可能是多个 IP 地址)。type: LoadBalancer
在支持外部负载均衡器的云提供商上,将 type 字段设置为 LoadBalancer 会为你的 Service 提供负载均衡器。负载均衡器的实际创建是异步发生的,有关已提供负载均衡器的信息会发布在 Service 的 .status.loadBalancer 字段中。例如:
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 的 Service,Kubernetes 通常首先会进行与你请求 type: NodePort Service 等效的更改。然后,cloud-controller-manager 组件配置外部负载均衡器,将流量转发到该分配的节点端口。
如果云提供商的实现支持,你可以将负载均衡 Service 配置为略过分配节点端口。
某些云提供商允许你指定 loadBalancerIP。在这些情况下,将使用用户指定的 loadBalancerIP 创建负载均衡器。如果未指定 loadBalancerIP 字段,则会使用临时 IP 地址设置负载均衡器。如果你指定了 loadBalancerIP 但你的云提供商不支持该功能,则你设置的 loadbalancerIP 字段将被忽略。
说明
Service 的 .spec.loadBalancerIP 字段在 Kubernetes v1.24 中已被弃用。
此字段描述不足,且其含义因实现而异。它也不支持双栈网络。此字段可能会在未来的 API 版本中移除。
如果你正与支持通过(提供商特定的)注解为 Service 指定负载均衡器 IP 地址的提供商进行集成,你应该切换到这种方式。
如果你正在为 Kubernetes 的负载均衡器集成编写代码,请避免使用此字段。你可以与 Gateway 而不是 Service 集成,或者可以在 Service 上定义你自己的(提供商特定的)注解来指定同等细节。
节点存活性对负载均衡器流量的影响
负载均衡器健康检查对于现代应用至关重要。它们用于确定负载均衡器应将流量分发到哪个服务器(虚拟机或 IP 地址)。Kubernetes API 并没有定义必须如何为 Kubernetes 管理的负载均衡器实现健康检查,而是由云提供商(以及实现集成代码的人员)决定其行为。负载均衡器健康检查在支持 Service 的 externalTrafficPolicy 字段的上下文中被广泛使用。
混合协议类型的负载均衡器
Kubernetes v1.26 [stable](默认启用)默认情况下,对于 LoadBalancer 类型的 Service,当定义了多个端口时,所有端口必须使用相同的协议,且该协议必须是云提供商支持的协议。
特性门控 MixedProtocolLBService(自 v1.24 起在 kube-apiserver 中默认启用)允许在定义了多个端口时,为 LoadBalancer 类型的 Service 使用不同的协议。
说明
可用于负载均衡 Service 的协议集由你的云提供商定义;他们可能会施加超出 Kubernetes API 强制要求的限制。禁用负载均衡器 NodePort 分配
Kubernetes v1.24 [稳定]你可以通过将 spec.allocateLoadBalancerNodePorts 字段设置为 false,来选择性地禁用 type: LoadBalancer Service 的节点端口分配。这仅应在直接将流量路由到 Pod 而非使用节点端口的负载均衡器实现中使用。默认情况下,spec.allocateLoadBalancerNodePorts 为 true,且 LoadBalancer 类型的 Service 将继续分配节点端口。如果对分配了节点端口的现有 Service 将 spec.allocateLoadBalancerNodePorts 设置为 false,则这些节点端口将 不会 自动取消分配。你必须显式移除每个 Service 端口中的 nodePorts 条目才能取消分配这些节点端口。
指定负载均衡器实现类
Kubernetes v1.24 [稳定]对于 type 设置为 LoadBalancer 的 Service,.spec.loadBalancerClass 字段允许你使用云提供商默认设置以外的负载均衡器实现。
默认情况下,.spec.loadBalancerClass 未设置,如果集群配置了使用 --cloud-provider 组件标志的云提供商,则 LoadBalancer 类型的 Service 将使用云提供商的默认负载均衡器实现。
如果你指定了 .spec.loadBalancerClass,则假定与指定类匹配的负载均衡器实现正在观察 Service。任何默认的负载均衡器实现(例如云提供商提供的实现)都将忽略设置了此字段的 Service。spec.loadBalancerClass 仅可设置在类型为 LoadBalancer 的 Service 上。一旦设置,就无法更改。spec.loadBalancerClass 的值必须是一个标签风格的标识符,带有可选的前缀,例如 "internal-vip" 或 "example.com/internal-vip"。不带前缀的名称保留给最终用户使用。
负载均衡器 IP 地址模式
对于 type: LoadBalancer 的 Service,控制器可以设置 .status.loadBalancer.ingress.ipMode。.status.loadBalancer.ingress.ipMode 指定负载均衡器 IP 的行为方式。仅当同时指定了 .status.loadBalancer.ingress.ip 字段时,才可以指定它。
.status.loadBalancer.ingress.ipMode 有两个可能的值:"VIP" 和 "Proxy"。默认值为 "VIP",意味着流量在传递到节点时,目标被设置为负载均衡器的 IP 和端口。当将其设置为 "Proxy" 时,有两种情况,具体取决于云提供商的负载均衡器如何传递流量:
- 如果流量被传递到节点然后 DNAT 到 Pod,则目标将被设置为节点的 IP 和节点端口;
- 如果流量直接传递到 Pod,则目标将被设置为 Pod 的 IP 和端口。
Service 实现可以使用此信息来调整流量路由。
内部负载均衡器
在混合环境中,有时需要路由来自同一(虚拟)网络地址块内的 Service 的流量。
在水平分割 DNS (Split-horizon DNS) 环境中,你需要两个 Service 才能将外部和内部流量路由到你的端点。
要设置内部负载均衡器,请根据你使用的云服务提供商,向你的 Service 添加以下注解之一:
选择其中一个标签页。
metadata:
name: my-service
annotations:
networking.gke.io/load-balancer-type: "Internal"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-scheme: "internal"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/azure-load-balancer-internal: "true"
metadata:
name: my-service
annotations:
service.kubernetes.io/ibm-load-balancer-cloud-provider-ip-type: "private"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/openstack-internal-load-balancer: "true"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/cce-load-balancer-internal-vpc: "true"
metadata:
annotations:
service.kubernetes.io/qcloud-loadbalancer-internal-subnetid: subnet-xxxxx
metadata:
annotations:
service.beta.kubernetes.io/alibaba-cloud-loadbalancer-address-type: "intranet"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/oci-load-balancer-internal: true
type: ExternalName
ExternalName 类型的 Service 将 Service 映射到 DNS 名称,而不是典型的选择算子,如 my-service 或 cassandra。你使用 spec.externalName 参数指定这些 Service。
例如,此 Service 定义将 prod 命名空间中的 my-service Service 映射到 my.database.example.com:
apiVersion: v1
kind: Service
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com
说明
type: ExternalName 的 Service 接受 IPv4 地址字符串,但将该字符串视为由数字组成的 DNS 名称,而不是 IP 地址(互联网不允许在 DNS 中使用此类名称)。名称类似于 IPv4 地址的外部名称 Service 不会被 DNS 服务器解析。
如果你想将 Service 直接映射到特定 IP 地址,请考虑使用 无头 (Headless) Service。
在查询主机 my-service.prod.svc.cluster.local 时,集群 DNS 服务返回值为 my.database.example.com 的 CNAME 记录。访问 my-service 的方式与其他 Service 相同,但关键区别在于重定向发生在 DNS 级别,而不是通过代理或转发。如果你稍后决定将数据库移动到集群中,可以启动其 Pod,添加适当的选择算子或端点,并更改 Service 的 type。
注意
在某些常见协议(包括 HTTP 和 HTTPS)中使用 ExternalName 可能会遇到问题。如果你使用 ExternalName,则集群内客户端使用的主机名与 ExternalName 引用的名称不同。
对于使用主机名的协议,这种差异可能会导致错误或意外响应。HTTP 请求将具有源服务器无法识别的 Host: 标头;TLS 服务器将无法提供与客户端连接的主机名匹配的证书。
无头服务
有时你不需要负载均衡和单一的 Service IP。在这种情况下,你可以通过显式为集群 IP 地址 (.spec.clusterIP) 指定 "None" 来创建所谓的 无头 (headless) Service。
你可以使用无头 Service 与其他服务发现机制进行交互,而无需与 Kubernetes 的实现绑定。
对于无头 Service,不会分配集群 IP,kube-proxy 不处理这些 Service,平台也不会为它们执行负载均衡或代理。
无头 Service 允许客户端直接连接到它喜欢的任何 Pod。无头 Service 不会使用 虚拟 IP 地址和代理 配置路由和数据包转发;相反,无头 Service 通过集群的 DNS 服务 提供的内部 DNS 记录来报告单个 Pod 的端点 IP 地址。要定义无头 Service,你需要创建一个将 .spec.type 设置为 ClusterIP(这也是 type 的默认值)的 Service,并另外将 .spec.clusterIP 设置为 None。
字符串值 None 是一个特殊情况,与不设置 .spec.clusterIP 字段不同。
如何自动配置 DNS 取决于 Service 是否定义了选择算子:
带选择算子
对于定义了选择算子的无头 Service,端点控制器在 Kubernetes API 中创建 EndpointSlice,并修改 DNS 配置以返回指向支持该 Service 的 Pod 的 A 或 AAAA 记录(IPv4 或 IPv6 地址)。
不带选择算子
对于未定义选择算子的无头 Service,控制平面不会创建 EndpointSlice 对象。但是,DNS 系统会查找并配置以下内容之一:
type: ExternalNameService 的 DNS CNAME 记录。- 对于除
ExternalName之外的所有 Service 类型,为 Service 就绪端点的所有 IP 地址配置 DNS A / AAAA 记录。- 对于 IPv4 端点,DNS 系统创建 A 记录。
- 对于 IPv6 端点,DNS 系统创建 AAAA 记录。
当你定义不带选择算子的无头 Service 时,port 必须与 targetPort 匹配。
发现服务
对于在集群内部运行的客户端,Kubernetes 支持两种主要的服务发现模式:环境变量和 DNS。
环境变量
当 Pod 在节点上运行时,kubelet 会为每个活动 Service 添加一组环境变量。它会添加 {SVCNAME}_SERVICE_HOST 和 {SVCNAME}_SERVICE_PORT 变量,其中 Service 名称为大写,且连字符转换为下划线。
例如,暴露 TCP 端口 6379 且已分配集群 IP 地址 10.0.0.11 的 Service redis-primary 会生成以下环境变量:
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
说明
当你有一个需要访问 Service 的 Pod,并且正在使用环境变量方法将端口和集群 IP 发布到客户端 Pod 时,你必须在客户端 Pod 存在 之前 创建 Service。否则,这些客户端 Pod 将无法填充其环境变量。
如果你仅使用 DNS 来发现 Service 的集群 IP,则无需担心此顺序问题。
Kubernetes 还支持并提供与 Docker Engine 的“旧版容器链接 (legacy container links)”特性兼容的变量。你可以阅读 makeLinkVariables 了解这在 Kubernetes 中是如何实现的。
DNS
你(并且几乎总是应该)使用 插件 为你的 Kubernetes 集群设置 DNS 服务。
集群感知的 DNS 服务器(例如 CoreDNS)会监视 Kubernetes API 以获取新 Service,并为每个 Service 创建一组 DNS 记录。如果在整个集群中启用了 DNS,那么所有 Pod 都应该能够通过其 DNS 名称自动解析 Service。
例如,如果你在 Kubernetes 命名空间 my-ns 中有一个名为 my-service 的 Service,则控制平面和 DNS 服务共同为 my-service.my-ns 创建一条 DNS 记录。my-ns 命名空间中的 Pod 应该能够通过执行 my-service 的名称查找来找到该服务(my-service.my-ns 也可以)。
其他命名空间中的 Pod 必须将名称限定为 my-service.my-ns。这些名称将解析为分配给该 Service 的集群 IP。
Kubernetes 还支持命名称端口的 DNS SRV(服务)记录。如果 my-service.my-ns Service 有一个名为 http 且协议设置为 TCP 的端口,你可以对 _http._tcp.my-service.my-ns 执行 DNS SRV 查询,以发现 http 的端口号以及 IP 地址。
Kubernetes DNS 服务器是访问 ExternalName Service 的唯一方式。你可以在 Service 和 Pod 的 DNS 中找到有关 ExternalName 解析的更多信息。
虚拟 IP 寻址机制
阅读 虚拟 IP 和服务代理 解释了 Kubernetes 提供的通过虚拟 IP 地址暴露 Service 的机制。
流量策略
你可以设置 .spec.internalTrafficPolicy 和 .spec.externalTrafficPolicy 字段来控制 Kubernetes 如何将流量路由到健康的(“就绪”)后端。
有关更多详情,请参阅 流量策略。
流量分配控制
.spec.trafficDistribution 字段提供了另一种影响 Kubernetes Service 内流量路由的方法。虽然流量策略专注于严格的语义保证,但流量分配允许你表达 偏好(例如路由到拓扑上更近的端点)。这有助于优化性能、成本或可靠性。在 Kubernetes 1.35 中,支持以下值:
PreferSameZone- 表示偏好将流量路由到与客户端处于同一区域 (Zone) 的端点。
PreferSameNode- 表示偏好将流量路由到与客户端处于同一节点的端点。
PreferClose(已弃用)- 这是
PreferSameZone的旧别名,其语义不够清晰。
如果未设置该字段,则实现将应用其默认路由策略。
有关更多详情,请参阅 流量分配。
会话粘性
如果你想确保来自特定客户端的连接每次都传递到同一个 Pod,可以根据客户端的 IP 地址配置会话亲和性 (Session Affinity)。阅读 会话亲和性 了解更多信息。
外部 IP
如果有外部 IP 路由到一个或多个集群节点,则 Kubernetes Service 可以在这些 externalIPs 上暴露。当网络流量进入集群,且外部 IP(作为目标 IP)和端口与该 Service 匹配时,Kubernetes 配置的规则和路由将确保流量路由到该 Service 的端点之一。
当你定义 Service 时,可以为任何 服务类型 指定 externalIPs。在下面的示例中,客户端可以使用 TCP 在 "198.51.100.32:80"(根据 .spec.externalIPs[] 和 .spec.ports[].port 计算得出)上访问名为 "my-service" 的 Service。
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
说明
Kubernetes 不管理externalIPs 的分配;这些由集群管理员负责。API 对象
Service 是 Kubernetes REST API 中的顶级资源。你可以在 Service API 对象 中找到更多详情。
接下来
了解更多关于 Service 及其在 Kubernetes 中的地位:
- 遵循 使用 Service 连接应用 教程。
- 阅读关于 Ingress,它将来自集群外部的 HTTP 和 HTTPS 路由暴露给集群内的 Service。
- 阅读关于 Gateway,它是 Kubernetes 的扩展,比 Ingress 提供更多的灵活性。
如需更多背景信息,请阅读以下内容: