一、RabbitMQ集群K8S部署
1.1 文件結(jié)構(gòu)和說明
分別對應(yīng)文件如下
- rabbitmq-cluster-statefulset.yaml
- rabbitmq-configmap.yaml
- rabbitmq-dashboard-service.yaml
- rabbitmq-headless-service.yaml
- rabbitmq-public-namespace.yaml
- rabbitmq-pv-hostpath.yaml
- rabbitmq-pv-hostpath.yaml
- rabbitmq-rbac.yaml
- rabbitmq-secret.yaml
1.2 部署步驟
1.2.1 創(chuàng)建namespace和rbac
(1) 創(chuàng)建namespace
kubectl create namespace public-service
如果不使用public-service,同步以下yaml文件中修改即可虾啦。一鍵修改如下:
sed -i "s#public-service#YOUR_NAMESPACE#g" *.yaml
(2) 創(chuàng)建rbac
創(chuàng)建rbac主要包含以下幾個文件
1.2.2 創(chuàng)建持久化存儲卷PV(運維人員創(chuàng)建)
PV通常由運維人員創(chuàng)建骂删,可以有hostpath方式和使用nfs方式以蕴,如果是開發(fā)環(huán)境讹堤,使用宿主機的hostpath方式即可梯码。
(1)hostpath方式
## 預(yù)定義有狀態(tài)副本集的三個PV存儲快
## 注意有狀態(tài)副本集使用哪個PV宝泵,按照名次+編號找的
## 注意此處使用的宿主機hostPath模式,因此需要配置affinity每個節(jié)點最多只能配置一個該有狀態(tài)副本集的POD轩娶,而使用nfs等指定IP則不需要
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-rmq-1
spec:
capacity:
storage: 4Gi
accessModes:
- ReadWriteMany
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Recycle
storageClassName: "rmq-storage-class"
##定義存儲路徑儿奶,如果為nfs網(wǎng)絡(luò)存儲,則為server
hostPath:
path: /opt/dockerdata/mq-system/rabbitmq2/data
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-rmq-2
spec:
capacity:
storage: 4Gi
accessModes:
- ReadWriteMany
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Recycle
storageClassName: "rmq-storage-class"
hostPath:
path: /opt/dockerdata/mq-system/rabbitmq2/data
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-rmq-3
spec:
capacity:
storage: 4Gi
accessModes:
- ReadWriteMany
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Recycle
storageClassName: "rmq-storage-class"
hostPath:
path: /opt/dockerdata/mq-system/rabbitmq2/data
(2)nfs方式
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-rmq-1
spec:
capacity:
storage: 4Gi
accessModes:
- ReadWriteMany
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Recycle
storageClassName: "rmq-storage-class"
nfs:
# real share directory
path: /k8s/rmq-cluster/rabbitmq-cluster-1
# nfs real ip
server: 192.168.147.220
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-rmq-2
spec:
capacity:
storage: 4Gi
accessModes:
- ReadWriteMany
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Recycle
storageClassName: "rmq-storage-class"
nfs:
# real share directory
path: /k8s/rmq-cluster/rabbitmq-cluster-2
# nfs real ip
server: 192.168.147.220
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-rmq-3
spec:
capacity:
storage: 4Gi
accessModes:
- ReadWriteMany
volumeMode: Filesystem
persistentVolumeReclaimPolicy: Recycle
storageClassName: "rmq-storage-class"
nfs:
# real share directory
path: /k8s/rmq-cluster/rabbitmq-cluster-3
# nfs real ip
server: 192.168.147.220
1.3 創(chuàng)建持久化聲明PVC
講到創(chuàng)建持久化存儲卷聲明時鳄抒,不一定需要單獨寫一個聲明文件闯捎,依據(jù)部署文件的不同可以選擇不單獨聲明,直接在部署文件中聲明即可许溅,見下文使用PV方式二瓤鼻。
(1)使用PV方式一
方式一為部署模板時,定義容器存儲卷volumes時通過persistentVolumeClaim字段指明PVC名稱贤重。
比如下面:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: rabbitmq
namespace: mq-system
spec:
serviceName: rabbitmq-headless # 必須與headless service的name相同茬祷,用于hostname傳播訪問pod
selector:
matchLabels:
app: rabbitmq # 在apps/v1中,需與 .spec.template.metadata.label 相同并蝗,用于hostname傳播訪問pod祭犯,而在apps/v1beta中無需這樣做
replicas: 1
template:
metadata:
labels:
app: rabbitmq # 在apps/v1中,需與 .spec.selector.matchLabels 相同
spec:
serviceAccountName: rabbitmq
terminationGracePeriodSeconds: 10
## 設(shè)置Affinity
affinity:
#如果某個maste節(jié)點上已有rabbitmq pod滚停,則不在這個master節(jié)點上建rabbitmq pod沃粗。確保3個rabbitmq pod分別在3個master節(jié)點上創(chuàng)建
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- rabbitmq
topologyKey: kubernetes.io/hostname
## 選擇在master上部署
nodeSelector:
role: master
containers:
- name: rabbitmq
image: rabbitmq:3.7.12
# 定義拉取策略為本地不存在才拉取
imagePullPolicy: IfNotPresent
# 定義容器環(huán)境變量
env:
- name: HOSTNAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: RABBITMQ_USE_LONGNAME
value: "true"
# 若在ConfigMap中設(shè)置了service_name,則此處無需再次設(shè)置铐刘,注意環(huán)境變量聲名的順序
- name: K8S_SERVICE_NAME
value: "rabbitmq-headless"
- name: RABBITMQ_NODENAME
value: "rabbit@$(HOSTNAME).$(K8S_SERVICE_NAME)"
- name: K8S_HOSTNAME_SUFFIX
value: ".$(K8S_SERVICE_NAME)"
# 將Rabbitmq的集群ErlangCookie環(huán)境變量必須一致陪每,設(shè)置相同,此處直接明文,建議使用secret的Opaque類型密文形式
- name: RABBITMQ_ERLANG_COOKIE
value: "mycookie"
# - name: RABBITMQ_LOGS
# value: "/var/log/rabbitmq/node@$(HOSTNAME).log"
# - name: RABBITMQ_UPGRADE_LOG
# value: "/var/log/rabbitmq/node@$(HOSTNAME).log"
ports:
- name: http
protocol: TCP
containerPort: 15672
- name: amqp
protocol: TCP
containerPort: 5672
livenessProbe:
exec:
command: ["rabbitmqctl", "status"]
initialDelaySeconds: 60
periodSeconds: 60
timeoutSeconds: 5
readinessProbe:
exec:
command: ["rabbitmqctl", "status"]
initialDelaySeconds: 20
periodSeconds: 60
timeoutSeconds: 5
# 配置容器中掛載卷
volumeMounts:
- name: config-volume
mountPath: /etc/rabbitmq
- name: timezone
mountPath: /etc/localtime
- name: log
mountPath: /var/log/rabbitmq
- name: rabbitmq-data
mountPath: /var/lib/rabbitmq/mnesia
## 定義存儲卷檩禾,分別對應(yīng)上述volimeMounts
volumes:
## 定義rabbitmq配置文件為指定config-map的文件
- name: config-volume
configMap:
name: rabbitmq-config
items:
- key: rabbitmq.conf
path: rabbitmq.conf
- key: enabled_plugins
path: enabled_plugins
## 定義rabbitmq的數(shù)據(jù)文件挂签,為采用PVC關(guān)聯(lián)的PV的存儲卷,即使用名為XXX的PVC指定的PV存儲空間
- name: rabbitmq-data
persistentVolumeClaim:
claimName: rabbitmq-data-claim
## 定義時區(qū)映射文件為宿主機路徑
- name: timezone
hostPath:
path: /etc/localtime
## 定義日志文件對應(yīng)宿主機路徑
- name: log
hostPath:
path: /opt/dockerdata/mq-system/rabbitmq/log
上面部署文件指定的PVC如下:
## PVC 描述的盼产,則是 Pod 所希望使用的持久化存儲的屬性饵婆。比如,Volume 存儲的大小戏售、可讀寫權(quán)限等等
## PVC 可以理解為持久化存儲的“接口”侨核,它提供了對某種持久化存儲的描述,但不提供具體的實現(xiàn)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
## 聲名一個名為rabbitmq-data-claim的PVC
name: rabbitmq-data-claim
namespace: mq-system
spec:
accessModes:
- ReadWriteMany
storageClassName: rabbitmq-storageClass
resources:
requests:
storage: 10Gi
selector:
## 其關(guān)聯(lián)PV的字段為release灌灾,內(nèi)容為rabbitmq-data
matchLabels:
release: rabbitmq-data
(2)使用PV方式二(不單獨寫PVC文件)
方式二為部署模板時搓译,直接通過volumeClaimTemplates指定方式一中聲明的PVC相關(guān)參數(shù),包括存儲大小锋喜、寫入模式些己、存儲類名稱等。
kind: StatefulSet
apiVersion: apps/v1
metadata:
labels:
app: rmq-cluster
name: rmq-cluster
namespace: public-service
spec:
replicas: 2
selector:
matchLabels:
app: rmq-cluster
## 定義無頭服務(wù)名稱
serviceName: rabbitmq-headless # 必須與headless 服務(wù)的name相同嘿般,用于hostname傳播訪問pod段标,且注意有狀態(tài)副本集為了互相感知節(jié)點存在RABBITMQ_NODENAME名稱也會使用無頭服務(wù)的DNS
template:
metadata:
labels:
app: rmq-cluster
spec:
serviceAccountName: rmq-cluster
terminationGracePeriodSeconds: 30
## 設(shè)置Affinity
affinity:
#如果某個maste節(jié)點上已有rabbitmq pod,則不在這個master節(jié)點上建rabbitmq pod炉奴。確保3個rabbitmq pod分別在3個master節(jié)點上創(chuàng)建
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- rmq-cluster
topologyKey: kubernetes.io/hostname
containers:
- args:
- -c
- cp -v /etc/rabbitmq/rabbitmq.conf ${RABBITMQ_CONFIG_FILE}; exec docker-entrypoint.sh
rabbitmq-server
command:
- sh
env:
## 用戶名逼庞,從密件文件中讀取
- name: RABBITMQ_DEFAULT_USER
valueFrom:
secretKeyRef:
key: username
name: rmq-cluster-secret
## 密碼,從密件文件中讀取
- name: RABBITMQ_DEFAULT_PASS
valueFrom:
secretKeyRef:
key: password
name: rmq-cluster-secret
## ERlang Cookie,從密件文件中讀取
- name: RABBITMQ_ERLANG_COOKIE
valueFrom:
secretKeyRef:
key: cookie
name: rmq-cluster-secret
## POD IP
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
## POD 名稱
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
## POD 命名空間
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
## 是否使用長節(jié)點名稱瞻赶,有點的就是長節(jié)點名稱赛糟,必須配置
- name: RABBITMQ_USE_LONGNAME
value: "true"
# 若在ConfigMap中設(shè)置了service_name,則此處無需再次設(shè)置共耍,注意環(huán)境變量聲名的順序
- name: K8S_SERVICE_NAME
value: rabbitmq-headless
## 節(jié)點名稱命名規(guī)則虑灰,注意此處節(jié)點名稱必須符合無頭服務(wù)的命名規(guī)則吨瞎,比如此處為rmq-cluster.rabbitmq
## 注意之前隨意改變了有狀態(tài)副本集的名稱serviceName痹兜,并且同步修改了無頭服務(wù)的對應(yīng)字段,但是沒有修改此處的$(K8S_SERVICE_NAME)名稱颤诀,造成提示
## "ERROR: epmd error for host rmq-cluster-0.rmq-cluster.public-service.svc.cluster.local: nxdomain (non-existing domain)"錯誤
- name: RABBITMQ_NODENAME
value: rabbit@$(POD_NAME).$(K8S_SERVICE_NAME).$(POD_NAMESPACE).svc.cluster.local
- name: RABBITMQ_CONFIG_FILE
value: /var/lib/rabbitmq/rabbitmq.conf
image: rabbitmq:3.7-management
imagePullPolicy: IfNotPresent
## 配置探針
livenessProbe:
exec:
command:
- rabbitmqctl
- status
initialDelaySeconds: 30
timeoutSeconds: 10
## 容器組名稱
name: rabbitmq
ports:
- containerPort: 15672
name: http
protocol: TCP
- containerPort: 5672
name: amqp
protocol: TCP
## 探針
readinessProbe:
exec:
command:
- rabbitmqctl
- status
initialDelaySeconds: 10
timeoutSeconds: 10
## 容器存儲聲明字旭,此處兩個容器存儲都必須通過volumes或者volumeClaimTemplates指定
volumeMounts:
## 定義配置文件,容器內(nèi)映射路徑為XXX
- name: config-volume
mountPath: /etc/rabbitmq
readOnly: false
## 定義數(shù)據(jù)存儲崖叫,容器內(nèi)映射路徑為XXX
- name: rabbitmq-storage
mountPath: /var/lib/rabbitmq
readOnly: false
## 外部存儲綁定
volumes:
- name: config-volume ## 名稱和volumeMounts中某個name相同
configMap:
name: rmq-cluster-config ## 名稱需和對應(yīng)的configMap名稱相同
items:
- key: rabbitmq.conf
path: rabbitmq.conf
- key: enabled_plugins
path: enabled_plugins
volumeClaimTemplates:
- metadata:
## 名稱需和volumeMounts中某個name相同
name: rabbitmq-storage
spec:
accessModes:
- ReadWriteMany
storageClassName: "rmq-storage-class"
resources:
requests:
storage: 4Gi
1.4 創(chuàng)建configMap(根據(jù)需求)
通常容器部署過程中遗淳,部分文件不喜歡寫死,因此通過configMap方式動態(tài)配置是大多數(shù)人的選擇心傀,如果是用戶名屈暗、密碼這種,建議使用secret這種加密存儲方式。
如下养叛,動態(tài)指定開啟的插件种呐,以及rabbitmq服務(wù)端的配置(比如配置集群信息等)。
kind: ConfigMap
apiVersion: v1
metadata:
name: rmq-cluster-config
namespace: public-service
labels:
addonmanager.kubernetes.io/mode: Reconcile
data:
enabled_plugins: |
[rabbitmq_management,rabbitmq_peer_discovery_k8s].
rabbitmq.conf: |
loopback_users.guest = false
## Clustering
cluster_formation.peer_discovery_backend = rabbit_peer_discovery_k8s
cluster_formation.k8s.host = kubernetes.default.svc.cluster.local
cluster_formation.k8s.address_type = hostname
#################################################
# public-service is rabbitmq-cluster's namespace#
#################################################
cluster_formation.k8s.hostname_suffix = .rmq-cluster.public-service.svc.cluster.local
cluster_formation.node_cleanup.interval = 10
cluster_formation.node_cleanup.only_log_warning = true
cluster_partition_handling = autoheal
## queue master locator
queue_master_locator=min-masters
1.5 Secret創(chuàng)建Erlang對應(yīng)的Cookie
Erlang對應(yīng)的Cookie信息比較敏感弃甥,因此需通過Secret方式創(chuàng)建如下:
kind: Secret
apiVersion: v1
metadata:
name: rmq-cluster-secret
namespace: public-service
stringData:
cookie: ERLANG_COOKIE
password: RABBITMQ_PASS
url: amqp://RABBITMQ_USER:RABBITMQ_PASS@rmq-cluster-balancer
username: RABBITMQ_USER
type: Opaque ## 模糊
1.6 創(chuàng)建部署文件
部署文件在1.3中使用PVC已經(jīng)寫明爽室,不再重復(fù)說明。
1.7 定義headless服務(wù)
定義headless(又稱無頭)服務(wù)前淆攻,須明白無頭服務(wù)的作用阔墩,見第三章節(jié)。
## 注意無頭服務(wù)必須創(chuàng)建在有狀態(tài)副本集前面瓶珊,否則無法利用無頭服務(wù)的DNS特性
## 詳細參考文章說明http://www.reibang.com/p/a6d8b28c88a2
kind: Service
apiVersion: v1
metadata:
labels:
app: rmq-cluster
name: rabbitmq-headless ## 必須與有狀態(tài)副本集的的serviceName相同啸箫,用于hostname傳播訪問pod
namespace: public-service
spec:
clusterIP: None
ports:
- name: amqp
port: 5672
targetPort: 5672
selector:
app: rmq-cluster
1.8 定義對外暴露服務(wù)
用戶根據(jù)需求對外暴露部分服務(wù),比如dashboard服務(wù)端口伞芹,或者直接暴露rabbitmq的broker端口筐高。
# 用于暴露dashboard到外網(wǎng)
kind: Service
apiVersion: v1
metadata:
labels:
app: rmq-cluster
type: LoadBalancer
name: rabbitmq-service
namespace: public-service
spec:
type: NodePort # 注意如果你想在外網(wǎng)下訪問mq,需要增配nodeport
ports:
- name: http
port: 15672
protocol: TCP
targetPort: 15672 # 注意k8s默認情況下丑瞧,nodeport要在30000~32767之間柑土,可以自行修改
- name: amqp
port: 5672
protocol: TCP
targetPort: 5672
selector:
app: rmq-cluster
二、有狀態(tài)副本集作用和組成
2.1 statefulset作用
在k8s中绊汹,statefulset主要管理一下特效的應(yīng)用:
? a)稽屏、每一個Pod穩(wěn)定且有唯一的網(wǎng)絡(luò)標(biāo)識符;--------- 即Pod重新調(diào)度后其PodName和HostName不變西乖,基于Headless Service(即沒有Cluster IP的Service)來實現(xiàn)
? b)狐榔、穩(wěn)定且持久的存儲設(shè)備; ---------即Pod重新調(diào)度后還是能訪問到相同的持久化數(shù)據(jù)获雕,基于PVC來實現(xiàn)
? c)薄腻、要求有序、平滑的部署和擴展届案; ------- (即從0到N-1)
? d)庵楷、要求有序、平滑的終止和刪除楣颠; ------- (即從N-1到0)
? e)尽纽、有序的滾動更新,應(yīng)該先更新從節(jié)點童漩,再更新主節(jié)點弄贿;
2.2 statefulset三要素
statefulset由三個組件組成
? a) headless service(無頭的服務(wù),即沒名字)矫膨;-------- 用于定義網(wǎng)絡(luò)標(biāo)志(DNS domain)
? b) statefulset控制器 差凹; -------- 定義具體應(yīng)用
? c) volumeClaimTemplate(存儲卷申請模板期奔,因為每個pod要有專用存儲卷,而不能共用存儲卷) --- 用于創(chuàng)建PersistentVolumes
如何理解無頭服務(wù)和普通服務(wù)的區(qū)別
2.3 statefulSet中DNS格式什么樣子
StatefulSet中每個Pod的DNS格式為statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local
危尿,其中
-
statefulSetName
為StatefulSet的名字 -
0..N-1
為Pod所在的序號能庆,從0開始到N-1 -
serviceName
為Headless Service的名字 -
namespace
為服務(wù)所在的namespace,Headless Servic和StatefulSet必須在相同的namespace -
.cluster.local
為Cluster Domain
三脚线、 關(guān)于headless服務(wù)
3.1 headless服務(wù)概念
headless service
headless service是一個特殊的ClusterIP類service搁胆,這種service創(chuàng)建時不指定clusterIP(--cluster-ip=None),因為這點邮绿,kube-proxy不會管這種service渠旁,于是node上不會有相關(guān)的iptables規(guī)則。
當(dāng)headless service有配置selector時船逮,其對應(yīng)的所有后端節(jié)點顾腊,會被記錄到dns中,在訪問service domain時kube-dns會將所有endpoints返回挖胃,選擇哪個進行訪問則是系統(tǒng)自己決定杂靶;
3.2 nslookup理解無頭服務(wù)
關(guān)于無頭服務(wù)理解,可以閱讀下面這篇文章酱鸭,普通服務(wù)DNS查詢時只能獲取服務(wù)ClusterIP吗垮,而headless服務(wù)可以獲取pod的真實IP地址。即
參考文章K8S容器編排之Headless淺談 -- 用nslookup實戰(zhàn)說明無頭服務(wù)到底提供了什么功能
1凹髓、
dns
查詢普通clusterIP類型的服務(wù)時只會返回Service
的地址烁登。具體client
訪問的是哪個Real Server
,是由iptables
來決定的2蔚舀、
dns
查詢headless服務(wù)時會如實的返回2個真實的endpoint
3.3 Headless Service有什么使用場景呢饵沧?
參考如下理解:
Headless Service
就是沒頭的Service
。有什么使用場景呢赌躺?
- 第一種:自主選擇權(quán)狼牺,有時候
client
想自己來決定使用哪個Real Server
,可以通過查詢DNS
來獲取Real Server
的信息礼患。- 第二種:
Headless Services
還有一個用處(PS:也就是我們需要的那個特性)是钥。Headless Service
的對應(yīng)的每一個Endpoints
,即每一個Pod
讶泰,都會有對應(yīng)的DNS
域名咏瑟;這樣Pod
之間就可以互相訪問。我們還是看上面的這個例子痪署。
3.4 無頭服務(wù)聲明注意事項
最后強調(diào)一點就是,無頭服務(wù)名稱需與有狀態(tài)副本集的的serviceName相同兄旬,用于hostname傳播訪問pod
且如果有狀態(tài)副本集不同節(jié)點需要互通時狼犯,需要使用當(dāng)headless service有配置selector時余寥,其對應(yīng)的所有后端節(jié)點,會被記錄到dns中
特性悯森,因此其無頭服務(wù)必須聲明宋舷。
以上文中環(huán)境變量為例
# 若在ConfigMap中設(shè)置了service_name,則此處無需再次設(shè)置瓢姻,注意環(huán)境變量聲名的順序
- name: K8S_SERVICE_NAME
value: rabbitmq-headless
## 節(jié)點名稱命名規(guī)則祝蝠,注意此處節(jié)點名稱必須符合無頭服務(wù)的命名規(guī)則,比如此處為rmq-cluster.rabbitmq
## 注意之前隨意改變了有狀態(tài)副本集的名稱serviceName幻碱,并且同步修改了無頭服務(wù)的對應(yīng)字段绎狭,但是沒有修改此處的$(K8S_SERVICE_NAME)名稱,造成提示
## "ERROR: epmd error for host rmq-cluster-0.rmq-cluster.public-service.svc.cluster.local: nxdomain (non-existing domain)"錯誤
- name: RABBITMQ_NODENAME
value: rabbit@$(POD_NAME).$(K8S_SERVICE_NAME).$(POD_NAMESPACE).svc.cluster.local
即以下幾個變量名稱必須相同:
1褥傍、無頭服務(wù)的name字段
2儡嘶、有狀態(tài)副本集的serviceName字段
3、有狀態(tài)副本集中容器對應(yīng)的K8S_SERVICE_NAME環(huán)境變量
本文到底結(jié)束恍风,如有描述不當(dāng)蹦狂,還望諒解指正!
源文件對應(yīng)github地址待整理更新朋贬。
附 參考文獻
- 淺析k8s service的應(yīng)用---說明K8S服務(wù)類型和區(qū)別
- K8S容器編排之Headless淺談 -- 用nslookup說明無頭服務(wù)
- k8s部署kafka集群 -- 描述了Kafka集群K8S部署文件和有狀態(tài)副本集的應(yīng)用場景和組成