Service
Kubernetes Service
定义了这样一种抽象:一个 Pod
的逻辑分组,一种可以访问它们的策略 —— 通常称为微服务。这一组 Pod
能够被 Service
访问到,通常是通过 Label Selector
来实现的。
Service
能够提供负载均衡的能力,但是在使用上有以下限制:只提供 4
层负载均衡能力,而没有 7
层功能,但有时我们可能需要更多的匹配规则来转发请求,这点上 4
层负载均衡是不支持的。
为什么需要service
解决服务返现与负载均衡
外部网络可以通过 service 去访问pod,pod之间访问,负载均衡pod组。
集群内访问service方式
-
首先我们可以通过 service 的虚拟 IP 去访问,比如说刚创建的 my-service 这个服务,通过 kubectl get svc 或者 kubectl discribe service 都可以看到它的虚拟 IP 地址是 172.29.3.27,端口是 80,然后就可以通过这个虚拟 IP 及端口在 pod 里面直接访问到这个 service 的地址。
-
直接访问服务名,依靠 DNS 解析,就是同一个 namespace 里 pod 可以直接通过 service 的名字去访问到刚才所声明的这个 service。不同的 namespace 里面,我们可以通过 service 名字加“.”,然后加 service 所在的哪个 namespace 去访问这个 service,例如我们直接用 curl 去访问,就是 my-service:80 就可以访问到这个 service。
-
环境变量访问,在同一个 namespace 里的 pod 启动时,K8s 会把 service 的一些 IP 地址、端口,以及一些简单的配置,通过环境变量的方式放到 K8s 的 pod 里面。在 K8s pod 的容器启动之后,通过读取系统的环境变量比读取到 namespace 里面其他 service 配置的一个地址,或者是它的端口号等等。比如在集群的某一个 pod 里面,可以直接通过 curl $ 取到一个环境变量的值,比如取到 MY_SERVICE_SERVICE_HOST 就是它的一个 IP 地址,MY_SERVICE 就是刚才我们声明的 MY_SERVICE,SERVICE_PORT 就是它的端口号,这样也可以请求到集群里面的 MY_SERVICE 这个 service。
Service四种类型
编号 | 类型 | 用途介绍 |
---|---|---|
1 | ClusterIp |
默认类型;自动分配一个仅 Cluster 内部可以访问的虚拟 IP 地址 |
2 | NodePort |
在 ClusterIP 基础上为 Service 在每台机器上绑定一个端口,这样就可以通过 :NodePort 来访问该服务 |
3 | LoadBalancer |
在 NodePort 的基础上,借助 cloud provider 创建一个外部负载均衡器,并将请求转发到 :NodePort 来访问该服务 |
4 | ExternalName |
把集群外部的服务引入到集群内部来,在集群内部直接使用。没有任何类型代理被创建,这只有 Kubernetes1.7 或更高版本的 kube-dns 才支持 |
- VIP(虚拟 IP 地址)和 Service 代理
在 Kubernetes
集群中,每个 Node
运行一个 kube-proxy
进程。kube-proxy
负责为 Service
实现了一种 VIP
(虚拟IP
)的形式,而不是 ExternalName
的形式。
在 Kubernetes v1.0
版本,代理完全在 userspace
。在 Kubernetes v1.1
版本,新增了 iptables
代理,但并不是默认的运行模式。从 Kubernetes v1.2
起,默认就是
iptables
代理。在 Kubernetes v1.8.0-beta.0
中,添加了 ipvs
代理。
在 Kubernetes 1.14
版本开始默认使用 ipvs
代理。在 Kubernetes v1.0
版本,Service
是 “4 层”(TCP/UDP over IP
)概念。在 Kubernetes v1.1
版本,新增了 Ingress API
(beta
版),用来表示 “7 层”(HTTP
)服务。
注意,ipvs
模式假定在运行 kube-proxy
之前的节点上都已经安装了 IPVS
内核模块。当 kube-proxy
以 ipvs
代理模式启动时,kube-proxy
将验证节点上是否安装了 IPVS
模块。如果未安装的话,则 kube-proxy
将回退到 iptables
的代理模式。
- 为什么不适用
Round-robin DNS
的形式进行负载均衡呢?
熟悉 DNS
的话,都知道 DNS
会在客户端进行缓存。当后端服务发生变动的话,我们是无法得到最新的地址的,从而无法达到负载均衡的作用了。
代理模式
- 使用 userspace 代理模式
- 使用 iptables 代理模式
- 使用 ipvs 代理模式
这种模式,kube-proxy
会监视 Kubernetes Service
对象和 Endpoints
,调用 netlink
接口以相应地创建 ipvs
规则并定期与 Kubernetes Service
对象和 Endpoints
对象同步 ipvs
规则,以确保 ipvs
状态与期望一致。访问服务时,流量将被重定向到其中一个后端 Pod
。
与 iptables
类似,ipvs
于 netfilter
的 hook
功能,但使用哈希表作为底层数据结构并在内核空间中工作。这意味着 ipvs
可以更快地重定向流量,并且在同步代理规则时具有更好的性能。此外,ipvs
为负载均衡算法提供了更多选项,例如:
编号 | 类型 | 用途介绍 |
---|---|---|
1 | rr |
轮询调度 |
2 | lc |
最小连接数 |
3 | dh |
目标哈希 |
4 | sh |
源哈希 |
5 | sed |
最短期望延迟 |
6 | nq |
不排队调度 |
与iptables区别:
-
iptables 是一个 Linux 内核功能,是一个高效的防火墙,并提供了大量的数据包处理和过滤方面的能力。它可以在核心数据包处理管线上用 Hook 挂接一系列的规则。iptables 模式中 kube-proxy 在
NAT pre-routing
Hook 中实现它的 NAT 和负载均衡功能。这种方法简单有效,依赖于成熟的内核功能,并且能够和其它跟 iptables 协作的应用(例如 Calico)融洽相处。然而 kube-proxy 的用法是一种 O(n) 算法,其中的 n 随集群规模同步增长,这里的集群规模,更明确的说就是服务和后端 Pod 的数量。
-
IPVS 是一个用于负载均衡的 Linux 内核功能。IPVS 模式下,kube-proxy 使用 IPVS 负载均衡代替了 iptable。这种模式同样有效,IPVS 的设计就是用来为大量服务进行负载均衡的,它有一套优化过的 API,使用优化的查找算法,而不是简单的从列表中查找规则。
这样一来,kube-proxy 在 IPVS 模式下,其连接过程的复杂度为 O(1)。换句话说,多数情况下,他的连接处理效率是和集群规模无关的。
另外作为一个独立的负载均衡器,IPVS 包含了多种不同的负载均衡算法,例如轮询、最短期望延迟、最少连接以及各种哈希方法等。而 iptables 就只有一种随机平等的选择算法。
-
为什么不用nginx之类的:负载均衡功能都强大,但毕竟还是基于用户态转发或者反向代理实现的,性能必然不如在内核态直接转发处理好
1 |
|
ClusterIP
ClusterIP
主要在每个 node
节点使用 ipvs/iptables
,将发向 ClusterIP
对应端口的数据,转发到 kube-proxy
中。然后 kube-proxy
自己内部实现有负载均衡的方法,并可以查询到这个 Service
下对应 pod
的地址和端口,进而把数据转发给对应的 pod
的地址和端口。
Kubernetes的服务发现 - ClusterIP
为了实现图上的功能,主要需要以下几个组件的协同工作:
apiserver
用户通过kubectl
命令向apiserver
发送创建service
的命令,apiserver
接收到请求后将数据存储到etcd
中。kube-proxy
在kubernetes
的每个节点中都有一个叫做kube-porxy
的进程,这个进程负责感知service
和pod
的变化,并将变化的信息写入本地的ipvs/iptables
规则中。ipvs/iptables
使用NAT
等技术将VirtualIP
的流量转至endpoint
中。
1 |
|
1 |
|
- 启动服务之后,可以查到对应的防火墙规则和默认的
SVC
服务。
1 |
|
Headless
有时不需要或不想要负载均衡,以及单独的 Service IP
。遇到这种情况,可以通过指定 Cluster IP
(spec.clusterIP
) 的值为 “None” 来创建 Headless Service
。这类 Service
并不会分配 Cluster IP
,kube-proxy
不会处理它们,而且平台也不会为它们进行负载均衡和路由。
pod 可以直接通过 service_name 用 DNS 的方式解析到所有后端 pod 的 IP 地址,通过 DNS 的 A 记录的方式会解析到所有后端的 Pod 的地址,由客户端选择一个后端的 IP 地址,这个 A 记录会随着 pod 的生命周期变化,返回的 A 记录列表也发生变化,这样就要求客户端应用要从 A 记录把所有 DNS 返回到 A 记录的列表里面 IP 地址中,客户端自己去选择一个合适的地址去访问 pod。
与clusterIP不同
- dns查询时只会返回Service的地址,具体client访问的是哪个Real Server,是由iptables来决定的。
- 无头表示这个svc的负载均衡是没有clusterip的, dns查询会如实的返回2个真实的endpoint,客户端自己去选择一个合适的地址去访问 pod(podname.svcname.namespace.svc.cluster-domain.example)每一个Pod,都会有对应的DNS域名;这样Pod之间就能互相访问,集群也能单独访问pod
对应配置文件,如下所示:
1 |
|
- 启动服务之后,可以查到对应的防火墙规则和默认的
SVC
服务。
1 |
|
访问外部IP
NodePort
nodePort
的原理在于在 node
上开了一个端口,将向该端口的流量导入到 kube-proxy
,然后由 kube-proxy
进一步到给对应的 pod
。
port区别从里到外
-
TargetPort - Pod和Container监听的Port.
- Port - 通过Service暴露出来的一个Port, 可以在Cluster内进行访问。
- NodePort - Cluster向外网暴露出来的端口,可以让外网能够访问到Pod/Container.
NodePort 服务是引导外部流量到你的服务的最原始方式。NodePort,正如这个名字所示,在所有节点(虚拟机)上开放一个特定端口,任何发送到该端口的流量都被转发到对应服务。
配置实例
- 对应配置文件,如下所示:
1 |
|
- 启动服务之后,可以查到对应的防火墙规则和默认的
SVC
服务。
1 |
|
LoadBalancer
loadBalancer
和 nodePort
其实是同一种方式。区别在于 loadBalancer
比 nodePort
多了一步,就是可以调用 cloud provider
去创建 LB
来向节点导流。
如果我们希望有一个单独的 IP 地址,将请求分配给所有的外部节点IP(比如使用 round robin),我们就可以使用 LoadBalancer 服务,所以它是建立在 NodePort 服务之上的。
它将给你一个单独的 IP 地址,转发所有流量到你的服务。
ExternalName
最后是 ExternalName 服务,这个服务和前面的几种类型的服务有点分离。它创建一个内部服务,其端点指向一个 DNS 名。
我们假设 pod-nginx 运行在 Kubernetes 集群中,但是 python api 服务在集群外部。
这里 pod-nginx 这个 Pod 可以直接通过 http://remote.server.url.com 连接到外部的 python api 服务上去,但是如果我们考虑到以后某个时间节点希望把这个 python api 服务集成到 Kubernetes 集群中去,还不希望去更改连接的地址,这个时候我们就可以创建一个 ExternalName 类型的 Service 服务了。
对应的 YAML 资源清单文件如下所示:
1 |
|
现在 pod-nginx 就可以很方便地通过 http://service-python:3000
进行通信了,就像使用 ClusterIP 服务一样,当我们决定将 python api 这个服务也迁移到我们 Kubernetes 集群中时,我们只需要将服务改为 ClusterIP 服务,并设置正确的标签即可,其他都不需要更改了。
Python api 仍然可以通过 http://service-python 访问
这种类型的 Service
通过返回 CNAME
和它的值,可以将服务映射到 externalName
字段的内容,例如:hub.escapelife.site
。ExternalName Service
是 Service
的特例,它没有 selector
,也没有定义任何的端口和 Endpoint
。相反的,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。
当查询主机 my-service.defalut.svc.cluster.local
时,集群的 DNS
服务将返回一个值 hub.escapelife.site
的 CNAME
记录。访问这个服务的工作方式和其他的相同,唯一不同的是重定向发生在 DNS
层,而且不会进行代理或转发。
- 对应配置文件,如下所示:
1 |
|
- 启动服务之后,可以查到对应的防火墙规则和默认的
SVC
服务。
1 |
|
架构设计
如上图所示,K8s 服务发现以及 K8s Service 是这样整体的一个架构。
K8s 分为 master 节点和 worker 节点:
- master 里面主要是 K8s 管控的内容;
- worker 节点里面是实际跑用户应用的一个地方。
在 K8s master 节点里面有 APIServer,就是统一管理 K8s 所有对象的地方,所有的组件都会注册到 APIServer 上面去监听这个对象的变化,比如说我们刚才的组件 pod 生命周期发生变化,这些事件。
这里面最关键的有三个组件:
- 一个是 Cloud Controller Manager,负责去配置 LoadBalancer 的一个负载均衡器给外部去访问;
- 另外一个就是 Coredns,就是通过 Coredns 去观测 APIServer 里面的 service 后端 pod 的一个变化,去配置 service 的 DNS 解析,实现可以通过 service 的名字直接访问到 service 的虚拟 IP,或者是 Headless 类型的 Service 中的 IP 列表的解析;
- 然后在每个 node 里面会有 kube-proxy 这个组件,它通过监听 service 以及 pod 变化,然后实际去配置集群里面的 node pod 或者是虚拟 IP 地址的一个访问。
实际访问链路是什么样的呢?
-
比如说从集群内部的一个 Client Pod3 去访问 Service,就类似于刚才所演示的一个效果。Client Pod3 首先通过 Coredns 这里去解析出 ServiceIP,Coredns 会返回给它 ServiceName 所对应的 service IP 是什么,这个 Client Pod3 就会拿这个 Service IP 去做请求,它的请求到宿主机的网络之后,就会被 kube-proxy 所配置的 iptables 或者 IPVS 去做一层拦截处理,之后去负载均衡到每一个实际的后端 pod 上面去,这样就实现了一个负载均衡以及服务发现。
-
对于外部的流量,比如说刚才通过公网访问的一个请求。它是通过外部的一个负载均衡器 Cloud Controller Manager 去监听 service 的变化之后,去配置的一个负载均衡器,然后转发到节点上的一个 NodePort 上面去,NodePort 也会经过 kube-proxy 的一个配置的一个 iptables,把 NodePort 的流量转换成 ClusterIP,紧接着转换成后端的一个 pod 的 IP 地址,去做负载均衡以及服务发现。
这就是整个 K8s 服务发现以及 K8s Service 整体的结构。