K8S--负载均衡

时间:2024-03-06 16:43:34

一、K8S云原生服务集群问题

(一)负载均衡原理  

  在之前的文章说过,每一个Pod都是独立的IP、HostName、存储,同时Pod是随时可以被动态创建和回收的,那么就有个问题,我们如何知道Pod的IP并进行访问的呢?其实K8S是使用Service VIP技术的虚拟ip + kube-proxy来解决这个问题,其中service VIP用来转发请求,kube-proxy用来监控pod状态,并且会及时修改pod的ip

  service是K8S的资源对象,service资源对象运行在每一个node节点上,每一个node节点都有一个service进程,service有自己的IP地址(虚拟IP),而service VIP相当于一个网关,所有的请求都要经过service VIP,通过service VIP进行转发,从而实现负载均衡。

  service VIP一旦被创建,是不会被修改的,除非删除service后重新创建service;同时由于service信息存储在高可用的etcd中,且service实例运行在多个node节点上,因此Service VIP不存在单点故障的问题;由于service VIP中使用的是虚拟IP,因此Service VIP只能在局域网内部进行访问,不能通过外网进行访问,如果想要进行外网访问,则需要借助物理网卡进行端口映射转发。

  在K8S中IP资源的分类如下:

    Node IP:Node物理节点IP

    Pod IP:物理机内部运行的一个虚拟容器pod的ip

    cluster IP:集群IP,也是个虚拟IP,是K8S抽象出来的一个service的IP。此IP只能局域网内部访问,不能通过外网访问,如果要使用外网访问,就必须开辟nodeport类型的IP地址。如下图所示,外网访问物理IP,然后将访问请求映射到service VIP上,service VIP从etcd上获取endpoints中pod的IP,然后使用负载均衡策略选择一个pod进行调用

      

(二)Pod服务发现

  Pod服务发现借助kube-proxy实现,该组件实现了三件事情:监控pod;pod如果发生了变化,及时修改映射关系;修改映射关系的同时,修改路由规则,以便在负载均衡时可以选择到新的pod。

      

 二、负载均衡方案(四层负载)

  K8S的负载均衡方案有三种:kube-proxy(userspace)、iptables(防火墙)、ipvs。

  1、kube-proxy

    使用kube-proxy的负载方案是使用kube-proxy来监控pod的状态,如果pod发生了变化,则需要kube-proxy去修改service和pod的映射关系(endpoints),同时修改路由规则,并且由kube-proxy转发请求,这种方式kube-proxy的压力比较大,性能可能会出现问题。

      

  2、IPtables

    IPtables是K8S默认采用的负载策略,这种方式中,kube-proxy同样用来监控pod和修改映射关系以及修改路由规则,但是转发请求是采用轮询iptables路由规则的方式进行调用处理的。

      

    这种模式kube-proxy主要做好watching Cluster API即可,路由和请求的转发都交给了iptables,但是kube-proxy在请求无响应时会换一个pod进行重试,而iptables则是一条条的路由规则,不会进行重试。

    在iptables中,默认的轮询策略是随机的轮询策略,但是也可以将其设置为轮询。

    (1)设置为随机策略

# 随机:(Random balancing)
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -m statistic --mode random --probability 0.33  -j DNAT --to-destination 10.0.0.2:1234
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -m statistic --mode random --probability 0.5 -j DNAT --to-destination 10.0.0.3:1234
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017  -j DNAT --to-destination 10.0.0.4:1234

    在iptables命令中,命令的执行和顺序有关,在上述命令中,用--probability 设置了命中几率,第一条设置了纪律是0.33,也就是整个请求的0.33,第二条命中率为0.5,实际是剩余请求的0.5,也就是总请求的0.335,第三条是剩余的流量全部打到该ip上,也就是0.335,基本上就是随机分配了。

    (2)设置为轮询策略

