從零開始建立 EMQ X MQTT 服務器 的 K8S 集羣

EMQ X Team 提供了 Helm chart 方便用戶在 kubernetes 集羣上一鍵部署 EMQ X MQTT 服務器, 這是 EMQ X Team 最推薦的在 kubernetes 或 k3s 集羣上部署 EMQ X MQTT 服務器的方法。 本文將使用手寫 yaml 文件的方法從零開始部署一個 EMQ X MQTT 服務器的 K8S 集羣, 分析部署中的細節與技巧,方便用戶在實際部署中靈活使用。

閱讀本文需要用戶瞭解 kubernetes 的基本概念,並有一個可操作的 kubernetes 集羣。

在 K8S 上部署單個 EMQ X MQTT服務器節點

使用 Pod 直接部署 EMQ X Broker

在Kubernetes中,最小的管理元素不是一個個獨立的容器,而是 Pod,Pod 是 Kubernetes 應用程序的基本執行單元,即它是 Kubernetes 對象模型中創建或部署的最小和最簡單的單元。Pod 表示在 集羣 上運行的進程。

EMQ X Broker 在 docker hub 上提供了鏡像, 因此可以很方便的在單個的 pod 上部署 EMQ X Broker,使用 kubectl run 命令創建一個運行着 EMQ X Broker 的 Pod:

$ kubectl run emqx --image=emqx/emqx:v4.1-rc.1  --generator=run-pod/v1
pod/emqx created

查看 EMQ X Broker 的狀態:

$ kubectl get pods -o wide
NAME   READY   STATUS    RESTARTS   AGE
emqx   1/1     Running   0          3m13s

$ kubectl exec emqx -- emqx_ctl status
Node '[email protected]' is started
emqx 4.1-rc.1 is running

刪除 Pod:

$ kubectl delete pods emqx
pod "emqx" deleted

Pod 並不是被設計成一個持久化的資源,它不會在調度失敗,節點崩潰,或者其他回收中(比如因爲資源的缺乏,或者其他的維護中)倖存下來,因此,還需要一個控制器來管理 Pod。

使用 Deoloyment 部署 Pod

Deployment 爲 Pod 和 ReplicaSet 提供了一個聲明式定義(declarative)方法,用來替代以前的ReplicationController 來方便的管理應用。典型的應用場景包括:

  • 定義Deployment來創建Pod和ReplicaSet
  • 滾動升級和回滾應用
  • 擴容和縮容
  • 暫停和繼續Deployment

使用 Deployment 部署一個 EMQ X Broker Pod:

  • 定義 Deployment:

    $ cat deployment.yaml
    
    apiVersion: 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
            	containerPort: 8081
            - name: ws
            	containerPort: 8083
            - name: wss
            	containerPort: 8084
            - name: dashboard
            	containerPort: 18083
    
  • 部署 Deployment:

    $  kubectl apply -f deployment.yaml
    deployment.apps/emqx-deployment created
    
  • 查看部署情況:

    $ 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    RESTARTS   AGE
    pod/emqx-deployment-7c44dbd68-8j77l   1/1     Running   0          74s
    
    $ kubectl exec pod/emqx-deployment-7c44dbd68-8j77l -- emqx_ctl status
    Node '[email protected]' 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    RESTARTS   AGE
    emqx-deployment-68fcb4bfd6-2nhh6   1/1     Running   0          59s
    

    輸出結果表明成功用 Deployment 部署了 EMQ X Broker Pod,即使是此 Pod 被意外終止,Deployment 也會重新創建一個新的 Pod。

使用 Services 公開 EMQ X Broker Pod 服務

Kubernetes Pods 是有生命週期的。他們可以被創建,而且銷燬不會再啓動。 如果使用 Deployment 來運行應用程序,則它可以動態創建和銷燬 Pod。

每個 Pod 都有自己的 IP 地址,但是在 Deployment 中,在同一時刻運行的 Pod 集合可能與稍後運行該應用程序的 Pod 集合不同。

