当前位置: 首页 > news >正文

K8S 上部署 Emqx

文章目录

      • 安装方式一:
        • 1. 快速部署一个简单的 EMQX 集群:
        • 2. 部署一个持久化的 EMQX 集群:
        • 3. 部署 EMQX Edge 集群和 EMQX 企业版集群:
      • 安装方式二:定制化部署
        • 1. 使用 Pod 直接部署 EMQX Broker
        • 2. 使用 Deoloyment 部署 Pod
        • 3. 使用 Services 公开 EMQX Broker Pod 服务
        • 4. 通过 kubernetes 自动集群 EMQX MQTT 服务器
        • 5. 修改 EMQX Broker 的配置
        • 6. 赋予 Pod 访问 kubernetes apiserver 的权限
        • 7. 持久化 EMQX Broker 集群
        • 8. StatefulSet
        • 9. StorageClasses、PersistentVolume 和 PersistentVolumeClaim

安装方式一:

1. 快速部署一个简单的 EMQX 集群:

  添加 helm 仓库:

$ helm repo add emqx https://repos.emqx.io/charts# 查询 EMQX
$ helm search repo emqx

  启动 EMQX 集群,设置 service.type=NodePort

$ helm install my-emqx emqx/emqx --set service.type=NodePort
NAME: my-emqx
LAST DEPLOYED: Thu Jul 11 10:56:16 2024
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None

  查看 EMQX 集群情况:

$ kubectl get pods -o wide
NAME                       READY   STATUS              RESTARTS       AGE    IP              NODE        NOMINATED NODE   READINESS GATES
my-emqx-0                  0/1     ContainerCreating   0              12s    <none>          master-01   <none>           <none>
my-emqx-1                  0/1     ContainerCreating   0              12s    <none>          worker-02   <none>           <none>
my-emqx-2                  0/1     ContainerCreating   0              12s    <none>          worker-01   <none>           <none>$ kubectl get pods -o wide
NAME                       READY   STATUS    RESTARTS       AGE    IP              NODE        NOMINATED NODE   READINESS GATES
my-emqx-0                  1/1     Running   0              3m7s   172.20.32.158   master-01   <none>           <none>
my-emqx-1                  1/1     Running   0              3m7s   172.20.58.232   worker-02   <none>           <none>
my-emqx-2                  1/1     Running   0              3m7s   172.20.85.250   worker-01   <none>           <none>$ kubectl exec -it my-emqx-0 -- emqx_ctl status
Node 'my-emqx@my-emqx-0.my-emqx-headless.default.svc.cluster.local' 5.7.1 is started$ kubectl exec -it my-emqx-0 -- emqx_ctl cluster status
Cluster status: #{running_nodes =>['my-emqx@my-emqx-0.my-emqx-headless.default.svc.cluster.local','my-emqx@my-emqx-1.my-emqx-headless.default.svc.cluster.local','my-emqx@my-emqx-2.my-emqx-headless.default.svc.cluster.local'],stopped_nodes => []}

  查看 EMQX service:

$ kubectl get svc -o wide
NAME                             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)
service/my-emqx                  NodePort    10.68.183.22    <none>        1883:32596/TCP,8883:31933/TCP,8083:32522/TCP,8084:32535/TCP,18083:32717/TCP   10m     app.kubernetes.io/instance=my-emqx,app.kubernetes.io/name=emqx
service/my-emqx-headless         ClusterIP   None            <none>        1883/TCP,8883/TCP,8083/TCP,8084/TCP,18083/TCP,4370/TCP                        10m     app.kubernetes.io/instance=my-emqx,app.kubernetes.io/name=emqx# 也可以执行该命令查看全部
$ sudo kubectl get all -o wide
NAME                           READY   STATUS    RESTARTS       AGE    IP              NODE        NOMINATED NODE   READINESS GATES
pod/my-emqx-0                  1/1     Running   0              10m    172.20.32.158   master-01   <none>           <none>
pod/my-emqx-1                  1/1     Running   0              10m    172.20.58.232   worker-02   <none>           <none>
pod/my-emqx-2                  1/1     Running   0              10m    172.20.85.250   worker-01   <none>           <none>NAME                             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)
service/my-emqx                  NodePort    10.68.183.22    <none>        1883:32596/TCP,8883:31933/TCP,8083:32522/TCP,8084:32535/TCP,18083:32717/TCP   10m     app.kubernetes.io/instance=my-emqx,app.kubernetes.io/name=emqx
service/my-emqx-headless         ClusterIP   None            <none>        1883/TCP,8883/TCP,8083/TCP,8084/TCP,18083/TCP,4370/TCP                        10m     app.kubernetes.io/instance=my-emqx,app.kubernetes.io/name=emqxNAME                                      READY   AGE    CONTAINERS   IMAGES
statefulset.apps/my-emqx                  3/3     10m    emqx         emqx/emqx:5.7.1

  可以看到 my-emqx 的 18083 端口对应的宿主机的 32717 端口。(NodePort 在每次部署的时候都会变化,以实际部署时为准。)

  访问 Kubernetes 的任意一台节点 IP 的 32717 端口,输入默认用户名:admin,默认密码:public,登陆 EMQX dashboard。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
  删除 EMQX 集群:

$ helm uninstall my-emqx
release "my-emqx" uninstalled
2. 部署一个持久化的 EMQX 集群:

  EMQX 通过 创建 PVC 资源挂载 /opt/emqx/data/mnesia 目录实现持久化 pods,在部署 EMQX 之前,用户需要部署 Haproxy 或 Nginx-PLUS 等负载均衡器,并自行在 Kubernetes 中创建 PVC 资源或是 Storage Classes 资源

  如果用户部署了 PVC 资源,那么设置 persistence.existingClaim=your_pv_name

$ helm install my-emqx emqx/emqx --set persistence.enabled=true --set persistence.existingClaim=your_pv_name

  如果用户部署了 Storage Classes 资源,那么设置 persistence.storageClass=your_storageClass_name

$ helm install my-emqx emqx/emqx --set persistence.enabled=true --set persistence.storageClass=your_storageClass_name

  查看 EMQX 集群情况

$ kubectl get pods
NAME       READY  STATUS             RESTARTS  AGE
my-emqx-0  1/1     Running   0          56
smy-emqx-1  1/1     Running   0          40s
my-emqx-2  1/1     Running   0          21s$ kubectl exec -it my-emqx-0 -- emqx_ctl cluster status
Cluster status: #{running_nodes =>['my-emqx@my-emqx-0.my-emqx-headless.default.svc.cluster.local',                       'my-emqx@my-emqx-1.my-emqx-headless.default.svc.cluster.local',                       'my-emqx@my-emqx-2.my-emqx-headless.default.svc.cluster.local'],                  stopped_nodes => []}

  以 Storage Classes 为例,可以看到 PVC 资源已经成功的建立

$ kubectl get pvc
NAME                  STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
emqx-data-my-emqx-0   Bound     pvc-8094cd75-adb5-11e9-80cc-0697b59e8064   1Gi        RWO            gp2            2m11s
emqx-data-my-emqx-1   Bound     pvc-9325441d-adb5-11e9-80cc-0697b59e8064   1Gi        RWO            gp2            99s
emqx-data-my-emqx-2   Bound     pvc-ad425e9d-adb5-11e9-80cc-0697b59e8064   1Gi        RWO            gp2            56s

  集群会将 EMQX 的 /opt/emqx/data/mnesia 目录挂载到 PVC 中,当 Pods 被重新调度之后,EMQX 会从 /opt/emqx/data/mnesia 目录中获取数据并恢复。查看 EMQX 的 ClusterIP

$ kubectl get svc
NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                                                  AGE
my-emqx              ClusterIP   10.100.205.13   <none>        1883/TCP,8883/TCP,8081/TCP,8083/TCP,8084/TCP,18083/TCP   26m
my-emqx-headless     ClusterIP   None            <none>        1883/TCP,8883/TCP,8081/TCP,8083/TCP,8084/TCP,18083/TCP   26m

  可以看到 my-emqx 的 ClusterIP 为 10.100.205.13 (ClusterIP 在每次部署的时候都会变化,以实际部署时为准。)

  将负载均衡监听的 URL 的 1883、8883、8081、8083、8084、18083 端口转发到 my-emqx 的 ClusterIP,如果有 TLS 连接的需要,推荐在负载均衡器终结 SSL 连接。客户端与负载均衡器之间 TLS 安全连接,LB 与 EMQX 之间普通 TCP 连接。

  访问 URL:18083,输入默认用户名:admin,默认密码:public,登陆 EMQX dashboard。

  使用 helm upgrade 命令可以轻松扩展 EMQX 集群,下面以增加 EMQX 节点为例展示 helm upgrade 命令

