深入理解 Kubernetes 的 Service:回归本源的场景需求
摘要
本文的目标是深入理解Kubernetes Service,为了更好理解这个概念,本文并非Kubernetes本身的实现出发,而是回归服务化原始的场景需求。
服务的概念
服务化是互联网实现的一种方法:服务可以视为功能单元,也是各个模块解耦合、互相调用的基础方案。
服务化建设可能包含下面内容:
1. 服务有明确的API,可以基于HTTP、TCP或私有RPC的方式;
2. 服务有明确的部署方式、依赖单元;
对于服务化的建设,如何暴露API、如何实现服务、如何调用服务,都是服务化体系建设需要定义、实现的部分。结合互联网时代高并发、高可用的特点,此处的服务并非单点的实现,而是分布式服务。
下面代表了服务体系的通用示意图——注意每个服务由多个实体来实现。
要实现一个服务化的架构,包括了以下几个部分:
1. 服务的描述
2. 分布式服务的实现者
3. 分布式服务的负载均衡
4. 服务的出口、入口
【服务的描述】:服务的实现者、调用者需要有一个公共认知的结构,这个结构通常包含了地址(IP或者域名)、调用方式(协议)、调用参数等元素。
【分布式服务的实现者】:在分布式系统的中,服务的实现者通常不仅包括一个实体,而是一组实体,它们通常是相同的程序。
【分布式服务的负载均衡】:由于服务的实现者是分布式的,因此对外提供服务的时候,常常有一个统一的负载均能(Load Balance)作为统一入口。在实践中,服务的入口也可能有多个,负载均衡并非唯一的入口方案。
【服务的出口、入口】服务的入口在实现者、服务的出口在调用者,中间则是RPC的调用方式。在简单的场景中,入口就是提供HTTP访问的统一LB、出口就是简单的HTTP调用。但在实际场景中,入口也有可能没有LB,而是一组地址,出口自我实现负载均能(称之为调用者负载均衡、客户端负载均衡)。
微服务(Micro Service)、服务网格(Service Mesh)等服务化是建设机制的高级阶段,也是Kubernetes Service本身则是一种服务化建设的基础机制。
Kubernetes对服务的基本支持
Kubernetes的【服务的描述】
在Kubernetes里面服务的描述就是Service对象(在kubctl中的实体为service或者svc)。
https://v1-16.docs.kubernetes.io/docs/concepts/services-networking/service/
Service描述文件通常是下面的样子。
Service对象仅是一个描述,它通常需要包括一个对外的地址和协议,一个selector对应到实现者。Service的也定义从外部访问的Endpoint(端口)。
Service对象有一个属性是type,它包括了下面几种情况
ClusterIP:名称的含义为(k8s集群内部)一个可调用的IP;
NodePort:名称的含义为一组Node IP及其端口号;
LoadBalancer:名称的含义为(外部负载均衡)的IP;
ExternalName:名称的含义为一个名字(别名);
这几种类型,分别针对不同的入口,下面图也是一种描述。
Kubernetes的【分布式服务的实现者】
Kubernetes的【分布式服务的实现者】本质上是若干个Pod,通常用一种workload的Controllers方式来表示,例如:Deployments、ReplicaSet、ReplicationController等。
https://v1-16.docs.kubernetes.io/docs/concepts/workloads/controllers/deployment/
https://v1-16.docs.kubernetes.io/docs/concepts/workloads/controllers/replicaset/
https://v1-16.docs.kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/
它们都是无状态服务的代表,Deployments本质上通过ReplicaSet来实现。
下面是ReplicationController的定义:
一种简单的场景就是,Service通过selector对应到这些Controllers。由于这些Controllers均为无状态服
务,因此它们通常也需要一个Kubernetes的Service表示对外的接口。
下面图表示了Service与Deployment的对应关系。
下面图表示了服务通过选择器找到其实现者。
从本质上来看,Service是服务的描述,而Deployment中的每一个Pod则是服务的实现者。
Kubernetes的【分布式服务的负载均衡】
Kubernetes中的【分布式服务的负载均衡】包括几个相关的含义,基本概念是Service的Type可以是LoadBalancer,还会有一个loadBalancer的字段。
Kubernetes概念中的LoadBalancer是利用云服务提供商的机制,它从外部访问到集群的内部,因此可以作为Service对外的口。
注意,Kubernetes的LoadBalancer仅仅是LoadBalancer当中的一种:
一方面,凡是作为代理,可以访问几个后端服务的东西,都可以叫负载均衡;
另一方面,但Kubernetes的LoadBalancer专指“在集群外部、云服务提供商提供的”;
Kubernetes的【服务的出口、入口】
Kubernetes的【服务的出口、入口】的变数也比较多。
Kubernetes的服务入口,就是Service的几种类型:
1. ClusterIP:属于集群内的IP,只能在集群内访问,可以是单一的;;
2. NodePort:用Node(机器)的地址、端口作为入口,这是一组;
3. LoadBalancer:外部的负载均衡作为入口,也是IP地址;
4. ExternalName:在无端点、无端口的情况下,定义一个Service的别名;
ClusterIP方式是给集群内部访问的,不能由k8s集群外部访问。如果需要对外暴露服务,还需要出口的支持。
当ClusterIP设置为None的时候,它是一个Headless Service,此时就没有内部的集群IP,只能通过一组Pod IP来访问。
Kubernetes服务体系的基本逻辑
Kubernetes 服务体系的基本逻辑(物理上存在的东西)比较简单:
【调用者】->〖负载均衡〗->【实现的Pod】
其中的〖负载均衡〗可以有,也可以没有:在集群内部调用,就可以没有;在集群外部,直接调用NodePort,也没有。
基本数据流走向:从NodePort流入Pod
在Kubernetes的服务体系中,Service对象只是一个描述性的东西,真正实现还是每一个Pod。因此基本的数据流走向是,从“某个地方”通过NodePort流入Pod。
下面的图表示Kubernetes Service的结构。
在物理上,在Kubernetes中的服务调用的最终结果是通过机器(Node)的Port流入Pod,Pod是分布式服务的每个实现。至于从“某个地方”流入,这其实可以是LB,可以是集群内的Pod,也可来自集群外部的调用者。
不同场景的调用方式:内外有别
Kubernetes的服务体系需要处理不同场景的调用,这些与Kubernetes自身的结构是有关系的:
【本Node的调用者】;
【集群内不同Pod的调用者】;
【集群外部的调用者】;
下面的图可以表示Kubernetes的调用关系(其中的kube-proxy只是其中一种形态)。
如果调用者、实现者是同一个Node上面的Pod,就属于【本Node的调用者】,它们的调用显然是最简单的;如果调用者、实现者是一个集群内的不同Pod,就属于【集群内不同Pod的调用者】。
【集群外部的调用者】可以是直接的调用程序,也可以是一个LB,它们共同的特点都是通过NodePort流入Pod。
不同的调用者,带来的是物理上数据流走向的区别,它还与本集群网络方案有关。
kube-proxy的变化逻辑
kube-proxy是一个从Kubernetes的“远古时代”就有的Node组件。
从Kubernetes的“远古时代”到1.9版本,kube-proxy的形态已经发生了很大的变化。它的发展也体现了Kubernetes对于服务化建设支持的思路变化。
kube-proxy的变与不变
下面的图表示了kube-proxy在Kubernetes中的位置。
在历代的Kubernetes中,kube-proxy的变与不变的地方是:
不变:都是每个Node上面的可执行程序;
变:流量数据通道从承担到不承担;从独立运行到运行在Pod里面;
kube-proxy本身分成了userspace、iptables、ipvs三种模式,这也体现了Kubernetes服务化体系的扮演角色的变化。
在Kubernetes的“远古时代”,kube-proxy本身是集群内的一个负载均衡(运行于userspace模式),也是“承担数据通道”。
随着Kubernetes当中服务体系的发展,这种方式的性能并不好,因此较高版本的Kubernetes里面,kube-proxy不承担数据通道(iptables模式、ipvs模式)。
kube-proxy的模式由kubernetes master的/etc/kubernetes/proxy,把KUBE_PROXY_ARGS=”–proxy-mode=<mode>“。
userspace模式:原始模式作为代理
userspace是kube-proxy最原始的模式,本模式中kube-proxy在Node上面打开端口,此端口的的请求会被转给某个后端Pod。
下面的图是运行于userspace是kube-proxy。
userspace模式中的kube-proxy其实也就是代理,从调用者(Client)到后端Pod,需要经过kube-proxy。
使用userspace模式,在网络方面需要在内核空间、用户空间传递流量,因此性能比较低。
iptables模式:变为旁路提升性能
kubernetes的较高版本(>=1.2)中,默认的模式已经为时iptables。在这种模式中,kube-proxy是一个旁路。
下面的图是运行于iptables是kube-proxy。
kube-proxy在iptables模式中,不承担流量的转发,而是通过安装iptables规则,对流量进行转发。在iptables模式中,其实也不需要进行内核空间、用户空间之间的数据切换。
ipvs模式:netfilter的实现模式
在kubernetes1.9版本中,ipvs模式依然是Beta状态。ipvs模式通过netlink网络接口实现。
下面的图是运行于ipvs是kube-proxy,数据流与iptables模式类似。
ipvs基于netfilter的Hook实现,运行于Linux Kernel,因此速度更快。如果本集不支持ipvs,这种方式也可能降级成iptables。
ipvs模式未来也有可能支持更多的负载均衡算法:
rr(Round Robin):循环选择
lc(Least Connection):最少连接
dh(Destination Hashing):目的地哈希
sh(Source Hashing):源哈希
sed(Shortest Expected Delay):最短预期延迟
nq(Never Queue):从不排队
kube-proxy的变化原则
在原始的userspace模式中,kube-proxy是一种“顾名思义”的实现,它作为Service代理,本身也在数据通道上面。每个Node提供代理能力。这中模式在逻辑结构上是清晰的,但是性能不佳。
在实际的应用中,很少有用kube-proxy的userspace模式当成真正代理的,其功能常常由外接的LB替代。
当kube-proxy改成了第二代的iptables模式后,它其实已经变成了旁路,不是那么“名副其实”。
在提升了性能的同时,iptables模式其实也比userspace模式少了一个功能:任选一个或者几个Node,就可以将服务暴露出去。
Ingress的用途
Ingres是Service的一个配套机制,提供Kubernetes集群内部的负载均衡、路由的功能,也有配套的安全设施。
Ingress的本质
在Kubernetes中,Ingress从来不是Service的强依赖,它在以下集中情况中有用:
没有外部的LoadBanlance可用(公有云一般有,私有云不一定有);
用NodePort暴露服务不好用,一个典型的麻烦就是需要在客户端的负载均衡;
用NodePort暴露服务不符合集群合规的要求;
下面是Kubernetes集群使用Ingress的示意图。
在上面图当中,红框中的IngressController是一个有实体Pod的东西,而绿框中的Service则是它本身的服务描述;IngressController之后的东西,被称之为后端。
Ingress的核心:Ingress和IngressController
Ingress核心的东西是有下面的两个:
Ingress是Kubernetes的一个对象(在kubectl中的名称为ingress或ing)
IngressController是一个分布式的应用,可以作其他应用的负载均衡。
Ingress可以视为一种配置描述 ,Ingress Controller是一个Kubernetes集群内部的实体
每个Ingress是一种配置,提供path到backend路由。
Ingress中的backend其实就是后端服务,serviceName就是服务的名字。
每个Ingress是一种配置,提供path到backend路由。
Ingress中的backend其实就是后端服务,serviceName就是服务的名字。
IngressController本身是一个代理程序,它负责处理Ingress定义的规则。下面是一个基于nginx的IngressController。
IngressController本身也有多种实现,下面的表格就是它的不同实现。
从上表中可以看出,IngressController实现大概就是几种:Nginx、HAProxy、Envoy中的实现。它们对外功能差别是支持哪种协议,内部实现的差别则是流控、负载均衡方法、认证等细节。
除了上面提到的Ingress、IngressController之外,还有一个东西是Ingress Backend,它是IngressController流向的默认服务。
https://github.com/nginxinc/kubernetes-ingress
Ingress的应用模式
在Kubernetes集群里面如果使用Ingress,完整的组件关系如下所示:
【公网 | 内网】->【LB】->【IngressController】->【后端Pod】
注意,上面图中只列出了程序得实体,省略了两层Service:
IngressController的Service类型应当是LoadBalancer;
后端的Service类型是ClusterIP或者NodePort;
用法1:对公网提供功能,一般是HTTP的页面或者服务:
【公网】->【LB对外】->【IngressController】->【后端Pod】
用法2:对内网提供功能,不用LB;
【内网】->【IngressController的Pod】->【后端Pod】
此种情况,IngressController当成内部的负载均衡,需要从NodePort暴露自己,代理到后端。
【内网】的含义并非是同一个集群,它们可以是通过VPC方案打通的不同集群。
用法3:对内网提供功能,LB不需要公网IP;
【内网】->【LB对内】->【IngressController】->【后端Pod】
这种用法其实有冗余,对内的情况 【LB对内】、【IngressController】通常只需要一个。其好处是通过【IngressController】统一了入口。
用法4:对内网提供功能,不用IngressController;
【内网】->【LB对内】->【后端Pod】
此处的【后端Pod】需要是一个NodePort方式的Service,通过集群外的【LB对内】提供对内网提供服务。
用法2与用法4相比:用IngressController可以做path路由,并且不用依赖LB;用LB则是可以更方便提供TCP服务。
IngressController的变数
IngressController本身的模式,也是分成三种:
Single Service Ingress:单服务模式
Simple fanout:简单分列模式
Name based virtual hosting:基于名称的虚拟托管
Single Service Ingress就是暴露单个 Service,因此不用路由。
Simple fanout Ingress根据请求的 HTTP URI 将流量从单个IP地址路由到多个服务。
foo.bar.com -> 178.91.123.132 -> / foo service1:4200
/ bar service2:8080
Name based virtual hosting将 HTTP 流量路由到同一 IP 地址上的多个主机名。
foo.bar.com --| |-> foo.bar.com service1:80
| 178.91.123.132 |
bar.foo.com --| |-> bar.foo.com service2:80
评论 (1 条评论)