來源: 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 使用 layer2模式
- 1. 準備工作
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
- 1. 部署MetalLB
kubectl Apply -f https://raw.Githubusercontent.com/metallb/metallb/v0.13.9/config/manifests/metallb-native.yaml
- 1. 配置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健康檢查的功能。
網絡架構圖如下:
解決了問題1,來看一看問題2怎么解。
問題2. 集群內的pod訪問集群外的mysql集群沒有負載均衡和健康檢查
集群內部訪問集群外部組件可以在集群內部部署hapoxy或者nginx做反向代理,haproxy和 nginx 支持對代理的四層和七層服務做健康檢查和負載均衡。
這樣我們可以在集群內部針對集群外部組件靈活高效的創建帶檢查的負載均衡器,負載均衡器是無狀態的可以創建多臺。
請見下面架構圖
這里我們選擇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.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
- 1. 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;
}
}
}
- 1. 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
nginx loadBalancing