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 'emqx@192.168.77.108' 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 'emqx-deployment-7c44dbd68-8j77l@192.168.77.117' 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 emqx-deployment-68fcb4bfd6-2nhh6@192.168.77.120 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 'emqx-deployment-68fcb4bfd6-2nhh6@192.168.77.120' is started
emqx 4.1-rc.1 is running
$ kubectl exec emqx-deployment-68fcb4bfd6-2nhh6 -- emqx_ctl cluster status
Cluster status: #{running_nodes =>
['emqx-deployment-68fcb4bfd6-2nhh6@192.168.77.120'],
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 hub 或 Github。
-
修改 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 'emqx@192.168.87.150' is started
emqx 4.1-rc.1 is running
$ kubectl exec emqx-deployment-5c8cfc4d75-67lmt -- emqx_ctl cluster status
Cluster status: #{running_nodes => ['emqx@192.168.87.150'],
stopped_nodes => []}
$ kubectl logs emqx-deployment-76f6895c46-4684f
···
(emqx@192.168.87.150)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 'emqx@192.168.77.92' is started emqx 4.1-rc.1 is running $ kubectl exec emqx-deployment-6b854486c-dhd7p -- emqx_ctl cluster status Cluster status: #{running_nodes => ['emqx@192.168.77.115','emqx@192.168.77.92', 'emqx@192.168.87.157'], 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 => ['emqx@192.168.77.115','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 在 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 'emqx@192.168.87.149' is started emqx 4.1-rc.1 is running $ kubectl exec emqx-deployment-5c7696b5d7-k9lzj -- emqx_ctl cluster status Cluster status: #{running_nodes => ['emqx@192.168.77.106','emqx@192.168.77.107', 'emqx@192.168.87.149'], 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-1statefulSetName
為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 => ['emqx@192.168.77.105','emqx@192.168.87.153', 'emqx@192.168.87.155'], 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 => ['emqx@emqx-statefulset-0.emqx-headless.default.svc.cluster.local', 'emqx@emqx-statefulset-1.emqx-headless.default.svc.cluster.local', 'emqx@emqx-statefulset-2.emqx-headless.default.svc.cluster.local'], 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 => ['emqx@emqx-statefulset-0.emqx-headless.default.svc.cluster.local', 'emqx@emqx-statefulset-1.emqx-headless.default.svc.cluster.local', 'emqx@emqx-statefulset-2.emqx-headless.default.svc.cluster.local'], 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