认识
Pod 会被创建,并且还会消失,这由 ReplicaSets 控制。每个 Pod 都有自己的 IP 地址,但是这些 IP 地址不能视为可靠的。那么如果前端的一部分 Pod 依赖于后端的 Pod,那前端的这些 Pod 如何找出并追踪后端的 Pod?
组成
Service,服务,用于暴露 Pod 以供访问;
Service 是一个抽象,定义 Pod 的逻辑集合以及谁可以访问它们的策略(有时称为微服务)。Service 指向的 Pod 集由 Label Selector 确定(由写 Service 也没有选择器);
一个场景,后端运行某个镜像的三个副本,前端并不关心访问哪个副本。后端的 Pod 副本可能会变化,但是前端不应该去关心或者追踪后端的变化。Service 的抽象提供了这种解耦;
补充说明:对于 Kubernetes 本机应用程序,Kubernetes 提供了一个简单的 Endpoints API,只要服务中的 Pod 集发生变化,它就会更新。对于非本机应用程序,Kubernetes 提供基于虚拟 IP 的服务桥接,重定向到后端 Pod
性质
Huawei Cloud / 网络概述_云容器引擎 CCE_用户指南_Standard和Turbo集群用户指南_网络
定义服务(Defining a Service)
Service 也是一个 REST 对象,像 Pod 一样。将定义提交给 apiserver 来创建对象;
假设有个 Pod 暴露 9376 端口,并且标签为 app: MyApp,到该 Pod 集的 Service 可以这么定义:
# foo.yaml # 定义一个服务 kind: Service apiVersion: v1 metadata: # 服务名 name: my-service spec: selector: # 流量发往标签为「app: MyApp」的 Pod 上 app: MyApp ports: - protocol: TCP port: 80 # 流量发往 Pod 的这个端口上; targetPort: 9376
??? Service 会被分配一个 IP 地址(有时称为 Cluster IP),被服务代理使用。将连续评估 Service 的选择器,并将结果发布到名为“my-service”的 Endpoints 对象;
注意事项,Service 可以映射一个入口 port 到任何 targetPort。默认 port 和 targetPort 是相同的;
targetPort 也可以是一个字符串,指向后端 Pod 的端口名(由 containers.ports.name 定义),分配到该字符串的端口号可以在每个 Pod 中不同。这非常利于部署和升级 Service。例如,下次 Pod 版本升级的时候可以修改端口,而无需打破客户端;
默认使用 TCP 协议,支持的协议参考 supported protocol 手册。某些服务需要暴露多个端口,可以在 Service 中定义多个端口,并使用不同的协议;
Services without selectors
除了抽象 Pod 以外,还可以抽象其他类型的后端,例如:
你想将 Service 指向其他 Namespace 中的 Service(或者其他集群)
迁移工作负载到 k8s 集群,但是有些后端服务运行在 k8s 之外
对于以上场景,可以这么做:
# service-foo.yaml # 定义没有选择器的后端服务,因此也不会创建对应的 EndPoints 对象 kind: Service apiVersion: v1 metadata: name: my-service spec: ports: - protocol: TCP port: 80 targetPort: 9376 # 然后将 Service 手动映射到特定的后端 # endpoints-bar.yaml kind: Endpoints apiVersion: v1 metadata: # 名称与 Service 的相同 name: my-service subsets: - addresses: - ip: 1.2.3.4 ports: - port: 9376 # !!! Endpoints 的 IP 可能不是环回(127.0.0.0/8),链路本地(169.254.0.0/16)或链路本地多播(224.0.0.0/24); # !!! 它们不能是其他 Kubernetes 服务的集群 IP,因为 kube-proxy 组件不支持 VIP 作为目标地址;
到达 Service 的流量会被发送到 Endpoints 定义的地址(1.2.3.4:9376);
ExternalName
是种特殊情况的 Service,它没有选择器而是使用 DNS 名称。有关详细信息,请参阅本文档后面的 ExternalName 部分;
Endpoint and Endpoint Slices
CSDN/ 江中散人 / 第六章容器基础 6.4.9.5 节——Service 特性端点切片(Endpoint Slices)_endpoints endpointslice
如果使用 Endpoint API,Service 只有一个 Endpoint 资源。这意味着它需要为 Service 的每个 Pod 都存储好 IP 地址和端口(网络端点),这需要大量的 API 资源。另外,kube-proxy 会在每个节点上运行,并监控 Endpoint 资源的任何更新。如果 Endpoint 资源中有一个端口发生更改,那么整个对象都会分发到 kube-proxy 的每个实例;
Endpoint API 另一个局限是,它会限制跟踪 Service 的网络端点数量。一般存储在 etcd 中的对象默认大小限制为 1.5MB。在某些情况下,它会将 Endpoint 资源限制为 5000 个 Pod IP。对于大多数用户而言,这没什么关系,但是对于接近这个大小的 Service 而言,就有大问题了;
EndpointSlice API 旨在通过类似于分片的方法来解决该问题。我们不跟踪 Service Pod IP 的单个 Endpoint 资源,而是将它们拆分为多个较小的 EndpointSlice;
举个例子,现在有 15 个 Pod 在支持一个 Service,那么就有跟踪这些的一个 Endpoint 资源。如果将 EndpointSlice 配置为每个 EndpointSlice 存储 5 个端点,就得到了 3 个不同的 EndpointSlice;
虚拟网络地址 / 服务代理
WIP
多个端口的服务(Multi-Port Services)
多端口的 Service 需要指定端口名称:
kind: Service apiVersion: v1 metadata: name: my-service spec: selector: app: MyApp ports: # 名称 - name: http protocol: TCP port: 80 targetPort: 9376 # 名称 - name: https protocol: TCP port: 443 targetPort: 9377 # !!! 名称只能使用小写字母数字和横线,并且必须以字母数字开始结束 # !!! 有效:123-web 或 web # !!! 无效:123_abc
选择自己的集群网络地址(Choosing your own IP address)
在服务创建是,可以指定 Cluster IP 地址。通过设置.spec.clusterIP字段。例如,你有一个想重新使用的 DNS 条目,或者系统中使用了某个 IP 地址并且很难配置。用户选择的 IP 地址必须是在 API Server 中的 service-cluster-ip-range 设置的 CIDR 范围。如果 IP 地址无效,则 API Server 返回 442 状态码;
为什么不使用轮循 DNS ?
为什么使用诸如 IPVS 来实现 Round-Robin 等等,而不使用 Round-Robin DNS 来实现:
许多应用程序执行 DNS 查找一次,并缓存结果;
即使应用程序和库进行了适当的重新解析,每个客户端反复重新解析 DNS 的负载也难以管理;
官方也说了,如果想用循环 DNS 的人多,人家也会实现这个功能;
发现服务(Discovering services)
两种查找服务的方式:(1)环境变量;(2)DNS;
环境变量
在 Node 上运行 Pod 时,kubelet 会为活动的 Server 添加一系列的环境变量。它支持 Dokcer link 兼容的环境变量,{SVCNAME}_SERVICE_HOST 和 {SVCNAME}_SERVICE_PORT 变量,服务名会被转化为大写,横线替换为下滑线;
例如,服务”redis-master”,端口”6379″,集群 IP 地址”10.0.0.11″,会生成如下环境变量:
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 访问的服务要先创建,否则服务的环境变量不会写入到 Pod 中(使用 DNS 就没有这个限制);
DNS
通过安装提供 DNS 服务的集群插件;
集群感知的 DNS Server 会观察 K8s API 的服务创建,然后为每个服务生成一条 DNS 记录。如果在整个群集中启用了 DNS,则所有 Pod 应该能够自动对服务进行名称解析;
例如,服务”my-service”,命名空间”my-ns”,则 DNS 为”my-service.my-ns”。同命名空间的 Pod 可以直接使用”my-service”访问,不同命名空间的服务可以使用”my-service.my-ns”进行访问。解析的结果是集群的 IP 地址;
除此之外还支持 DNS SRV 记录。例如,”my-service.my-ns” 的 Service 有个名为 ”http“ 的端口,使用”tcp“协议,则可以进行 DNS SRV 查询 ”_http._tcp.my-service.my-ns“ 来发现端口号;
Kubernetes DNS 是访问 ExternalName 类型服务的唯一方式。更多信息参考 DNS Pods and Services 中的说明;
Headless Services
有时候不需要负载均衡或者服务网络地址,可以将 .spec.clusterIP 设置为 ”None“ 来创建无头服务;
此选项允许开发人员”通过允许他们自由地以自己的方式进行发现“来减少与 Kubernetes 系统的耦合。应用程序仍然可以使用自注册模式,并且可以轻松地在此 API 上构建适用于其他发现系统的适配器;
对于此类服务,未分配 Cluster IP,kube-proxy 不处理这些服务,并且平台没有为它们执行负载均衡或代理。如何自动配置 DNS 取决于服务是否已定义选择器:
已定义 Selector 的服务
已经定义选择器的无头服务,Endpoints Controller 会在 API 中创建 Endpoints 记录,并修改 DNS 配置来返回一个 A 记录,该记录直接指向 Service 后端的 Pod 实例;
没有 Cluster IP 参数(None),因此 kube-proxy 也不会管理该 Serivce 对象,因此也不会有什么 ipvs、port、targetPort 等等参数;
未定义 Selector 的服务
如果没有定义选择器,Endpoints Controller 不会创建 Endpoints 记录。但是 DNS 系统会查找并配置:
1)ExternalName 类型服务的 CNAME 记录
2)为所有其他类型的,与服务共享名称的任何 Endpoints 的记录
Publishing Services (ServiceTypes)
有时我们想将服务暴露出来供外部访问,可以使用 ServiceTypes 来指定服务类型以暴露服务;
类型的行为如下:
1)ClusterIP – 在集群内部的 IP 地址上暴露服务,只能由集群内部访问。这是默认的服务类型
2)NodePort – 在每个 Node 的 IP 地址上暴露一个端口(NodePort)。会自动创建一个 ClusterIP 服务,NodePort 的服务会路由到该服务上。可以使用 NodeIP:NodePort 来访问该服务;
3)LoadBalancer – 使用云商的负载均衡器在外部公开服务。将自动创建”外部负载均衡器将路由到的“NodePort和ClusterIP服务;
4)ExternalName – 通过返回带有其值的 CNAME 记录,将服务映射到 externalName 字段的内容(例如 foo.bar.example.com)。没有设置任何类型的代理。这需要版本 1.7 或更高版本的 kube-dns
NodePort
从–service-node-port-range 标志指定的范围(30000-32767)分配一个端口,每个节点都会代理该端口到服务。在服务的.spec.ports[*].nodePort 字段查看端口;
如果要指定用于代理端口的特定 IP 地址,可以将 kube-proxy 中的–nodeport-addresses 标志设置为特定的 IP 块(自 Kubernetes v1.10 起支持)。以逗号分隔的 IP 块列表(例如 10.0.0.0/8,1.2.3.4/32)用于过滤此节点的本地地址。例如,如果您使用标志–nodeport-addresses=127.0.0.0/8 来启动 kube-proxy,则 kube-proxy 将仅为 NodePort 服务选择环回接口。–nodeport-addresses 默认为空([]),这意味着选择所有可用的接口,并符合当前的 NodePort 行为;
如果需要特定的端口号,可以在 nodePort 字段中指定一个值,系统将为您分配该端口,否则 API 事务将失败(即您需要自己处理可能的端口冲突)。您指定的值必须在节点端口的配置范围内;
这使开发人员可以自由地设置自己的负载均衡器,配置 Kubernetes 不完全支持的环境,甚至直接暴露一个或多个节点的 IP;
请注意,此服务将同时显示为<NodeIP>:spec.ports[*].nodePort和.spec.clusterIP:spec.ports[*].port。(如果设置了 kube-proxy 中的–nodeport-addresses 标志,则会过滤 NodeIP。)
LoadBalancer
在支持提供外部负载均衡器的云商上,将 type 设置为 LoadBalancer 将会为你的 Service 规定一个负载均衡器。负载均衡器的实际创建是异步发生的,有关已配置的均衡器的信息将发布在 Service 的.status.loadBalancer字段中。例如:
kind: Service apiVersion: v1 metadata: name: my-service spec: selector: app: MyApp ports: - protocol: TCP port: 80 targetPort: 9376 clusterIP: 10.0.171.239 loadBalancerIP: 78.11.24.19 type: LoadBalancer status: loadBalancer: ingress: - ip: 146.148.47.155
来自外部负载均衡器的流量被直接重定向到 Pod,虽然具体如何运作取决于云提供商;
某些云商允许指定 loadBalancerIP 字段,在这些情况下,在这些情况下,将使用用户指定的 loadBalancerIP 创建负载均衡器。如果未指定 loadBalancerIP 字段,则会将临时 IP 分配给 loadBalancer。如果指定了 loadBalancerIP,但云提供程序不支持该功能,则该字段将被忽略;
WIP 负载均衡类型的 Service 服务
ExternalName
该类型的服务将服务映射到 DNS 名,而不是一个选择器,可以使用 spec.externalName 指定参数:
kind: Service apiVersion: v1 metadata: name: my-service namespace: prod spec: type: ExternalName externalName: my.database.example.com # ExternalName 接受 IPv4 地址字符串,但是被解释为由数字组成的 DNS 名称,而不是网络地址; # 类似于 IPv4 地址的 ExternalNames 不会被 CoreDNS 或 ingress-nginx 解析,因为 ExternalName 旨在指定规范的 DNS 名称; # 要对 IP 地址进行硬编码,请考虑无头服务;
当查找 my-service.prod.svc.cluster.local 域名时,DNS 服务将会返回一个 CNAME,值为 my.database.example.com。访问该服务时,与访问其他服务没有区别,关键是这发生在 DNS 层面,而不时代理或者转发。之后,如果你想将外部服务迁移到 k8s 中,你只需要创建对应的 Pod,添加选择器或者 Endpoints,然后修改 type 字段;
External IP
Service | Kubernetes/External IPs
Learn how to use Kubernetes External IP service type | by Mohamad Fadhil | The Startup | Medium
如果有外部 IP 路由到一个或多个群集节点,则可以在这些 External IP 上公开 Kubernetes 服务。在 Service 的 Port 上使用 External IP(作为目标 IP)进入群集的流量将路由到其中一个服务 Endpoints。externalIPs 不由 Kubernetes 管理,是集群管理员的责任;
在 Service.spec 中,任何服务类型都能指定 externalIPs 属性;
如下示例,通过 80.11.12.10:80(externalIP:port)来访问 my-service 服务:
kind: Service apiVersion: v1 metadata: name: my-service spec: selector: app: MyApp ports: - name: http protocol: TCP port: 80 targetPort: 9376 externalIPs: - 80.11.12.10
在内网中,如果是测试环境:
1)将 externalIPs 设置为 Worker 的 IP-Address,则能通过 IP-Address:port 直接访问服务,但存在不确定性,还会影响节点状态,所以不建议使用;
2)或者使用同网段的其他地址,但是需要管理员在某个 Worker 上绑定该地址(externalIPs 由管理员负责管理),还会影响到网络插件检测地址,需要额外调整,不建议使用;
WIP ! Kubernetes Service ExernalIP 的使用方法,能否使用同网段内网地址;
当使用 ExternalIP 方式时,externalIPs 应为公网地址(或在私网内不同网段地址);
缺点
使用 VIP 的用户空间的代理将在中小规模上工作,但不会扩展到具有数千个服务的非常大的集群。有关详细信息,请参阅 门户网站的原始设计提案;
使用用户空间代理会模糊访问服务的数据包的源 IP,这使得某些类型的防火墙变得不可能。iptables 代理器,不会掩盖集群内的源 IP,但它仍会影响通过负载均衡器或节点端口的客户端;
“type”字段设计为嵌套功能 – 每个级别都添加到前一个级别。并非所有云提供商都严格要求这样做(例如,Google Compute Engine 不需要分配 NodePort 来使 LoadBalancer 正常工作,但 AWS 会这样做)但当前的 API 需要它;
未来的工作
在未来,我们设想代理策略可以比简单的循环平衡更加细微,例如主选或分片。我们还设想一些服务将具有“真正的”负载均衡器,在这种情况下,VIP 将简单地在那里传输数据包;
我们打算改进对 L7(HTTP)服务的支持;
我们打算为 Service 提供更灵活的 Ingress 模式,包括当前的 ClusterIP,NodePort 和 LoadBalancer 模式等等
有关虚拟 IP 的更多细节
对于许多只想使用服务的人来说,之前的信息应该足够了。然而,幕后有很多可能值得理解的事情
避免碰撞
Kubernetes 的主要哲学之一是用户不应该暴露于可能导致他们的行为失败的情况,而不是他们自己的过错。在这种情况下,我们正在寻找网络端口 – 如果该选择可能与另一个用户发生冲突,则用户不必选择端口号。这是隔离失败;
为了允许用户为其服务选择端口号,我们必须确保没有两个服务可以冲突。我们通过为每个服务分配自己的 IP 地址来做到这一点;
为了确保每个服务都接收到唯一的 IP,内部分配器在创建每个服务之前以原子方式更新 etcd 中的全局分配映射。映射对象必须存在于注册表中以获取 IP 的服务,否则创建将失败,并显示一条消息,指示无法分配 IP。后台控制器负责创建该映射(从内存锁定中使用的旧版 Kubernetes 迁移)以及由于管理员干预而检查无效分配,并清除已分配但当前没有服务使用的任何 IP;
IP 与 VIP
与实际路由到固定目的地的 Pod IP 地址不同,服务 IP 实际上并未由单个主机应答。相反,我们使用 iptables(Linux 中的数据包处理逻辑)来定义根据需要透明重定向的虚拟 IP 地址。当客户端连接到 VIP 时,其流量会自动传输到适当的端点。服务的环境变量和 DNS 实际上是根据 Service 的 VIP 和端口填充的;
我们支持三种代理模式 – userspace,iptables 和 ipv,它们的运行方式略有不同
Userspace
作为示例,考虑上述镜像应用程序。创建后端服务时,Kubernetes 主服务器会分配一个虚拟 IP 地址,例如 10.0.0.1。假设服务端口为 1234,则群集中的所有 kube-proxy 实例都会观察到该服务。当代理看到新服务时,它会打开一个新的随机端口,建立从 VIP 到这个新端口的 iptables 重定向,并开始接受它上面的连接;
当客户端连接到 VIP 时,iptables 规则启动,并将数据包重定向到服务代理自己的端口。服务代理选择后端,并开始代理从客户端到后端的流量;
这意味着服务所有者可以选择他们想要的任何端口而不会发生冲突。客户端可以简单地连接到 IP 和端口,而无需知道他们实际访问的是哪些 Pod;
Iptables
再次,考虑上述镜像应用程序。创建后端服务时,Kubernetes 主服务器会分配一个虚拟 IP 地址,例如 10.0.0.1。假设服务端口为 1234,则群集中的所有 kube-proxy 实例都会观察到该服务。当代理看到新服务时,它会安装一系列 iptables 规则,这些规则从 VIP 重定向到每服务规则。每服务规则链接到每端点规则,该规则将(目标 NAT)重定向到后端;
当客户端连接到 VIP 时,iptables 规则启动。选择后端(基于会话亲和性或随机),并将数据包重定向到后端。与用户空间代理不同,数据包永远不会复制到用户空间,因此不必运行 kube-proxy 以使 VIP 工作,并且不会更改客户端 IP;
当流量通过节点端口或通过负载均衡器进入时,执行相同的基本流程,但在这些情况下,客户端 IP 确实会被更改
Ipvs
Iptables 操作在大规模集群中显着放缓,例如 10,000 个服务。IPVS 旨在实现负载均衡并基于内核中的哈希表。因此,我们可以从基于 IPVS 的 kube-proxy 实现大量服务的性能一致性。同时,基于 IPVS 的 kube-proxy 具有更复杂的负载均衡算法(最少的 conns,局部性,加权,持久性)
API 对象
Service 是 Kubernetes REST API 中的顶级对象。更多 API 对象的细节可以参考 Service API object 手册;
支持的协议
TCP
Kubernetes v1.0 TCP
UDP
Kubernetes v1.0 UDP 可以为大多数服务使用,对于 type=LoadBalancer 类型的服务,是否支持要看云商的提供的设施;
HTTP
Kubernetes v1.1 HTTP 如果您的云提供商支持它,您可以使用 LoadBalancer 模式下的服务来设置外部 HTTP/HTTPS 反向代理,转发到服务的端点;
PROXY
Kubernetes v1.1 PROXY 如果您的云提供商支持它(例如,AWS),您可以使用 LoadBalancer 模式下的服务来配置 Kubernetes 之外的负载均衡器,它将转发带有 PROXY 协议前缀的连接。负载均衡器将发送一系列描述传入连接的初始八位字节,类似于此示例 PROXY TCP4 192.0.2.202 10.0.42.7 12345 7\r\n,然后是来自客户的数据
SCTP
Kubernetes v1.12 SCTP Kubernetes 支持 SCTP 作为 Service,Endpoint,NetworkPolicy,Pod 定义中的协议值,作为 alpha 功能。要启用此功能,集群管理员需要在 apiserver 上启用 SCTPSupport 功能门,例如“–feature-gates=SCTPSupport=true,…”。启用功能后,用户可以将 Service, Endpoint, NetworkPolicy,Pod 的 protocol 字段设置为 SCTP。Kubernetes 相应地为 SCTP 关联设置网络,就像它对 TCP 连接一样;
支持多宿主 SCTP 关联
多宿主 SCTP 关联的支持要求 CNI 插件可以支持将多个接口和 IP 地址分配给 Pod;
用于多宿主 SCTP 关联的 NAT 在相应的内核模块中需要特殊逻辑;
Service with type=LoadBalancer
仅当云商的负载均衡器实现支持 SCTP 作为协议时,才能创建类型为 LoadBalancer 和协议 SCTP 的服务。否则,将拒绝 Service 的创建请求。当前的云负载均衡器提供程序集(Azure,AWS,CloudStack,GCE,OpenStack)不支持 SCTP;
Windows
基于 Windows 的节点不支持 SCTP;
用户空间的 kube-proxy
当 kube-proxy 处于用户空间模式时,它不支持管理 SCTP 关联;
相关链接
关于 Service 网络:Understanding kubernetes networking: services
配置 Pod 权重:Can Kubernetes Service control traffic percentage to a given pod? – Stack Overflow
参考文献
kubernetes/CONCEPTS/Services
in json schema for service port is a number, but containerPort is a string #2123
Get a Shell to a Running Container
kube-proxy
Kubernetes API v1.18/Service v1 core