1. 程式人生 > 實用技巧 >k8s叢集部署rabbitmq叢集

k8s叢集部署rabbitmq叢集

1、構建rabbitmq映象

RabbitMQ提供了一個Autocluster外掛,可以自動建立RabbitMQ叢集。下面我們將基於RabbitMQ的官方docker映象,新增這個autocluster外掛,構建我們自己的Rabbit映象,以便在Kubernetes上使用這個映象。
首選需要從這裡下載autocluster和rabbitmq_aws外掛,我這裡下載的是0.8.0的最新版本。

mkdir -p rabbitmq/plugins
cd rabbitmq/plugins
wget https://github.com/rabbitmq/rabbitmq-autocluster/releases/download/0.8.0/autocluster-0.8.0.ez
wget https://github.com/rabbitmq/rabbitmq-autocluster/releases/download/0.8.0/rabbitmq_aws-0.8.0.ez
cd ..

我的Dockerfile內容如下:

FROM rabbitmq:3.6.11-management-alpine

MAINTAINER yangyuhang

RUN apk update && apk add ca-certificates && \
    apk add tzdata && \
    ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
    echo "Asia/Shanghai" > /etc/timezone


ADD plugins/*.ez /opt/rabbitmq/plugins/
RUN rabbitmq-plugins enable --offline autocluster
  • 這裡選擇是rabbitmq:3.6.11-management-alpine作為基礎映象
  • 添加了autocluster外掛

製作映象並推送至阿里雲個人倉庫:

docker build -t registry.cn-zhangjiakou.aliyuncs.com/beibei_dtstack/cashier_rabbitmq:base .
docker push registry.cn-zhangjiakou.aliyuncs.com/beibei_dtstack/cashier_rabbitmq:base

2、以statefulset部署rabbitmq叢集

在部署叢集之前需要為叢集建立一個Storage Class(儲存類)來作為叢集資料的持久化後端。本例中使用nfs作為後端儲存,在建立儲存類之前需要先搭建好nfs,並保證在k8s叢集各個節點上均能掛載該nfs儲存。搭建好nfs後,需要先建立一個儲存類,yaml檔案如下:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: first-storage
parameters:
  archiveOnDelete: "false"
provisioner: nfs-first-storage
reclaimPolicy: Retain
volumeBindingMode: Immediate

然後需要再建立一個PersistentVolumeClaim(PVC,儲存卷),作為rabbitmq叢集的後端儲存:

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: rabbitmq
  namespace: cashier
spec:
  accessModes:
    - ReadWriteMany             #可被多節點讀寫
  resources:
    requests:
      storage: 5Gi
  storageClassName: first-storage               #宣告pv
  volumeMode: Filesystem

相繼執行kubectl apply -f 即可將後端儲存建好。
前面在構建RabbitMQ的Docker映象時,我們添加了autocluster外掛,這個外掛基於很多種backend做服務發現自動發現的RabbitMQ節點新增到RabbitMQ叢集中,autocluster當前支援如下幾種backend:

  • AWS EC2 tags
  • AWS Autoscaling Groups
  • Kubernetes
  • DNS A records
  • Consul
  • etcd

Kubernetes赫然在列,實際上當使用Kubernetes作為rabbitmq-autocluster的backend時,autocluster會通過訪問Kubernetes的API Server獲取RabbitMQ服務的endpoints,這樣就能拿到Kubernete叢集中的RabbitMQ的Pod的資訊,從而可以將它們新增到RabbitMQ的叢集中去。 這裡也就是說要在autocluster實際上是在RabbitMQ Pod中要訪問Kubernetes的APIServer。
可是然後呢?因為已經對Kubernetes的API Server啟用了TLS認證,同時也為API Server起到用了RBAC,要想從Pod中訪問API Server需要藉助Kubernetes的Service Account。 Service Account是Kubernetes Pod中的程式用於訪問Kubernetes API的Account(賬號),它為Pod中的程式提供訪問Kubernetes API的身份標識。下面我們建立rabbitmq Pod的ServiceAccount,並針對Kubernetes的endpoint資源做授權,建立相關的role和rolebinding。
先說明一下,我們的部署是在cashier這個namespace下的。建立如下的rabbitmq.rbac.yaml檔案:

---
apiVersion: v1
kind: ServiceAccount      #叢集訪問apiserver的憑證
metadata:
  name: rabbitmq
  namespace: cashier
---
kind: Role   #建立sa角色
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: rabbitmq
  namespace: cashier
rules:
  - apiGroups:
      - ""
    resources:
      - endpoints
    verbs:
      - get
---
kind: RoleBinding            #將角色繫結
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: rabbitmq
  namespace: cashier
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: rabbitmq
subjects:
- kind: ServiceAccount
  name: rabbitmq
  namespace: cashier

在Kubernetes上建立rabbitmq這個ServiceAccount以及相關的role和rolebinding:

kubectl create -f rabbitmq.rbac.yaml

然後建立訪問rabbitmq叢集的service,建立rabbitmq.service.yaml:

---
apiVersion: v1
kind: Service
metadata:
  name: rabbitmq-management
  namespace: cashier
  labels:
    app: rabbitmq
spec:
  ports:
  - port: 15672
    name: http
    nodePort: 32001          #叢集外訪問rabbitmq管理web介面,http://nodeip:32001
  - port: 5672
    name: amqp
    nodePort: 32002
  selector:
    app: rabbitmq
  type: NodePort
---
apiVersion: v1
kind: Service
metadata:
  name: rabbitmq
  namespace: cashier
  labels:
    app: rabbitmq
spec:
  clusterIP: None
  ports:
  - port: 5672
    name: amqp
  selector:
    app: rabbitmq

在Kubernetes上建立rabbitmq的service:

kubectl create -f rabbitmq.service.yaml

然後通過statefulset型別建立rabbitmq叢集,建立rabbitmq.statefulset.yaml:

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  labels:
    app: rabbitmq
    k8s.eip.work/layer: cloud
    k8s.eip.work/name: rabbitmq
  name: rabbitmq
  namespace: cashier
spec:
  replicas: 3
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: rabbitmq
      k8s.eip.work/layer: cloud
      k8s.eip.work/name: rabbitmq
  serviceName: rabbitmq
  template:
    metadata:
      labels:
        app: rabbitmq
        k8s.eip.work/layer: cloud
        k8s.eip.work/name: rabbitmq
    spec:
      containers:
        - env:
            - name: RABBITMQ_DEFAULT_USER
              valueFrom:
                secretKeyRef:
                  key: rabbitDefaulUser
                  name: devsecret                     #登陸使用者名稱和密碼都儲存在一個secret物件中
            - name: RABBITMQ_DEFAULT_PASS
              valueFrom:
                secretKeyRef:
                  key: rabbitDefaultPass
                  name: devsecret
            - name: RABBITMQ_ERLANG_COOKIE
              valueFrom:
                secretKeyRef:
                  key: erlang.cookie
                  name: devsecret
            - name: MY_POD_NAME
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.name
            - name: K8S_SERVICE_NAME
              value: "rabbitmq"
            - name: RABBITMQ_USE_LONGNAME
              value: 'true'
            - name: RABBITMQ_NODENAME
              value: "rabbit@$(MY_POD_NAME).$(K8S_SERVICE_NAME)"
            - name: RABBITMQ_NODE_TYPE
              value: "disc"
            - name: AUTOCLUSTER_TYPE
              value: "k8s"
            - name: AUTOCLUSTER_DELAY
              value: '10'
            - name: AUTOCLUSTER_CLEANUP
              value: 'true'
            - name: CLEANUP_WARN_ONLY
              value: 'false'
            - name: K8S_ADDRESS_TYPE
              value: "hostname"
            - name: K8S_HOSTNAME_SUFFIX
              value: ".$(K8S_SERVICE_NAME)"
          image: "registry.cn-zhangjiakou.aliyuncs.com/beibei_dtstack/cashier_rabbitmq:base"
          imagePullPolicy: IfNotPresent
          name: rabbitmq
          ports:
            - containerPort: 5672
              name: amqp
              protocol: TCP
          resources:
            limits:
              cpu: 250m
              memory: 512Mi
            requests:
              cpu: 150m
              memory: 256Mi
          volumeMounts:
            - mountPath: /var/lib/rabbitmq
              name: rabbitmq-volume
      dnsPolicy: ClusterFirst
      imagePullSecrets:
        - name: aliyunsecret
      restartPolicy: Always
      schedulerName: default-scheduler
      serviceAccount: rabbitmq
      serviceAccountName: rabbitmq
      volumes:
        - name: rabbitmq-volume
          persistentVolumeClaim:
            claimName: rabbitmq          #繫結pvc
  updateStrategy:
    rollingUpdate:
      partition: 0
    type: RollingUpdate

在建立之前還需要建立一個secret物件,用來儲存rabbitmq的使用者名稱、密碼及erlang.cookie,具體建立步驟如下:

#首先需要生成一個erlang.cookie的檔案:
echo $(openssl rand -base64 32) > erlang.cookie
#然後將該檔案的內容複製下來,編寫一個secret物件yaml:
apiVersion: v1
kind: Secret
metadata:
  name: devsecret
  namespace: cashier
type: Opaque
data:
  rabbitDefaulUser: "cmFiYml0dXNlcgo="
  rabbitDefaultPass: "cmFiYml0cGFzcwo="
  erlang.cookie: "ClmQ9uk2OYk/e+F6wxQEj49rcWT0XzJFWvWIC8RHOiA="
  • secret物件不允許儲存明碼的內容,必須將data下所有的資料轉換為base64資料才ok,然後create一下:
kubectl creat -f secret.yaml
  • 通過環境變數RABBITMQ_USE_LONGNAME,RABBITMQ_NODENAME,AUTOCLUSTER_TYPE,AUTOCLUSTER_DELAY,K8S_ADDRESS_TYPE,AUTOCLUSTER_CLEANUP等環境變數配置了autocluster外掛,具體可以參考 RabbitMQ Autocluster中的文件內容
  • 通過RABBITMQ_ERLANG_COOKIE指定了Erlang cookie。RabbitMQ的叢集是通過Erlang OTP實現的,而Erlang節點間通訊的認證通過Erlang cookie來允許通訊,這裡從devsecret這個Secret中掛載。關於devsecret這個Secret這裡不再給出。
  • 通過RABBITMQ_DEFAULT_USER和RABBITMQ_DEFAULT_PASS指定了RabbitMQ的管理員使用者名稱和密碼,也是從devsecret這個Secret中掛載的
  • 通過RABBITMQ_NODE_TYPE設定叢集所有節點型別為disc,即為磁碟節點

為了在Kubernetes上執行RabbitMQ叢集,必須保證各個RabbitMQ節點之間可以通訊,也就是SatefulSet的Pod可以通訊。 採用的RabbitMQ節點的命名方式為rabbit@hostdomainname的形式:

[email protected] ([email protected])
[email protected] ([email protected])
[email protected] ([email protected])

可以看出採用的是長節點名的命名方式,因此設定了RABBITMQ_USE_LONGNAME為true。為了保證節點間可以通過訪問rabbitmq-0.rabbit, rabbitmq-1.rabbit, rabbitmq-2.rabbit這些域名通訊,必須使用Headless Service,上面rabbitmq Service的clusterIP: None這個必須設定。
在Kubernetes上建立Service和StatefulSet:

kubectl create -f rabbitmq.statefulset.yaml
[root@k8s-master1 ~]# kubectl get statefulset rabbitmq -n cashier
NAME       READY   AGE
rabbitmq   3/3     5h15m

最後可以在RabbitMQ Management中檢視RabbitMQ的3個節點已經組成了叢集:

參考資料