# 将 EMQX 的节点数量变更为5个
# 注意:EMQX 的节点数量建议为单数
$ helm upgrade --set replicaCount=5 my-emqx emqx/emqx
Release "my-emqx" has been upgraded. Happy Helming!
$ kubectl get pods  NAME       READY  STATUS             RESTARTS  AGE
my-emqx-0  1/1    Running            0         4m25s
my-emqx-1  1/1    Running            0         4m14s
my-emqx-2  1/1    Running            0         4m
my-emqx-3  1/1    Running            0         31s
my-emqx-4  1/1    Running            0         15s$ kubectl exec -it my-emqx-0 -- emqx_ctl cluster status  Cluster status: #{running_nodes =>['my-emqx@my-emqx-0.my-emqx-headless.default.svc.cluster.local',   'my-emqx@my-emqx-1.my-emqx-headless.default.svc.cluster.local','my-emqx@my-emqx-2.my-emqx-headless.default.svc.cluster.local','my-emqx@my-emqx-3.my-emqx-headless.default.svc.cluster.local','my-emqx@my-emqx-4.my-emqx-headless.default.svc.cluster.local'],stopped_nodes => []}

  删除 EMQX 集群

$ helm uninstall my-emqx
release "my-emqx" uninstalled

  注意:EMQX 集群删除掉之后 PVC 资源不会自动释放掉,以便恢复 EMQX,确认不需要恢复后需要手动删除 PVC 资源

$ kubectl get pvc
NAME                  STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
emqx-data-my-emqx-0   Bound     pvc-8094cd75-adb5-11e9-80cc-0697b59e8064   1Gi        RWO            gp2            84m
emqx-data-my-emqx-1   Bound     pvc-9325441d-adb5-11e9-80cc-0697b59e8064   1Gi        RWO            gp2            84m
emqx-data-my-emqx-2   Bound     pvc-ad425e9d-adb5-11e9-80cc-0697b59e8064   1Gi        RWO            gp2            83m
emqx-data-my-emqx-3   Bound     pvc-b6c5a565-adbd-11e9-80cc-0697b59e8064   1Gi        RWO            gp2            25m
emqx-data-my-emqx-4   Bound     pvc-c626cafd-adbd-11e9-80cc-0697b59e8064   1Gi        RWO            gp2            25m$ kubectl delete pvc emqx-data-my-emqx-0 emqx-data-my-emqx-1 emqx-data-my-emqx-2 emqx-data-my-emqx-3 emqx-data-my-emqx-4                    
persistentvolumeclaim "emqx-data-my-emqx-0" deleted
persistentvolumeclaim "emqx-data-my-emqx-1" deleted
persistentvolumeclaim "emqx-data-my-emqx-2" deleted
persistentvolumeclaim "emqx-data-my-emqx-3" deleted
persistentvolumeclaim "emqx-data-my-emqx-4" deleted

参考:
在K8S上部署EMQX企业版集群

  实测可以执行的命令:

helm install my-emqx emqx/emqx -n emqx --create-namespace --set service.type=NodePort --set service.nodePorts.dashboard=32717 --set service.nodePorts.mqtt=32596 --set persistence.storageClass=nfs
3. 部署 EMQX Edge 集群和 EMQX 企业版集群:

  EMQX Edge:部署 EMQX Edge 集群指定 image.repository=emqx/emqx-edge,其他设置与部署 EMQX 集群保持一致

$ helm install my-emqx-edge emqx/emqx --set image.repository=emqx/emqx$ kubectl get pods
NAME            READY   STATUS    RESTARTS   AGEmy-emqx-edge-0  1/1     Running   0          35smy-emqx-edge-1  1/1     Running   0          23smy-emqx-edge-2  1/1     Running   0          9s

  EMQX EE:部署 EMQX 企业版 集群首先需要前往 www.emqx.com 申请并下载 License 文件,并将 License 文件创建为 Secret 资源

$ kubectl create secret generic your-license-secret-name --from-file=/path/to/emqx.lic

  然后在部署时指定 repo 为 emqx/emqx-ee, emqxLicneseSecretName=your-license-secret-name, 其他设置与部署 EMQX 集群保持一致

$ helm install my-emqx-ee emqx/emqx-ee emqxLicneseSecretName=your-license-secret-name

安装方式二:定制化部署

  参考自:从零开始建立 EMQX MQTT 服务器的 K8S 集群

1. 使用 Pod 直接部署 EMQX Broker

  EMQX Broker 在 docker hub 上提供了镜像, 因此可以很方便的在单个的 pod 上部署 EMQX Broker,使用 kubectl run 命令创建一个运行着 EMQX Broker 的 Pod:

$ kubectl run emqx --image=emqx/emqx:v4.1-rc.1  --generator=run-pod/v1
error: unknown flag: --generator
See 'kubectl run --help' for usage.$ kubectl run emqx --image=emqx/emqx:v4.1-rc.1
pod/emqx created

  查看 EMQX Broker 的状态:

$ kubectl get pods -o wide
NAME                       READY   STATUS              RESTARTS       AGE    IP              NODE        NOMINATED NODE   READINESS GATES
emqx                       0/1     ContainerCreating   0              8s     <none>          master-01   <none>           <none>$ kubectl get pods -o wide
NAME                       READY   STATUS    RESTARTS       AGE    IP              NODE        NOMINATED NODE   READINESS GATES
emqx                       1/1     Running   0              97s    172.20.32.139   master-01   <none>           <none>$ kubectl exec emqx -- emqx_ctl status
Node 'emqx@172.20.32.139' is started
emqx 4.1-rc.1 is running

  删除 Pod:

$ kubectl delete pods emqx
pod "emqx" deleted

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

2. 使用 Deoloyment 部署 Pod

  Deployment 为 Pod 和 ReplicaSet 提供了一个声明式定义(declarative)方法,用来替代以前的 ReplicationController 来方便的管理应用。典型的应用场景包括:

  • 定义Deployment来创建Pod和ReplicaSet
  • 滚动升级和回滚应用
  • 扩容和缩容
  • 暂停和继续Deployment

  使用 Deployment 部署一个 EMQX Broker Pod:

$ vim deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:name: emqx-deploymentlabels:app: emqx
spec:replicas: 1selector:matchLabels:app: emqxtemplate:metadata:labels:app: emqxspec:containers:- name: emqximage: emqx/emqx:v4.1-rc.1ports:- name: mqttcontainerPort: 1883- name: mqttsslcontainerPort: 8883- name: mgmtcontainerPort: 8081- name: wscontainerPort: 8083- name: wsscontainerPort: 8084- name: dashboardcontainerPort: 18083

  部署 Deployment:

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

  查看部署情况:

$ kubectl get deployment
NAME              READY   UP-TO-DATE   AVAILABLE   AGE
emqx-deployment   0/1     1            0           49s$ kubectl get pods
NAME                               READY   STATUS    RESTARTS       AGE
emqx-deployment-75bd4f75b6-wf9sh   1/1     Running   0              73s$ kubectl exec pod/emqx-deployment-75bd4f75b6-wf9sh -- emqx_ctl status
Node 'emqx-deployment-75bd4f75b6-wf9sh@172.20.58.199' is started
emqx 4.1-rc.1 is running$ kubectl get pods
NAME                               READY   STATUS    RESTARTS       AGE
emqx-deployment-75bd4f75b6-8njhv   1/1     Running   0              55s

  尝试手动删除 Pod:

$ kubectl delete pods emqx-deployment-75bd4f75b6-8njhv
pod "emqx-deployment-75bd4f75b6-8njhv" deleted$ kubectl get pods
NAME                              READY   STATUS    RESTARTS   AGE
emqx-deployment-68fcb4bfd6-2nhh6   1/1     Running   0          59s

  输出结果表明成功用 Deployment 部署了 EMQX Broker Pod,即使是此 Pod 被意外终止,Deployment 也会重新创建一个新的 Pod。

3. 使用 Services 公开 EMQX Broker Pod 服务

  Kubernetes Pods 是有生命周期的。他们可以被创建,而且销毁不会再启动。 如果使用 Deployment 来运行应用程序,则它可以动态创建和销毁 Pod。

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

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

  答案是:Service

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

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

vim service.yaml
apiVersion: v1
kind: Service
metadata:name: emqx-service
spec:selector:app: emqxports:- name: mqttport: 1883protocol: TCPtargetPort: mqtt- name: mqttsslport: 8883protocol: TCPtargetPort: mqttssl- name: mgmtport: 8081protocol: TCPtargetPort: mgmt- name: wsport: 8083protocol: TCPtargetPort: ws- name: wssport: 8084protocol: TCPtargetPort: wss- name: dashboardport: 18083protocol: TCPtargetPort: 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.68.228.164   <none>        1883/TCP,8883/TCP,8081/TCP,8083/TCP,8084/TCP,18083/TCP   16s

  使用 Service 提供的 IP 查看 EMQX Broker 的 API

