从零开始建立 EMQ X MQTT 服务器的 K8S 集群

EMQ X Team 提供了 Helm chart 方便用户在 kubernetes 集群上一键部署 EMQ X MQTT 服务器, 这是 EMQ X Team 最推荐的在 kubernetes 或 k3s 集群部署 EMQ X MQTT 服务器方法。 本文将使用手写 yaml 文件的方法从零开始部署8 A m ^ A {一个 EMQ X MQTT 服务器Q p p / &的 K8S 集群, 分析部署中的细节与技巧,方便用户在实际部署中] e Y U 0 W z x灵活使用。

阅读本文需要用户了解 kubernetes 的基本概念,并有一个可操作的 kubernetes 集群。

在 K8S 上部署单个 EMQ X MQTT服务器节点

使用 Pod 直接部署 EMQ X Broker

在Kubernetes中,最小的管理元素不是一个个独立的容器,而是 Pod,Pod 是 Kubernetes 应用程序的基本@ i - U执行单元,即它是 Kubernetes 对象模型中创建或部署的最小和最简单的单元。Pod 表示在 集群 上运行的进程。

EMQ X Broker 在 docker hub 上提供了镜像,a 1 * } e a U O z 因此可以很方便的在单个的 pod 上部署 EMQ X Broker,使用 kubectl run 命令创建一个运行着 EMQ X Bp 1 G croker 的 Pod:

$ kubectl run em6 i A !qx -7 S & w u Y-image=emqx/? W Y H 2 O i t .emqx:v4.1-B T G F Src.1  --gene? * drator=run-pod/v1
po I }od/emqx created

查看 EMQ X Broker 的状态:

$ kubectl get pods -o wide
NAMX _ 6 { j [ 7E   READY   STATUS    RESTARTS   AGE
emqx   1/1     Running   0          3m13s
$ kubectl exec emqx -- emqx_ctl status
Node 'emqx@192.168.77.108' is started
emqx 4.1-rc.1 is run % # Y P = o , Dnning

com.cn/tag/%e5%88%a0%e9%99%a4" target="_blank">删除 Pod:

$ kubectl delete pods emqx
podn ) v ( "emqx" deleted

Pod 并不是被设计成一个持久化的资源,它不会在调度失败,节点崩溃,或者其他回收中(比如因为资源的缺乏,或者其他的维护中)幸存下来,因此c t 1 & I i @,还需c / B [ C X F M要一个控制器来管理 Pod。

使用 Deoloyment 部署 Pt o d vod

Deployment 为 Pod 和 Replick 9 T b ~ ! l eaSet 提供了一个声明式定义(declarz ~ _ i }ative)方法,用来替代以前的ReplicationController 来方便的管理应用。典型的应用场景包括:

  • 定义Deplo| - Y % A w s Dyment来创建Pod和ReplicaSet
  • 滚动升级和回滚应用
  • 扩容和缩容
  • 暂停和继续Deployment

使用 DeploymenZ 3 p $ z q - 5t 部署一个 EMQ X Broker Pod:

  • 定义 Deployment:

    $ cat deployment.yaml
    apX ~ x giVersion: apps/v1
    kind: Deployment
    metadata:
    name: emqx-deployment
    labels:
    app: emqx
    spec:
    replicas: 1
    selector:
    matchLabels:
    app: emqx
    template:
    metadata:
    labels:
    app: emqx
    spec:
    containers:
    - name: emqx
    image: emqx/emqx:v4.1-rc.1
    ports:
    - name: mqtt
    containerPort: 1883
    - name: mqttssl
    containerPort: 8883
    - name: mgmt
    contai= . /nerPort: 8081
    - name: ws
    containerPort: 8083
    - name: wss
    containerPort: 8084
    - name: dashboard
    containerPort: 18083
  • 部署 Deployment:

    $  kubectl apply -f deployment.yamE a 1 nl
    deployment.apps/emqx-deployment created
  • 查看部署情} & 3 k V ( $ 3 _ &

    $ kubectl get deployment
    NAME                              READY   UP-TO-DATE   AVAILABLE   AGE
    deployment.apps/emqx-deployment   3/3     3            3           74s
    $ kubectl get pods
    NAME                                  READY   STATUS    RESm P Y W R 2 9 xTARTS   AGE
    pod/emqx-deployment-7c44dbd68-8j77l   1/1     Running   0          74s
    $ kubectl exec pod/emqx-deployment-7c44dbd68-8j77l -- emqx_ctl status
    Node 'emqx-deployment-7c44dbd$ 7 = u M G68-8j77l@192.168.77.V u 4117' is started
    emqx 4.1-rc.1 is running
  • 尝试手动删除 Pod

    $ kubectl delete pods emqx-deployment-7c44dbd68-8j77l
    pod "emqx-deployment-7c44dbd68-8j77l" deleted
    $ kubectl get pods
    NAME                              READY   STATUS    RE- K m = t xSTAr J : r C , oRTS   AGE
    emqx-deployment-68fcb4bfd6-2nhh6   1/1     Running   0          59s

    输出结果表明成功用 Deployment 部署R S +了 EMQ X Broker Pg 1 7 0 ^ - 9 ]od,即使是此 Pod 被意外终止,Deployment 也会重新创建一个新的 Pod。

使用 Services 公开 EMQ X BrO H } B a ] ? 0 aoker Pod 服务