這導致了一個問題:如果使用 EMQ X Broker Pod 爲 MQTT 客戶端提供服務,那麼客戶端應該如何如何找出並跟蹤要連接的 IP 地址,以便客戶端使用 EMQ X Broker 服務呢?

答案是:Service

Service 是將運行在一組 Pods 上的應用程序公開爲網絡服務的抽象方法。

使用 Service 將 EMQ X Broker Pod 公開爲網絡服務:

  • 定義 Service:

    $cat service.yaml
    
    apiVersion: v1
    kind: Service
    metadata:
      name: emqx-service
    spec:
      selector:
        app: emqx
      ports:
        - name: mqtt
          port: 1883
          protocol: TCP
          targetPort: mqtt
        - name: mqttssl
          port: 8883
          protocol: TCP
          targetPort: mqttssl
        - name: mgmt
          port: 8081
          protocol: TCP
          targetPort: mgmt
        - name: ws
          port: 8083
          protocol: TCP
          targetPort: ws
        - name: wss
          port: 8084
          protocol: TCP
          targetPort: wss
        - name: dashboard
          port: 18083
          protocol: TCP
          targetPort: dashboard
    
    
  • 部署 Service:

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

    $ kubectl get svc
    NAME           TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)                                        AGE
    emqx-service   ClusterIP   10.96.54.205   <none>        1883/TCP,8883/TCP,8081/TCP,8083/TCP,8084/TCP,18083/TCP   58s
    
  • 使用 Service 提供的 IP 查看 EMQ X Broker 的 API

    $ curl 10.96.54.205:8081/status
    Node [email protected] is started
    emqx is running
    

至此,單個 EMQ X Broker 節點在 kubernetes 上部署完畢,通過 Deployment 管理 EMQ X Broker Pod,通過 Service 將 EMQ X Broker 服務暴露出去。

通過 kubernetes 自動集羣 EMQ X MQTT 服務器

上文中通過 Deployment 部署了單個的 EMQ X Broker Pod,通過 Deployment 擴展 Pod 的數量是極爲方便的,執行 kubectl scale deployment ${deployment_name} --replicas ${numer} 命令即可擴展 Pod 的數量,下面將 EMQ X Broker Pod 擴展爲 3 個:

$ kubectl scale deployment emqx-deployment --replicas 3
deployment.apps/emqx-deployment scaled

$ kubectl get pods
NAME                               READY   STATUS    RESTARTS   AGE
emqx-deployment-68fcb4bfd6-2nhh6   1/1     Running   0          18m
emqx-deployment-68fcb4bfd6-mpvch   1/1     Running   0          6s
emqx-deployment-68fcb4bfd6-mx55q   1/1     Running   0          6s

$ kubectl exec emqx-deployment-68fcb4bfd6-2nhh6 -- emqx_ctl status
Node '[email protected]' is started
emqx 4.1-rc.1 is running

$ kubectl exec emqx-deployment-68fcb4bfd6-2nhh6 -- emqx_ctl cluster status
Cluster status: #{running_nodes =>
                      ['[email protected]'],
                  stopped_nodes => []}

可以看到 EMQ X Broker Pod 的數量被擴展爲 3 個,但是每個 Pod 都是獨立的,並沒有集羣,接下來嘗試通過 kubernetes 自動集羣 EMQ X Broker Pod。

修改 EMQ X Broker 的配置

查看 EMQ X Broker 文檔中關於自動集羣的內容,可以看到需要修改 EMQ X Broker 的配置:

cluster.discovery = kubernetes
cluster.kubernetes.apiserver = http://10.110.111.204:8080
cluster.kubernetes.service_name = ekka
cluster.kubernetes.address_type = ip
cluster.kubernetes.app_name = ekka

