服务
在 Kubernetes 中,服务是一种方法,用于公开作为集群中一个或多个 Pod 运行的网络应用程序。
Kubernetes 中服务的关键目标是,您无需修改现有应用程序即可使用不熟悉的服务发现机制。您可以在 Pod 中运行代码,无论此代码是为云原生世界设计的,还是您已容器化的旧应用程序。您使用服务使该组 Pod 在网络上可用,以便客户端可以与之交互。
如果您使用 Deployment 运行您的应用程序,则该 Deployment 可以动态地创建和销毁 Pod。从那一刻起,您不知道有多少 Pod 正在工作且处于健康状态;您甚至可能不知道这些健康 Pod 的名称。Kubernetes Pod 被创建和销毁以匹配集群的预期状态。Pod 是短暂的资源(您不应该期望单个 Pod 是可靠且持久的)。
每个 Pod 都获得自己的 IP 地址(Kubernetes 期望网络插件确保这一点)。对于集群中的给定 Deployment,在某一时刻运行的 Pod 集可能与稍后运行该应用程序的 Pod 集不同。
这会导致一个问题:如果某些 Pod 集(称为“后端”)为集群中的其他 Pod(称为“前端”)提供功能,那么前端如何找出并跟踪要连接的 IP 地址,以便前端可以使用后端部分工作负载?
这就是 服务 的作用。
Kubernetes 中的服务
作为 Kubernetes 的一部分,服务 API 是一种抽象,可帮助您通过网络公开 Pod 组。每个服务对象都定义了一组逻辑端点(通常这些端点是 Pod),以及有关如何使这些 Pod 可访问的策略。
例如,考虑一个无状态的图像处理后端,它以 3 个副本运行。这些副本是可互换的——前端不关心使用哪个后端。虽然构成后端集的实际 Pod 可能会发生变化,但前端客户端不应该需要了解这一点,也不应该需要自己跟踪后端集。
服务抽象使这种解耦成为可能。
服务所针对的 Pod 集通常由您定义的 选择器 确定。要了解定义服务端点的其他方法,请参阅 没有选择器的服务。
如果您的工作负载使用 HTTP,您可能选择使用 Ingress 来控制网络流量如何到达该工作负载。Ingress 不是服务类型,但它充当集群的入口点。Ingress 允许您将路由规则合并到单个资源中,以便您可以在单个侦听器后面公开工作负载的多个组件,这些组件在集群中单独运行。
Kubernetes 的 Gateway API 提供了超出 Ingress 和服务的额外功能。您可以将 Gateway 添加到您的集群 - 它是一系列扩展 API,使用 CustomResourceDefinitions 实现 - 然后使用它们来配置对集群中运行的网络服务的访问。
云原生服务发现
如果您能够在应用程序中使用 Kubernetes API 进行服务发现,则可以查询 API 服务器 以获取匹配的 EndpointSlices。每当服务中的 Pod 集发生变化时,Kubernetes 都会更新服务的 EndpointSlices。
对于非原生应用程序,Kubernetes 提供了在应用程序和后端 Pod 之间放置网络端口或负载均衡器的方法。
无论哪种方式,您的工作负载都可以使用这些 服务发现 机制来查找它想要连接的目标。
定义服务
服务是一个 对象(与 Pod 或 ConfigMap 作为对象的方式相同)。您可以使用 Kubernetes API 创建、查看或修改服务定义。通常,您使用 kubectl
等工具为您执行这些 API 调用。
例如,假设您有一组 Pod,每个 Pod 都监听 TCP 端口 9376,并被标记为 app.kubernetes.io/name=MyApp
。您可以定义一个服务来发布该 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”的新服务,并具有默认的 ClusterIP 服务类型。该服务针对具有 app.kubernetes.io/name: MyApp
标签的任何 Pod 上的 TCP 端口 9376。
Kubernetes 为此服务分配一个 IP 地址(集群 IP),该地址由虚拟 IP 地址机制使用。有关该机制的更多详细信息,请阅读 虚拟 IP 和服务代理。
该服务的控制器会不断扫描与选择器匹配的 Pod,然后对服务的 EndpointSlices 集进行任何必要的更新。
服务对象的名称必须是有效的 RFC 1035 标签名称。
注意
服务可以将 任何 入站port
映射到 targetPort
。默认情况下,为了方便起见,targetPort
设置为与 port
字段相同的值。端口定义
Pod 中的端口定义有名称,您可以在服务的 targetPort
属性中引用这些名称。例如,我们可以通过以下方式将服务的 targetPort
绑定到 Pod 端口
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
即使服务中存在使用单个配置名称的 Pod 混合,并且通过不同的端口号提供相同的网络协议,这也适用。这为部署和演化您的服务提供了很大的灵活性。例如,您可以在后端软件的下一个版本中更改 Pod 公开的端口号,而不会破坏客户端。
服务的默认协议是 TCP;您也可以使用任何其他 支持的协议。
由于许多服务需要公开多个端口,因此 Kubernetes 支持单个服务的 多个端口定义。每个端口定义可以具有相同的 protocol
,也可以具有不同的 protocol
。
没有选择器的服务
服务最常通过选择器抽象对 Kubernetes Pod 的访问,但在与一组相应的 EndpointSlices 对象一起使用且没有选择器的情况下,服务可以抽象其他类型的后端,包括在集群外部运行的后端。
例如
- 您希望在生产环境中拥有一个外部数据库集群,但在测试环境中使用自己的数据库。
- 您希望将服务指向不同 命名空间 或其他集群中的服务。
- 您正在将工作负载迁移到 Kubernetes。在评估方法时,您只在 Kubernetes 中运行一部分后端。
在任何这些场景中,您都可以定义一个服务,无需指定选择器来匹配 Pod。例如
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
由于此服务没有选择器,因此不会自动创建相应的 EndpointSlice(和传统 Endpoints)对象。您可以通过手动添加 EndpointSlice 对象,将服务映射到其运行的网络地址和端口。例如
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"
自定义 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,因为 kube-proxy 不支持虚拟 IP 作为目标。
对于您自己创建的或在您自己的代码中创建的 EndpointSlice,您还应该选择一个值来用于标签 endpointslice.kubernetes.io/managed-by
。如果您创建自己的控制器代码来管理 EndpointSlices,请考虑使用类似于 "my-domain.example/name-of-controller"
的值。如果您使用的是第三方工具,请使用工具的名称(全部小写),并将空格和其他标点符号更改为连字符 (-
)。如果人们直接使用诸如 kubectl
之类的工具来管理 EndpointSlices,请使用描述此手动管理的名称,例如 "staff"
或 "cluster-admins"
。您应该避免使用保留值 "controller"
,该值标识由 Kubernetes 自己的控制平面管理的 EndpointSlices。
访问没有选择器的服务
访问没有选择器的服务的工作方式与它有选择器时相同。在 示例 中,对于没有选择器的服务,流量将路由到 EndpointSlice 清单中定义的两个端点之一:到 10.1.2.3 或 10.4.5.6 上的端口 9376 的 TCP 连接。
注意
Kubernetes API 服务器不允许代理到未映射到 Pod 的端点。诸如kubectl port-forward service/<service-name> forwardedPort:servicePort
之类的操作,其中服务没有选择器,将由于此约束而失败。这可以防止 Kubernetes API 服务器用作对调用者可能没有授权访问的端点的代理。ExternalName
服务是服务的一种特殊情况,它没有选择器,而是使用 DNS 名称。有关更多信息,请参阅 ExternalName 部分。
端点切片
Kubernetes v1.21 [稳定]
EndpointSlices 是表示服务的支持网络端点子集(切片)的对象。
您的 Kubernetes 集群跟踪每个 EndpointSlice 代表的端点数量。如果服务的端点数量达到阈值,则 Kubernetes 会添加另一个空的 EndpointSlice 并将新的端点信息存储在那里。默认情况下,Kubernetes 在现有 EndpointSlices 都包含至少 100 个端点时创建一个新的 EndpointSlice。在需要添加额外的端点之前,Kubernetes 不会创建新的 EndpointSlice。
有关此 API 的更多信息,请参阅 EndpointSlices。
Endpoints
在 Kubernetes API 中,Endpoints(资源类型为复数)定义了网络端点的列表,通常由服务引用以定义可以将流量发送到的 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 个端点。
应用程序协议
Kubernetes v1.20 [稳定]
appProtocol
字段提供了一种为每个服务端口指定应用程序协议的方法。这用作实现的提示,以便为它们理解的协议提供更丰富的行为。此字段的值由相应的 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 |
多端口服务
对于某些服务,您需要公开多个端口。Kubernetes 允许您在服务对象上配置多个端口定义。当为服务使用多个端口时,您必须为所有端口指定名称,以确保其明确无误。例如
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 地址,该地址可从集群外部访问。
Kubernetes 服务类型允许您指定所需的 Service 类型。
可用的 type
值及其行为如下
ClusterIP
- 在集群内部 IP 上公开服务。选择此值会使服务只能从集群内部访问。这是默认值,如果您没有为服务显式指定
type
,则会使用此值。您可以使用 Ingress 或 Gateway 将服务公开到公共互联网。 NodePort
- 在每个节点的 IP 上的静态端口(
NodePort
)上公开服务。为了使节点端口可用,Kubernetes 会设置一个集群 IP 地址,与您请求type: ClusterIP
的服务相同。 LoadBalancer
- 使用外部负载均衡器在外部公开服务。Kubernetes 不会直接提供负载均衡组件;您必须提供一个,或者您可以将 Kubernetes 集群与云提供商集成。
ExternalName
- 将服务映射到
externalName
字段的内容(例如,映射到主机名api.foo.bar.example
)。映射会配置您的集群的 DNS 服务器以返回具有该外部主机名值的CNAME
记录。不会设置任何类型的代理。
服务 API 中的 type
字段被设计为嵌套功能 - 每个级别都添加到前一个级别。但是,此嵌套设计有一个例外。您可以通过 禁用负载均衡器 NodePort
分配 来定义 LoadBalancer
服务。
type: ClusterIP
此默认服务类型会从集群为该目的保留的 IP 地址池中分配一个 IP 地址。
其他几种服务类型以 ClusterIP
类型为基础。
如果您定义的服务具有设置为 "None"
的 .spec.clusterIP
,则 Kubernetes 不会分配 IP 地址。有关更多信息,请参阅 无头服务。
选择您自己的 IP 地址
您可以在 Service
创建请求中指定您自己的集群 IP 地址。为此,请设置 .spec.clusterIP
字段。例如,如果您已经拥有要重用的现有 DNS 条目,或者遗留系统已配置为特定 IP 地址,并且难以重新配置。
您选择的 IP 地址必须是 API 服务器为其配置的 service-cluster-ip-range
CIDR 范围内的有效 IPv4 或 IPv6 地址。如果您尝试使用无效的 clusterIP
地址值创建服务,API 服务器将返回 422 HTTP 状态代码以指示存在问题。
阅读 避免冲突 以了解 Kubernetes 如何帮助降低两个不同的服务都尝试使用相同 IP 地址的风险和影响。
type: NodePort
如果您将 type
字段设置为 NodePort
,Kubernetes 控制平面会从 --service-node-port-range
标志(默认值:30000-32767)指定的范围内分配一个端口。每个节点都会将该端口(每个节点上的相同端口号)代理到您的服务。您的服务会在其 .spec.ports[*].nodePort
字段中报告分配的端口。
使用 NodePort 可以让你自由地设置自己的负载均衡解决方案,配置 Kubernetes 不完全支持的环境,甚至直接公开一个或多个节点的 IP 地址。
对于 NodePort 服务,Kubernetes 还会分配一个端口(TCP、UDP 或 SCTP,与服务的协议匹配)。集群中的每个节点都会配置自己以监听分配的端口,并将流量转发到与该服务关联的一个就绪端点。你将能够从集群外部连接到 type: NodePort
服务,方法是使用适当的协议(例如:TCP)和适当的端口(分配给该服务)连接到任何节点。
选择你自己的端口
如果你想要一个特定的端口号,可以在 nodePort
字段中指定一个值。控制平面将为你分配该端口,或报告 API 事务失败。这意味着你需要自己处理可能的端口冲突。你还必须使用有效的端口号,即在为 NodePort 使用配置的范围内。
以下是一个 type: NodePort
服务的示例清单,它指定了一个 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 范围以避免冲突
Kubernetes v1.29 [稳定]
为 NodePort 服务分配端口的策略适用于自动分配和手动分配两种情况。当用户想要创建一个使用特定端口的 NodePort 服务时,目标端口可能会与已经分配的另一个端口冲突。
为了避免这个问题,NodePort 服务的端口范围被划分为两个频段。动态端口分配默认使用上频段,当上频段用完后,它可能会使用下频段。然后,用户可以从下频段分配,降低端口冲突的风险。
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 地址)。type: LoadBalancer
在支持外部负载均衡器的云提供商上,将 type
字段设置为 LoadBalancer
会为你的服务配置一个负载均衡器。负载均衡器的实际创建是异步发生的,有关配置的均衡器的信息将发布在服务的 .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
的服务,Kubernetes 通常会首先进行与你请求 type: NodePort
的服务相同的更改。然后,cloud-controller-manager 组件会配置外部负载均衡器,以将流量转发到分配的节点端口。
你可以配置一个负载均衡的服务,以 省略 分配节点端口,前提是云提供商的实现支持这一点。
一些云提供商允许你指定 loadBalancerIP
。在这些情况下,负载均衡器将使用用户指定的 loadBalancerIP
创建。如果未指定 loadBalancerIP
字段,则负载均衡器将使用一个短暂的 IP 地址设置。如果你指定了 loadBalancerIP
,但你的云提供商不支持此功能,则你设置的 loadbalancerIP
字段将被忽略。
注意
服务的 .spec.loadBalancerIP
字段在 Kubernetes v1.24 中已弃用。
此字段定义不足,其含义在不同的实现中有所不同。它也不能支持双栈网络。此字段可能会在将来的 API 版本中删除。
如果你正在与支持通过(提供商特定)注释为服务指定负载均衡器 IP 地址的提供商集成,你应该切换到这种方式。
如果你正在为负载均衡器与 Kubernetes 的集成编写代码,请避免使用此字段。你可以与 Gateway 集成,而不是与服务集成,或者你可以在服务上定义自己的(提供商特定)注释,以指定等效的详细信息。
节点存活性对负载均衡器流量的影响
负载均衡器健康检查对于现代应用程序至关重要。它们用于确定负载均衡器应该将流量分派到哪个服务器(虚拟机或 IP 地址)。Kubernetes API 没有定义如何为 Kubernetes 管理的负载均衡器实现健康检查,而是由云提供商(以及实现集成代码的人员)决定行为。负载均衡器健康检查在支持服务的 externalTrafficPolicy
字段的上下文中被广泛使用。
具有混合协议类型的负载均衡器
Kubernetes v1.26 [稳定]
默认情况下,对于 LoadBalancer 类型的服务,当定义了多个端口时,所有端口必须具有相同的协议,并且该协议必须是云提供商支持的协议。
功能门 MixedProtocolLBService
(从 v1.24 开始,kube-apiserver 默认启用)允许在定义了多个端口时,为 LoadBalancer 类型的服务使用不同的协议。
注意
可以用于负载均衡服务的协议集由你的云提供商定义;它们可能会施加超出 Kubernetes API 强制执行的限制。禁用负载均衡器 NodePort 分配
Kubernetes v1.24 [稳定]
你可以选择为 type: LoadBalancer
的服务禁用节点端口分配,方法是将 spec.allocateLoadBalancerNodePorts
字段设置为 false
。这应该只用于将流量直接路由到 Pod 而不是使用节点端口的负载均衡器实现。默认情况下,spec.allocateLoadBalancerNodePorts
为 true
,并且 LoadBalancer 类型的服务将继续分配节点端口。如果在具有分配的节点端口的现有服务上将 spec.allocateLoadBalancerNodePorts
设置为 false
,则这些节点端口将不会自动取消分配。你必须显式删除每个服务端口中的 nodePorts
条目,以取消分配这些节点端口。
指定负载均衡器实现的类别
Kubernetes v1.24 [稳定]
对于 type
设置为 LoadBalancer
的服务,.spec.loadBalancerClass
字段使你能够使用除云提供商默认值以外的负载均衡器实现。
默认情况下,.spec.loadBalancerClass
未设置,如果集群使用 --cloud-provider
组件标志配置了云提供商,则 LoadBalancer
类型的服务将使用云提供商的默认负载均衡器实现。
如果你指定了 .spec.loadBalancerClass
,则假定与指定类别匹配的负载均衡器实现正在监视服务。任何默认的负载均衡器实现(例如,由云提供商提供的实现)将忽略设置了此字段的服务。spec.loadBalancerClass
只能在 LoadBalancer
类型的服务上设置。一旦设置,就不能更改。spec.loadBalancerClass
的值必须是标签样式的标识符,可以包含可选的前缀,例如 "internal-vip
" 或 "example.com/internal-vip
"。未加前缀的名称保留给最终用户。
指定负载均衡器状态的 IPMode
Kubernetes v1.30 [beta]
作为 Kubernetes 1.30 中的 Beta 功能,名为 LoadBalancerIPMode
的 功能门 允许你为 type
设置为 LoadBalancer
的服务设置 .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 和端口。
服务实现可以使用此信息来调整流量路由。
内部负载均衡器
在混合环境中,有时需要将来自同一(虚拟)网络地址块内服务的流量路由。
在分层 DNS 环境中,你需要两个服务才能将外部和内部流量都路由到你的端点。
要设置内部负载均衡器,请根据你使用的云服务提供商,向你的服务添加以下注释之一
选择其中一个选项卡。
metadata:
name: my-service
annotations:
networking.gke.io/load-balancer-type: "Internal"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-internal: "true"
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 类型的服务将服务映射到 DNS 名称,而不是映射到典型的选择器,例如 my-service
或 cassandra
。你可以使用 spec.externalName
参数指定这些服务。
例如,此服务定义将 prod
命名空间中的 my-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
的服务接受 IPv4 地址字符串,但将该字符串视为由数字组成的 DNS 名称,而不是 IP 地址(然而,互联网不允许在 DNS 中使用此类名称)。具有类似于 IPv4 地址的外部名称的服务不会被 DNS 服务器解析。
如果您想将服务直接映射到特定 IP 地址,请考虑使用 无头服务。
当查找主机 my-service.prod.svc.cluster.local
时,集群 DNS 服务返回一个值为 my.database.example.com
的 CNAME
记录。访问 my-service
的方式与其他服务相同,但关键区别在于重定向发生在 DNS 级别,而不是通过代理或转发。如果您以后决定将数据库迁移到集群中,您可以启动其 Pod,添加适当的选择器或端点,并更改服务的 type
。
注意
您可能在使用 ExternalName 时遇到一些常见协议(包括 HTTP 和 HTTPS)的问题。如果您使用 ExternalName,则集群内客户端使用的主机名与 ExternalName 引用的名称不同。
对于使用主机名的协议,这种差异可能会导致错误或意外响应。HTTP 请求将具有源服务器无法识别的 Host:
标头;TLS 服务器将无法提供与客户端连接到的主机名匹配的证书。
无头服务
有时您不需要负载均衡和单个服务 IP。在这种情况下,您可以通过明确为集群 IP 地址(.spec.clusterIP
)指定 "None"
来创建所谓的无头服务。
您可以使用无头服务与其他服务发现机制交互,而无需绑定到 Kubernetes 的实现。
对于无头服务,不会分配集群 IP,kube-proxy 不会处理这些服务,并且平台不会为它们进行任何负载均衡或代理。
无头服务允许客户端直接连接到它喜欢的任何 Pod。无头服务不会使用 虚拟 IP 地址和代理 配置路由和数据包转发;相反,无头服务通过集群的 DNS 服务 通过内部 DNS 记录报告各个 Pod 的端点 IP 地址。要定义无头服务,您需要创建一个服务,其 .spec.type
设置为 ClusterIP(这也是 type
的默认值),并且您还需要将 .spec.clusterIP
设置为 None。
字符串值 None 是一个特殊情况,与不设置 .spec.clusterIP
字段不同。
DNS 如何自动配置取决于服务是否定义了选择器
带有选择器
对于定义了选择器的无头服务,端点控制器会在 Kubernetes API 中创建 EndpointSlices,并修改 DNS 配置以返回指向支持服务的 Pod 的 A 或 AAAA 记录(IPv4 或 IPv6 地址)。
没有选择器
对于没有定义选择器的无头服务,控制平面不会创建 EndpointSlice 对象。但是,DNS 系统会查找并配置以下任一内容:
- DNS CNAME 记录用于
type: ExternalName
服务。 - DNS A/AAAA 记录用于所有服务类型的服务的所有准备就绪端点的所有 IP 地址,除了
ExternalName
。- 对于 IPv4 端点,DNS 系统会创建 A 记录。
- 对于 IPv6 端点,DNS 系统会创建 AAAA 记录。
当您定义没有选择器的无头服务时,port
必须与 targetPort
匹配。
发现服务
对于在集群中运行的客户端,Kubernetes 支持两种主要的服务查找模式:环境变量和 DNS。
环境变量
当 Pod 在节点上运行时,kubelet 会为每个活动服务添加一组环境变量。它会添加 {SVCNAME}_SERVICE_HOST
和 {SVCNAME}_SERVICE_PORT
变量,其中服务名称大写,连字符转换为下划线。
例如,服务 redis-primary
公开 TCP 端口 6379,并已分配集群 IP 地址 10.0.0.11,它会生成以下环境变量
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,则无需担心此排序问题。
Kubernetes 还支持并提供与 Docker Engine 的“传统容器链接”功能兼容的变量。您可以阅读 makeLinkVariables
,了解如何在 Kubernetes 中实现这一点。
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
服务的唯一方法。您可以在 服务和 Pod 的 DNS 中找到有关 ExternalName
解析的更多信息。
虚拟 IP 地址机制
阅读 虚拟 IP 和服务代理 解释了 Kubernetes 提供的机制,用于使用虚拟 IP 地址公开服务。
流量策略
您可以设置 .spec.internalTrafficPolicy
和 .spec.externalTrafficPolicy
字段来控制 Kubernetes 如何将流量路由到健康的(“准备就绪”)后端。
有关更多详细信息,请参阅 流量策略。
流量分配
Kubernetes v1.30 [alpha]
.spec.trafficDistribution
字段提供了另一种影响 Kubernetes 服务内流量路由的方法。虽然流量策略侧重于严格的语义保证,但流量分配允许您表达偏好(例如,路由到拓扑上更接近的端点)。这有助于优化性能、成本或可靠性。如果您已为您的集群及其所有节点启用了 ServiceTrafficDistribution
功能网关,则可以使用此可选字段。在 Kubernetes 1.30 中,支持以下字段值
PreferClose
- 表示优先将流量路由到拓扑上靠近客户端的端点。对“拓扑上靠近”的解释可能因实现而异,可能包括同一节点、机架、区域甚至区域内的端点。设置此值会允许实现做出不同的权衡,例如,优化接近度而不是负载的均衡分配。如果此类权衡不可接受,用户不应设置此值。
如果未设置该字段,则实现将应用其默认路由策略。
有关更多详细信息,请参阅 流量分配
会话粘性
如果您想确保来自特定客户端的连接每次都传递到同一个 Pod,您可以根据客户端的 IP 地址配置会话亲和性。阅读 会话亲和性,了解更多信息。
外部 IP
如果存在将流量路由到一个或多个集群节点的外部 IP,则可以在这些 externalIPs
上公开 Kubernetes 服务。当网络流量到达集群时,使用外部 IP(作为目标 IP)和与该服务匹配的端口,Kubernetes 配置的规则和路由会确保流量被路由到该服务的某个端点。
当您定义服务时,您可以为任何 服务类型 指定 externalIPs
。在下面的示例中,名为 "my-service"
的服务可以通过客户端使用 TCP 访问,在 "198.51.100.32:80"
上(从 .spec.externalIPs[]
和 .spec.ports[].port
计算得出)。
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 对象 的更多详细信息。
下一步
了解有关服务及其在 Kubernetes 中的作用的更多信息
- 按照 使用服务连接应用程序 教程进行操作。
- 阅读有关 Ingress 的内容,它将集群外部的 HTTP 和 HTTPS 路由公开到集群内的服务。
- 阅读有关 Gateway 的内容,它是 Kubernetes 的扩展,提供了比 Ingress 更多的灵活性。
有关更多上下文,请阅读以下内容