来源: 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
业务架构图
上图中存在的两个问题
不通的原因:集群外的主机并不知道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可以通过集群外部的硬件负载均衡设备或者自建的软负载均衡比如LVS、HAproxy或nginx等解决
缺点:硬件LB成本高,软LB在集群外维护成本高
也有人说可以已通过DNS解析多个数据库的ip A记录,通过域名解析来实现LB的功能,业务配置域名
缺点:一般DNS服务没有健康检查的功能,没办法实现故障db的自动剔除。
自建k8s,有没有类似于云厂商一样提供LB一样提供和node机器同一个网段的ExternalIP解决方案?
有,通过开源metalLB、OpenELB或者pureLB提供LoadBalancer的能力。
在生产环境中使用的calico VXLAN 模式,考虑到简单易用性我们使用LB基于layer2模式,也即LB的EXTERNAL-IP和node的ip同网段。
通过下面表格综合对比一下,metallb出道最早,迭代快,开发者多,且实测对externalTrafficPolicy: Local 支持。
综合选择metallb的layer2模式作为生产环境负载均衡。
部署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
kubectl Apply -f https://raw.Githubusercontent.com/metallb/metallb/v0.13.9/config/manifests/metallb-native.yaml
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健康检查的功能。
网络架构图如下:
解决了问题1,来看一看问题2怎么解。
集群内部访问集群外部组件可以在集群内部部署hapoxy或者nginx做反向代理,haproxy和 nginx 支持对代理的四层和七层服务做健康检查和负载均衡。
这样我们可以在集群内部针对集群外部组件灵活高效的创建带检查的负载均衡器,负载均衡器是无状态的可以创建多台。
请见下面架构图
这里我们选择nginx做负载均衡器,负载均衡器部署在k8s集群里,负载均衡器的RealServer是集群外的MySql从库集群。
业务pod1和pod2里配置的是负载均衡器nginx的svc的域名,使用域名和数据库ip解耦,通过负载均衡nginx实现mysqlslave的LB和HA的功能。
针对每一个组件分别启用一个新的负载均衡器而非共用负载均衡器,这样各个组件的负载均衡器变更或者出故障相互之间不受影响。牺牲一些服务器成本换取稳定性。
下面我们看一下负载均衡nginx的配置
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: lb-mysql-server
namespace: release
labels:
app.kube.NETes.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
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;
}
}
}
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
nginx loadBalancing