其中 cluster.kubernetes.apiserver 爲 kubernetes apiserver 的地址,可以通過 kubectl cluster-info 命令獲取,cluster.kubernetes.service_name 爲上文中 Service 的 name, cluster.kubernetes.app_name 爲 EMQ X Broker 的 node.name@ 符號之前的部分,所以還需要將集羣中 EMQ X Broker 設置爲統一的 node.name 的前綴。

EMQ X Broker 的 docker 鏡像提供了通過環境變量修改配置的功能,具體可以查看 docker hubGithub

  • 修改 Deployment 的 yaml 文件,增加環境變量:

    $ cat deployment.yaml
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: emqx-deployment
      labels:
        app: emqx
    spec:
      replicas: 3
      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
              containerPort: 8081
            - name: ws
              containerPort: 8083
            - name: wss
              containerPort: 8084
            - name: dashboard
              containerPort: 18083
            env:
            - name: EMQX_NAME
            	value: emqx
            - name: EMQX_CLUSTER__DISCOVERY
              value: k8s
            - name: EMQX_CLUSTER__K8S__APP_NAME
              value: emqx
            - name: EMQX_CLUSTER__K8S__SERVICE_NAME
              value: emqx-service
            - name: EMQX_CLUSTER__K8S__APISERVER
              value: "https://kubernetes.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 apiserver 的地址。

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

    $ kubectl delete deployment emqx-deployment
    deployment.apps "emqx-deployment" deleted
    
    $ kubectl apply -f deployment.yaml
    deployment.apps/emqx-deployment created
    

賦予 Pod 訪問 kubernetes apiserver 的權限

上文部署 Deployment 之後,查看 EMQ X Broker 的狀態,可以看到 EMQ X Broker 雖然成功啓動了,但是依然沒有集羣成功,查看 EMQ X Broker Pod 的 log:

$ kubectl get pods
NAME                               READY   STATUS    RESTARTS   AGE
emqx-deployment-5c8cfc4d75-67lmt   1/1     Running   0          5s
emqx-deployment-5c8cfc4d75-r6jgb   1/1     Running   0          5s
emqx-deployment-5c8cfc4d75-wv2hj   1/1     Running   0          5s

$ kubectl exec emqx-deployment-5c8cfc4d75-67lmt -- emqx_ctl status
Node '[email protected]' is started
emqx 4.1-rc.1 is running

$ kubectl exec emqx-deployment-5c8cfc4d75-67lmt -- emqx_ctl cluster status
Cluster status: #{running_nodes => ['[email protected]'],
                  stopped_nodes => []}
                  
$ kubectl logs emqx-deployment-76f6895c46-4684f

···
([email protected])1> 2020-05-20 01:48:39.726 [error] Ekka(AutoCluster): Discovery error: {403,
                                     "{\"kind\":\"Status\",\"apiVersion\":\"v1\",\"metadata\":{},\"status\":\"Failure\",\"message\":\"endpoints \\\"emqx-service\\\" is forbidden: User \\\"system:serviceaccount:default:default\\\" cannot get resource \\\"endpoints\\\" in API group \\\"\\\" in the namespace \\\"default\\\"\",\"reason\":\"Forbidden\",\"details\":{\"name\":\"emqx-service\",\"kind\":\"endpoints\"},\"code\":403}\n"}
···

Pod 因爲權限問題在訪問 kubernetes apiserver 的時候被拒絕,返回 HTTP 403,所以集羣失敗。