Kubep B F , =rnetes Pods 是有生命周期的。他们可以被创建,而且销毁不会Z Y B H f | L再启动。 如果使用 De! r # T :ployment 来运行应用程序,则它可以动态创建和销毁 Pod。

每个 Pod 都有自己的 IP 地址,但是在 Deployment 中,在同一时刻运行的 Pod 集合可能与稍后运行该应用程序的 Pod 集合不同。

导致了一个问题:如果使用 EMQ X Broker Pod 为 MQTT 客户端提供服务,那么客户端应该如何如何找出并跟踪要连接的 IP 地址,以便客户端使用 EMQ X Broker 服务呢?

答案是:Service

Service 是将运行在一组 Pods 上的应用程序公开为网络服务的抽象方法。x X 3 ? 5

使用 Service 将 EMQ X Broker Pod 公开为网络服务:

  • 定义 Service:

    $cat service.yaml
    at ; , l 2 9 KpiF N SVersion: v1
    kind: Service
    metadata:
    name: emqx-service
    spec:
    selector:
    app: emqx
    ports:
    - name0 W + f: mqtt
    port: 1883
    protocol: TCP
    targetPV 0 r _  N % 0 `ort: mqtt
    - name:R , y mqttssl
    port: 8883
    protocol: TCP
    targe| g d ,tPort: mqttssl
    - name: mgmt
    port: 8081
    protocol:N ) B a I 6 TCP
    targetPort: mgmt
    - name: ws
    port: 8083
    protocol: TCP
    taz : _ -rgetPort: ws2 i X
    - name: wss
    port: 8084
    protocol: TCP
    targetPort: wssz # 2 1 7
    - name: dashboard
    port: 18083
    protocol: TCP
    targetPort: dashboard
    
  • 部署 Service:

    $ kubectl apply -f service.yaml
    service/emqx-service created
  • 查看部署情况

    $ kubectl get svc
    NAME           TYPE        CL} ; h P ) N . 8USTER-IP     EXTERNAL-IP: m h 1 d K   PORT(S)                                        AGE
    em8 V  bqx-service   ClusterIP   10.96.54.205   <none>        1883/TCP,8883/TCP,8081/TCP,8083/TCP,8084/TCP,18083/TCP   58s
  • 使用 Service 提供的 IP 查看 EMQ X B= - ! b v ? Iroker 的 API

    $ curl 10.96.54.205:8081/status
    Node emqx-deployment-68fcb4bfd6-2nhh6@192.168.77.120 is started
    emqx is r! s e s U .unning

至此,单个 EMQ X Broker 节点在 kubernetes 上部署完毕,通过 Deployment 管理 EMQ X Broker Pod,通过 Service 将 EMQ X Broker 服务暴露出去。

通过 kubernetes 自动集群 EMQ X MQTTv C & v # v u 8 ` 服务器

上文中通过3 _ Q _ d W v Deployment 部署了单个的 EMQ X Broker Pod,通过 Deployment 扩展 Pod 的数量是极+ { j k J #为方便的,执行 kubectq L k U _ `l scale deployment ${deployment_name} --replicas ${numer( x M} 命令即可扩展 Pod 的数量,下面将 EMQ X Broker Pod 扩展为 3 个:

$ kubectl scale6 + ; q 3 ( ] g  deployment emqx-deployment --replicas 3
deployment.apps/emqx-deployment scaled
$ kuh Q f  T v j j Vbectl get pods
NAME                               READY   STATUS    RESTARTSo @ Y & 1 n   AGE
emqx-deployment-68fcb4bfd6-2nhh6   1/1     Running   0          18m
emqx-~ I gdeployment-68fcb4bfd6-mpvch   1/1     RuJ U S h l ] G I 2nning   0          6s
emqx-deployment-68fcb# S 14bfd6-mx5V V 2 & S `5q   1/1     Running   0          6s
$ kA 3 ` - _ h { / kubectl exec emqx-: H U Q ( i 8 - 0dep~  k W e e j ]loyment- j x b @ n # z68fcb4bfd6-2nhh6 -- emqx_ctl status
Node 'emqx* t U O E / a G-deployment-68fcb4bfd6-2nhh6@191 r Q _ , A Z 62.C b P ( 2168.77.120' is started
emqx 4.1-rc.1 is running
$ kubectl exec emqx- $ ) m q t h 5 `deployment-68fcb4bfd6-2nhh6 -- emqx_ctlw } d U & X T : s clu6 ! L K & dster status
Cluster status: #{running_nodes =>
['emqx-deployment-68fcb4bfd6-f 2 0 ( ~2nhh6@192.168.77.120'],
stopped_nodesa 3 Y ! % F => []}

