自建k8s集群之负载均衡使用

DevOpSec
• 阅读 518

来源: DevOpSec公众号 作者: DevOpSec

背景

自建k8s而非云环境,组件mysql类(部分有状态服务)部署在虚机里也即集群外,业务服务部署在k8s集群内。

需求:集群内、集群外,业务服务和组件相互间通过负载均衡、高可用的形式连通。

此需求拆解成两个问题进行解决,接着往下看。

集群内:k8s集群

集群外:k8s集群外的应用部署在虚拟机或物理机环境

网络环境

局域网:192.168.0.0/16

集群外
虚拟机网络:192.168.0.0/16
mysql网络:192.168.0.0/16
nginx网络:192.168.0.0/16

集群内(k8s集群)
master网络:192.168.0.0/16
node网络:192.168.0.0/16
pod网络:10.234.0.0/18
SVC网络:10.234.64.0/18

业务架构图如下:

自建k8s集群之负载均衡使用

上图中存在的两个问题

问题1. 集群外访问集群内pod和svc的网络不通?

不通的原因:集群外的主机并不知道svc和pod的网络,在路由器上没有对应svc和pod ip的路由。

cni网络插件的实现是会按node分配网段,通过在路由器上增加不同node上的pod ip段和svc ip段路由给多个node的路由即可实现集群外部和内部svc和pod的互通。

缺点是当节点或者节点上的ip段有变化需要修改路由器上的路由策略,需要人工干预或者写个程序实现成本也不低。

可能有人说可以用nodePort的形式把服务暴露给nginx,但nodePort的方式不便于运维,

缺点有两个:

a、需要维护模块和端口的映射不通模块映射端口不能重复

b、集群外访问集群内部需要通过node的ip加端口访问,node可能随时下线

问题2. 集群内的pod访问集群外的mysql集群没有负载均衡和健康检查

针对问题2可以通过集群外部的硬件负载均衡设备或者自建的软负载均衡比如LVS、HAproxy或nginx等解决

缺点:硬件LB成本高,软LB在集群外维护成本高

也有人说可以已通过DNS解析多个数据库的ip A记录,通过域名解析来实现LB的功能,业务配置域名

缺点:一般DNS服务没有健康检查的功能,没办法实现故障db的自动剔除。

解决方案

问题1. 集群外访问集群内pod和svc的网络不通?

自建k8s,有没有类似于云厂商一样提供LB一样提供和node机器同一个网段的ExternalIP解决方案?

有,通过开源metalLB、OpenELB或者pureLB提供LoadBalancer的能力。

在生产环境中使用的calico VXLAN 模式,考虑到简单易用性我们使用LB基于layer2模式,也即LB的EXTERNAL-IP和node的ip同网段。

通过下面表格综合对比一下,metallb出道最早,迭代快,开发者多,且实测对externalTrafficPolicy: Local 支持。

综合选择metallb的layer2模式作为生产环境负载均衡。

功能 metalLB pureLB openELB
IPAM 内置的 内置的 & 外部的 内置的
使用Linux网络子系统 no yes no
本地地址 yes yes 未完成
路由地址 yes yes yes
支持的协议 BGP any (BGP,OSPF,ISIS,RIP) 部分 BGP
与路由类的CNI整合 困难 容易 有可能
使用 CRD 配置 no yes yes
冗余和故障转移 yes yes
多个vip是否是单节点 多节点 多节点 多节点
ipv6支持 yes yes no
github活跃度 一般 一般般

部署MetalLB 使用 layer2模式

  1. 准备工作

a. 确保防火墙对74727946端口开放,否则可能造成meltallb脑裂。 7472是 MetalLB 控制平面的 API 端口。MetalLB 提供了一个 REST API,用于管理和配置负载均衡服务。 7946端口是 MetalLB 使用的控制平面(Control nPlane)通信端口。它用于集群中的 MetalLB Speaker 之间进行通信,以便在整个集群中协调负载均衡服务的配置和状态。

b. 开始strict ARP模式

kubectl edit configmap -n kube-system kube-proxy

设置

apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs"
ipvs:
  strictARP: true

重启 kube-proxy

kubectl -n kube-system  rollout ds kube-proxy

注意:如果是kubespray部署的k8s集群,需要修改kubespray 配置

vim inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml

修改

kube_proxy_strict_arp: true
  1. 部署MetalLB
    kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.9/config/manifests/metallb-native.yaml
    
3. 配置ip池
注意:ip 地址池和node网络同一个网段,且ip地址池的ip没有其他主机使用