普通 Pod 是無法訪問 kubernetes apiserver 的,解決這個問題有兩種方法,一種是開放 kubernetes apiserver 的 http 接口,但是這種方法存在一定的安全隱患,另外一種是通過 ServiceAccount、Role 和 RoleBinding 配置 RBAC 鑑權。

  • 定義 ServiceAccount、Role 和 RoleBinding:

    $ cat rbac.yaml
    
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      namespace: default
      name: emqx
    ---
    kind: Role
    apiVersion: rbac.authorization.kubernetes.io/v1beta1
    metadata:
      namespace: default
      name: emqx
    rules:
    - apiGroups:
      - ""
      resources:
      - endpoints 
      verbs: 
      - get
      - watch
      - list
    ---
    kind: RoleBinding
    apiVersion: rbac.authorization.kubernetes.io/v1beta1
    metadata:
      namespace: default
      name: emqx
    subjects:
      - kind: ServiceAccount
        name: emqx
        namespace: default
    roleRef:
      kind: Role
      name: emqx
      apiGroup: rbac.authorization.kubernetes.io
    
  • 部署相應的資源:

    $ kubectl apply -f rbac.yaml
    serviceaccount/emqx created
    role.rbac.authorization.kubernetes.io/emqx created
    rolebinding.rbac.authorization.kubernetes.io/emqx created
    
  • 修改 Deployment 的 yaml 文件,增加 spec.template.spec.serviceAccountName,並重新部署:

    $cat deployment.yaml
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: emqx-deployment
      labels:
        app: emqx
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: emqx
      template:
        metadata:
          labels:
            app: emqx
        spec:
          serviceAccountName: emqx
          containers:
          - name: emqx
            image: emqx/emqx:v4.1-rc.1
            ports:
            - name: mqtt
              containerPort: 1883
            - name: mqttssl
              containerPort: 8883
            - name: mgmt
              containerPort: 8081
            - name: ws
              containerPort: 8083
            - name: wss
              containerPort: 8084
            - name: dashboard
              containerPort: 18083
            env:
            - name: EMQX_NAME
            	value: emqx
            - name: EMQX_CLUSTER__DISCOVERY
              value: kubernetes
            - name: EMQX_CLUSTER__K8S__APP_NAME
              value: emqx
            - name: EMQX_CLUSTER__K8S__SERVICE_NAME
              value: emqx-service
            - name: EMQX_CLUSTER__K8S__APISERVER
              value: "https://kubernetes.default.svc:443"
            - name: EMQX_CLUSTER__K8S__NAMESPACE
              value: default
              
    $ kubectl delete deployment emqx-deployment
    deployment.apps "emqx-deployment" deleted
    
    $ kubectl apply -f deployment.yaml
    deployment.apps/emqx-deployment created
    
  • 查看狀態:

    $ kubectl get pods
    NAME                              READY   STATUS    RESTARTS   AGE
    emqx-deployment-6b854486c-dhd7p   1/1     Running   0          10s
    emqx-deployment-6b854486c-psv2r   1/1     Running   0          10s
    emqx-deployment-6b854486c-tdzld   1/1     Running   0          10s
    
    $ kubectl exec emqx-deployment-6b854486c-dhd7p  -- emqx_ctl status
    Node '[email protected]' is started
    emqx 4.1-rc.1 is running
    
    $ kubectl exec emqx-deployment-6b854486c-dhd7p  -- emqx_ctl cluster status
    Cluster status: #{running_nodes =>
                          ['[email protected]','[email protected]',
                           '[email protected]'],
                      stopped_nodes => []}
    
  • 中止一個 Pod:

    $ kubectl delete pods emqx-deployment-6b854486c-dhd7p
    pod "emqx-deployment-6b854486c-dhd7p" deleted
    
    $ kubectl get pods
    NAME                              READY   STATUS    RESTARTS   AGE
    emqx-deployment-6b854486c-846v7   1/1     Running   0          56s
    emqx-deployment-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: #{running_nodes =>
                          ['[email protected]','[email protected]',
                           '[email protected]'],
                      stopped_nodes => ['[email protected]']}
    

    輸出結果表明 EMQ X Broker 會正確的顯示已經停掉的 Pod,並將 Deployment 新建的 Pod 加入集羣。

至此,EMQ X Broker 在 kubernetes 上成功建立集羣。

持久化 EMQ X Broker 集羣

上文中使用的 Deployment 來管理 Pod,但是 Pod 的網絡是不停變動的,而且當 Pod 被銷燬重建時,儲存在 EMQ X Broker 的數據和配置也就隨之消失了,這在生產中是不能接受的,接下來嘗試把 EMQ X Broker 的集羣持久化,即使 Pod 被銷燬重建,EMQ X Broker 的數據依然可以保存下來。