$ curl 10.68.228.164:8081/status
Node emqx-deployment-75bd4f75b6-8njhv@172.20.32.140 is started
emqx is running

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

4. 通过 kubernetes 自动集群 EMQX MQTT 服务器

  上文中通过 Deployment 部署了单个的 EMQX Broker Pod,通过 Deployment 扩展 Pod 的数量是极为方便的,执行 kubectl scale deployment ${deployment_name} --replicas ${numer} 命令即可扩展 Pod 的数量,下面将 EMQX 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-75bd4f75b6-8njhv   1/1     Running             0              8m41s
emqx-deployment-75bd4f75b6-kjv22   0/1     ContainerCreating   0              12s
emqx-deployment-75bd4f75b6-thcbv   1/1     Running             0              12s$ kubectl exec emqx-deployment-75bd4f75b6-8njhv -- emqx_ctl status
Node 'emqx-deployment-75bd4f75b6-8njhv@172.20.32.140' is started
emqx 4.1-rc.1 is running$ kubectl exec emqx-deployment-75bd4f75b6-8njhv -- emqx_ctl cluster status
Cluster status: #{running_nodes =>['emqx-deployment-75bd4f75b6-8njhv@172.20.32.140'],stopped_nodes => []}

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

5. 修改 EMQX Broker 的配置

  查看 EMQX Broker 文档中关于 自动集群 的内容,可以看到需要修改 EMQX 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.apiserverkubernetes apiserver 的地址,可以通过 kubectl cluster-info 命令获取,cluster.kubernetes.service_name 为上文中 Service 的 name, cluster.kubernetes.app_name 为 EMQX Broker 的 node.name@ 符号之前的部分,所以还需要将集群中 EMQX Broker 设置为统一的 node.name 的前缀。

  EMQX Broker 的 docker 镜像提供了通过环境变量修改配置的功能,具体可以查看 docker hub 或 Github。

$ vim deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:name: emqx-deploymentlabels:app: emqx
spec:replicas: 3selector:matchLabels:app: emqxtemplate:metadata:labels:app: emqxspec:containers:- name: emqximage: emqx/emqx:v4.1-rc.1ports:- name: mqttcontainerPort: 1883- name: mqttsslcontainerPort: 8883- name: mgmtcontainerPort: 8081- name: wscontainerPort: 8083- name: wsscontainerPort: 8084- name: dashboardcontainerPort: 18083env:- name: EMQX_NAMEvalue: emqx- name: EMQX_CLUSTER__DISCOVERYvalue: k8s- name: EMQX_CLUSTER__K8S__APP_NAMEvalue: emqx- name: EMQX_CLUSTER__K8S__SERVICE_NAMEvalue: emqx-service- name: EMQX_CLUSTER__K8S__APISERVERvalue: "https://kubernetes.default.svc:443"- name: EMQX_CLUSTER__K8S__NAMESPACEvalue: 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$ root@k8s-master01:/home/ubuntu/huiq/emqx# kubectl apply -f deployment.yaml
deployment.apps/emqx-deployment created
6. 赋予 Pod 访问 kubernetes apiserver 的权限

  上文部署 Deployment 之后,查看 EMQX Broker 的状态,可以看到 EMQX Broker 虽然成功启动了,但是依然没有集群成功,查看 EMQX Broker Pod 的 log:

