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的形式:

rabbit@rabbitmq-0.rabbit (rabbit@rabbitmq-0.rabbit.cashier.svc.cluster.local)
rabbit@rabbitmq-1.rabbit (rabbit@rabbitmq-1.rabbit.cashier.svc.cluster.local)
rabbit@rabbitmq-2.rabbit (rabbit@rabbitmq-2.rabbit.cashier.svc.cluster.local)

可以看出采用的是长节点名的命名方式,因此设置了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个节点已经组成了集群:image.png

参考资料


posted @ 2020-11-20 09:23  大碗油泼  阅读(4578)  评论(0编辑  收藏  举报