ConfigMap

ConfigMap 是 configMap 是一種 API 對象,用來將非機密性的數據保存到健值對中。使用時可以用作環境變量、命令行參數或者存儲卷中的配置文件。

ConfigMap 將您的環境配置信息和 容器鏡像 解耦,便於應用配置的修改。

ConfigMap 並不提供保密或者加密功能。如果你想存儲的數據是機密的,請使用 Secret ,或者使用其他第三方工具來保證你的數據的私密性,而不是用 ConfigMap。

接下來使用 ConfigMap 記錄 EMQ X Broker 的配置,並將它們以環境變量的方式導入到 Deployment 中。

  • 定義 Configmap,並部署:

    $cat configmap.yaml
    
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: emqx-config
    data:
      EMQX_CLUSTER__K8S__ADDRESS_TYPE: "hostname"
      EMQX_CLUSTER__K8S__APISERVER: "https://kubernetes.default.svc:443"
      EMQX_CLUSTER__K8S__SUFFIX: "svc.cluster.local"
      
    $ kubectl apply -f configmap.yaml
    configmap/emqx-config created
    
  • 配置 Deployment 來使用 Configmap

    $cat deployment.yaml
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: emqx-deployment
      labels:
        app: emqx
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: emqx
      template:
        metadata:
          labels:
            app: emqx
        spec:
          serviceAccountName: emqx
          containers:
          - name: emqx
            image: emqx/emqx:v4.1-rc.1
            ports:
            - name: mqtt
              containerPort: 1883
            - name: mqttssl
              containerPort: 8883
            - name: mgmt
              containerPort: 8081
            - name: ws
              containerPort: 8083
            - name: 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 deployment.yaml
    deployment.apps/emqx-deployment created
    
    $ kubectl get pods
    NAME                               READY   STATUS    RESTARTS   AGE
    emqx-deployment-5c7696b5d7-k9lzj   1/1     Running   0          3s
    emqx-deployment-5c7696b5d7-mdwkt   1/1     Running   0          3s
    emqx-deployment-5c7696b5d7-z57z7   1/1     Running   0          3s
    
    $ kubectl exec emqx-deployment-5c7696b5d7-k9lzj -- emqx_ctl status
    Node '[email protected]' is started
    emqx 4.1-rc.1 is running
    
    $ kubectl exec emqx-deployment-5c7696b5d7-k9lzj -- emqx_ctl cluster status
    Cluster status: #{running_nodes =>
                          ['[email protected]','[email protected]',
                           '[email protected]'],
                      stopped_nodes => []}
    

EMQ X Broker 的配置文件已經解耦到 Configmap 中了,如果有需要,可以自由的配置一個或多個 Configmap,並把它們作爲環境變量或是文件引入到 Pod 內。

StatefulSet

StatefulSet 是爲了解決有狀態服務的問題(對應 Deployments 和 ReplicaSets 是爲無狀態服務而設計),其應用場景包括

  • 穩定的持久化存儲,即 Pod 重新調度後還是能訪問到相同的持久化數據,基於 PVC 來實現
  • 穩定的網絡標誌,即 Pod 重新調度後其 PodName 和 HostName 不變,基於 Headless Service(即沒有Cluster IP的Service)來實現
  • 有序部署,有序擴展,即 Pod 是有順序的,在部署或者擴展的時候要依據定義的順序依次依次進行(即從0到N-1,在下一個Pod運行之前所有之前的 Pod 必須都是 Running 和 Ready 狀態),基於 init containers 來實現
  • 有序收縮,有序刪除(即從N-1到0)

從上面的應用場景可以發現,StatefulSet由以下幾個部分組成:

  • 用於定義網絡標誌(DNS domain)的 Headless Service
  • 用於創建 PersistentVolumes 的 volumeClaimTemplates
  • 定義具體應用的 StatefulSet

