写点什么

深入理解 Kubernetes 的 Service:回归本源的场景需求

用户头像
韩超
关注
发布于: 2020 年 06 月 28 日
深入理解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描述文件通常是下面的样子。

apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376




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的定义:

kind: ReplicationController
metadata:
name: nginx
spec:
replicas: 3
selector:
app: nginx
template:
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80

一种简单的场景就是,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自身的结构是有关系的:

  1. 【本Node的调用者】;

  2. 【集群内不同Pod的调用者】;

  3. 【集群外部的调用者】;

下面的图可以表示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本身分成了userspaceiptablesipvs三种模式,这也体现了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模式未来也有可能支持更多的负载均衡算法:

  1. rr(Round Robin):循环选择

  2. lc(Least Connection):最少连接

  3. dh(Destination Hashing):目的地哈希

  4. sh(Source Hashing):源哈希

  5. sed(Shortest Expected Delay):最短预期延迟

  6. 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的强依赖,它在以下集中情况中有用:

  1. 没有外部的LoadBanlance可用(公有云一般有,私有云不一定有);

  2. 用NodePort暴露服务不好用,一个典型的麻烦就是需要在客户端的负载均衡;

  3. 用NodePort暴露服务不符合集群合规的要求;



下面是Kubernetes集群使用Ingress的示意图。



在上面图当中,红框中的IngressController是一个有实体Pod的东西,而绿框中的Service则是它本身的服务描述;IngressController之后的东西,被称之为后端。



Ingress的核心:Ingress和IngressController



Ingress核心的东西是有下面的两个:

  • Ingress是Kubernetes的一个对象(在kubectl中的名称为ingress或ing)

  • IngressController是一个分布式的应用,可以作其他应用的负载均衡。



Ingress可以视为一种配置描述 ,Ingress Controller是一个Kubernetes集群内部的实体



每个Ingress是一种配置,提供path到backend路由。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test-ingress
  annotations:
    ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      host: *
      paths:
      - path: /testpath
        backend:
          serviceName: test
          servicePort: 80

Ingress中的backend其实就是后端服务,serviceName就是服务的名字。

每个Ingress是一种配置,提供path到backend路由。

Ingress中的backend其实就是后端服务,serviceName就是服务的名字。



IngressController本身是一个代理程序,它负责处理Ingress定义的规则。下面是一个基于nginx的IngressController。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: nginx-ingress
component: controller
name: nginx-ingress-controller
spec:
selector:
matchLabels:
app: nginx-ingress
component: controller
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: nginx-ingress
component: controller
spec:
containers:
- args:
- /nginx-ingress-controller
- --default-backend-service=kube-system/nginx-ingress-default-backend
- --election-id=ingress-controller-leader
- --ingress-class=nginx
- --configmap=kube-system/nginx-ingress-controller
env:
- name: POD_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.12.0
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
name: nginx-ingress-controller
ports:
- containerPort: 80
name: http
protocol: TCP
- containerPort: 443
name: https
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
terminationGracePeriodSeconds: 60

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本身的模式,也是分成三种:

  1. Single Service Ingress:单服务模式

  2. Simple fanout:简单分列模式

  3. 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



用户头像

韩超

关注

还未添加个人签名 2017.10.20 加入

还未添加个人简介

评论 (1 条评论)

发布
用户头像
不错的系列
2020 年 06 月 28 日 18:22
回复
没有更多了
深入理解Kubernetes的Service:回归本源的场景需求