来源: 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
业务架构图如下:
上图中存在的两个问题
问题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模式
- 准备工作
a. 确保防火墙对7472
、7946
端口开放,否则可能造成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
- 部署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/)