StatefulSet 中每個 Pod 的 DNS 格式爲 statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local ,其中

  • serviceName 爲 Headless Service 的名字
  • 0..N-1 爲 Pod 所在的序號,從 0 開始到 N-1
  • statefulSetName 爲StatefulSet的名字
  • namespace 爲服務所在的 namespace,Headless Servic 和 StatefulSet 必須在相同的 namespace
  • .cluster.local 爲 Cluster Domain

接下來使用 StatefulSet 代替 Deployment 來管理 Pod。

  • 刪除 Deployment:

    $ kubectl delete deployment emqx-deployment
    deployment.apps "emqx-deployment" deleted
    
  • 定義 StatefulSet:

    $cat statefulset.yaml
    
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: emqx-statefulset
      labels:
        app: emqx
    spec:
    	serviceName: emqx-headless
      updateStrategy:
        type: RollingUpdate
      replicas: 3
      selector:
        matchLabels:
          app: emqx
      template:
        metadata:
          labels:
            app: emqx
        spec:
          serviceAccountName: emqx
          containers:
          - name: emqx
            image: emqx/emqx:v4.1-rc.1
            ports:
            - name: mqtt
              containerPort: 1883
            - name: mqttssl
              containerPort: 8883
            - name: mgmt
              containerPort: 8081
            - name: ws
              containerPort: 8083
            - name: wss
              containerPort: 8084
            - name: dashboard
              containerPort: 18083
            envFrom:
              - configMapRef:
                  name: emqx-config
    

    注意,StatefulSet 需要 Headless Service 來實現穩定的網絡標誌,因此需要再定義一個 Service

    $cat headless.yaml
    
    apiVersion: v1
    kind: Service
    metadata:
      name: emqx-headless
    spec:
      type: ClusterIP
      clusterIP: None
      selector:
        app: emqx
      ports:
      - name: mqtt
        port: 1883
        protocol: TCP
        targetPort: 1883
      - name: mqttssl
        port: 8883
        protocol: TCP
        targetPort: 8883
      - name: mgmt
        port: 8081
        protocol: TCP
        targetPort: 8081
      - name: websocket
        port: 8083
        protocol: TCP
        targetPort: 8083
      - name: wss
        port: 8084
        protocol: TCP
        targetPort: 8084
      - name: dashboard
        port: 18083
        protocol: TCP
        targetPort: 18083
    

    因爲 Headless Service 並不需要 IP,所以配置了 clusterIP: None

  • 部署相應的資源:

    $ kubectl apply -f headless-service.yaml
    service/emqx-headless created
    
    $ kubectl apply -f statefulset.yaml
    statefulset.apps/emqx-deployment created
    
    $ kubectl get pods
    NAME                               READY   STATUS    RESTARTS   AGE
    emqx-statefulset-0                 1/1     Running   0          2m59s
    emqx-statefulset-1                 1/1     Running   0          2m57s
    emqx-statefulset-2                 1/1     Running   0          2m54s
    
    $ kubectl exec emqx-statefulset-0 -- emqx_ctl cluster status
    Cluster status: #{running_nodes =>
                          ['[email protected]','[email protected]',
                           '[email protected]'],
                      stopped_nodes => []}
    
  • 更新 Configmap:

    StatefulSet 提供了穩定的網絡標誌,EMQ X Broker 支持使用 hostname 和 dns 規則來代提 IP 實現集羣,以 hostname 爲例,需要修改 emqx.conf

    cluster.kubernetes.address_type = hostname
    cluster.kubernetes.suffix = "svc.cluster.local"
    

    kubernetes 集羣中 Pod 的 DNS 規則可以由用戶自定義,EMQ X Broker 提供了 cluster.kubernetes.suffix 方便用戶匹配自定的 DNS 規則,本文使用默認的 DNS 規則:statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local ,DNS 規則中的 serviceName 爲 StatefulSet 使用的 Headless Service,所以還需要將 cluster.kubernetes.service_name 修改爲 Headless Service Name。

    將配置項轉爲環境變量,需要在 Configmap 中配置:

    EMQX_CLUSTER__K8S__ADDRESS_TYPE: "hostname"
    EMQX_CLUSTER__K8S__SUFFIX: "svc.cluster.local"
    EMQX_CLUSTER__K8S__SERVICE_NAME: emqx-headless
    

    Configmap 提供了熱更新功能,執行 $ kubectl edit configmap emqx-config 來熱更新 Configmap。

  • 重新部署 StatefulSet:

    Configmap 更新之後 Pod 並不會重啓,需要我們手動更新 StatefulSet

    $ kubectl delete statefulset emqx-statefulset
    statefulset.apps "emqx-statefulset" deleted
    
    $ kubectl apply -f statefulset.yaml
    statefulset.apps/emqx-statefulset created
    
    $ kubectl get pods
    NAME                 READY   STATUS    RESTARTS   AGE
    emqx-statefulset-0   1/1     Running   0          115s
    emqx-statefulset-1   1/1     Running   0          112s
    emqx-statefulset-2   1/1     Running   0          110s
    
    $ kubectl exec emqx-statefulset-2 -- emqx_ctl cluster status
    Cluster status: #{running_nodes =>
                          ['[email protected]',
                           '[email protected]',
                           '[email protected]'],
                      stopped_nodes => []}
    

    可以看到新的 EMQ X Broker 集羣已經成功的建立起來了。

  • 中止一個 Pod:

    StatefulSet 中的 Pod 重新調度後其 PodName 和 HostName 不變,下面來嘗試一下:

    $ kubectl get pods
    kuNAME                 READY   STATUS    RESTARTS   AGE
    emqx-statefulset-0   1/1     Running   0          6m20s
    emqx-statefulset-1   1/1     Running   0          6m17s
    emqx-statefulset-2   1/1     Running   0          6m15s
    
    $ kubectl delete pod emqx-statefulset-0
    pod "emqx-statefulset-0" deleted
    
    $ kubectl get pods
    NAME                 READY   STATUS    RESTARTS   AGE
    emqx-statefulset-0   1/1     Running   0          27s
    emqx-statefulset-1   1/1     Running   0          9m45s
    emqx-statefulset-2   1/1     Running   0          9m43s
    
    $ kubectl exec emqx-statefulset-2 -- emqx_ctl cluster status
    Cluster status: #{running_nodes =>
                          ['[email protected]',
                           '[email protected]',
                           '[email protected]'],
                      stopped_nodes => []}
    

    跟預期的一樣,StatefulSet 重新調度了一個具有相同網絡標誌的 Pod,Pod 中的 EMQ X Broker 也成功的加入了集羣。