$ kubectl get pods
NAME                               READY   STATUS    RESTARTS       AGE
emqx-deployment-64d5cc575c-dsrgj   1/1     Running   0              41s
emqx-deployment-64d5cc575c-k4twn   1/1     Running   0              41s
emqx-deployment-64d5cc575c-ktdqk   1/1     Running   0              41s$ kubectl exec emqx-deployment-64d5cc575c-dsrgj -- emqx_ctl status
Node 'emqx@172.20.58.200' is started
emqx 4.1-rc.1 is running$ kubectl exec emqx-deployment-64d5cc575c-dsrgj -- emqx_ctl cluster status
Cluster status: #{running_nodes => ['emqx@172.20.58.200'],stopped_nodes => []}root@k8s-master01:/home/ubuntu/huiq/emqx# kubectl logs emqx-deployment-64d5cc575c-dsrgj
...
(emqx@172.20.58.200)1> 2024-07-11 08:14:37.525 [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 鉴权。

$ vim rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:namespace: defaultname: emqx
---
kind: Role
apiVersion: rbac.authorization.kubernetes.io/v1beta1
metadata:namespace: defaultname: emqx
rules:
- apiGroups:- ""resources:- endpoints verbs: - get- watch- list
---
kind: RoleBinding
apiVersion: rbac.authorization.kubernetes.io/v1beta1
metadata:namespace: defaultname: emqx
subjects:- kind: ServiceAccountname: emqxnamespace: default
roleRef:kind: Rolename: emqxapiGroup: rbac.authorization.kubernetes.io

  部署相应的资源:

$ kubectl apply -f rbac.yaml
serviceaccount/emqx created
resource mapping not found for name: "emqx" namespace: "default" from "rbac.yaml": no matches for kind "Role" in version "rbac.authorization.kubernetes.io/v1beta1"
ensure CRDs are installed first
resource mapping not found for name: "emqx" namespace: "default" from "rbac.yaml": no matches for kind "RoleBinding" in version "rbac.authorization.kubernetes.io/v1beta1"
ensure CRDs are installed first$ kubectl explain role
GROUP:      rbac.authorization.k8s.io
KIND:       Role
VERSION:    v1$ kubectl explain RoleBinding
GROUP:      rbac.authorization.k8s.io
KIND:       RoleBinding
VERSION:    v1

  解决上面的问题,重新编辑 rbac.yaml 文件:

apiVersion: v1
kind: ServiceAccount
metadata:namespace: defaultname: emqx
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:namespace: defaultname: emqx
rules:
- apiGroups:- ""resources:- endpointsverbs:- get- watch- list
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:namespace: defaultname: emqx
subjects:- kind: ServiceAccountname: emqxnamespace: default
roleRef:kind: Rolename: emqxapiGroup: rbac.authorization.k8s.io$ kubectl delete serviceaccount emqx
serviceaccount "emqx" deleted
$ kubectl delete role emqx
role.rbac.authorization.k8s.io "emqx" deleted
$ kubectl delete rolebinding emqx
rolebinding.rbac.authorization.k8s.io "emqx" deleted$ kubectl apply -f rbac.yaml
serviceaccount/emqx created
role.rbac.authorization.k8s.io/emqx created
rolebinding.rbac.authorization.k8s.io/emqx created$ kubectl get serviceaccount
NAME                    SECRETS   AGE
emqx                    0         16s$ kubectl get role
NAME   CREATED AT
emqx   2024-07-11T08:45:48Z$ kubectl get rolebinding
NAME   ROLE        AGE
emqx   Role/emqx   51s

  修改 Deployment 的 yaml 文件,增加 spec.template.spec.serviceAccountName,并重新部署:

 vim deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:name: emqx-deploymentlabels:app: emqx
spec:replicas: 3selector:matchLabels:app: emqxtemplate:metadata:labels:app: emqxspec:serviceAccountName: emqxcontainers:- name: emqximage: emqx/emqx:v4.1-rc.1ports:- name: mqttcontainerPort: 1883- name: mqttsslcontainerPort: 8883- name: mgmtcontainerPort: 8081- name: wscontainerPort: 8083- name: wsscontainerPort: 8084- name: dashboardcontainerPort: 18083env:- name: EMQX_NAMEvalue: emqx- name: EMQX_CLUSTER__DISCOVERYvalue: kubernetes- name: EMQX_CLUSTER__K8S__APP_NAMEvalue: emqx- name: EMQX_CLUSTER__K8S__SERVICE_NAMEvalue: emqx-service- name: EMQX_CLUSTER__K8S__APISERVERvalue: "https://kubernetes.default.svc:443"- name: EMQX_CLUSTER__K8S__NAMESPACEvalue: 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-7c4675f785-6r6dg   1/1     Running   0              28s
emqx-deployment-7c4675f785-cqbgh   1/1     Running   0              28s
emqx-deployment-7c4675f785-nghf6   1/1     Running   0              28s$ kubectl exec emqx-deployment-7c4675f785-6r6dg  -- emqx_ctl status
Node 'emqx@127.0.0.1' not responding to pings.
command terminated with exit code 1

  尝试解决:本以为是文章里下划线写多了导致的,虽然 emqx 可以运行了但不是集群模式

$ vim deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:name: emqx-deploymentlabels:app: emqx
spec:replicas: 3selector:matchLabels:app: emqxtemplate:metadata:labels:app: emqxspec:serviceAccountName: emqxcontainers:- name: emqximage: emqx/emqx:v4.1-rc.1ports:- name: mqttcontainerPort: 1883- name: mqttsslcontainerPort: 8883- name: mgmtcontainerPort: 8081- name: wscontainerPort: 8083- name: wsscontainerPort: 8084- name: dashboardcontainerPort: 18083env:- name: EMQX_NAMEvalue: emqx- name: EMQX_CLUSTER_DISCOVERYvalue: kubernetes- name: EMQX_CLUSTER_K8S_APP_NAMEvalue: emqx- name: EMQX_CLUSTER_K8S_SERVICE_NAMEvalue: emqx-service- name: EMQX_CLUSTER_K8S_APISERVERvalue: "https://kubernetes.default.svc:443"- name: EMQX_CLUSTER_K8S_NAMESPACEvalue: 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-794478ff56-2rqwp   1/1     Running   0              19s
emqx-deployment-794478ff56-4jnmt   1/1     Running   0              19s
emqx-deployment-794478ff56-m4mk4   1/1     Running   0              19s
$ kubectl exec emqx-deployment-794478ff56-2rqwp -- emqx_ctl status
Node 'emqx@172.20.85.238' is started
emqx 4.1-rc.1 is running
$ kubectl exec emqx-deployment-794478ff56-bzck8 -- emqx_ctl cluster status
Cluster status: #{running_nodes => ['emqx@172.20.85.244'],stopped_nodes => []}

  最终解决:文章里给留了个大坑,需要将 kubernetes 改为 k8s

$ vim deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:name: emqx-deploymentlabels:app: emqx
spec:replicas: 3selector:matchLabels:app: emqxtemplate:metadata:labels:app: emqxspec:serviceAccountName: emqxcontainers:- name: emqximage: emqx/emqx:v4.1-rc.1ports:- name: mqttcontainerPort: 1883- name: mqttsslcontainerPort: 8883- name: mgmtcontainerPort: 8081- name: wscontainerPort: 8083- name: wsscontainerPort: 8084- name: dashboardcontainerPort: 18083env:- name: EMQX_NAMEvalue: emqx- name: EMQX_CLUSTER__DISCOVERYvalue: k8s- name: EMQX_CLUSTER__K8S__APP_NAMEvalue: emqx- name: EMQX_CLUSTER__K8S__SERVICE_NAMEvalue: emqx-service- name: EMQX_CLUSTER__K8S__APISERVERvalue: "https://kubernetes.default.svc:443"- name: EMQX_CLUSTER__K8S__NAMESPACEvalue: 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-5884fd896f-b6hz9   1/1     Running   0            3s
emqx-deployment-5884fd896f-hvd6v   1/1     Running   0            3s
emqx-deployment-5884fd896f-jv4ck   1/1     Running   0            3s$ kubectl exec emqx-deployment-5884fd896f-b6hz9 -- emqx_ctl status
Node 'emqx@172.20.32.168' is started
emqx 4.1-rc.1 is running
$ kubectl exec emqx-deployment-5884fd896f-b6hz9 -- emqx_ctl cluster status
Cluster status: #{running_nodes =>['emqx@172.20.32.168','emqx@172.20.58.237','emqx@172.20.85.197'],stopped_nodes => []}

  中止一个 Pod:

$ kubectl delete pods emqx-deployment-5884fd896f-b6hz9 --force
Warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
pod "emqx-deployment-5884fd896f-b6hz9" force deleted$ kubectl get pods
NAME                               READY   STATUS    RESTARTS     AGE
emqx-deployment-5884fd896f-84wxw   1/1     Running   0            34s
emqx-deployment-5884fd896f-hvd6v   1/1     Running   0            5m11s
emqx-deployment-5884fd896f-jv4ck   1/1     Running   0            5m11s$ kubectl exec emqx-deployment-5884fd896f-84wxw -- emqx_ctl cluster status
Cluster status: #{running_nodes =>['emqx@172.20.32.169','emqx@172.20.58.237','emqx@172.20.85.197'],stopped_nodes => ['emqx@172.20.32.168']}

  输出结果表明 EMQX Broker 会正确的显示已经停掉的 Pod,并将 Deployment 新建的 Pod 加入集群。至此,EMQX Broker 在 kubernetes 上成功建立集群。

7. 持久化 EMQX Broker 集群

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

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

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

  接下来使用 ConfigMap 记录 EMQX Broker 的配置,并将它们以环境变量的方式导入到 Deployment 中。定义 Configmap,并部署:

vim 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

$ vim deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:name: emqx-deploymentlabels:app: emqx
spec:replicas: 3selector:matchLabels:app: emqxtemplate:metadata:labels:app: emqxspec:serviceAccountName: emqxcontainers:- name: emqximage: emqx/emqx:v4.1-rc.1ports:- name: mqttcontainerPort: 1883- name: mqttsslcontainerPort: 8883- name: mgmtcontainerPort: 8081- name: wscontainerPort: 8083- name: wsscontainerPort: 8084- name: dashboardcontainerPort: 18083envFrom:- configMapRef:name: emqx-config

  重新部署 Deployment,查看状态:

$ 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-5958799d95-7lfw6   1/1     Running   0            20s
emqx-deployment-5958799d95-hfrvt   1/1     Running   0            20s
emqx-deployment-5958799d95-q6d7p   1/1     Running   0            20s$ kubectl exec emqx-deployment-5958799d95-7lfw6 -- emqx_ctl status
Node 'emqx-deployment-5958799d95-7lfw6@172.20.58.246' is started
emqx 4.1-rc.1 is running$ kubectl exec emqx-deployment-5958799d95-7lfw6 -- emqx_ctl cluster status
Cluster status: #{running_nodes =>['emqx-deployment-5958799d95-7lfw6@172.20.58.246'],stopped_nodes => []}

  解决:

$ vim configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:name: emqx-config
data:EMQX_NAME: "emqx"EMQX_CLUSTER__DISCOVERY: "k8s"EMQX_CLUSTER__K8S__APP_NAME: "emqx"EMQX_CLUSTER__K8S__SERVICE_NAME: "emqx-service"EMQX_CLUSTER__K8S__APISERVER: "https://kubernetes.default.svc:443"EMQX_CLUSTER__K8S__NAMESPACE: "default"$ kubectl delete configmap emqx-config
configmap "emqx-config" deleted
$ kubectl delete deployment emqx-deployment
deployment.apps "emqx-deployment" deleted$ kubectl get pods
NAME                               READY   STATUS    RESTARTS       AGE
emqx-deployment-5958799d95-6vkth   1/1     Running   0              76m
emqx-deployment-5958799d95-dtnwf   1/1     Running   0              76m
emqx-deployment-5958799d95-j4vcf   1/1     Running   0              76m$ kubectl exec emqx-deployment-5958799d95-6vkth -- emqx_ctl status
Node 'emqx@172.20.58.245' is started
emqx 4.1-rc.1 is running$ kubectl exec emqx-deployment-5958799d95-6vkth -- emqx_ctl cluster status
Cluster status: #{running_nodes =>['emqx@172.20.32.143','emqx@172.20.58.245','emqx@172.20.85.211'],stopped_nodes => []}

  EMQX Broker 的配置文件已经解耦到 Configmap 中了,如果有需要,可以自由的配置一个或多个 Configmap,并把它们作为环境变量或是文件引入到 Pod 内。

8. StatefulSet

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

  • 稳定的持久化存储,即 Pod 重新调度后还是能访问到相同的持久化数据,基于 PVC 来实现
  • 稳定的网络标志,即 Pod 重新调度后其 PodName 和 HostName 不变,基于 Headless Service(即没有Cluster IP的Service)来实现
  • 稳定的网络标志,即 Pod 重新调度后其 PodName 和 HostName 不变,基于 Headless Service(即没有Cluster IP的Service)来实现
  • 有序收缩,有序删除(即从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:

vim statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:name: emqx-statefulsetlabels:app: emqx
spec:serviceName: emqx-headlessupdateStrategy:type: RollingUpdatereplicas: 3selector:matchLabels:app: emqxtemplate:metadata:labels:app: emqxspec:serviceAccountName: emqxcontainers:- name: emqximage: emqx/emqx:v4.1-rc.1ports:- name: mqttcontainerPort: 1883- name: mqttsslcontainerPort: 8883- name: mgmtcontainerPort: 8081- name: wscontainerPort: 8083- name: wsscontainerPort: 8084- name: dashboardcontainerPort: 18083envFrom:- configMapRef:name: emqx-config

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

$ vim headless.yaml
apiVersion: v1
kind: Service
metadata:name: emqx-headless
spec:type: ClusterIPclusterIP: Noneselector:app: emqxports:- name: mqttport: 1883protocol: TCPtargetPort: 1883- name: mqttsslport: 8883protocol: TCPtargetPort: 8883- name: mgmtport: 8081protocol: TCPtargetPort: 8081- name: websocketport: 8083protocol: TCPtargetPort: 8083- name: wssport: 8084protocol: TCPtargetPort: 8084- name: dashboardport: 18083protocol: TCPtargetPort: 18083

  因为 Headless Service 并不需要 IP,所以配置了 clusterIP: None 。部署相应的资源:

$ kubectl apply -f headless.yaml
service/emqx-headless created$ kubectl get svc
NAME                     TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                                                                       AGE
emqx-headless            ClusterIP   None            <none>        1883/TCP,8883/TCP,8081/TCP,8083/TCP,8084/TCP,18083/TCP                        53semqx-service             ClusterIP   10.68.228.164   <none>        1883/TCP,8883/TCP,8081/TCP,8083/TCP,8084/TCP,18083/TCP                        21h$ 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              2m
emqx-statefulset-1         1/1     Running   0              24s
emqx-statefulset-2         1/1     Running   0              22s$ kubectl exec emqx-statefulset-0 -- emqx_ctl cluster status
Cluster status: #{running_nodes =>['emqx@172.20.32.174','emqx@172.20.58.248','emqx@172.20.85.214'],stopped_nodes => []}

  更新 Configmap:StatefulSet 提供了稳定的网络标志,EMQX Broker 支持使用 hostname 和 dns 规则来代提 IP 实现集群,以 hostname 为例,需要修改 emqx.conf:

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

  kubernetes 集群中 Pod 的 DNS 规则可以由用户自定义,EMQX 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。

$ kubectl edit configmap emqx-config
# 修改前
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:EMQX_CLUSTER__DISCOVERY: k8sEMQX_CLUSTER__K8S__APISERVER: https://kubernetes.default.svc:443EMQX_CLUSTER__K8S__APP_NAME: emqxEMQX_CLUSTER__K8S__NAMESPACE: defaultEMQX_CLUSTER__K8S__SERVICE_NAME: emqx-serviceEMQX_NAME: emqx
kind: ConfigMap
metadata:annotations:kubectl.kubernetes.io/last-applied-configuration: |{"apiVersion":"v1","data":{"EMQX_CLUSTER__DISCOVERY":"k8s","EMQX_CLUSTER__K8S__APISERVER":"https://kubernetes.default.svc:443","EMQX_CLUSTER__K8S__APP_NAME":"emqx","EMQX_CLUSTER__K8S__NAMESPACE":"default","EMQX_CLUSTER__K8S__SERVICE_NAME":"emqx-service","EMQX_NAME":"emqx"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"emqx-config","namespace":"default"}}creationTimestamp: "2024-07-12T05:26:16Z"name: emqx-confignamespace: defaultresourceVersion: "1734159"uid: 13ccc86a-9ab3-450d-8a91-d73a7745d75b# 修改后
apiVersion: v1
data:EMQX_CLUSTER__DISCOVERY: k8sEMQX_CLUSTER__K8S__APISERVER: https://kubernetes.default.svc:443EMQX_CLUSTER__K8S__APP_NAME: emqxEMQX_CLUSTER__K8S__NAMESPACE: defaultEMQX_CLUSTER__K8S__SERVICE_NAME: emqx-headlessEMQX_NAME: emqxEMQX_CLUSTER__K8S__ADDRESS_TYPE: hostnameEMQX_CLUSTER__K8S__SUFFIX: svc.cluster.local
kind: ConfigMap
metadata:annotations:kubectl.kubernetes.io/last-applied-configuration: |{"apiVersion":"v1","data":{"EMQX_CLUSTER__DISCOVERY":"k8s","EMQX_CLUSTER__K8S__APISERVER":"https://kubernetes.default.svc:443","EMQX_CLUSTER__K8S__APP_NAME":"emqx","EMQX_CLUSTER__K8S__NAMESPACE":"default","EMQX_CLUSTER__K8S__SERVICE_NAME":"emqx-headless","EMQX_NAME":"emqx","EMQX_CLUSTER__K8S__ADDRESS_TYPE":"hostname","EMQX_CLUSTER__K8S__SUFFIX":"svc.cluster.local"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"emqx-config","namespace":"default"}}creationTimestamp: "2024-07-12T05:26:16Z"name: emqx-confignamespace: defaultresourceVersion: "1734159"uid: 13ccc86a-9ab3-450d-8a91-d73a7745d75b

  重新部署 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 exec emqx-statefulset-0 -- emqx_ctl cluster status
Cluster status: #{running_nodes =>['emqx@emqx-statefulset-0.emqx-headless.default.svc.cluster.local'],stopped_nodes => []}$ kubectl exec emqx-statefulset-0 -- emqx_ctl cluster status
Cluster status: #{running_nodes =>['emqx@emqx-statefulset-0.emqx-headless.default.svc.cluster.local'],stopped_nodes => []}$ kubectl logs pod/emqx-statefulset-0 -n default
...
(emqx@emqx-statefulset-0.emqx-headless.default.svc.cluster.local)1> 2024-07-12 07:59:16.261 [error] Ekka(AutoCluster): Discover error: {badkey,<<"hostname">>}
[{maps,get,[<<"hostname">>,#{<<"ip">> => <<"172.20.32.158">>,<<"nodeName">> => <<"master-01">>,<<"targetRef">> =>#{<<"kind">> => <<"Pod">>,<<"name">> => <<"my-emqx-0">>,<<"namespace">> => <<"default">>,<<"uid">> => <<"de590aa1-d6da-42e0-9515-fe7f1fa68dfe">>}}],[]},{ekka_cluster_k8s,extract_host,2,[{file,"/emqx_rel/_build/emqx/lib/ekka/src/ekka_cluster_k8s.erl"},{line,114}]},{ekka_cluster_k8s,'-extract_addresses/2-lc$^1/1-1-',2,[{file,"/emqx_rel/_build/emqx/lib/ekka/src/ekka_cluster_k8s.erl"},{line,110}]},{ekka_cluster_k8s,'-extract_addresses/2-lc$^1/1-1-',2,[{file,"/emqx_rel/_build/emqx/lib/ekka/src/ekka_cluster_k8s.erl"},{line,111}]},{ekka_cluster_k8s,'-extract_addresses/2-lc$^0/1-0-',2,[{file,"/emqx_rel/_build/emqx/lib/ekka/src/ekka_cluster_k8s.erl"},{line,111}]},{ekka_cluster_k8s,extract_addresses,2,[{file,"/emqx_rel/_build/emqx/lib/ekka/src/ekka_cluster_k8s.erl"},{line,112}]},{ekka_cluster_k8s,discover,1,[{file,"/emqx_rel/_build/emqx/lib/ekka/src/ekka_cluster_k8s.erl"},{line,48}]},{ekka_autocluster,discover_and_join,2,[{file,"/emqx_rel/_build/emqx/lib/ekka/src/ekka_autocluster.erl"},{line,125}]}]

  原因:我当时是已经存在一套 emqx 集群在 k8s 上且在正常使用,将这套 my-emqx 的删除即可。

$ 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 => []}

  注意:当改为版本 5.7.1 版本的时候,修改 statefulset.yaml 相关内容为 image: emqx/emqx:5.7.1;configmap.yaml 文件里的配置参数名称有变化,需要改为

apiVersion: v1
kind: ConfigMap
metadata:name: emqx-config
data:EMQX_NAME: "emqx"EMQX_CLUSTER__DISCOVERY_STRATEGY: "k8s"EMQX_CLUSTER__K8S__APISERVER: "https://kubernetes.default.svc:443"EMQX_CLUSTER__K8S__SERVICE_NAME: "emqx-headless"EMQX_CLUSTER__K8S__NAMESPACE: "default"EMQX_CLUSTER__K8S__ADDRESS_TYPE: "hosname"EMQX_CLUSTER__K8S__SUFFIX: "svc.cluster.local"

  可以看到新的 EMQX 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 中的 EMQX Broker 也成功的加入了集群。

9. 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 代表的什么。这个概念在其他存储系统中有时被称为“配置文件”。

  在部署 EMQX Broker 的时候,可以预先创建好 PV 或 StorageClass,然后利用 PVC 将 EMQX Broker 的 /opt/emqx/data/mnesia 目录挂载出来,当Pods被重新调度之后,EMQX 会从 /opt/emqx/data/mnesia 目录中获取数据并恢复,从而实现 EMQX Broker 的持久化。

  定义 StatefulSet:

$ vim statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:name: emqx-statefulsetlabels:app: emqx
spec:replicas: 3serviceName: emqx-headlessupdateStrategy:type: RollingUpdateselector:matchLabels:app: emqxtemplate:metadata:labels:app: emqxspec:volumes:- name: emqx-datapersistentVolumeClaim:claimName: emqx-pvcserviceAccountName: emqxcontainers:- name: emqximage: emqx/emqx:v4.1-rc.1ports:- name: mqttcontainerPort: 1883- name: mqttsslcontainerPort: 8883- name: mgmtcontainerPort: 8081- name: wscontainerPort: 8083- name: wsscontainerPort: 8084- name: dashboardcontainerPort: 18083envFrom:- configMapRef:name: emqx-configvolumeMounts:- name: emqx-datamountPath: "/opt/emqx/data/mnesia"volumeClaimTemplates:- metadata:name: emqx-pvcannotations:volume.alpha.kubernetes.io/storage-class: manualspec: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 目录下。

$ kubectl get pvc
NAME                                  STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS        AGE
emqx-pvc-emqx-statefulset-0           Pending$ kubectl get pods
NAME                       READY   STATUS    RESTARTS   AGE
emqx-statefulset-0         0/1     Pending   0          75s$ kubectl describe pvc emqx-pvc-emqx-statefulset-0
Events:Type    Reason         Age               From                         Message----    ------         ----              ----                         -------Normal  FailedBinding  4s (x2 over 15s)  persistentvolume-controller  no persistent volumes available for this claim and no storage class is set$ kubectl describe pod emqx-statefulset-0
Events:Type     Reason            Age   From               Message----     ------            ----  ----               -------Warning  FailedScheduling  109s  default-scheduler  0/3 nodes are available: persistentvolumeclaim "local-pv-sdc" not found. preemption: 0/3 nodes are available: 3 No preemption victims found for incoming pod..

  解决:手动创建 emqx-pvc。参考:在K8S中使用Local持久卷

$ kubectl delete statefulset emqx-statefulset
statefulset.apps "emqx-statefulset" deleted$ kubectl delete pvc emqx-pvc-emqx-statefulset-0
persistentvolumeclaim "emqx-pvc-emqx-statefulset-0" deleted$ vim emqx-pvc.yml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:name: emqx-pvc
spec:accessModes:- ReadWriteOncestorageClassName: local-redis-storageresources:requests:storage: 1Gi$ kubectl create -f emqx-pvc.yml
persistentvolumeclaim/emqx-pvc created$ kubectl get pvc
NAME                                  STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
emqx-pvc           Pending                                                                        local-redis-storage   5s$ vim statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:name: emqx-statefulsetlabels:app: emqx
spec:replicas: 3serviceName: emqx-headlessupdateStrategy:type: RollingUpdateselector:matchLabels:app: emqxtemplate:metadata:labels:app: emqxspec:volumes:- name: emqx-datapersistentVolumeClaim:claimName: emqx-pvcserviceAccountName: emqxcontainers:- name: emqximage: emqx/emqx:v4.1-rc.1ports:- name: mqttcontainerPort: 1883- name: mqttsslcontainerPort: 8883- name: mgmtcontainerPort: 8081- name: wscontainerPort: 8083- name: wsscontainerPort: 8084- name: dashboardcontainerPort: 18083envFrom:- configMapRef:name: emqx-configvolumeMounts:- name: emqx-datamountPath: "/opt/emqx/data/mnesia"$ kubectl apply -f statefulset.yaml
statefulset.apps/emqx-statefulset created$ kubectl get pvc
NAME                                  STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
emqx-pvc           Bound    pvc-3fd906c3-5372-4e92-b971-062bc235b268   1Gi        RWO            local-redis-storage   22s$ kubectl get pods
NAME                       READY   STATUS    RESTARTS   AGE
emqx-statefulset-0         1/1     Running   0          11s
emqx-statefulset-1         1/1     Running   0          6s
emqx-statefulset-2         1/1     Running   0          5s

  总感觉原文章里配置的有问题,如果想有三个 pvc 的话也得手动创建,无法像原文章里那样,而且他们的前置条件是 emqx-pvc 已经存在。参考:helm或者k8s部署pod时遇到pod一直处于pending状态

$ kubectl get pvc emqx-pvc-emqx-statefulset-0 -o yaml > emqx-pvc-emqx-statefulset-0.yaml$ vim emqx-pvc-emqx-statefulset-0.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:annotations:volume.alpha.kubernetes.io/storage-class: local-redis-storagecreationTimestamp: "2024-07-18T05:09:03Z"finalizers:- kubernetes.io/pvc-protectionlabels:app: emqxname: emqx-pvc-emqx-statefulset-0namespace: defaultresourceVersion: "3204901"uid: 74f369fb-08d0-41a7-8230-83fddb0efe50
spec:accessModes:- ReadWriteOnceresources:requests:storage: 1GivolumeMode: Filesystem
status:phase: Pending# 添加如下内容:
spec:storageClassName: local-redis-storage$ kubectl create -f emqx-pvc-emqx-statefulset-0.yaml
persistentvolumeclaim/emqx-pvc-emqx-statefulset-0 created$ kubectl get pvc
NAME                                  STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
emqx-pvc-emqx-statefulset-0           Pending                                                                        local-redis-storage   5s$ kubectl get pvc emqx-pvc-emqx-statefulset-0 -o yaml > emqx-pvc-emqx-statefulset-1.yaml
# 同样添加 storageClassName: local-redis-storage 并修改 name 为 emqx-pvc-emqx-statefulset-1
$ kubectl create -f emqx-pvc-emqx-statefulset-1.yaml$ kubectl get pvc emqx-pvc-emqx-statefulset-0 -o yaml > emqx-pvc-emqx-statefulset-2.yaml
# 同样添加 storageClassName: local-redis-storage 并修改 name 为 emqx-pvc-emqx-statefulset-2
$ kubectl create -f emqx-pvc-emqx-statefulset-2.yaml$ kubectl get pvc
NAME                                  STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
emqx-pvc                              Bound     pvc-c12d4f80-07c8-4c72-8a97-765f74f34d53   1Gi        RWO            local-redis-storage   50m
emqx-pvc-emqx-statefulset-0           Pending                                                                        local-redis-storage   5m22s
emqx-pvc-emqx-statefulset-1           Pending                                                                        local-redis-storage   3s
emqx-pvc-emqx-statefulset-2           Pending                                                                        local-redis-storage   70s$ vim statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:name: emqx-statefulsetlabels:app: emqx
spec:replicas: 3serviceName: emqx-headlessupdateStrategy:type: RollingUpdateselector:matchLabels:app: emqxtemplate:metadata:labels:app: emqxspec:volumes:- name: emqx-datapersistentVolumeClaim:claimName: emqx-pvcserviceAccountName: emqxcontainers:- name: emqximage: emqx/emqx:v4.1-rc.1ports:- name: mqttcontainerPort: 1883- name: mqttsslcontainerPort: 8883- name: mgmtcontainerPort: 8081- name: wscontainerPort: 8083- name: wsscontainerPort: 8084- name: dashboardcontainerPort: 18083envFrom:- configMapRef:name: emqx-configvolumeMounts:- name: emqx-datamountPath: "/opt/emqx/data/mnesia"volumeClaimTemplates:- metadata:name: emqx-pvcannotations:volume.alpha.kubernetes.io/storage-class: local-redis-storagespec:accessModes: [ "ReadWriteOnce" ]resources:requests:storage: 1Gi$ 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          2m3s
emqx-statefulset-1         1/1     Running                  0          117s
emqx-statefulset-2         1/1     Running                  0          112s$ kubectl get pvc
NAME                                  STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
emqx-pvc                              Bound    pvc-c12d4f80-07c8-4c72-8a97-765f74f34d53   1Gi        RWO            local-redis-storage   54m
emqx-pvc-emqx-statefulset-0           Bound    pvc-a718e44c-0b2d-433c-be49-d264eb44b878   1Gi        RWO            local-redis-storage   8m39s
emqx-pvc-emqx-statefulset-1           Bound    pvc-1bb39cb2-4a23-49ab-b874-756cd1a2502f   1Gi        RWO            local-redis-storage   3m20s
emqx-pvc-emqx-statefulset-2           Bound    pvc-7df2cdf4-fc81-47f8-b02b-ffa8e8bb0ae6   1Gi        RWO            local-redis-storage   4m27s

  综上所述,也可以不用手动创建 emqx-pvc-emqx-statefulset-0emqx-pvc-emqx-statefulset-1emqx-pvc-emqx-statefulset-2,其实就是在 statefulset.yaml 中加一行配置的事,最终的文件内容为:

$ vim statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:name: emqx-statefulsetlabels:app: emqx
spec:replicas: 3serviceName: emqx-headlessupdateStrategy:type: RollingUpdateselector:matchLabels:app: emqxtemplate:metadata:labels:app: emqxspec:volumes:- name: emqx-datapersistentVolumeClaim:claimName: emqx-pvcserviceAccountName: emqxcontainers:- name: emqximage: emqx/emqx:v4.1-rc.1ports:- name: mqttcontainerPort: 1883- name: mqttsslcontainerPort: 8883- name: mgmtcontainerPort: 8081- name: wscontainerPort: 8083- name: wsscontainerPort: 8084- name: dashboardcontainerPort: 18083envFrom:- configMapRef:name: emqx-configvolumeMounts:- name: emqx-datamountPath: "/opt/emqx/data/mnesia"volumeClaimTemplates:- metadata:name: emqx-pvcannotations:volume.alpha.kubernetes.io/storage-class: local-redis-storagespec:storageClassName: local-redis-storageaccessModes: [ "ReadWriteOnce" ]resources:requests:storage: 1Gi$ kubectl get pvc
NAME                                  STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
emqx-pvc                              Bound    pvc-ce861efc-751c-4fd5-b96f-2605fb41e7d2   1Gi        RWO            local-redis-storage   15m$ kubectl apply -f statefulset.yaml
statefulset.apps/emqx-statefulset createdkubectl get pvc
NAME                                  STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
emqx-pvc                              Bound    pvc-ce861efc-751c-4fd5-b96f-2605fb41e7d2   1Gi        RWO            local-redis-storage   15m
emqx-pvc-emqx-statefulset-0           Bound    pvc-21a89457-5c05-4df3-b447-c7fad1424df8   1Gi        RWO            local-redis-storage   3m39s
emqx-pvc-emqx-statefulset-1           Bound    pvc-234da635-d316-4256-b559-98dab56cc5a4   1Gi        RWO            local-redis-storage   3m33s
emqx-pvc-emqx-statefulset-2           Bound    pvc-ec3bcfee-27ac-4776-8662-aa504676ca6a   1Gi        RWO            local-redis-storage   3m28s$ kubectl get pods
NAME                                 READY   STATUS                   RESTARTS   AGE
emqx-statefulset-0                   1/1     Running                  0          5m43s
emqx-statefulset-1                   1/1     Running                  0          5m37s
emqx-statefulset-2                   1/1     Running                  0          5m32s

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

相关文章:

K8S 上部署 Emqx

文章目录 安装方式一&#xff1a;1. 快速部署一个简单的 EMQX 集群&#xff1a;2. 部署一个持久化的 EMQX 集群&#xff1a;3. 部署 EMQX Edge 集群和 EMQX 企业版集群&#xff1a; 安装方式二&#xff1a;定制化部署1. 使用 Pod 直接部署 EMQX Broker2. 使用 Deoloyment 部署 …...

[React]利用Webcomponent封装React组件

[React]利用Webcomponent封装React组件 为什么这么做 我个人认为&#xff0c;最重要的点是可以很方便地跨框架挂载和卸载wc元素&#xff08;至少我在项目里是这么玩的&#xff09;&#xff0c;此外&#xff0c;基于wc的css沙箱以及它的shadowRoot机制&#xff0c;可以提供一套…...

Linux C服务需要在A服务和B服务都启动成功后才能启动

需求 C服务需要在A服务和B服务都启动成功后才能启动 服务编号服务名服务Anginx.service服务Bmashang.service服务Credis.service 实验 如果您想要 redis.service 在 nginx.service 和 mashang.service 都成功启动后才能启动&#xff0c;那么需要在 redis.service 的服务单元…...

VSCODE 下 openocd Jlink 的配置笔记

title: VSCODE 下 openocd Jlink 的配置笔记 tags: STM32HalCubemax 文章目录 内容VSCODE 下 openocd Jlink 的配置笔记安装完成后修改jlink的配置文件然后修改你的下载器为jlink烧录你的项目绝对会出现下面的问题那么打开下载的第一个软件 &#xff08;点到这个jlink右键&…...

JVM--HostSpot算法细节实现

1.根节点枚举 定义&#xff1a; 我们以可达性分析算法中从GC Roots 集合找引用链这个操作作为介绍虚拟机高效实现的第一个例 子。固定可作为GC Roots 的节点主要在全局性的引用&#xff08;例如常量或类静态属性&#xff09;与执行上下文&#xff08;例如 栈帧中的本地变量表&a…...

【Unity实战100例】Unity声音可视化多种显示效果

目录 一、技术背景 二、界面搭建 三、 实现 UIAudioVisualizer 基类 四、实现 AudioSampler 类 五、实现 IAudioSample 接口 六、实现MusicAudioVisualizer 七、实现 MicrophoneAudioManager 类 八、实现 MicrophoneAudioVisualizer 类 九、源码下载 Unity声音可视化四…...

[Cesium for Supermap] 加载3dTiles,点击获取属性

代码&#xff1a; // 设为椭球var obj [6378137.0, 6378137.0, 6356752.3142451793];Cesium.Ellipsoid.WGS84 Object.freeze(new Cesium.Ellipsoid(obj[0], obj[1], obj[2]));var viewer new Cesium.Viewer(cesiumContainer);var scene viewer.scenescene.lightSource.ambi…...

【stm32项目】基于stm32智能宠物喂养(完整工程资料源码)

基于STM32宠物喂养系统 前言&#xff1a; 随着人们生活幸福指数的提高&#xff0c;越来越多的家庭选择养宠物来为生活增添乐趣。然而&#xff0c;由于工作等原因&#xff0c;许多主人无法及时为宠物提供充足的食物与水。为了解决这一问题&#xff0c;我设计了一款便捷的宠物喂…...

选择Maya进行3D动画制作与渲染的理由

如果你对3D动画充满热情并追求成为专业3D动画师的梦想&#xff0c;你一定听说过Maya——近年来3D动画的行业标准。Maya被3D艺术家广泛使用&#xff0c;你是否想知道为什么Maya总是他们的首选&#xff1f;下面一起来了解下。 一、什么是Maya&#xff1f; 由Autodesk开发的Maya是…...

Promise应用

创建一个 Promise 对象 let promise showLabelText() {return new Promise((resolve, reject) > {axios({method: "post",url: "/code/expose/interface/queryActionPlan",data: { situationOneId: 19999, labels: [1, 2, 3] }}).then(response > {…...

51单片机嵌入式开发:13、STC89C52RC 之 RS232与电脑通讯

STC89C52RC 之 RS232与电脑通讯 第十三节课&#xff0c;RS232与电脑通讯1 概述2 Uart介绍2.1 概述2.2 STC89C52UART介绍2.3 STC89C52 UART寄存器介绍2.4 STC89C52 UART操作 3 C51 UART总结 第十三节课&#xff0c;RS232与电脑通讯 1 概述 RS232&#xff08;Recommended Stand…...

当代政治制度(练习题)

当代政治制度&#xff08;练习题&#xff09; Rz整理 仅供参考 干部鉴定必须坚持德才兼备原则&#xff0c;考核内容包括&#xff08;A.德 B.廉 C.能 D.勤 F.绩 &#xff09; A.德 B.廉 C.能 D.勤 E.信 F.绩我国干部任用的方式主要包括&#xff08;A.考任 C.委任 D.聘任 F.选任…...

前端pc和小程序接入快递100(跳转方式和api方式)====实时查询接口

文章目录 跳转方式微信小程序&#xff08;我以uniapp为例&#xff09;pc api接入说明关于签名计算成功示例 跳转方式 没有任何开发成本&#xff0c;直接一键接入 可以直接看官方文档 https://www.kuaidi100.com/openapi/api_wxmp.shtml 微信小程序&#xff08;我以uniapp为例…...

电脑永久性不小心删除了东西还可以恢复吗 电脑提示永久性删除文件怎么找回 怎么恢复电脑永久删除的数据

永久删除电脑数据的操作&#xff0c;对于很多常用电脑设备的用户来说&#xff0c;可以说时有发生&#xff01;但是&#xff0c;因为这些情况大都发生在不经意间&#xff0c;所以每每让广大用户感觉到十分苦恼。永久删除也有后悔药&#xff0c;轻松找回电脑中误删的文件。恢复文…...

LeetCode热题100刷题16:74. 搜索二维矩阵、33. 搜索旋转排序数组、153. 寻找旋转排序数组中的最小值、98. 验证二叉搜索树

74. 搜索二维矩阵 class Solution { public:bool searchMatrix(vector<vector<int>>& matrix, int target) {int row matrix.size();int col matrix[0].size();for(int i0;i<row;i) {//先排除一下不存在的情况if(i>0&&matrix[i][0]>target…...

C++仿函数

在C中&#xff0c;我们经常需要对类中的元素进行比较&#xff0c;例如在排序、查找等操作中。为了使类更加灵活&#xff0c;我们可以通过自定义比较函数来实现不同的比较方式。在本文中&#xff0c;我们将探讨如何在类中使用仿函数和 Lambda 表达式来定义自定义比较函数。 1. …...

文献阅读:tidyomics 生态系统:增强组学数据分析

文献介绍 文献题目&#xff1a; The tidyomics ecosystem: enhancing omic data analyses 研究团队&#xff1a; Stefano Mangiola&#xff08;澳大利亚沃尔特和伊丽莎霍尔医学研究所&#xff09;、Michael I. Love&#xff08;美国北卡罗来纳大学教堂山分校&#xff09;、Ant…...

MySQL运维实战之Clone插件(10.1)使用Clone插件

作者&#xff1a;俊达 clone插件介绍 mysql 8.0.17版本引入了clone插件。使用clone插件可以对本地l或远程的mysql实例进行clone操作。clone插件会拷贝innodb存储引擎表&#xff0c;clone得到的是原数据库的一个一致性的快照&#xff0c;可以使用该快照数据来启动新的实例。cl…...

【系统架构设计】数据库系统(三)

数据库系统&#xff08;三&#xff09; 数据库模式与范式数据库设计备份与恢复分布式数据库系统分布式数据库的概念特点分类目标 分布式数据库的架构分布式数据库系统与并行数据库系统 数据仓库数据挖掘NoSQL大数据 数据库模式与范式 数据库设计 备份与恢复 分布式数据库系统…...

免费视频批量横版转竖版

简介 视频处理器 v1.3 是一款由是貔貅呀开发的视频编辑和处理工具&#xff0c;提供高效便捷的视频批量横转竖&#xff0c;主要功能&#xff1a; 导入与删除文件&#xff1a;轻松导入多个视频文件&#xff0c;删除不必要的文件。暂停与继续处理&#xff1a;随时暂停和继续处理。…...

内存管理(知识点)

c语言与c对于内存管理的区别 c语言中动态内存管理方式 malloc\calloc\realloc\free c内存管理方式 c兼容c语言所以c语言中的方式也可以使用 但是用起来太麻烦 所以通过new和delete操作符竞选动态内存管理 单个对象 c语言: int* p2(int*)malloc(sizeof(int)) c : int*p3 new…...

【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【29】Sentinel

持续学习&持续更新中… 守破离 【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【29】Sentinel 简介熔断降级什么是熔断什么是降级相同点不同点 整合Sentinel自定义sentinel流控返回数据使用Sentinel来保护feign远程调用自定义资源给网关整合Sentinel参考 简介 熔断降…...

防范UDP Flood攻击的策略与实践

UDP Flood攻击是一种常见的分布式拒绝服务&#xff08;DDoS&#xff09;攻击手段&#xff0c;通过向目标服务器发送大量无效的UDP数据包&#xff0c;消耗其网络带宽和处理资源&#xff0c;最终导致合法的网络服务无法正常运行。本文将深入探讨UDP Flood攻击的原理、常见的防御策…...

昇思25天学习打卡营第17天 | CycleGAN图像风格迁移互换

通过深入学习CycleGAN模型&#xff0c;我对无监督图像到图像的转换技术有了更深的理解。CycleGAN不仅能在没有成对训练样本的情况下实现域之间的转换&#xff0c;而且在保持内容结构的同时成功转换图像风格&#xff0c;这在许多应用中都非常有用&#xff0c;如艺术风格转换、季…...

Leetcode 2520. 统计能整除数字的位数

问题描述&#xff1a; 给你一个整数 num &#xff0c;返回 num 中能整除 num 的数位的数目。 如果满足 nums % val 0 &#xff0c;则认为整数 val 可以整除 nums 。 示例 1&#xff1a; 输入&#xff1a;num 7 输出&#xff1a;1 解释&#xff1a;7 被自己整除&#xff0…...

WEB前端08-综合案例(动态表格)

使用 HTML、CSS 和 JavaScript 创建动态表格 在本教程中&#xff0c;我们将创建一个动态表格&#xff0c;允许用户添加行、选择项目&#xff0c;并执行批量操作&#xff0c;如全选或删除选中的行。我们将通过 HTML、CSS 和 JavaScript 来实现这一功能。让我们逐步了解每个部分…...

【面试题】Redo log和Undo log

Redo log 介绍Redo log之前我们需要了解一下&#xff0c;mysql数据操作的流程&#xff1a; 上述就是数据操作的流程图&#xff0c;可以发现sql语句并不是直接操作的磁盘而是通过操作内存&#xff0c;然后进行内存到磁盘的一个同步。这里我们必须要了解一些区域&#xff1a; 缓…...

开发实战经验分享:互联网医院系统源码与在线问诊APP搭建

作为一名软件开发者&#xff0c;笔者有幸参与了多个互联网医院系统的开发项目&#xff0c;并在此过程中积累了丰富的实战经验。本文将结合我的开发经验&#xff0c;分享互联网医院系统源码的设计与在线问诊APP的搭建过程。 一、需求分析 在开发任何系统之前&#xff0c;首先要…...

springboot系列教程(十六):配置Actuator组件,实现系统监控

一、Actuator简介 1、监控组件作用 在生产环境中&#xff0c;需要实时或定期监控服务的可用性。Spring Boot的actuator&#xff08;健康监控&#xff09;功能提供了很多监控所需的接口&#xff0c;可以对应用系统进行配置查看、相关功能统计等。 2、监控分类 Actuator 提供…...

单臂路由组网实验,单臂路由的定义、适用情况、作用

一、定义 单臂路由是指通过在路由器的一个接口上配置许多子接口,从而实现原来相互隔离的不同VLAN之间的互通。 子接口:把路由器上的实际的物理接口划分为多个逻辑上的接口,这些被划分的逻辑接口就是子接口。 二、适用情况 用在没有三层交换机,却要实现不同VLAN之间的互…...

p2p网上贷款网站建设方案/黄冈网站seo

一、首先需要在项目中引入better-scroll1. 在package.json 直接写入 "better-scroll":"^1.11.1" 版本以github上为准(目前最新)2.cpnm install 在node_modules 可以查看版本是否安装3.直接在你的组件里面写入import BScroll from better-scroll;二、bet…...

手机网站开发模板/百度网站

在自上而下的继承层次结构中&#xff0c;位于上层的类更具有通用性&#xff0c;甚至可能更加抽象。从某种角度看&#xff0c;祖先类更加通用&#xff0c;它只包含一些最基本的成员&#xff0c;人们只将它作为派生其他类的基类&#xff0c;而不会用来创建对象。甚至&#xff0c;…...

网站建站wordpress/手机打开国外网站app

--可以把外键约束暂时停止了&#xff0c;再删除数据&#xff0c;删除后再恢复外键约束具体语句是,sql2000中用法alter table 表的名字nochek constraint 外键约束名字truncate table 表的名字alter table 表的名字check constraint 外键约束名字 -- -- 停止c_MxbCardInfo所有约…...

临淄房产信息网123/历下区百度seo

marker&#xff1a; 边缘&#xff1a;’MarkerEdgeColor’, [],&#xff08;RGB 配色&#xff09;填充&#xff1a;’MarkerFaceColor’, []&#xff08;RGB 配色&#xff09;转载于:https://www.cnblogs.com/mtcnn/p/9421894.html...

做的网站一模一样会被告吗/快速排名软件案例

winformSystem.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;//可获得当前执行的exe的文件名。 Environment.CurrentDirectory;//获取和设置当前目录&#xff08;即该进程从中启动的目录&#xff09;的完全限定路径。 //备注 按照定义&#xff0c;如果该进程在…...

自做闪图网站/什么是网店推广

Oracle_lhr_CentOS 7.3 ECS上搭建RAC 18c单实例DGEMCCDG的FSFO快速故障转移配置 【大型连续免费公开课】Oracle 18c racdg13.3的emcc部署配置&#xff08;2018年最后一次免费公开课&#xff09; 报名连接&#xff1a;https://ke.qq.com/course/315575 讲师&#xff1a;小麦…...