cat ippools.yaml

apiVersion: metallb.io/v1beta1 kind: IPAddressPool metadata: name: first-pool namespace: metallb-system spec: addresses:

  • 192.168.200.0/24
  • 192.168.201.1-192.168.201.250

apiVersion: metallb.io/v1beta1 kind: L2Advertisement metadata: name: ip-advertisement namespace: metallb-system spec: ipAddressPools:

  • first-pool

加LoadBlancer后,nginx通过同网段的`ExternalIP`就能联通业务pod。

业务pod使用`readiness probe` 和 `liveness probe`检测pod里的服务是否正常,如果服务不正常k8s控制平面通知kube-proxy 从svc中剔除或更新endpoints,间接实现了svc对业务pod健康检查的功能。

网络架构图如下:

![image](https://img-hello-world.oss-cn-beijing.aliyuncs.com/imgs/2fb443d2899d297e9014f521c050996c.png)



解决了问题1,来看一看问题2怎么解。



#### 问题2. 集群内的pod访问集群外的mysql集群没有负载均衡和健康检查

集群内部访问集群外部组件可以在集群内部部署hapoxy或者nginx做反向代理,haproxy和 nginx 支持对代理的四层和七层服务做健康检查和负载均衡。

可以在集群内部针对集群外部组件灵活高效的创建带健康检测的负载均衡器,负载均衡器是无状态的可以创建多台。

请见下面架构图

![image](https://img-hello-world.oss-cn-beijing.aliyuncs.com/imgs/e61ef61279eae3bfabbf4436395d62f2.png)


这里我们选择nginx做负载均衡器,负载均衡器部署在k8s集群里,负载均衡器的RealServer是集群外的MySql从库集群。

业务pod1和pod2里配置的是负载均衡器nginx的svc的域名,使用域名和数据库ip解耦,通过负载均衡nginx实现mysqlslave的LB和HA的功能。

针对每一个组件分别启用一个新的负载均衡器而非共用负载均衡器,这样各个组件的负载均衡器变更或者出故障相互之间不受影响。牺牲一些服务器成本换取稳定性。

下面我们看一下负载均衡nginx的配置
1. workload 配置
cat rollout.yaml

apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: lb-mysql-server namespace: release labels: app.kubernetes.io/name: lb-mysql-server spec: minAvailable: 1 selector: matchLabels: app.kubernetes.io/name: lb-mysql-server


apiVersion: argoproj.io/v1alpha1 kind: Rollout metadata: labels: app.kubernetes.io/name: lb-mysql-server name: lb-mysql-server namespace: release spec: replicas: 1 strategy: canary: steps: - setWeight: 20 - pause: { "duration": 30s } revisionHistoryLimit: 3 selector: matchLabels: app.kubernetes.io/name: lb-mysql-server template: metadata: labels: app.kubernetes.io/name: lb-mysql-server spec: imagePullSecrets: - name: your-registry-secrets volumes: - name: etc-nginx configMap: name: lb-mysql-server-cm containers: - image: nginx:1.23.2-alpine imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 3 httpGet: path: /healthz port: 8081 scheme: HTTP periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 name: nginx-proxy readinessProbe: failureThreshold: 3 httpGet: path: /healthz port: 8081 scheme: HTTP periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 resources: requests: cpu: 25m memory: 32M terminationMessagePath: /dev/termination-log terminationMessagePolicy: File volumeMounts: - mountPath: /etc/nginx name: etc-nginx readOnly: true enableServiceLinks: true restartPolicy: Always terminationGracePeriodSeconds: 30


2. configMap配置,配置代理Realserver
cat cm.yaml

apiVersion: v1 kind: ConfigMap metadata: name: lb-mysql-server-cm namespace: release labels: app.kubernetes.io/name: lb-mysql-server data: nginx.conf: | error_log stderr notice;

worker_processes 2;
worker_rlimit_nofile 130048;
worker_shutdown_timeout 10s;

events {
  multi_accept on;
  use epoll;
  worker_connections 16384;
}

stream {
  upstream lb_mysql_server {
    least_conn;
    server 192.168.1.11:3306 max_fails=2 fail_timeout=30s;
    server 192.168.1.12:3306 max_fails=2 fail_timeout=30s;
    server 192.168.1.13:3306 max_fails=2 fail_timeout=30s;
    }

  server {
    listen        3306;
    proxy_pass    lb_mysql_server;
    proxy_timeout 10m;
    proxy_connect_timeout 1s;
  }
}

http {
  aio threads;
  aio_write on;
  tcp_nopush on;
  tcp_nodelay on;

  keepalive_timeout 5m;
  keepalive_requests 100;
  reset_timedout_connection on;
  server_tokens off;
  autoindex off;

  server {
    listen 8081;
    location /healthz {
      access_log off;
      return 200;
    }
    location /stub_status {
      stub_status on;
      access_log off;
    }
  }
}

3. svc配置
cat svc.yaml

apiVersion: v1 kind: Service metadata: name: lb-mysql-server namespace: release labels: app.kubernetes.io/name: lb-mysql-server spec: selector: app.kubernetes.io/name: lb-mysql-server ports:

  • name: lb-mysql-server protocol: TCP port: 3306 targetPort: 3306

nginx负载均衡健康检查具体请见:[TCP Health Checks](https://docs.nginx.com/nginx/admin-guide/load-balancer/tcp-health-check/)   
[nginx loadBalancing](https://docs.nginx.com/nginx/admin-guide/load-balancer/tcp-udp-load-balancer/)

点赞
收藏
评论区
推荐文章
爱库里 爱库里
3年前
Kubernetes笔记:十分钟部署一套K8s环境
Kubernetes是Goole开源的一个容器编排引擎,它支持自动化部署、大规模可伸缩、应用容器化管理——百度百科。接触K8s也有半年多了,也基于阿里云平台搭建了包含多级服务、目前运行较为稳定的K8s集群(感兴趣的可参考\k8s云集群混搭模式,可能帮你节省50%以上的服务成本\,\k8s云集群混搭模式落地分享\,但一直没来得及对其进行系统
Stella981 Stella981
3年前
K8s——Ingress
在Kubernetes中,服务和Pod的IP地址仅可以在集群网络内部使用,对于集群外的应用是不可见的。为了使外部的应用能够访问集群内的服务,在Kubernetes中目前提供了以下几种方案:1.NodePort2.LoadBalancer3.IngressNodePort,简单来说,就是通过service这种资源对象,为后端
Wesley13 Wesley13
3年前
3、交付Dubbo微服务到kubernetes集群
1.基础架构1.1.架构图!(https://img2018.cnblogs.com/blog/1373757/201912/137375720191212011346697105741216.png)Zookeeper是Dubbo微服务集群的注册中心它的高可用机制和k8s的etcd集群
Stella981 Stella981
3年前
Flink从入门到真香(Flink环境部署
FlinkStandalone模式部署集群是最简单的一种部署方式,不依赖于其他的组件,另外还支持YARN/Mesos/K8S等模式下的部署Standalone执行架构图:!Flink从入门到真香(Flink环境部署集群standalone模式)(https://s4.51cto.com/images/blog/202011/05/8073eb
Stella981 Stella981
3年前
Kubernetes集群安装(自己搭过,已搭好)
k8s安装目录1\.组件版本&&集群环境组件版本etcd集群&&k8smaster机器&&k8snode机器集群环境变量2\.创建CA证书和密钥安装
Wesley13 Wesley13
3年前
vivo 基于原生 RabbitMQ 的高可用架构实践
一、背景说明vivo在2016年引入RabbitMQ,基于开源RabbitMQ进行扩展,向业务提供消息中间件服务。2016~2018年,所有业务均使用一个集群,随着业务规模的增长,集群负载越来越重,集群故障频发。2019年,RabbitMQ进入高可用建设阶段,完成了高可用组件MQ名字服务以及RabbitMQ集群
DevOpSec DevOpSec
1年前
kubespray离线安装k8s集群
国内安装k8s集群,下载镜像往往失败或者半天下载不下来,导致安装不成功,影响摸鱼心情。是否可以通过kubespray离线部署k8s集群。完全可以的,准备就绪后,只需10多分钟即可搭建出一套高可用的k8s集群,心情畅悦。
京东云开发者 京东云开发者
3个月前
广告在线模型系统负载均衡策略实践
一、背景简介1.1、现状•实际生产环境中,复杂业务系统对分布式服务集群架构的依赖。•服务集群异质化节点的容器化部署,机器性能超卖现象不可避免、性能不均情况时有发生。•服务集群各硬件组件出错率不可避免•集群内负载不均,整体资源利用率低。•单节点过载容易触发集
DevOpSec
DevOpSec
Lv1
懂开发的运维,懂安全的运维。公众号:DevOpSec
文章
57
粉丝
6
获赞
26