k8s服务实现浅谈
集群服务实现
近些年来,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上层的服务来实现。那么请求链路就可以理解成下图了:
服务有哪些种类和各自的使用场景
常见的服务类型
- 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
- 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
- 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
- ExternalName类型
映射一个外部的域名,返回一个CNAME记录。
apiVersion: v1 kind: Service metadata: name: baidu namespace: default spec: type: ExternalName externalName: www.baidu.com
externalTrafficPolicy为Cluster请求链路如左图,为Local时请求链路为右图
其实使用场景也比较简单,可以根据发起访问的地点决定使用什么类型的service,如果业务不需要在集群外部访问,那么就使用ClusterIP的service类型,如果需要在外部访问,那么建议使用负载均衡的service类型,同时建议不要在集群内(集群节点和pod内)访问负载均衡的service对应的slb地址,请求会被iptables拦截,不会请求到slb上,具体的原因和实现我们在后续再说。一般客户可能有保留请求源ip的需求,service这一层保留源ip的功能就是通过externalTrafficPolicy:Local来配置的,具体实现我们也后续再说。
服务实现
kube-proxy
要了解k8s服务是如何实现的,首先我们需要先了解一下边车模式(Sidecar),边车模式是微服务领域的核心概念,通俗的说法就是自带通信员,k8s集群的服务实现也是基于Sidecar模式的。在k8s集群中,服务的实现实际上是为集群的每一个节点,部署了一个反向代理Sidecar,所有对集群服务的访问都被节点上的反向代理转换成后端pod的访问。
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。
从上图我们可以想象常见的某些场景中,报文的流向:
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
目前我们是192.168.2.26节点上,我们直接来看下clusterip类型的service的相关iptables规则呢。
我们可以看到KUBE_SERVICE这个链是整个服务反向代理的入口链:
我们然后看下nginx-clusterip服务的clusterip:172.23.3.114 我们可以如果发起访问的客户端是当前节点上的pod会增加一个标记:KUBE-MARK-MASQ ,接下来会跳转到KUBE-SVC-W6AHQRLWJMEKPFAV链
我们可以把KUBE-SVC-W6AHQRLWJMEKPFAV链看作是某个具体的服务的链,然后会跳转到KUBE-SEP-23XRHPDH6DC7LBN7,这个可以理解为服务对应的endpoint链
我们接着看下KUBE-SEP-23XRHPDH6DC7LBN7链,我们可以看到这里面做了两件事,1、如果发起访问的地址是该服务本身关联的pod的地址,那么做一个标记,这样做的原因是为了发弯卡回环。现象是不允许在service关联的pod内去通过该服务去访问pod的业务,如果需要访问pod自身的业务,直接使用127.0.0.1即可。2、做DNAT实现反向代理。
总的来说自定义链的链路是:KUBE_SERVICE-->KUBE-SVC-XXX-->KUBE-SEP-XXX实现DNAT反向代理的。
接下来我们来看下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再往后续走。
下面这两个节点上规则解释了为什么:“externalTrafficPolicy:Cluster时,集群所有节点可以通过nodeport访问,如果externalTrafficPolicy:Local时,只能在该service关联的pod所在的节点上通过nodeport进行访问,才能访问到该service”
我们在另一个节点看下对应节点的规则,该节点上没有运行对应的pod。可以看到负载均衡的service的扩展ip(即slb的ip)会被iptables拦截,请求不会请求到slb。所以不要在集群内访问service关联的slb的地址。
至此,k8s的服务实现也分析得差不多了,不过使用iptables实现还是有一些问题的,我们在后续文章再聊一下。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
k8s从入门到放弃(1)基本概念
前言:服务网格演变史 还记得17年的夏天,我第一次接触docker,立刻就被容器化的新颖理念以及它带来的种种优势所震撼了 容器化带给业界的冲击是巨大的,不到短短一年的时间里,容器化的部署和运维就已经彻底替代传统机器部署成为了主流,同时docker也为服务端的发展形态带来了非常多的可能性,使得“微服务”这一架构如雨后春笋般生长起来,迅速成熟 当服务的载体由虚拟机器变为容器后,部署和运维的粒度更小了,但从宏观的角度来看,一个完整的服务系统变得更加零碎和复杂了 —— 数量众多的微服务以及承载他们的容器交织成一张脉络复杂的巨网,如何对这样庞大的系统进行管理便成为一个难题 那一年,k8s还没有“爆红”,谈到容器管理,人们的第一反应仍然是docker官方团队著名的“三剑客”: docker-machine提供底层的跨平台虚拟 docker-compose
- 下一篇
Nginx Ingress Controller 入门实践
Ingress是什么? 在 Kubernetes 集群中,Ingress是授权入站连接到达集群服务的规则集合,提供七层负载均衡能力。可以给 Ingress 配置提供外部可访问的 URL、负载均衡、SSL、基于名称的虚拟主机等。简单来说Ingress说一些规则集合,这些规则可以实现url到Kubernetes中的service这一层的路由功能。既然Ingress只是规则,那么这些规则的具体实现是怎么样的呢?是有Ingress-Controller来实现的,目前比较常见使用较多的是Nginx-Ingress-Controller。 可以这样理解一下,nginx-ingress-controller是一个nginx应用,它能干什么呢?它能代理后端的service,它能根据Ingress的配置,将对应的配置翻译成nginx应用的配置,来实现七层路由的功能。既然nginx-ingress-controller是作为一个类似网关的这么一个应用,那么我的nginx-ingress-controller这个应用本身就是需要在集群外能够访问到的,那么我是需要对外暴露nginx-ingress-contr...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- CentOS关闭SELinux安全模块
- CentOS8编译安装MySQL8.0.19
- CentOS7设置SWAP分区,小内存服务器的救世主
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题