您现在的位置是:首页 > 文章详情

k8s服务实现浅谈

日期:2019-10-20点击:577

集群服务实现

近些年来,k8s使用也是越来越多,在很多领域都已经开始使用k8s了。今天就主要跟大家讲一下k8s的服务(service)实现,使用场景和一些可能遇到的问题。这边今天所讲的内容是使用的阿里云的托管版k8s为例,kube-proxy的mode为iptables。

什么是服务?

首先请大家思考这样一个问题,常见的我们访问一个应用是通过域名加端口或者ip加端口的方式来访问。我们知道在k8s中,pod相当于我们的实际业务应用,pod是有一个pod ip的,如果在集群内我们通过这边pod ip+pod暴露的端口,那我们就可以访问到这个应用。但是这样访问有什么弊端呢?首先我们需要在客户端固定pod ip地址,那么当pod重建后,pod ip地址发生变化后,我们就访问不到对应的业务了,又需要手动修改成新的pod ip,这样会导致运维起来非常麻烦。其次时,如果一个应用有多个副本,也就是完全相同的pod有多个,我们怎么能达到负载均衡的效果呢?基于这两个问题,k8s在设计之初就考虑到了,于是针对pod的代理进行了抽象,定义了一种叫做服务(service)的资源对象,用来代理pod,做pod的负载均衡。由于名称叫做服务,可能不熟悉的同学会有误解,将服务理解成后端的应用服务,造成混淆,可以把服务(service)理解成pod的代理,不是pod本身,是两种k8s的资源对象。所有k8s中对后端pod的请求都可以请求pod上层的服务来实现。那么请求链路就可以理解成下图了:

image.png

服务有哪些种类和各自的使用场景

常见的服务类型

image.png

  1. ClusterIP类型

通过服务的集群ip来暴露服务,那么如果我们是集群内发起对该服务的访问,那么可以直接通过ClusterIP+端口来访问,注意:这里说的集群内是指:pod内和集群的节点上,都认为是集群内。

apiVersion: v1 kind: Service metadata: name: nginx-svc namespace: sidecar spec: clusterIP: 172.21.3.50 ports: - name: tcp port: 80 protocol: TCP targetPort: 80 selector: app: nginx-svc sessionAffinity: None type: ClusterIP
  1. NodePort类型

通过节点的ip来暴露对应的服务(service),会在宿主机上监听nodeport这个端口,可以通过宿主机ip+nodeport端口访问到service。需要注意的是:NodePort和LoadBalancer的服务可以配置externalTrafficPolicy ,可选值:Cluster和Local。

externalTrafficPolicy:Cluster时,集群所有节点可以通过nodeport访问,如果externalTrafficPolicy:Local时,只能在该service关联的pod所在的节点上通过nodeport进行访问,才能访问到该service。

apiVersion: v1 kind: Service metadata: name: nginx-svc namespace: sidecar spec: clusterIP: 172.21.3.50 externalTrafficPolicy: Cluster ports: - name: tcp nodePort: 31357 port: 80 protocol: TCP targetPort: 80 selector: app: nginx-svc sessionAffinity: None type: NodePort
  1. LoadBalancer类型

通过外部的一个负载均衡器将外部请求转发到集群节点的nodeport端口上,然后请求到服务(service),所以说lb类型的service是基于nodeport的service的,这块的功能要求云厂商来实现负载均衡器的配置必须跟随lb类型的service的变化而变化,阿里云的k8s已经实现了该功能,lb类型的服务使用的slb的相关配置由cloud-controller-manager控制,不需要手动去配置slb的配置。同样lb类型的服务可以配置externalTrafficPolicy ,可选值:Cluster和Local。

externalTrafficPolicy:Cluster时,集群所有节点可以通过nodeport访问,如果externalTrafficPolicy:Local时,只能在该service关联的pod所在的节点上通过nodeport进行访问,才能访问到该service。那么就要求slb的后端服务器需要根据externalTrafficPolicy策略来调整,目前的策略是这样的:如果externalTrafficPolicy是Cluster,slb的后端服务器是所有的worker节点(节点状态如果是隔离的,该worker不会出现在slb的后端服务器),如果externalTrafficPolicy是Local,slb的后端服务器是service关联的pod所在的ecs(同时只对worker有效,隔离的worker不会出现在后端服务器中,master节点也不会出现在slb的后端服务器中)。