StorageClasses、PersistentVolume 和 PersistentVolumeClaim

PersistentVolume(PV)是由管理員設置的存儲,它是羣集的一部分。就像節點是集羣中的資源一樣,PV 也是集羣中的資源。 PV 是 Volume 之類的卷插件,但具有獨立於使用 PV 的 Pod 的生命週期。此 API 對象包含存儲實現的細節,即 NFS、iSCSI 或特定於雲供應商的存儲系統。

PersistentVolumeClaim(PVC)是用戶存儲的請求。它與 Pod 相似。Pod 消耗節點資源,PVC 消耗 PV 資源。Pod 可以請求特定級別的資源(CPU 和內存)。聲明可以請求特定的大小和訪問模式(例如,可以以讀/寫一次或 只讀多次模式掛載)。

StorageClass 爲管理員提供了描述存儲 “class(類)” 的方法。 不同的 class 可能會映射到不同的服務質量等級或備份策略,或由羣集管理員確定的任意策略。 Kubernetes 本身不清楚各種 class 代表的什麼。這個概念在其他存儲系統中有時被稱爲“配置文件”。

在部署 EMQ X Broker 的時候,可以預先創建好 PV 或 StorageClass,然後利用 PVC 將 EMQ X Broker 的 /opt/emqx/data/mnesia 目錄掛載出來,當Pods被重新調度之後,EMQ X 會從 /opt/emqx/data/mnesia 目錄中獲取數據並恢復,從而實現 EMQ X Broker 的持久化。

  • 定義 StatefulSet

    $cat statefulset.yaml
    
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: emqx-statefulset
      labels:
        app: emqx
    spec:
      replicas: 3
      serviceName: emqx-headless
      updateStrategy:
        type: RollingUpdate
      selector:
        matchLabels:
          app: emqx
      template:
        metadata:
          labels:
            app: emqx
        spec:
          volumes:
          - name: emqx-data
            persistentVolumeClaim:
              claimName: emqx-pvc
          serviceAccountName: emqx
          containers:
          - name: emqx
            image: emqx/emqx:v4.1-rc.1
            ports:
            - name: mqtt
              containerPort: 1883
            - name: mqttssl
              containerPort: 8883
            - name: mgmt
              containerPort: 8081
            - name: ws
              containerPort: 8083
            - name: wss
              containerPort: 8084
            - name: dashboard
              containerPort: 18083
            envFrom:
              - configMapRef:
                  name: emqx-config
            volumeMounts:
            - name: emqx-data
              mountPath: "/opt/emqx/data/mnesia"
      volumeClaimTemplates:
      - metadata:
          name: emqx-pvc
          annotations:
            volume.alpha.kubernetes.io/storage-class: manual
        spec:
          accessModes: [ "ReadWriteOnce" ]
          resources:
            requests:
              storage: 1Gi
    

    該文件首先通過 volumeClaimTemplates 指定了使用 StorageClass 的 name 爲 manual 的存儲類創建名稱爲 emqx-pvc 的 PVC 資源,PVC 資源的讀寫模式爲 ReadWriteOnce,需要 1Gi 的空間,然後將此 PVC 定義爲 name 爲 emqx-data 的 volumes,並將此 volumes 掛載在 Pod 中的 /opt/emqx/data/mnesia 目錄下。

  • 部署資源:

    部署 StatefulSet 之前,需要用戶或 kubernetes 集羣管理員自行創建存儲類。

    $ kubectl apply -f statefulset.yaml
    statefulset.apps/emqx-statefulset created
    
    $ kubectl get pods
    NAME                 READY   STATUS    RESTARTS   AGE
    emqx-statefulset-0   1/1     Running   0          27s
    emqx-statefulset-1   1/1     Running   0          9m45s
    emqx-statefulset-2   1/1     Running   0          9m43s
    
    $ kubectl get pvc
    NAME                  			   STATUS    VOLUME                                 CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    emqx-data-emqx-statefulset-0   Bound     pvc-8094cd75-adb5-11e9-80cc-0697b59e8064   1Gi        RWO            gp2            2m11s
    emqx-data-emqx-statefulset-0   Bound     pvc-9325441d-adb5-11e9-80cc-0697b59e8064   1Gi        RWO            gp2            99s
    emqx-data-emqx-statefulset-0   Bound     pvc-ad425e9d-adb5-11e9-80cc-0697b59e8064   1Gi        RWO            gp2            56s
    

    輸出結果表明該 PVC 的狀態爲 Bound,PVC 存儲已經成功的建立了,當 Pod 被重新調度時,EMQ X Broker 會讀取掛載到 PVC 中的數據,從而實現持久化。

EMQ X Broker 在 kubernetes 上建立持久化的集羣就完成了,本文略過了部分細節,部署的過程也是偏向簡單的 Demo,用戶可以自行閱讀 kubernetes 文檔 與 EMQ X Team 提供的 Helm chart 源碼 來繼續深入研究,當然也歡迎在 Github 貢獻 issue、pull requests 以及 start。

版權聲明: 本文爲 EMQ 原創,轉載請註明出處。

原文鏈接:https://www.emqx.io/cn/blog/emqx-mqtt-broker-k8s-cluster

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章