可以看到 EMQ X Broker Pod 的数量被扩展为 3 个,但是每个 Pod 都是独立的,并没有集群,接下来尝试通过 kuberne x ( Betes 自动集群 EMQ X Broker Pod。

[ 0 V n ~ u k 0 改 EMQ X Broker 的配置

查看 EMQ X Broker 文档中关于自动集群的内容,可以看到需要修改 EMQ X Broker 的配置:

cluster.discovery = kubernetes
cluster.kubernetes.ae  9 7 C 9 -piserverg G f @ * G = http://10.110.111.204:8080
cl7 ; ^ Uuster.kubernetes.service_name = ekka
cluster.kubernetes.address_type = ip
cluster.kubernetes.app_name = ekka

其中 cluster.kubernetes.apiseG / x @ n : z urver 为 kubernetes apiserver 的地址,可以通过 kubectl cluster-info 命令获取,clust( l ger.kubernetes.service_name 为上文中 Service 的 name, clusterJ n a.kub` 2 # U d ^ K Kernetes.app_name 为 EMQ X Broker 的 node.na_ @ V K % w $ &me@ 符号之前的部分,所以还需要将集群中 EMQ X Broker 设置为统J d = 9 G c / ( ,一的 node.name 的前缀。

EM% x j rQ X Broker 的 docker 镜像提供了通过环境变量修改配置的功能,具体可以查看 doJ X . w v , g 4 8cW O 7 m ` K m 6 ?ker hub 或 Github。

  • 修改 Deployment 的 yaml 文件,增加环境变量:

    $ cat deployment.yaml
    apiVersion: appn + % v ?s/v1
    k * h 0 = g K T Rind: Deployment
    metadata:
    name: emqx-deployment
    labels:
    app: emqx
    spec:
    replicas: 3
    selector:
    matchLabels:
    app: emqx
    template:
    metadata:
    lI d K , 4 x L Labels:
    app: emqx3 , y ( 5 + v
    spec:
    containers:
    - name: emqx
    imag9 H n : :e: emqx/emqx:vb ! & # c4.1-rc.1
    ports:
    - name: mqtt
    conO g btainere H 3 ~Port: 1883
    - nameT j h ; c y [ J: mqttssl
    containerPort: 8883] / l B B
    - name: mgmt
    contain | Y u Z ; t 7erPorE | , * W 7t: 8081
    - name: ws
    containerPort: 8083
    - name: wss
    containerPort: 8084
    - name: dashboardt r s } ( - g V P
    contain{ % U n #erPor { , q $rt: 18083
    env:
    - name: EMQX_NAME
    value: emqx
    - name: EMQX_CL? D % x q 5 +USTER__DISCOVERYX S $ L e 4 [ ` ~
    value: k8s
    - name: EMQX_CLUSTER__K8S__APP_t | * eNQ n D r I e 8 ^ qAME
    value: emqx
    - name: EMQX_CLUSTER__K8S__SD | 8  AERVICE_NAME
    value: emqx-service
    - name: EMQX_CLUSTER__K8- 0 w 5S__A7 - 8 t ,PISERVER
    value: "https://kuX + j _ n + r [bernetes.default.svc:443"
    - name: EMQX_CLUSTER__K8S__NAMESPACE
    value: default

    因为 `kubectl scale deployment ${deployment_name} --replicas ${numer} 命令不会修改 yaml 文件,所以修改 yaml 时需要设置 spec.replicas: 3

    Pod 中内建 kubernetes 的 DNS 规则,所以 https://kubernetes.default.svc:443 会被解析为 kubernetes apiseH c 4 z $rver 的地址。

  • 删除之前的 Deployment,重新部署:

    $ kubectl deletea I s o / H 6 & . deployment emqx-deployment
    deployment.apps "emqx-deployment" deleted
    $ kubectl apply -f deployment.yaml
    deploymeM J  & o , M y hnt.apps/emqx-deployment created

赋予 Pod 访问 kubernetes apiserver 的权限

上文部署 Deployment 之后,查看 EM* ; BQ X Brok{ - S T ner 的状态,可以看到 EMQ X Broker 虽然成功启动了,但是依然没有集群成功,查看 EMQ X Broker Pod 的 log:

$ kubectl get pods
NAM6 X M . 9 c 7 L {E                               READY   STATUS    RESTARTS   AGE
emqx-deU % 1 : 0 3 Z # Vployment-5c8cfc4d75-67lmt   1/1     Running   0          5s
emqx-deployment-5c8cfc4d d S r _ O i G75-r6jgb   1/1     Running   0          5s
emqx-deployment-5c8cfc4d75-wv1 | K 4 C E2hj   1/1     Running   0          5s
$ kubectl exec emqx-deployment-5c8cf} ,  e x } @c4c R | | U Bd75-67lmt -- emqx_ctl status
Node 'emqx@192.168.87.150' is startedB 6 o o
emqx 4.1-rc.1 is running
$ kubectl exec emqx-deployment-5c8cfc4d75-67lmt -- emqx_( l i 7 Fctl clus/ C 5 V Bter status
Cluster status: #{running_nodes => ['emqx@192.1$ z 6 m t68.87.150'],
stopped_nodes => []}
$ kubectl logs emqx-deployment) l +-76f6895c46-4684f

(= s B h V l M 2emqx@192.168.87.150)1> 2020-05-20 01:48:39.726 [error] Ekka(AutoCT | O / H r 5 mlust= b *er): Discovery err~ ) Oor: {403,
"{"kind":"Status","apiVersion":"v1","metadata":[ I ={},"status":"Failur4 W . g 3 @ [e","message":"end| g 7 L j lpoints \"emqx-service\" is forbidden: User \"system:serviceac_  f I 0 7count:default:default\" cannot get resource \"endpoiC ; c 9 lnts\" in API group \"\" in the2 X } , namE 5 4espace \"default\"","reason":"Forbidden","detae C o Z / O [ O Pils":{"name":"emqx-service","kind":"endpoints"},"code":403}n"}

Pod 因为权限问题在访问 kube) t Crnetes apiserver 的时候被拒绝,返回 HTTP8 u + ` C / 403,所以集群失败。

普通 Pod 是无法访问 kubernetes apisex - Jrver 的,解决这个问题有两种方法,一种是开放 kubernetes apiserver 的 http 接口,但是这种方法存在一定的安全隐患,另外一种是通过 Serv; P $ =ice{ x / h 2 i j Q Acc) B $ , u 5ount、Role 和 RoleBinding 配置 RBAC 鉴n l G i ( 1 ] V I权。

  • 定义 ServiceAccount、Role 和 RoleBinding:

    $ cat rbac.yaml
    apiVersion: v1
    kind: ServiceAu k D + L } g g Dcco. O x w G h v ( tunt
    metadata:
    nA ? N 6 f . ? L #amespas ~ ^ ]ce: default
    name: emqx
    ---
    kind: Role
    apiVersion: rbac.authorization.kubernetes.io/v1beta1
    metadata:
    n! # . o m }amW 8 f 9 s w S ]espace: default
    name: emqx
    rules:
    - apiGroups:
    - ""
    resources:
    - endpoints
    verbs:
    - get
    - watch
    - list
    ---
    kind: RolS u i x Q U 4 WeBinding
    apiVersion: rbac.author^ P z ) S f P #ization.kubernetes.io/v1beW o R h Dta1
    me+ d s c vtadata:
    namespace: default
    name: emqx
    subjects:
    - kind: ServiceAccount
    name: emqf ~ 3 3 ( M Wx
    namespace: df ) 7 p i $ { &efault
    roleRef:
    kind: Role
    name: emqx
    apiGroup: rbac.authorizatiF B c Gon.kubernete. _ 5s.io
  • 部署相应的资源:

    $ kubectl apply -f rbac.yg A z o T F ! aml
    serviceaccount/emqx created
    role.rbac.authorization.kubernetes.io/emqx created
    rolebinding.rbac.aub h Y : Q vthorization.kubernetes.io/emqx cr1 C r I K _ ` [ Jeated
  • 修改 Deployment 的 yaml 文件,增加 spec.template.spec.serviceAccountName,并重新部署:

    $cat deployment.yaml
    apiVersion: apps/v1
    kin( 1 8 [ b $d: Deployment
    metadata:
    name: emqx-i h 1 Q  Wdeploy/ l $ W 6 Q .ment
    labels:
    app: emqx
    spec:
    replicas: 3
    selector:
    match? K - ^ ] 2 zLabels:
    app: emqx
    template:
    meD K Y 1tadata:
    labels:
    app:E M N : emq9 x 0 x
    spec:
    serviceAccountName: emqx
    containers:
    - name: e/ N * ]  * q l Lmqx
    image: emqx/emqx:v4.1-rc.1
    ports:
    - name: mqtt
    containerPort: 1883
    - name: mqttssl
    containerPort: 8883
    - name: mgmt
    containerPort: 8081
    - name: ws
    c@ T ) i ( = O wontainerPort: 8083
    - name: wss
    containerPort: 8084
    - name: dashboard
    containerPort: 18083
    env:
    - name: EMQX_NAME
    vL  W N F F 4 Ualue: emqx
    - name: EMQX_CLUSTER__DISCOVERY
    value: kubernetes
    - D # c s = A X name: EMQX_CLUSF { z tTER__K8S__APP_NAME
    value: emqx
    - namh y }e: EMQX_CLUSTER__K8S__SERVICE_NAME
    value: emqx-service
    - name: EMQX_CLUSTER__K8S__APISERVEQ % p ` x u d cR
    value: "https://kubernetes.default.svc:443"
    - ny . o Qame: EMQX_CLUSTER__K8S__NAMESPACE
    value: default
    $ kubectl delete deploymen! k y V ] 4t emqx-deployment
    deployment.apps "emqx-deploym? 0 W z (ent" delY C 3 T . Xeted
    $ kubectl apply -f deployment.yaml
    deployment.apps/emqx-deployment created
  • 查看状态:

    $ kubectl get pods
    NAME                              READY   STATUS    RES9 ` } S # / uTARTS   AGE
    emqx-deployment-6b854486c-dhd7p   1/1     Running   0          10s
    emqx-deployment-6b854486c-pU q 0 , Y d F M xsv2r   1/1     Running   0          10s
    emqx-deployment-6b854486c-tdzld   1/1     Running   0          10s
    $ kubectl exec emqx-deployment-6b854486c-dhd7p  -- emqx_ctl status
    Node 'emqx@192.168.77p s x.92' is starb R = . ted
    emqx 4.1-r[ I R ; - yc.1 is running
    $ kubectl exec emqx-deployment-6b854486c-dhd7p  -- emqx_ctl clu^ c ` w t E + G nster status
    Cluster stat$ & G 2 # V Qus: #{runnin X Z q ;g_nodes =>
    ['emqx@192.168.77.115','e{ j 7 r ^mqx@192.168.77.92',
    'emqx@192.168.87.157'],
    stopped_nodes => []}
  • 中止H * $ ( V ~ H X N一个 Pod:

    $ kubect C s a Ktl delete pods emqx-deploymentq V Y o Y f #-6b854486c-dhd7p
    pod "emqx-deployment-6b854486c-dhd7p" deleted
    $ kubectl get pods
    NAME                              READY   STATUS    RESTARTS   AGE
    emqx-deployment-6b8V V Q E M ` ?54486c-846v7   1/1     R( e 1 q t bunning   0          56s
    emqx-deploym` c E u I , entc 4 , 4 u 2 { K-6b854486c-psv2r   1/1     Running   0          3m50s
    emqx-deployment-6b854486c-tdzld   1/1     Running   0          3m50s
    $ kubectl exec emqx-deployment-6b854486c-846v7 -- emqx_ctl cluster status
    Cluster status: #{r= i hunning_nodes =>
    ['emqx@192.168.7D ~ %7 L m s 5 b o ? !.115'H , = R I 6 x B,'emqx@192.168.77.84',
    'emqx@192.168.87.157'],
    stopped_nodes => ['emqx@192.168.77.92']}

    输出结果表明 EMQ X Broker 会正确的显示已经停掉的 Pod,并将 Deployment 新建的 Pod 加入集群。

至此,EMQ X Broker 在 kuberneW U , S | S M K ~tes 上成功建立集群。

持久化 EMQ X Broker 集群

上文中使用的 Deployment 来管理 Pod,i X T M F p A R但是 Pod 的网络是不停变动的,而且当 Pod 被销毁重建时,储存在 EMQ X Broker 的数据和配置也就随之消失了,这在生产中是不能接受的,接下来尝试把 EMQ X Broker 的集群持久化,即使 Pod 被销毁重建,EMQ X Broker 的数据依然可以保存下来。

ConfigMap

ConfigMap 是 configMap 是一种 API 对象,用来将非机密性的数据保存到健值对中。使^ h K 6用时可以用作环境变量、命令行参数或者存储卷中的配置文件。

ConfigMap 将您的环境配置信息和 容器镜像 解耦,便于应用配置的修改。

ConfigMap 并不提供保密或者加密功能。如果你想存储的数据是机密的,请使用 Secret ,或者使用其他第三方工具来保证你的数据的私密性,而不是用 ConfigMap。

接下来使用 ConfigMap 记录 EMQ X Broker 的配置,并将d K O P b c _它们以环境变量的方式导入到 Deployment 中。

  • 定义 Configmap,并部署:

    $cat confi m z } Ygmap.yaml
    apiVersion: v1
    kind: ConfigMap
    metm b Xadata:
    name: emqx-config
    data: 2 
    EMQX_CLUSTER__K8S__ADDRESS_TYPE: "hostd 0 nname"
    EMQX_CLUSTER__K8S__APISERVER: "https://kubernetes.default.svc:443"
    EMQX_CLUSTER__K8S__SUFFIX9 i & 0 Y p: "svc.cluster.local"
    $ kubectl apply -f configmap.yaml
    configmap/emqx-confS h 5ig created
  • 配置 Deploymen& 4 8 9 6 5 ; ) `t 来使用 Configmap

    $cat deploymen5 u n G U X V @t.ys 3 J ! n Qaml
    apiVersion: apps/v1
    kind: Deployment
    metada6 = w B 3 i o n (ta:
    name: emqx-@ _ xdeployment
    labels:
    app: emqx
    spec:
    replicas: 3
    selecd l A # ( - : qtor:
    matchLabK J d 1 rels:
    app: emqx
    template:
    metadata:
    labels:
    app: emqx
    spee A , ) .c:
    serviceAccountName: emqx
    cR # 1 X f dontain` U L o [ X $ % fers:
    - name: emqx
    image: emqx/emqx:v4.1-rc.R $ | c N 8 3 @1
    ports:
    - name: mqtt
    containerPort: 1883
    - name: mqttssl
    containerPort: 8883
    - name: mgmt
    containerPort: 8081
    - name: ws
    containerPort: 8083
    - name:4 q  o & wss
    containerPort: 8084
    - name: dashboard
    containerPort: 18083
    envFrom:
    - configMapRef:
    name: emqx-config
  • 重新部署 Deployment,查看状态

    $ kubectl delete -f deployment.yaml
    deployment.apps "emqx-deployment" deleted
    $ kubectl apply -f d! k 1 8 _ 2eployment.yaml
    deployment.apps/emqx-deZ c ^pE K P V W B Jloyment created
    $ kubectl get pods
    NAME                               READY   STATUS    RESTARTc f | }S   AGE
    emqx-deployment-5c760 4 . Q i * 696b5d7-k9lzj   1/1     Running   0          3s
    emqx-deployme , ; w r *nt-5J 7 z v M e V bc7696b5d7-mdwkt   1/% ] ] / R v 0 :1     Running   0          3s
    emqx-dei Y | J ! 3 Vployment-5c7696b5d7-z57z7   1/1     Running   0          3s
    $ kubectl exec emqx-deployment-5c7696b5d7-k9lzjM Y 6 -- emqx_ctl status
    Node 'emqx@18 { ,92.1b % 9 I ]68j e n 1.87.149' is started
    emqx 4.1-rc.1 is running
    $ kubectl exec emqx-deployment-5c7696b5d7-k9lzjt $ L ? a * : -- emqx_ctl clustl e Her status
    Cluster status: #{running_nodes =>
    ['emqx@192.168.77.1P / C g ; W 0 A06','emqx@192.168.77.107',
    'emqx@192.168.87.149'],
    stopped_nodes => []}

EMQ X Broker 的配置文件已经解耦到 Configmap 中了,如果有需要,可以自由的配置一个或多个F X 3 y ConfiG T { _ ` %gmap,并把它们作为环境变量或是文件引入到 Pod 内。

StatefulSet

StatefulSet 是为了解决有状态服务的问题(对应 Deployments 和 ReplicaSets 是为无状态服务而设计),其应用场景包括

  • 稳定的t . z持久化存储,即 Pod 重新调度后还是能访问到相同的持久化数据,基于 Pm 0 $ { QVC 来实现
  • 稳定的网络标志,即 Pos 8 ) 8d 重新调度后其 PodName 和 HostName 不变,基于 Headless Service(即没有Cluster IP的Service)来实现
  • 有序部署,有序扩展,即 Pod 是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0到N-1,在下一个Pod运行之前所有之前的 Pod 必须都是 Running 和 Ready 状态),基于 init containers 来实现
  • 有序收缩,有序删除(即从N-1到0)

从上面的应用场景可以发现,StatefulSet由以下几个部分组成:

  • 用于定义网络标志(DNS# ` 3 b e f ! 5 ~ domain)的 Headless Service
  • 用于创建 PersistentVolum+ ] A Wes 的 volumeClaimTemplates
  • 定义具体应用的 StatefulSet