apiVersion: v1 kind: Service metadata: name: nginx-svc namespace: sidecar spec: clusterIP: 172.21.3.50 externalTrafficPolicy: Cluster ports: - name: tcp nodePort: 31357 port: 80 protocol: TCP targetPort: 80 selector: app: nginx-svc sessionAffinity: None type: LoadBalancer status: loadBalancer: ingress: - ip: xx.xx.xx.xx #slb的ip
  1. ExternalName类型

映射一个外部的域名,返回一个CNAME记录。

apiVersion: v1 kind: Service metadata: name: baidu namespace: default spec: type: ExternalName externalName: www.baidu.com

externalTrafficPolicy为Cluster请求链路如左图,为Local时请求链路为右图

image.png

其实使用场景也比较简单,可以根据发起访问的地点决定使用什么类型的service,如果业务不需要在集群外部访问,那么就使用ClusterIP的service类型,如果需要在外部访问,那么建议使用负载均衡的service类型,同时建议不要在集群内(集群节点和pod内)访问负载均衡的service对应的slb地址,请求会被iptables拦截,不会请求到slb上,具体的原因和实现我们在后续再说。一般客户可能有保留请求源ip的需求,service这一层保留源ip的功能就是通过externalTrafficPolicy:Local来配置的,具体实现我们也后续再说。

服务实现

kube-proxy

要了解k8s服务是如何实现的,首先我们需要先了解一下边车模式(Sidecar),边车模式是微服务领域的核心概念,通俗的说法就是自带通信员,k8s集群的服务实现也是基于Sidecar模式的。在k8s集群中,服务的实现实际上是为集群的每一个节点,部署了一个反向代理Sidecar,所有对集群服务的访问都被节点上的反向代理转换成后端pod的访问。

image.png

K8S集群中每个节点上都运行着这样一个组件kube-proxy,它通过集群apiserver监听集群变化,当有新的服务被创建的时候,kube-proxy将集群服务的状态,属性,配置等翻译成反向代理的配置。目前kube-proxy的mode主要有三种:userspace、iptables、ipvs,今天只分析iptalbes的实现方式。

iptables

此处先描述防火墙相关的概念,从物理上区分,可以分为硬件防火墙和软件防火墙。

硬件防火墙:在硬件级别实现部分防火墙功能,另一部分功能集于软件实现,性能高,成本高。

软件防火墙:应用软件处理逻辑运行于通用硬件平台之上的防火墙,性能低,成本低。

iptables其实并不是真正的防火墙,我们可以把它理解成一个客户端代理,一个命令行工具,位于用户空间,用户通过iptables这个代理,将用的安全设定执行到对应的“安全框架”中,这个“安全框架”才是真正的防火墙,这个框架的名字叫netfilter,netfilter位于内核空间,netfilter/iptables(下文中简称iptables)组成了Linux平台下的包过滤防火墙,完成封包过滤、封包重定向和网络地址转换(NAT)等功能。

netfilter是一种过滤器框架,我们想象一下,一个过滤器会有哪些组成部分,首先肯定有一个入口和一个出口,然后会有很多“关卡”,每个“关卡”会做很多检查,每项检查根据不同的结果做不同的处理。就像我们去做飞机一样,到机场后需要先过安检,检票,识别身份,核实通过才能候机。而每个步骤里面会有很多的检查项,需要通过层层检查才能最后登机。这些“关卡”在iptables中被称作“链”,我们知道,防火墙的作用就是对经过的报文进行匹配“规则”,然后执行对应的“动作”,所以当报文经过这些“关卡”的时候必须匹配这个“关卡”上的规则,但是“关卡”上可能不止一条规则,所以将这些规则串到一条链路上,我们称为“链”。

iptabels中有5条链,分别是PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING。

image.png

从上图我们可以想象常见的某些场景中,报文的流向:

1、到本机某进程的报文:PREROUTING-->INPUT

2、由本机转发的报文:PREROUTING-->FORWARD-->POSTROUTING

3、由本机的某进程发出的报文:OUTPUT-->POSTROUTING

同时我们将具有相关功能的规则放在一起称为表:

  • filter表:负责过滤功能,防火墙
  • nat表:网络地址转换功能
  • mangle表:拆解报文,做出修改,并重新封装
  • raw表:关闭nat表上启用的连接追踪机制

我们可以看下链表关系,就是哪些表里面的规则可以被哪些链使用。

