Kubernetes Service(虚拟ip和服务代理 userspace iptables ipvs 会话粘性)
Kubernetes ServiceKubernetes Service概述什么是Service为什么要有ServiceService实现原理Service 的类型虚拟IP和服务代理userspace代理模式iptables代理模式ipvs代理模式Service示例准备工作ClusterlP类型NodePort编辑资源清单端口号区分应用位置不同porttargetportnodeport小结Ser
Kubernetes Service
Kubernetes Service
概述
访问k8s集群中的pod, 客户端需要知道pod地址,需要感知pod的状态。那如何获取各个pod的地址?若某一node上的pod故障,客户端如何感知?
什么是Service
Kubernetes service 定义了这样一种抽象:一个 Pod 的逻辑分组,一种可以访问它们的策略 —— 通常称为微服务。这一组 pod 能够被 Service 访问到,通常是通过 Label selector
Service能够提供负载均衡的能力,但是在使用上有以下限制:
- 只提供4层负载均衡能力,而没有7层功能,但有时我们可能需要更多的匹配规则来转发请求,这点上4层负载均衡是不支持的
Kubernetes Service 从逻辑上代表了一组 Pod,具体是哪些 Pod 则是由 label 来挑选,Service 有自己 IP,而且这个 IP 是不变的。
- 客户端只需要访问 Service 的 IP,Kubernetes 则负责建立和维护 Service 与 Pod 的映射关系。
- 无论后端 Pod 如何变化,对客户端不会有任何影响,因为 Service 没有变
为什么要有Service
当Pod宕机后重新生成时,其IP等状态信息可能会变动,Service会根据Pod的Label对这些状态信息进行监控和变更,保证上游服务不受Pod的变动而影响。
Kubernetes Pods 是有生命周期的。他们可以被创建,而且销毁不会再启动。 如果您使用Deployment来运行您的应用程序,则它可以动态创建和销毁 Pod。
一个Kubernetes的Service是一种抽象,它定义了一组Pods的逻辑集合和一个用于访问它们的策略 - 有的时候被称之为微服务。一个Service的目标Pod集合通常是由Label Selector 来决定的。
如下图所示,当Nginx Pod作为客户端访问Tomcat Pod中的应用时,IP的变动或应用规模的缩减会导致客户端访问错误。而Pod规模的扩容又会使得客户端无法有效的使用新增的Pod对象,从而影响达成规模扩展之目的。为此,Kubernetes特地设计了Service资源来解决此类问题。
Service实现原理
Service资源基于标签选择器将一组Pod定义成一个逻辑组合,并通过自己的IP地址和端口调度代理请求至组内的Pod对象之上,如下图所示,它向客户端隐藏了真实的、处理用户请求的Pod资源,使得客户端的请求看上去就像是由Service直接处理并响应一样。
Service对象的IP地址也称为Cluster IP,它位于Kubernetes集群配置指定专用IP地址的范围之内,是一种虚拟IP地址,它在Service对象创建后既保持不变,并且能够被同一集群中的Pod资源所访问。Service端口用于接收客户端请求并将其转发至其后端的Pod中的相应端口之上,因此,这种代理机构也称为“端口代理”(port proxy)或四层代理,工作于TCP/IP协议栈的传输层。
Service资源会通过API Server持续监视着(watch)标签选择器匹配到的后端Pod对象,并实时跟踪各对象的变动,例如,IP地址变动、对象增加或减少等。Service并不直接链接至Pod对象,它们之间还有一个中间层——Endpoints资源对象,它是一个由IP地址和端口组成的列表,这些IP地址和端口则来自由Service的标签选择器匹配到的Pod资源。当创建service对象时,其关联的Endpoints对象会自动创建。
Service 的类型
Service 在 K8s 中有以下四种类型
- Clusterlp:默认类型,自动分配一个仅 Cluster 内部可以访问的虚拟 IP
- NodePort:在 ClusterIP 基础上为 Service 在每台机器上绑定一个端口,这样就可以通过:NodePort来访问该服务
- LoadBalancer:在 NodePort 的基础上,借助 cloud provider创建一个外部负载均衡器,并将请求转发到:NodePort
- ExternalName:把集群外部的服务引入到集群内部来,在集群内部直接使用。没有任何类型代理被创建,这只有kubernetes1.7或更高版本的 kube-dns 才支持
虚拟IP和服务代理
Kubernetes Pods 是有生命周期的。他们可以被创建,而且销毁不会再启动。 如果您使用Deployment来运行您的应用程序,则它可以动态创建和销毁 Pod。
一个Kubernetes的Service是一种抽象,它定义了一组Pods的逻辑集合和一个用于访问它们的策略 - 有的时候被称之为微服务。一个Service的目标Pod集合通常是由Label Selector 来决定的。
kube-proxy将请求代理至相应端点的方式有三种:userspace(用户空间)、iptables和ipvs。
一个Service对象就是工作节点上的一些iptables或ipvs规则,用于将到达Service对象IP地址的流量调度转发至相应的Endpoints对象指定的IP地址和端口之上。
kube-proxy组件通过API Server持续监控着各Service及其关联的Pod对象,并将其创建或变动实时反映到当前工作节点上的iptables规则或ipvs规则上。
ipvs是借助于Netfilter实现的网络请求报文调度框架,支持rr、wrr、lc、wlc、sh、sed和nq等十余种调度算法,用户空间的命令行工具是ipvsadm,用于管理工作与ipvs之上的调度规则。
Service IP事实上是用于生成iptables或ipvs规则时使用的IP地址,仅用于实现Kubernetes集群网络的内部通信,并且能够将规则中定义的转发服务的请求作为目标地址予以相应,这也是将其称为虚拟IP的原因之一。
userspace代理模式
userspace是Linux操作系统的用户空间。
这种模式下,kube-proxy负责跟踪API Server上Service和Endpoints对象的变动(创建或移除),并据此调整Service资源的定义。对于每个Service对象,它会随机打开一个本地端口(运行于用户控件的kube-proxy进程负责监听),任何到达此端口的连接请求都将代理至当前Service资源后端的各Pod对象上,至于会挑选中哪个Pod对象则取决于当前Service资源的调度方式(通过Service的SessionAffinity来确定),默认的调度算法是轮循(round-robin)。
其代理的过程是:请求到达service后,其被转发至内核空间,经由套接字送往用户空间的kube-proxy,而后再由它送回内核空间,并调度至后端Pod。其传输效率太低,在1.1版本前是默认的转发策略。
iptables代理模式
iptables代理模式中,kube-proxy负责跟踪API Server上Service和Endpoints对象的变动(创建或移除),并据此作出Service资源定义的变动。同时,对于每个Service对象,它都会创建iptables规则直接捕获到达Cluster IP(虚拟IP)和Port的流量,并将其重定向至当前Service的后端。对于每个Endpoints对象,Service资源会为其创建iptables规则并关联至挑选的后端Pod资源,默认的调度算法是随机调度(random)。实现基于客户端IP的会话亲和性(来自同一个用户的请求始终调度到后端固定的一个Pod),可将service.spec.sessionAffinity的值设置为“ClientIP”(默认值为“None”)。
其代理过程是:请求到达service后,其请求被相关service上的iptables规则进行调度和目标地址转换(DNAT)后再转发至集群内的Pod对象之上。
相对userspace模式来说,iptables模式无须将流量在用户空间和内核空间来回切换,因而更加高效和可靠。其缺点是iptables代理模型不会在被挑中的Pod资源无响应时自动进行重定向;而userspace模式则可以。
ipvs代理模式
kube-proxy跟踪API Server上Service的Endpoints对象的变动,据此来调用netlink接口创建ipvs规则,并确保与API Server中的变动保持同步,其请求流量的调度功能由ipvs实现,其余的功能由iptables实现。ipvs支持众多调度算法,如rr、lc、dh、sh、sed和nq等。
Service示例
准备工作
创建deployment
vi nginx-deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1 # 只有一个副本
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.20
ports:
- containerPort: 80
启动deployment
kubectl apply -f nginx-deployment.yml
kubectl get pods -o wide
访问测试
curl 10.244.2.54
ClusterlP类型
类型为ClusterIP的service,这个service有一个Cluster-IP,其实就一个VIP,具体实现原理依靠kubeproxy组件,通过iptables或是ipvs实现。
注意:这种类型的service 只能在集群内访问
编辑资源清单
vi cluster-iP-service.yml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
namespace: default
labels:
app: nginx-service
spec:
type: ClusterIP
ports:
- port: 8000 # Service 暴漏端口
targetPort: 80 # 代理的对象的端口
selector:
app: nginx # 绑定标签是nginx的对象
应用Service
kubectl apply -f cluster-iP-service.yml
kubectl get service -o wide
访问测试
curl 10.1.206.66:8000
删除Pod
删除pod让Pod的节点漂移再次访问Service节点
kubectl delete pod nginx-deployment-6897679c4b-2hqmk
访问测试
我们发现虽然pod的IP以及节点变化了但是Service访问IP没有任何变化
curl 10.1.206.66:8000
工作原理
clusterIP 主要在每个 node 节点使用 iptables,将发向 clusterlP 对应端口的数据,转发到 kube-proxy 中。然后 kube-proxy 自己内部实现有负载均衡的方法,并可以查询到这个 service 下对应 pod 的地址和端口,进而把数据转发给对应的 pod 的地址和端口
为了实现图上的功能,主要需要以下几个组件的协同工作:
- apiserver 用户通过 kubectl 命令向 apiserver 发送创建 service 的命令,apiserver接收到请求后将数据存储到 etcd 中
- kube-proxy kubernetes 的每个节点中都有一个叫做 kube-porxy 的进程,这个进程负责感知service,pod 的变化,并将变化的信息写入本地的 iptables 规则中
- iptables 使用 NAT 等技术将 virtuallP 的流量转至 endpoint 中
NodePort
当我们需要集群外业务访问,那么ClusterIP就满足不了了,NodePort当然是其中的一种实现方案
编辑资源清单
vi node-port-service.yml
apiVersion: v1
kind: Service
metadata:
name: nginx-node-port-service
namespace: default
labels:
app: nginx-service
spec:
type: NodePort
ports:
- port: 8000 # Service 暴漏端口
targetPort: 80 # 代理的对象的端口
selector:
app: nginx # 绑定标签是nginx的对象
应用Service
kubectl apply -f node-port-service.yml
kubectl get service -o wide
访问测试
curl 10.1.137.200:8000
删除Pod
删除pod让Pod的节点漂移再次访问Service节点
kubectl delete pod nginx-deployment-6897679c4b-2hqmk
访问测试
我们发现虽然pod的IP以及节点变化了但是Service访问IP没有任何变化
curl 10.1.137.200:8000
外网访问
NodePort的最主要功能是进行外网访问,我们是不能通过访问8000端口访问的,他是内网端口,外网访问需要访问映射出来的端口8000:31337/TCP这里映射出来的nodeport是31337,还可以通过指定nodePort来指定端口,要求端口必须大于30000
curl 192.168.64.160:31337
通过浏览器访问
工作原理
nodePort 的原理在于在node上开了一个端口,将向该端口的流量导入到 kube-proxy,然后由 kube-proxy 进一步到给对应的pod
端口号区分
k8s 中 port nodePort targetPort有什么区别呢
应用位置不同
port:是service的的端口
targetport:是pod也就是容器的端口
nodeport:是容器所在宿主机的端口(实质上也是通过service暴露给了宿主机,而port却没有)
port
k8s集群内部服务之间访问service的入口。即clusterIP:port是service暴露在clusterIP上的端口
主要作用是集群内其他pod访问本pod的时候,需要的一个port,如nginx的pod访问mysql的pod,那么mysql的pod的service可以如下定义,由此可以这样理解,port是service的port,nginx访问service的33306
apiVersion: v1
kind: Service
metadata:
name: mysql-service
spec:
ports:
- port: 33306
targetPort: 3306
selector:
name: mysql-pod
targetport
容器的端口(最终的流量端口)。targetPort是pod上的端口,从port和nodePort上来的流量,经过kube-proxy流入到后端pod的targetPort上,最后进入容器。
看上面的targetport,targetport说过是pod暴露出来的port端口,当nginx的一个请求到达service的33306端口时,service就会将此请求根据selector中的name,将请求转发到mysql-pod这个pod的3306端口上
nodeport
nodeport就很好理解了,它是集群外的客户访问,集群内的服务时,所访问的port,比如客户访问下面的集群中的nginx,就是这样的方式,ip:30001
外部流量访问k8s集群中service入口的一种方式(另一种方式是LoadBalancer),即nodeIP:nodePort是提供给外部流量访问k8s集群中service的入口
比如外部用户要访问k8s集群中的一个Web应用,那么我们可以配置对应service的type=NodePort,nodePort=30001。其他用户就可以通过浏览器http://node:30001访问到该web服务。而数据库等服务可能不需要被外界访问,只需被内部服务访问即可,那么我们就不必设置service的NodePort。
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort # 有配置NodePort,外部流量可访问k8s中的服务
ports:
- port: 30080 # 服务访问端口
targetPort: 80 # 容器端口
nodePort: 30001 # NodePort
selector: name: nginx-pod
小结
nodeport是集群外流量访问集群内服务的端口类型,比如客户访问nginx,apache,port是集群内的pod互相通信用的端口类型,比如nginx访问mysql,而mysql是不需要让客户访问到的,最后targetport,顾名思义,目标端口,也就是最终端口,也就是pod的端口。
Service会话粘性
Service资源支持Session affinity(粘性会话或会话粘性)机制,能够将来自同一个客户端的请求始终转发至同一个后端的Pod对象。这意味着会影响调度算法的流量分发功能,进而降低其负载均衡的效果。所以,当客户端访问pod中的应用程序时,如果有基于客户端身份保存某些私有信息,并基于这些私有信息追踪用户的活动等一类的需求时,就可以启用session affinity机制。
Session affinity的效果仅在一段时间期限内生效,默认值为10800秒,超出此时长之后,客户端的再次访问会被调度算法重新调度。Service资源的Session affinity机制仅能基于客户端的IP地址识别客户端身份,把经由同一个NAT服务器进行源地址转换的所有客户端识别为同一个客户端,便导致调度效果不佳,所以,这种方法并不常用。
Service资源通过service.spec.sessionAffinity和service.spec.sessionAffinityConfig两个字段配置粘性会话。sessionAffinity字段用于定义要使用的粘性会话的类型,仅支持使用“None”和“ClientIp”两种属性值。
- None:不使用sessionAffinity,默认值。
- ClientIP:基于客户端IP地址识别客户端身份,把来自同一个源IP地址的请求始终调度至同一个Pod对象。
修改资源清单
# 定义pod
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
spec:
replicas: 2
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: nginx
image: ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
---
#定义service
apiVersion: v1
kind: Service
metadata:
name: myapp-service #service名称
spec:
selector: #用于匹配后端的Pod资源对象,需和上面定义pod的标签一致
app: myapp
ports:
- port: 8000 #service端口号
targetPort: 80 #后端Pod端口号
protocol: TCP #使用的协议
sessionAffinity: ClientIP #指定使用ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10 #配置session超时时间,这里为了看效果设置的比较短
生效配置
kubectl apply -f myapp-demo.yml
测试
使用curl命令测试
for i in 1 2 3 4 5; do curl 10.1.216.151:8000/hostname.html; done
我们发现请求数据都在同一个客户端
等待10S后在次访问
for i in 1 2 3 4 5; do curl 10.1.216.151:8000/hostname.html; done
我们发现现在已经在不同的客户端了
更多推荐
所有评论(0)