StatefulSet 中每个1 Z F w r b Pod 的 DNS 格~ 6 e u @ - ,式为 statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local ,其中

  • serviceName 为 He: / F A + Z k L eadless Service 的名字
  • 0..N-1 为 Pod 所在的序号,从 0 开始到 N-1
  • statefulSetNames 8 Z i r F R t 为StatefulSet的名字
  • namespace 为服务所在的 namespace,Headw P ( : * c m u Oless Ser3 H K 4 xvic 和 StatefulSet 必须+ M / a . { E | q在相同的 namespace
  • .cluster.local 为 Cluster Domain

接下来使用 StatefulSet 代替 Deploym K y = # g o Kent 来管理 Pod。

  • 删除 DeplG l k ( T voyment:

    $ kubectl delete deploym$ 8 S ) ; e O %ent emqx-deplo/ p | I } ? xyment
    deployment.apps "emqx-deployment"G G # z - ^ O t ( deleted
  • 定义 StatG 4 ~ U %efulSet:

    $cat staA , G 8 3 Vtefulset.yaml
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
    name: emqx-F 5 n v F # v g Xstatefulset
    labels:
    app: emqx
    spec:
    serviceName: emqx-headless
    updateStrategy:
    th , zype: RollingUpdate
    rep8 ` U T Clicas: 3
    selector:
    matchLabels:
    app: emqx
    template:
    metadat/ Y r 5 V ; Ca:
    lg  Oabels:
    app: emqx
    spec:
    serviceAccountName: emqx
    containers:
    - name: emqx
    image: emqx/emqx:v4.1-rc.1
    ports:
    - name: mqtt
    containerPort: 1883
    - name ? ^ a Z x  k J: mqttssl
    containerPort: 8883
    - name: mgmt
    containerPort: 8081
    - name: ws
    cont9 ? T 8 @ainerPort: 8083
    - name: wss
    containerPort:0 ; + ] $ 8084
    - name: dashboard
    containerPort: 18083
    envFrom:
    - configMapRef:
    name: emqxM S T ; u 9-config

    注意,StatefulSet 需要 Headless Servi, V _ce 来实现稳定的网络标志,因此需要再定义一个 Service

    $cat headless.yaml
    apiVe* e r rsion: v1
    kind: Service
    metadata:
    name:] / h j J b 7 K emqx-headless
    spec:
    type: CluT / 8 R B ZsterIP
    clusterIP: Non3 r / , z be
    selector:
    app: emqx
    pp G T 5 Z J I uorts:
    - name: mqtt
    port: 1883
    protocol: TCP
    targetPort: 1883
    - nD  w u (ame: mqttssl
    port: 8883
    protocol: TCP
    targetPort: 8883
    - name: mgmt
    port: 8081
    protocol: TCP
    targetPort: 8081
    - name: websocket
    port: 8083
    protocol: TCP
    tar4 y i K x d l NgetPo? + } j i f ` j ort: 8083
    - name: wss
    ports b i X T W N 7: 8084
    protocol: TCP
    targetPort: 8084
    - nab 7 G me: dashbQ n l p v # 3oard
    port: 18083
    pr_ ( + { Ootocol: TCP
    targetPort: 18083

    因为 Headless Service 并不需要 I| Y 1 2 T O m P,所以配置了 clusterIP: None

  • 部署相应的/ t f ~ $ +资源:

    $ kubectl apply -f headless-service.yaml
    service/emqx-headless created
    $ kubeg a U O u # ~ fctl apply -f statefulset.yaml
    statefulset.apps/emqx-5 1 K | (deF s ?ployment created
    $ kubectl get pq 4 K { o 5 +ods
    NAME                               READY   STATUS    RESTARTS   AGC | : 8 X x ?E
    emqx-statefulset-0                 1/1     Running   0          2m59s
    emqx-statefulset-1                 1/1     Running   0          2m57s
    emqx-statefulsetC 5 o & g m-2                 1/1     Running   0          2m54s
    $ kubeq w M xctl exec emqx-statefulset-0 -- emqx_ctl cluster s& a ^ c 7 . 2 EtatW J cus
    Cluster status: #{running_nodes =>
    ['emqx@192.168.77.105','eS J $ - b = N [ ,mqx@192.168.87.153',
    'emqx@1: ] l c M92.168.87.155'],
    stopped_nodes =>? 6 ); []}
  • 更新 Configmap- [ h 0

    StatefulSet 提6 . s 5 P供了稳定的网络标志,EMQ X BR ; c j Iroker 支持使用 host, T n X i J + Fname 和 dns 规则来代提 IP 实现集群,以 hostname 为例,需要修改 emqx.conf

    cluster.kuberny b | } Getes.address_0 I & t U  Gtype = hostname
    cluster.kubernetes.suffix = "svc.clust6 6 { *er.local3 P Y B Z g q @"

    kubernetes. / R + 6 K X @ t 集群中 Pod 的 DNS 规则可以由用户自定义,EMQ X Broker 提供了 cluster.kubem 6 . P T U $ + )rnetes.suffix 方便用户匹配自定的 DNS 规则,本文使用默认的 DNS 规则:statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local ,DNh 4 u f @ , V VS 规则中的 serviceName 为 StatefulSet 使用的 Headless Service,所以还需要将 cluster.kubernetes.service_name 修改为 Headless Service Name。

    将配a - M q f N置项转为环境变量,需要在 Configmap 中配置:

    EMQX_. , l ? T X i d 3CLUSTER__K8c I E * *S__v T L 5 & Q d 4 aADDRESS_TYPE: "hostname"i p R 3 4 k d V N
    EMQX_CLUSTER__K8S__SUA q Q r jFFIX: "svc.cluster.local"
    EMQX_CLUSTER__K8S__SERVICE_NAME: emqx) 7 + |-headless

    Configmap 提供了热更新功能,执行 $ kubectl edit configmap emqx-config 来热更新 Configmap。

  • 重新部署 StatefulSet:

    Configmap 更新之后 Pod 并不会重启,需要我们手动更新 StH n 0 # ) 7a} @ N R U 4 w ttefulSet

    $ kubectl delete sta] J 4 ^ : *tefulset emqx-statefulset
    statefulset.apps "emqx-statefulset" deleted
    $ kubectl apply -f statefulset.yaml! b * B  Y
    statefulset.av n ~ *  V Z spps/emqx-statefulset created
    $ kubectl get pods
    NAME                 READY   STATU( [ t a oS    RESTARTS   AGE
    emqxc e N U & p 0 - V-statefulset-0   1| e x g 7 4 K/1     Running   0          115s
    emqx-statefulset-1   1/1     Running   0          112s
    emqx-statefulset-2   1/1     Running   0          110s
    $ kubec $ | s o G Btl exec emqx-statefulset-2 -- emqx_ctl cluster status
    Cluster status: #{running_nodes =>
    [~ K g I'emqx@emqx-statefulset-0.e* % m q [ # cmqx-headless.default.svc.cluster.local',
    'emqxD Y n b e l K 5@emqx-statefulset-1.eN R A ! m ` 8 e wmqx-headl/ 7 fess.default.M A : ! [ ~  Qsvc.cluster.lo: } 7 ? a c )cal',
    'emqx@emqx-statefulset-2.emqx-headless.default.svc.cluster.local'],
    stopped_nodes => []}

    可以看到新的 EMQ X Broker 集群已经成功的建立起来了。

  • 中止一个 Pod:

    StatefulSet 中的 Pod 重新调* E % g m Y r o度后其 PodName 和 HostName 不变,下面来尝试一下:

    $ kubectl get pods
    kuNAME                 READY   STATUS    RESTARTS   AGE
    emqx-statefulset-0   1/1     Running   0          6m20s
    emqb ( k cx-statefulset-4 % Y v o1   1/1     Running   0          6m17s
    emqx-statefulset-2   1/1     Running   0          6m15s
    $ kubectl delete pod emqx-stateS P | K 2 L  Dfulset-0
    pod% 7 Z x H 9 F b ? "emqx-statefulset-0" deleted
    $ kubectl get pods
    NAME                 READY   STATUS    RESTARTS   AGE
    emqx-statefulset-0   1/1     Running   0          27s
    emqD z m ( Z #x-statefulset-1   1/1     Running   0          9m45s
    emqx-statefulset-2   1/1     Running   0          9m43s
    $} 9 $ & b = H @ y kubectl exec emqx-statefulsa ? V 6 c 2 _ A Bet-2 -- emqx_ctl cluster status
    Cluster status: #{running_nodes =&w L T h I $ 1 $ 6gt;
    ['emqx@emqx-statefulset-0.emqx-headless.default.svc.cluster.local',
    'emqx@emqx-statefulset-1.emqx-headless.default.sr C c r ; e 9 d ?vc.cluster.local',
    'emqx@emqx-stam $ . {tefulset-26 ) 7 w 7.emqx-headless.de: z  z H z U rfault.svc.cluster.local'],
    stopped_nodes => []}

    跟预期的一样,StatefulSet 重新调度了一个具有相同网络标志的 Pod,Pod 中的 EMQ X Brok T a ( 3 c 7 &er 也成功的加入了集群。

StorageClasses、Ps m g 1ersistentVolume 和 PersistentVolumeClaim

PersistentVol* | ] [ume(PV)是由管理员设置的存储,它是群集的一部分。就像节点是集群中的资源一样,PV 也是集群中的资源。 PV 是 Volume 之类的卷插件,但具有独立于使用 PV 的 Pod 的生命周期。此 API 对象包含存储实现的细节,即 NFS、iSCh , 9 U 3 t z @ VSI 或特定于云供应商的存储系统。

PersistentVolumeClaim(PVC)是用户存储( a z的请求。它与 Pod 相似。Po| : , m gd 消耗节点资源,PVC 消耗 PV 资源。Pod 可以请求特定级别的资源(CP* 9 8 K + z T yU 和内存)。声明可以请求特o 3 U定的大小和访问模式(例如,可以以读/写一次或 只读多次模式挂载)。

StorageClass 为管理员提供了描述存储 "class(类)" 的方法。 不同的 class 可能会映射到不同的服务质量等级或备份N $ X x V : 1 X略,或由群集管理员确| | ? G u定的任意策略。 Kubernetes 本身不清楚各种 class 代表的什么。这个概念在其他存储系统中有时被J o r J [ m 3 2称为“配置文件”。

在部署 EMQ X Broker 的时候,可以预先创建好 PV 或 StorZ | t 6 y x - 5 jageClass0 : M H m I a,然后利用 PVC 将 EMQ X Broker 的 /opt/emqx/data/mnesia 目录挂载出来,当Pods被重新调度之后,EMQ X 会从 /opJ ? :t/emqx/data/mnesia 目录中获取数据并恢复,从而实现 EMQ X Broker 的持久化。

  • 定义 StatefulSet

    $cat statefulset.yaml
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
    name: emqx-sta| b ) ptefulset
    labels:
    app: emqx
    spec:
    replicas: 3
    serviceName: emqx-headless
    updateStrategy:
    type: RollingUpdate
    selector:
    matchLabels:6 J n
    app: emqx
    template:
    metadata:
    labels:
    app: emqx
    spec:z ? . }
    volumes:
    - name: emqx-data
    persistentVolumeClaim:
    c@ : 2 C u _ e Flaimd % h $ qNamb O W M ] _e: emqx-pvc
    serviceAccountName: emqx
    containers:
    - name: emqx
    image: emqp N  P 7 Vx/emqx:v4z : N @.1-rc.1
    ports:
    - name: mqt_ Q D 8 p It
    containerPort: 1883
    - name: mqttssl
    containerPort: 8883
    - name: mgmt
    containerPort: 808_ l L 81
    - name: w{ b # Q q 8 & 5s
    containerPort: 8- F 7 l O g s 4 V083
    - na9 b d q B l 7me: wss
    containerPort: 8084
    - name: dashboard
    containerPort:& t 8 O W v e g M 18083
    envFrom:
    - configMapRef:
    name: emqx-config
    volumeMounts:
    - name: emqx-data
    mountPath: "/opt/emqx/data/mnesia"
    volumeClaimTemplates:
    - metadata:
    name: emqx-pvc
    annota$ 5 jtions:
    volume.alpha.kubernetes.io/storage-class: manualP T | X
    spec:
    accessModes: [ "ReadWriteOnce" ]
    resources:
    requests:
    storage: 1Gi

    该文件首先通过 volumeClaimTemplates 指定了使用 StorageClass 的 name 为 manual 的存储类创建名称为 emqx-pvc 的 PVC 资源,PVC 资源的读写模式为 ReadWriteOnce,需要 1Gi 的空间,然后将此 PVp ^ 8 K u 0 uC 定义为 name 为 emqx-data 的 volumes,并将此 volumes 挂载在 Pod 中的 /opt/emqx/datas K e/mnesia 目录下。

  • 部署资源:

    部署 StatefulSet 之前,需% & !要用户或 kubernetes 集群管理员自行创建存储类。

    $ kubectl apply -f statefulset.yaml
    statefulset.apps/emqx-statefulset created
    $ kubectl get pods
    NAME                 READY! { | K l X * o O   STATUS    RESTARTS   AGE
    emqx-statefulset-0   1/1     Running   0          27s
    emqx-statefulset-1   1/R O ~ z1     Running   0          9m45s| H  g F @ [ ) u
    emqx-statefulset-2   1/1     Running   0          9m43s
    $ kubectl get pvc
    NAME                                 STATUSG ) E B d 1 ~ y    VO% , ) CLUME                                 CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    emqx-data-emqx-statefulset-0   Bound     pvcq 7 ^ D-8094cd75-adb5-11e9-80cc-0697b59e8064   1Gi        RWO            gpe K ; Q2            2m11s
    emqx-data-emqx-statefulset-0   Bound     pvc-9325441d-adb5-11e9-80cc-0697b59e8l r 4 Q064   1Gi        RWO            gp2            99s
    emqx-data-emqx-statefulset-0   Bou[ E [ f } M O Fnd     pvc-ad425e9d-adb5-11e9-80cc-0697b59e8064   1Gi        RWO            gp2            56s

    输出结果表} E s明该 PVC 的状态为 Bound,PVC 存储W 7 g已经成功的建立了,当 Pod 被重新调度时,EMQ X Broker 会读取挂载到 PVC 中的数据,从而实现持久化。

EMQ X Broker 在 kubernetes 上建立持久化的集群就完成了,本文略过了部分细节,部署的过程也是偏向简单[ s w $的 Demo,用户可以自行阅读 kubernetes 文档 与 EMQ X Team 提供的 Helm chart 源码 来继) G L w V J , { A续深入研究,当然也欢迎在 Github 贡献 issue、pull requests 以2 _ V ? 4及 start。