raw表:PREROUTING,OUTPUT

mangle表:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTING

nat表:PREROUTING,OUTPUT,POSTROUTING

filter表:INPUT,FORWARD,OUTPUT

自定义链实现反向代理

回归正题,要实现反向代理,是需要做DNAT ,那么要求kube-proxy下发的iptables规则实现:将请求集群服务的ip和端口的数据包,修改成具体的后端的pod ip和端口,根据上面的链表关系,我们可以知道,我们需要做nat表来做,那么可以在PREROUTING,OUTPUT,POSTROUTING三个链里面加入nat规则来改变数据包的源地址和目的地址。在k8s的服务实现中,是在PREROUTING,OUTPUT两个链里面来做DNAT,如果在发起访问的客户端pod所在的节点或者访问服务的节点跟服务关联的pod不在同一个节点上,那么此时是需要做SNAT的,是在POSTROUTING链中来做的。

接下来我们在具体的集群中来看下具体的情况和分析一下之前的问题(nodeport和保留源ip)的相关实现。

环境配置:

  • kube-proxy 代理模式:iptables
  • Pod 网络 CIDR:10.1.0.0/16
  • Service CIDR:172.23.0.0/20
  • 节点:192.168.2.26和192.168.2.25

这边创建了三个服务,都关联到同一个pod,其中nodeport类型服务的externalTrafficPolicy为Cluster,loadbalancer类型服务的externalTrafficPolicy为Local,service关联的pod在节点192.168.2.26上,pod ip是:10.1.0.16

image.png

目前我们是192.168.2.26节点上,我们直接来看下clusterip类型的service的相关iptables规则呢。

我们可以看到KUBE_SERVICE这个链是整个服务反向代理的入口链:

image.png

我们然后看下nginx-clusterip服务的clusterip:172.23.3.114 我们可以如果发起访问的客户端是当前节点上的pod会增加一个标记:KUBE-MARK-MASQ ,接下来会跳转到KUBE-SVC-W6AHQRLWJMEKPFAV链

image.png

我们可以把KUBE-SVC-W6AHQRLWJMEKPFAV链看作是某个具体的服务的链,然后会跳转到KUBE-SEP-23XRHPDH6DC7LBN7,这个可以理解为服务对应的endpoint链

image.png

我们接着看下KUBE-SEP-23XRHPDH6DC7LBN7链,我们可以看到这里面做了两件事,1、如果发起访问的地址是该服务本身关联的pod的地址,那么做一个标记,这样做的原因是为了发弯卡回环。现象是不允许在service关联的pod内去通过该服务去访问pod的业务,如果需要访问pod自身的业务,直接使用127.0.0.1即可。2、做DNAT实现反向代理。

image.png

总的来说自定义链的链路是:KUBE_SERVICE-->KUBE-SVC-XXX-->KUBE-SEP-XXX实现DNAT反向代理的。

image.png

接下来我们来看下SNAT策略:如果源地址是集群的pod的地址,目的地址不是集群的pod的地址,那么进行SNAT;当源地址不是集群的pod地址时,目的地址是本机上的pod地址时不进行SNAT,目的地址是其他节点的pod地址时需要进行SNAT。这也就告诉我们应该如何保留源ip不被SNAT掉。当使用负载均衡的服务时,slb转发请求的节点必须是pod所在的节点,这样才能实现暴露源ip,这也导致了slb的后端服务器策略有所不同,在其他节点(service关联的pod不在对应的节点上),同时externalTrafficPolicy为Local时,只有在pod所在的节点上才能通过nodeport访问到。

通过nodeport访问,链路有变化:KUBE_SERVICE-->KUBE-NODEPORTS再往后续走。

image.png

下面这两个节点上规则解释了为什么:“externalTrafficPolicy:Cluster时,集群所有节点可以通过nodeport访问,如果externalTrafficPolicy:Local时,只能在该service关联的pod所在的节点上通过nodeport进行访问,才能访问到该service”

image.png

我们在另一个节点看下对应节点的规则,该节点上没有运行对应的pod。可以看到负载均衡的service的扩展ip(即slb的ip)会被iptables拦截,请求不会请求到slb。所以不要在集群内访问service关联的slb的地址。

image.png

至此,k8s的服务实现也分析得差不多了,不过使用iptables实现还是有一些问题的,我们在后续文章再聊一下。

原文链接:https://yq.aliyun.com/articles/721567
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章