K8s环境中部署各种开发应用服务(五 )rocketMQ集群
K8s环境中部署各种开发应用服务(五 )rocketMQ集群
准备工作StorageClass与provisioner
StorageClass与provisioner我们这次给rocketMQ服务创建一套新来用
RBAC这套东西还是使用之前那套
首先在NFS服务器的挂载目录下创建一个新的文件夹rocketmq
mkdir /mnt/data/nfsdata/rocketmq
顺便赋予权限
chmod 777 -R /mnt/data/nfsdata/rocketmq/
Kubernetes Dashboard去导入YAML内容
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-rocketmq-provisioner #这个provisioner的Deployment我们专门给rocketmq使用
namespace: default
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: kopkop/nfs-client-provisioner-arm64:latest
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: nfs-rocketmq-provisioner #此处对应下面StorageClass的provisioner
- name: NFS_SERVER
value: 192.168.31.83 #之前部署好的NFS服务端地址
- name: NFS_PATH
value: /mnt/data/nfsdata/rocketmq #NFS服务主机挂载到硬盘下的目录 这里给rocketmq创建了一个目录作为rocketmq专用
volumes:
- name: nfs-client-root
nfs:
server: 192.168.31.83 #NFS服务端地址
path: /mnt/data/nfsdata/rocketmq #NFS服务主机挂载到硬盘下 这里给rocketmq创建了一个目录作为rocketmq专用
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-rocketmq #这个存储类专门给rocketmq用
provisioner: nfs-rocketmq-provisioner #和上面provisioner的PROVISIONER_NAME内容一致
reclaimPolicy: Retain
准备工作结束
写在开始之前
RocketMQ的服务端主要有两个部分
Broker 也就是 RocketMQ 的 Server ,负责中转消息,存储消息,转发消息。这是一个有状态的服务,我们准备的NFS就是为了它
NameServer 可以说是 Broker 的注册中心,Broker启动的时候向所有NameServer进行注册。NameServer 负责监控Broker的存活状态,剔除挂掉的Broker。同时NameServer也担当了负载均衡的角色
部署RocketMQ的方式有几种
2m-noslave 多Master模式,无Slave 双主
2m-2s-sync 多Master多Slave模式,同步双写 双主双从+同步
2m-2s-async 多Master多Slave模式,异步复制 双主双从+异步
我们接下来用的是第二种
一共需要部署6个节点:
4个Broker 双主双从
2个NameServer
另外还有一个console用来可视化操作
有状态服务的迷思
前面我们部署Redis的时候提到过在Kubernetes环境下使用有状态服务的持久化存储,最后用StatefulSet来解决。
但是对于RocketMQ这类应用来讲,除了要解决持久化问题,还需要在节点重生的时候正确配置新Broker的config 参数,包括brokerName和NameServer IP List等。这样一来用StatefulSet就费劲了,如果说手动去部署4个固定的StatefulSet来解决的话,又失去使用Kubernetes的意义。
然后,就出现了这样一个好东西 —— Kubernetes Operator
以下内容都是抄来的
实际上 Kubernetes 开发人员也发现了这些问题,因此引入了自定义资源和控制器的概念,让开发人员可以直接用 Go 语言调用 Kubernetes API,编写自定义资源和对应的控制器逻辑来解决复杂有状态应用的管理问题,提供特定应用相关的自定义资源的这类代码组件称之为 Operator。由具备 RocketMQ 领域知识的专家编写 Operator,屏蔽了应用领域的专业知识,让用户只需要关心和定义希望达到的集群终态,这也是 Kubernetes 声明式 API 的设计哲学。
Operator 是在 Kubernetes 基础上通过扩展 Kubernetes API,用来创建、配置和管理复杂的有状态应用,如分布式数据库等。Operator 基于 Kubernetes 1.7 版本以来引入的自定义控制器的概念,在自定义资源和控制器之上构建,同时又包含了应用程序特定的领域知识。实现一个 Operator 的关键是 CRD(自定义资源)和 Controller(控制器)的设计。
Operator 站在 Kubernetes 内部视角,为应用的云原生化打开了新世界的大门。自定义资源可以让开发人员扩展添加新功能,更新现有的功能,并且可以自动执行一些管理任务,这些自定义的控制器就像 Kubernetes 原生的组件一样,Operator 可以直接使用 Kubernetes API 进行开发,也就是说他们可以根据这些控制器编写的自定义规则来创建和更改 Pods / Services、对正在运行的应用进行扩缩容。
简单来讲就是RocketMQ Operator已经准备好了全套我们部署RocketMQ集群所需要的东西。同时在部署完成之后,当节点重生的时候,RocketMQ Operator还会对新的Broker节点进行命名并且自动配置好相应的NameServer IP List
这就非常适合明明是程序员却非要折腾什么原生云同时又并不具备特别丰富的开发运维经验的这类人 (就是我)
RocketMQ Operator 官方页面在这里
rocketmq-operator 官方git在这里
文档非常详细
正式开始
首先我们先来到树莓派集群的Master节点主机上(零号机)
来从git上拉取整个rocketmq-operator 项目下来
git clone https://github.com/apache/rocketmq-operator.git
接着我们盯着这个画面
几分钟过去了 毫无进展 习以为常的出师不利
这个就是众所周知的网络原因了
解决办法可以从国内git源去拉取,但是首先还要传上去,反正很麻烦
所以我们直接下载了这个zip包,然后从本地电脑同步到了零号机里面
同步成功就可以看到了,这时候unzip一下
看着文件夹最后的master不顺眼 重命名一下
然后进入到rocketmq-operator文件夹下
按照官网上说的,这个时候只要 ./install-operator.sh 执行一下就可以完成第一步的 rocketmq-operator 安装了
然而事实并不简单,我们先打开这个install-operator.sh看一眼
可以看到其中的内容就是依次执行了以上YAML的内容
先说明一下红框外的其他文件
他们就是rocketmq-operator操作权限用到的RBAC那一套,这里没有问题
问题就出在了红框内的这个文件中
打开deploy/operator.yaml去看一眼
vi deploy/operator.yaml
定位到 image 这里可以看到下面的镜像
然后来到docker hub上面去看一眼
可以看到这又是一个不支持arm64的镜像
所以直接运行 ./install-operator.sh 就直接报错了
由于目前还没有搭建私有镜像仓库
因此这里我们是直接拉取下来apacherocketmq/rocketmq-operator:0.3.0-snapshot之后本地重新构建成了linux/arm64/v8平台镜像又提交到了docker hub上去
包括后面会遇到的其他几个YAML文件也有同样的问题也是这样处理的
用这里的镜像就可以
https://hub.docker.com/u/weiweiplus
我们先把刚才那个文件中的镜像替换为这个
weiweiplus/rocketmq-operator-arm64:0.3.0-snapshot
保存修改之后就可以去执行 install-operator.sh 了
cd rocketmq-operator
./install-operator.sh
执行成功之后查看一下pod
kubectl get pods
rocketmq-operator成功运行起来了
接下来我们查看一下example这个文件夹下的内容
cd example/
rocketmq_v1alpha1_rocketmq_cluster.yaml 这个文件的内容就是我们利用刚装好的rocketmq-operator来创建RocketMQ集群用到的YAML了
我们可以通过vi编辑这个文件,修改一些配置然后通过kubectl apply -f来执行
kubectl apply -f example/rocketmq_v1alpha1_rocketmq_cluster.yaml
也可以从git上找到这个文件复制下来本地来修改,最后通过Kubernetes Dashboard里面的导入YAML来执行
example/rocketmq_v1alpha1_rocketmq_cluster.yaml
这里需要注意的是除了修改配置相关的内容,还要把镜像地址也改过来
修改后的内容如下
apiVersion: v1
kind: ConfigMap
metadata:
name: broker-config
namespace: default
data:
# BROKER_MEM sets the broker JVM, if set to "" then Xms = Xmx = max(min(1/2 ram, 1024MB), min(1/4 ram, 8GB))
BROKER_MEM: " -Xms512M -Xmx512M -Xmn128m "
broker-common.conf: |
# brokerClusterName, brokerName, brokerId are automatically generated by the operator and do not set it manually!!!
deleteWhen=04
fileReservedTime=48
flushDiskType=ASYNC_FLUSH
# set brokerRole to ASYNC_MASTER or SYNC_MASTER. DO NOT set to SLAVE because the replica instance will automatically be set!!!
brokerRole=ASYNC_MASTER
---
apiVersion: rocketmq.apache.org/v1alpha1
kind: Broker
metadata:
name: broker
namespace: default
spec:
# size is the number of the broker cluster, each broker cluster contains a master broker and [replicaPerGroup] replica brokers.
size: 2
# nameServers is the [ip:port] list of name service
nameServers: ""
# replicaPerGroup is the number of each broker cluster
replicaPerGroup: 1
brokerImage: weiweiplus/rocketmq-broker-arm64:4.5.0-alpine-operator-0.3.0
imagePullPolicy: IfNotPresent
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1024Mi"
cpu: "500m"
allowRestart: true
# storageMode can be EmptyDir, HostPath, StorageClass
storageMode: StorageClass
# hostPath is the local path to store data
hostPath: /data/rocketmq/broker
# scalePodName is [Broker name]-[broker group number]-master-0
scalePodName: broker-0-master-0
# env defines custom env, e.g. BROKER_MEM
env:
- name: BROKER_MEM
valueFrom:
configMapKeyRef:
name: broker-config
key: BROKER_MEM
# volumes defines the broker.conf
volumes:
- name: broker-config
configMap:
name: broker-config
items:
- key: broker-common.conf
path: broker-common.conf
# volumeClaimTemplates defines the storageClass
volumeClaimTemplates:
- metadata:
name: broker-storage
spec:
accessModes:
- ReadWriteOnce
storageClassName: nfs-rocketmq
resources:
requests:
storage: 1Gi
---
apiVersion: rocketmq.apache.org/v1alpha1
kind: NameService
metadata:
name: name-service
namespace: default
spec:
# size is the the name service instance number of the name service cluster
size: 2
# nameServiceImage is the customized docker image repo of the RocketMQ name service
nameServiceImage: weiweiplus/rocketmq-nameserver-arm64:4.5.0-alpine-operator-0.3.0
# imagePullPolicy is the image pull policy
imagePullPolicy: IfNotPresent
# hostNetwork can be true or false
hostNetwork: true
# Set DNS policy for the pod.
# Defaults to "ClusterFirst".
# Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'.
# DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy.
# To have DNS options set along with hostNetwork, you have to specify DNS policy
# explicitly to 'ClusterFirstWithHostNet'.
dnsPolicy: ClusterFirstWithHostNet
# resources describes the compute resource requirements and limits
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1024Mi"
cpu: "500m"
# storageMode can be EmptyDir, HostPath, StorageClass
storageMode: StorageClass
# hostPath is the local path to store data
hostPath: /data/rocketmq/nameserver
# volumeClaimTemplates defines the storageClass
volumeClaimTemplates:
- metadata:
name: namesrv-storage
spec:
accessModes:
- ReadWriteOnce
storageClassName: nfs-rocketmq
resources:
requests:
storage: 1Gi
---
apiVersion: rocketmq.apache.org/v1alpha1
kind: Console
metadata:
name: console
namespace: default
spec:
# nameServers is the [ip:port] list of name service
nameServers: ""
# consoleDeployment define the console deployment
consoleDeployment:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: rocketmq-console
spec:
replicas: 1
selector:
matchLabels:
app: rocketmq-console
template:
metadata:
labels:
app: rocketmq-console
spec:
containers:
- name: console
image: weiweiplus/rocketmq-console-arm64:4.5.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: rmq-console-service
namespace: default
spec:
ports:
- name: rocketmq-console-port
port: 8080
protocol: TCP
targetPort: 8080
selector:
app: rocketmq-console
---
文件的内容首先创建了一个ConfigMap
其中BROKER_MEM: ” -Xms512M -Xmx512M -Xmn128m “是需要注意一下的 如果JVM的Xmx大于当前主机的内存时就会出现OOMkilled的错误导致节点无法启动
接着创建Broker的服务 其中的size指的是集群中有几组主/从的组合,可以理解为有几个主。我们需要两组所以size设置为了2
下面的replicaPerGroup说的是每一个组合中有几个从,我们要做双主双从,因此每一个主/从组合中只有一个从,这里replicaPerGroup设置为了1
到了下面volumeClaimTemplates这里把storageClassName设置为我们文章最开头准备的nfs-rocketmq这个存储类
下面创建NameService服务 这里的size指的就是集群中有几个NameService节点,此处设置为了2
hostNetwork: true这里给NameService节点设置了主机网络,同时配套使用dnsPolicy: ClusterFirstWithHostNet
这样一来我们的NameService就拥有了和树莓派主机同样的集群外部网络服务了
同样下面volumeClaimTemplates这里storageClassName设置为nfs-rocketmq
其实NameService本身应该是无状态服务,这里只是挂载存储了日志文件
Console这里就是部署Deployment用了1个节点提供可视化服务的应用
我们在最后加了一个ClusterIP Service 给上面的Console提供了网络服务
就这些东西了
稍微注意一下每一个服务用到的镜像修改成了arm64的
其他的像是 memory: “512Mi” cpu: “250m” 这种节点上的资源限制根据具体情况调整就好,目前来看树莓派4的8G内存也很吃紧,所以都没敢调的太大。
执行完整个YAML之后,K8s集群就会在RocketMQ Operator的法力加持下完成整个部署
我们可以再顺手创建一个Ingress指向刚才最后创建的那个rmq-console-service
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: rmq-console-ingress
namespace: default
spec:
defaultBackend:
service:
name: rmq-console-service
port:
number: 8080
rules:
- host: raspi.rocketmq.console
http:
paths:
- backend:
service:
name: rmq-console-service
port:
number: 8080
path: /
pathType: Prefix
本地电脑的hosts 再来设置一下刚刚弄的假域名
192.168.31.82 raspi.rocketmq.console
这样通过访问 raspi.rocketmq.console 就能看到RocketMQ集群的console了
Kubernetes Dashboard 查看一下POD
也都成功启动了起来
我们的双主双从rocket MQ 就算是部署成功了 可喜可贺
至于从开发应用中怎么去连接到这个MQ
刚才部署NameService的时候给两个节点开了hostNetwork
从上面POD节点的图里能看到name-service-0和name-service-1这两个nameservice的节点地址分别是192.168.31.84和192.168.31.83
这就是两台树莓派主机的地址了,加上9876这个端口就可以连接MQ了
同时由于这两个节点开启了hostNetwork,他们就拥有了和主机一样的DNS解析
也就是说通过主机名就可以直接在集群内部访问
集群内的SpringBoot的配置可以这样写
rocketmq.name-server=raspberrypi.4:9876;raspberrypi.3:9876
rocketmq.producer.group=producer
rocketmq.producer.send-message-timeout=3000
为了防止NameService重新部署后的主机不固定,我们还可以为NameService的服务再开启一个Service提供集群内部的网络,同时也为几个NameService节点提供了一个固定的统一dns域名,并实现了负载均衡
#集群内访问nameService
apiVersion: v1
kind: Service
metadata:
name: rmqnamesrv-service
namespace: default
spec:
ports:
- name: main
port: 9876
protocol: TCP
targetPort: 9876
selector:
app: name_service
这样一来就可以通过K8s环境的CoreDNS提供的域名来访问到这个服务了,格式如下
rmqnamesrv-service.default.svc
只不过通过rocketmq-operator部署的节点并没有hostname和subdomain
这可以去找到在上篇文章用StatefulSet创建的redis的那几个pod,打开YAML内容对比一下
所以我们这里直接使用Service的内部域名去访问网络,而不是通过pod域名
集群内的SpringBoot的配置可以这样写
rocketmq.name-server=rmqnamesrv-service.default.svc:9876
rocketmq.producer.group=producer
rocketmq.producer.send-message-timeout=3000
可以用刚才启动的console当做一个跑在集群内的JAVA服务来测试一下域名是否有效果
我们修改了console配置中的环境变量值
重新启动console之后工作一切正常
并且NameServerAddressList这里的内容已经变成rmqnamesrv-service的固定域名了
结束