#every:每n个包匹配一次规则
#packet:从第p个包开始
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -m statistic --mode nth --every 3 --packet 0 -j DNAT --to-destination 10.0.0.2:1234
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -m statistic --mode nth --every 2 --packet 0  -j DNAT --to-destination 10.0.0.3:1234
iptables -A PREROUTING -t nat -p tcp -d 192.168.1.1 --dport 27017 -j DNAT --to-destination 10.0.0.4:1234

  3、IPVS

    ipvs (IP Virtual Server) 实现传输层负载均衡,通常称为第四层LAN交换,是Linux内核的一部分。

    ipvs与iptables的区别:

      ipvs为大型集群提供了更好的可扩展性和性能

      ipvs支持比iptables更复杂的负载均衡算法

      ipvs支持服务器的健康检查和连接重试等。

    对于上述差异做个说明:

      在linux中iptables设计是用于防火墙的,对于比较少的规则,没有太多的性能影响,如果对于一个庞大的K8S集群,会有上千Service服务,Service服务会对应多个pod,每条都是一个iptables规则,那么对于集群来说,每个node上都有大量的规则,简直是噩梦。而IPVS则是使用hash tables来存储网络转发规则的,比iptables更有优势,而且ipvs主要工作在kerbespace,减少了上下文的切换。

      IPVS有轮询(rr)、最小连接数(lc)、目的地址hash(dh)、源地址hash(sh)、最短期望延迟(sed)、无须队列等待(nq)等负载均衡算法,在node上通过 “–ipvs-scheduler”参数,指定kube-proxy的启动算法。

    kube-proxy和IPVS合作的流程:

      (1)kube-proxy仍然是watching Cluster API,获取新建、删除Service或Endpoint pod指令,如果有新的Service建立,kube-proxy回调网络接口,构建IPVS规则。

      (2)kube-proxy会定期同步Service和Pod的转发规则,确保失效的转发可以被及时修复

      (3)有请求转发到后端的集群时,IPVS的负载均衡直接将其转发到对应的Pod上

三、Ingres-nginx(七层负载均衡)

(一)为什么要使用Ingres

  1、先介绍以下部署K8S服务时使用到的配置文件:

    创建配置文件(ingres.yaml),用来部署一个nginx的deployment和service

# 创建一个service资源对象
# kubectl expose deployment xxName –port=80 –target-port=80    k8s指令模式创建一个service
# k8s部署yaml方式
apiVersion: v1
kind: Service
metadata:
  name: nginx
  namespace: default
spec:
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 80
---

# 部署deployment对象,k8s创建3个资源对象:Deployment,ReplicaSet,POD
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.16
        ports:
        - containerPort: 80

  对上面的配置文件做个描述:

    

   首先看Deployment:

    apiVersion标记版本,Kind标记类型,说明要创建一个Deployment,该deployment的名字叫nginx,空间为默认空间。

    spec下是rs和pod的配置,首先replicas是副本的数量,然后就是选择器的名称为nginx。

    template下是副本的配置,首先副本的标签是nginx,容器的名字是nginx,镜像为nginx1.16,这里可以配置为镜像仓库地址和对应的镜像,最后ports是容器开放的端口。

    这里说明几个比较重要的配置,就是选择器的名称和容器的标签一定要一致(绿色连接线),否则容器不会被副本控制器RS所控制。

  然后说一下Service:

    apiVersion和Kind都和Deployment一样,Kind的值表明了这是一个Service配置。

    然后就是这个service的名字,service标签选择器的名字,以及目标容器的端口。

    这里说几个比较重要的配置,和Deployment一样,选择器的名字要和容器的名字一致(绿色连接线),目标容器端口要和容器的端口保持一致(紫色连接线)

  执行配置文件,生成deployment和service

kubectl apply -f ingres.yaml

  执行后查看deployment、service、rs、pod的情况(注意:这一步一般时间比较久,需要等一会)

      

   从上面图片的输出中可以看到,nginx的type为ClusterIp,这种类型的service只能在集群内部访问,而不能在外部直接访问,那么就需要修改类型为NodePort,修改命令:

kubectl edit svc nginx

      

   更改完毕之后,重新查看service,发现type已经变为NodePort,且port也变更了,前面的80是容器内的端口,后面的31758是对外开放的端口,也就是宿主机的端口。

      

   此时,使用宿主机的ip + 31758访问,即可访问nginx

      

   但是有个问题,就是如果每个服务都要对外开启一个端口,那么就需要开启很多的端口,这样即麻烦又有点浪费,因此就需要Ingres来解决这个问题,Ingres只需要一个NodePort就可以解决上述的问题。因为ingress相当于一个7层的负载均衡器,是k8s对反向代理的一个抽象。大概的工作原理也确实类似于Nginx,可以理解成在 Ingress 里建立一个个映射规则 , ingress Controller 通过监听 Ingress这个api对象里的配置规则并转化成 Nginx 的配置(kubernetes声明式API和控制循环) , 然后对外部提供服务。

(二)Ingres-Nginx介绍  

  Ingres是K8S对于nginx进行云原生模式的封装,使得nginx更适合云原生的结构,使用Ingres可以对Service进行负载均衡,因此Ingres工作在七层,属于七层负载均衡。

  Ingres通过http协议的方式实现Service的负载均衡:

      

   对于K8S来说,Ingres就是一个资源控制器,用来控制资源的访问策略,

(三)部署Ingres及使用同一个域名访问不同服务

  Ingres的配置文件如下所示:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx
  namespace: default
  labels:
    app: nginx
spec:
  rules:
  - host: ingress.lcl.com
    http:
      paths:
      - backend:
          serviceName: nginx
          servicePort: 80

   在上面文件中,配置了Ingres的规则,那么对于使用同一个域名访问不同服务的配置,则是在paths下面增加多个path路径,让path指向不同的服务,并且在metadata中新增请求重写配置annotations,去除path的路径,保证访问到指定服务上的路径不带有该path,具体配置如下所示:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx
  namespace: default
  labels:
    app: nginx
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: / # 请求重写
spec: 
  rules:
  - host: ingress.lcl.com
    http:
      paths:
        - path: /nginx   # 把path追加到域名后面 ingress.lcl.com/nginx   把/nignx当成服务请求的一部分
          backend:
            serviceName: nginx
            servicePort: 80
        - path: /tomcat
          backend:
            serviceName: tomcat
            servicePort: 8080

(四)不同域名访问不同服务

  不同域名访问不同服务主要是在rules下面配置不同的规则即可。

# 使用多个域名,访问不同的服务
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx
  namespace: default
  labels:
    app: nginx
spec:
  rules:
  - host: ingress.lcl.com
    http:
      paths:
      - path: /
        backend:
         serviceName: nginx
         servicePort: 80
  - host: ingress.lcl.com
    http:
      paths:
      - path: /
        backend:
         serviceName: tomcat
         servicePort: 8080

(五)Ingres和https

  如果想将http请求升级为https,我们就需要制作证书

# 生成私钥
openssl genrsa -out lcl.key 2048
# 自签发证书
openssl req -new -x509 -key lcl.key -out lcl.crt -subj /C=CN/ST=Shanghai/L=Shanghai/O=DevOps/CN=ingres.lcl.com
# 创建K8S使用的证书
kubectl create secret tls lcl-secret --cert=lcl.crt --key=lcl.key

  创建ingres,使用证书的Ingres

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-tomcat-tls
  namespace: default
  annotations:
    kubernetes.io/ingress.class: "nginx"
  labels:
    app: tomcat
spec:
  tls:
  - hosts:
    - ingress.lcl.com
    secretName: lcl
  rules:
  - host: ingres.lcl.com
    http:
      paths:
        - backend:
            serviceName: nginx
            servicePort: 8080

  查看secret资源

kubectl describe secret lcl

  修改原来的ingres配置文件

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx
  namespace: default
  labels:
    app: nginx
spec:
  tls:
  - hosts:
    - ingres.lcl.com
    secretName: lcl-secret
  rules:
  - host: ingress.lcl.com
    http:
      paths:
      - backend:
          serviceName: nginx
          servicePort: 80

  主要就是新增了spec下面的tls配置,使用了刚才创建的secret文件,此时使用https访问即可正常访问。