跳至主内容

将 RabbitMQ 部署到 Kubernetes:涉及哪些内容?

·26 分钟阅读

随着时间的推移,我们看到在我们社区的 邮件列表Slack 频道上关于 Kubernetes 的查询数量飙升。

到 2024 年,大多数 Kubernetes 相关问题的答案是:使用 RabbitMQ 核心团队构建的 Kubernetes Operator。它包含了所有最佳实践,是强烈推荐的选项。

本文档介绍了在 Kubernetes 上自行部署 RabbitMQ 的基础知识:需要哪些 Kubernetes 资源,如何确保 RabbitMQ 节点使用持久化存储,如何处理敏感值的配置等等。

2024 年更新

提示

与其自行在 Kubernetes 上部署 RabbitMQ,不如考虑使用 RabbitMQ 核心团队构建的 Kubernetes Operator。它包含了所有最佳实践,并且是强烈推荐的选项。

简介

在没有使用 Kubernetes Operator 的情况下,在 Kubernetes 上部署像 RabbitMQ 这样的状态化数据服务,有点像在组装一个拼图。

涉及多个部分

在本文中,我们将尝试涵盖关键部分,并提及另外几个步骤,这些步骤在技术上不是在 Kubernetes 上运行 RabbitMQ 所必需的,但每个生产系统运维人员迟早都会遇到。

  • 如何使用 Prometheus 和 Grafana 设置集群监控
  • 如何部署 PerfTest 实例来对集群进行基本功能和负载测试

本文绝不涵盖在 Kubernetes 上部署 RabbitMQ 时可能相关的每个方面;我们的目标是强调最重要的部分。部署和工作负载特定的决策,例如为 RabbitMQ 节点 Pod(容器)应用资源限制,使用何种持久化存储,如何处理 TLS 证书/密钥轮换,日志聚合和升级,都是独立博客文章的绝佳主题。请告诉我们您希望在后续文章中看到什么!

可执行示例

与本文配套的文件可以在DIY RabbitMQ on Kubernetes 示例仓库中找到。本文使用 Google Kubernetes Engine (GKE) 集群,但 Kubernetes 的概念是通用的。

要跟随示例进行操作,

本文假设读者熟悉kubectl 的基本用法,并且该工具已配置为与 GKE 集群配合使用

RabbitMQ Docker 镜像

我们建议使用社区 RabbitMQ Docker 镜像。该镜像由Docker 社区维护,并构建了最新版本的 RabbitMQ、Erlang 和 OpenSSL。该镜像有一个使用 RabbitMQ Release Candidates 构建的变体,用于早期测试和采用。

现在,让我们开始在 Kubernetes 上运行 RabbitMQ 集群的第一个构建块:选择一个要部署的命名空间。

Kubernetes 命名空间和权限 (RBAC)

所有 Kubernetes 对象都属于一个Kubernetes 命名空间。RabbitMQ 集群资源也不例外。

我们建议使用专用的命名空间将 RabbitMQ 集群与其他可能在 Kubernetes 集群中部署的服务分开。拥有一个专用的命名空间在逻辑上有意义,并且可以轻松地授予集群节点恰当的权限。这是一个良好的安全实践。

RabbitMQ 的 Kubernetes 对等发现插件依赖于 Kubernetes API 作为数据源。首次启动时,每个节点都将尝试使用 Kubernetes API 发现其对等节点并尝试加入它们。完成启动的节点会发出一个Kubernetes 事件,以便在集群活动(事件)日志中更轻松地发现此类事件。

该插件需要对 Kubernetes 资源的以下访问权限

  • endpoints 资源的 get 访问权限
  • events 资源的 create 访问权限

指定一个Role、RoleBinding 和 Service Account 来配置此访问权限。

示例命名空间以及 RBAC 规则可以在rbac.yaml 示例文件中找到。

如果跟随示例,请使用以下命令创建命名空间和所需的 RBAC 规则。请注意,这将创建一个名为 test-rabbitmq 的命名空间。

kubectl apply -f namespace.yaml
kubectl apply -f rbac.yaml

下面的 kubectl 示例将使用 test-rabbitmq 命名空间。为了方便起见,可以将此命名空间设置为默认命名空间。

# set the namespace to be the current (default) one
kubectl config set-context --current --namespace=test-rabbitmq
# verify
kubectl config view --minify | grep namespace:

或者,可以在下面演示的所有 kubectl 命令后附加 --namespace="test-rabbitmq"

使用 StatefulSet

RabbitMQ 要求使用StatefulSet 在 Kubernetes 上部署 RabbitMQ 集群。StatefulSet 确保 RabbitMQ 节点按顺序、一次一个地部署。这可以避免在部署多节点 RabbitMQ 集群时遇到潜在的对等发现竞争条件

除了上述原因,还有其他同样重要的原因可以在 StatefulSet 而非 Deployment 中使用:粘性身份、简单的网络标识符、稳定的持久存储以及执行有序滚动升级的能力。

StatefulSet 定义文件包含了许多详细信息,例如挂载配置、挂载凭证、打开端口等,这些将在后面的章节中分主题进行解释。

最终的 StatefulSet 文件可以在gke 目录下找到。

为集群和 CLI 工具创建服务

StatefulSet 定义可以引用一个 Service,该 Service 为 StatefulSet 的 Pod 提供网络身份。在这里,我们引用v1.StatefulSet.Spec.serviceName 属性

RabbitMQ 集群需要此项,并且如 Kubernetes 文档中所述,必须在 StatefulSet 之前创建。

RabbitMQ 使用端口 4369 进行节点发现,使用端口 25672 进行节点间通信。由于此 Service 用于内部且不需要暴露,因此我们创建一个Headless Service。它可以在示例 headless-service.yaml 文件中找到。

如果跟随示例,请运行以下命令以创建用于节点间和 CLI 工具流量的 Headless Service

kubectl apply -f rabbitmq-headless.yaml

现在可以在 test-rabbitmq 命名空间中观察到该 Service。

kubectl get all
# => NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# => service/rabbitmq-headless ClusterIP None <none> 4369/TCP 7s

为节点数据使用持久卷

为了让 RabbitMQ 节点在 Pod 重启后仍能保留数据,节点的数据目录必须使用持久化存储。必须为每个 RabbitMQ Pod挂载持久卷

如果使用瞬时卷来支持 RabbitMQ 节点,则节点将在重启时丢失其身份和所有本地数据。这包括模式持久化队列数据。每次节点重启时同步所有这些数据将非常低效。在滚动重启期间丢失共识的情况下,这也会导致数据丢失。

在我们statefulset.yaml 示例中,我们创建了一个 Persistent Volume Claim 来置备持久卷。

持久卷挂载在 /var/lib/rabbitmq/mnesia。此路径用于RABBITMQ_MNESIA_BASE 位置:节点所有持久化数据的基目录。

可以在 RabbitMQ 文档中找到RabbitMQ 默认文件路径的描述。

如果需要,可以使用 RABBITMQ_MNESIA_BASE 变量更改节点数据目录基。请确保将持久卷挂载到更新后的路径。

RabbitMQ 节点和 CLI 工具使用一个称为Erlang Cookie 的共享 Secret 来相互身份验证。Cookie 值是一个最多 255 个字符的字母数字字符串。必须在创建 RabbitMQ 集群之前生成该值,因为节点需要它来形成集群

使用社区 Docker 镜像,RabbitMQ 节点将期望 Cookie 位于 /var/lib/rabbitmq/.erlang.cookie。我们建议创建一个 Secret 并将其作为 Volume 挂载到此路径上的 Pod。

这在statefulset.yaml 示例文件中得到了演示。

该 Secret 预计具有以下键/值对

cookie: {value}

要创建 Cookie Secret,请运行

echo -n "this secret value is JUST AN EXAMPLE. Replace it!" > cookie
kubectl create secret generic erlang-cookie --from-file=./cookie

这将创建一个具有单个键 cookie(取自文件名)和文件内容作为其值的 Secret。

管理员凭证

RabbitMQ 在首次启动时会使用广为人知的凭证来填充一个默认用户。该用户的用户名和密码均为 guest

此默认用户默认只能从 localhost 连接。可以通过选择加入来解除此限制。这可能对测试有用,但非常不安全。相反,必须使用生成的凭证创建一个管理员用户。

管理员用户凭证应存储在Kubernetes Secret 中,并将其挂载到 RabbitMQ Pod。然后,可以将 RABBITMQ_DEFAULT_USERRABBITMQ_DEFAULT_PASS 环境变量设置为 Secret 值。社区 Docker 镜像将使用它们来覆盖默认用户凭证

示例供参考.

该 Secret 预计具有以下键/值对

user: {username}
pass: {password}

要创建管理员用户 Secret,请使用

# this is merely an example, you are welcome to use a different username
echo -n "administrator" > user
# this is merely an example, you MUST use a different, generated password value!
echo -n "g3N3rAtED-Pa$$w0rd" > pass
kubectl create secret generic rabbitmq-admin --from-file=./user --from-file=./pass

这将创建一个具有两个键 userpass 的 Secret,分别取自文件名,文件内容作为其相应的值。

也可以使用 CLI 工具显式创建用户。请参阅 RabbitMQ 用户管理文档部分以了解更多信息。

节点配置

多种方法来配置 RabbitMQ 节点。推荐的方法是使用配置文件。

配置文件可以表示为ConfigMap,并作为 Volume 挂载到 RabbitMQ Pod。

要创建带有 RabbitMQ 配置的 ConfigMap,请应用我们的最小 configmap.yaml 示例

kubectl apply -f configmap.yaml

使用 Init 容器

自 Kubernetes 1.9.4 起,ConfigMap 被挂载为 Pod 的只读卷。这对于 RabbitMQ 社区 Docker 镜像来说是个问题:镜像可以在容器启动时尝试更新配置文件。

因此,挂载 RabbitMQ 配置的路径必须是读写模式。如果 Docker 镜像检测到只读文件,您将看到以下警告

touch: cannot touch '/etc/rabbitmq/rabbitmq.conf': Permission denied

WARNING: '/etc/rabbitmq/rabbitmq.conf' is not writable, but environment variables have been provided which request that we write to it
We have copied it to '/tmp/rabbitmq.conf' so it can be amended to work around the problem, but it is recommended that the read-only
source file should be modified and the environment variables removed instead.

虽然 Docker 镜像可以解决此问题,但将配置文件存储在 /tmp 中并非理想选择,我们建议改为使挂载路径为读写模式。

与其他一些 Kubernetes 社区项目一样,我们使用Init 容器来克服这个问题。

示例

rabbitmq 用户身份运行 Pod

Docker 镜像rabbitmq 用户(UID 为 999)身份运行并写入 rabbitmq.conf 文件。因此,rabbitmq.conf 的文件权限必须允许这样做。可以在 StatefulSet 定义中添加Pod 安全上下文来实现这一点。在安全上下文中将 runAsUserrunAsGroupfsGroup 设置为 999。

请参阅 StatefulSet 定义文件中的安全上下文

导入定义

RabbitMQ 节点可以导入从另一个 RabbitMQ 集群导出的定义。这也可以在节点启动时完成。

按照 RabbitMQ 文档的说明,可以通过以下步骤完成此操作

  1. 从您希望复制的 RabbitMQ 集群导出定义并保存文件
  2. 创建一个 ConfigMap,其中键为文件名,值为文件内容(请参阅 rabbitmq.conf ConfigMap 示例)
  3. 在 StatefulSet 定义中将 ConfigMap 挂载为 Pod 的 Volume
  4. 更新 rabbitmq.conf ConfigMap,设置 load_definitions = /path/to/definitions/file

就绪探测

Kubernetes 使用一种称为就绪探测的检查来确定 Pod 是否已准备好处理客户端流量。这实际上是一种由系统运维人员定义的专用健康检查

当使用有序 Pod 部署策略(这是 RabbitMQ 集群推荐的选项)时,探测决定了 Kubernetes 控制器何时认为当前部署的 Pod 已就绪并继续部署下一个。如果选择不当,此检查可能会导致滚动集群节点重启死锁。

属于集群的 RabbitMQ 节点将在启动时尝试从其对等节点同步模式。如果在可配置的时间窗口内(默认为五分钟)没有对等节点上线,节点将放弃并自愿停止。在同步完成之前,节点不会将自身标记为完全启动。

因此,如果就绪探测假定节点已完全启动并正在运行,使用此类探测进行 RabbitMQ 节点 Pod 的滚动重启将会死锁:探测将永远不会成功,也不会继续部署下一个 Pod,而下一个 Pod 必须上线才能使原始 Pod 被认为是就绪状态。

因此,建议对就绪探测使用非常基本的 RabbitMQ 健康检查

rabbitmq-diagnostics ping

虽然此检查并不详尽,但它允许所有 Pod 在一定时间内启动并重新加入集群,即使 Pod 是一个接一个有序地重启。

这在 RabbitMQ 集群指南的专门部分中进行了介绍:重启和健康检查(就绪探测)

StatefulSet 定义文件中的就绪探测部分演示了如何配置就绪探测。

活跃度探测

与上面描述的就绪探测类似,Kubernetes 允许使用另一种称为活跃度探测的健康检查来检查 Pod 的健康状况。该检查决定了是否必须重启 Pod。

与所有健康检查一样,没有一个解决方案可以推荐给所有部署。健康检查可能会产生误报,这意味着相当健康、运行正常的节点可能会无缘无故地被重启甚至销毁和重新创建,从而降低系统可用性。

此外,重启 RabbitMQ 节点不一定能解决问题。例如,重启一个处于报警状态(因为可用磁盘空间不足)的节点是无济于事的。

这一切都是为了说明活跃度探测必须谨慎选择,并考虑到误报和“通过重启可恢复性”。活跃度探测还必须使用节点本地健康检查而不是集群范围的检查

RabbitMQ CLI 工具提供了一些预定义的健康检查,它们在详尽程度、侵入性以及在不同场景(例如系统负载过大时)产生误报的可能性方面有所不同。这些检查是可组合的,可以组合使用。正确的活跃度探测选择是一个系统特定的决定。如有疑问,请从更简单、侵入性更小、详尽程度较低的选项开始,例如

rabbitmq-diagnostics -q ping

以下检查可以作为合理的活跃度探测候选

rabbitmq-diagnostics -q check_port_connectivity
rabbitmq-diagnostics -q check_local_alarms

但请注意,它们将无法用于被“暂停少数”分区处理策略暂停的节点

StatefulSet 定义文件中的活跃度探测部分演示了如何配置活跃度探测。

插件

RabbitMQ支持插件。在 Kubernetes 上运行 RabbitMQ 时,一些插件是必不可少的,例如 Kubernetes 特定的对等发现实现。

部署 RabbitMQ 到 Kubernetes 需要rabbitmq_peer_discovery_k8s 插件。通常还会启用rabbitmq_management 插件以获得基于浏览器的管理 UI 和 HTTP API,以及rabbitmq_prometheus 用于监控。

插件可以以不同方式启用。我们建议将插件文件 enabled_plugins 挂载到节点配置目录 /etc/rabbitmq。可以使用 ConfigMap 来表示 enabled_plugins 文件。然后可以将其作为 Volume 挂载到 StatefulSet 定义中的每个 RabbitMQ 容器。

在我们configmap.yaml 示例文件中,我们演示了如何填充 enabled_plugins 文件并将其挂载到 /etc/rabbitmq 目录。

端口

StatefulSet 的最后考虑是要在 RabbitMQ Pod 上打开的端口。RabbitMQ 支持的所有协议都是基于 TCP 的,并且要求在 RabbitMQ 节点上打开协议端口。根据节点上启用的插件,所需端口列表可能会有所不同。

上面提到的示例 enabled_plugins 文件启用了几个插件:rabbitmq_peer_discovery_k8s(必需)、rabbitmq_managementrabbitmq_prometheus。因此,服务必须打开与核心服务器和已启用插件相关的几个端口

  • 5672:AMQP 0-9-1 和 AMQP 1.0 客户端使用
  • 15672:管理 UI 和 HTTP API)
  • 15692:Prometheus 抓取端点)

部署 StatefulSet

这些是 StatefulSet 文件中的关键组件。请查看文件,如果您正在跟随示例,请部署 StatefulSet

kubectl apply -f statefulset.yaml

这将开始启动一个 RabbitMQ 集群。要观看进度

watch kubectl get all
# => NAME READY STATUS RESTARTS AGE
# => pod/rabbitmq-0 0/1 Pending 0 8s
# =>
# => NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# => service/rabbitmq-headless ClusterIP None <none> 4369/TCP 61m
# =>
# => NAME READY AGE
# => statefulset.apps/rabbitmq 0/1 8s

为客户端连接创建服务

如果以上所有步骤都成功,您应该已经在 Kubernetes 上部署了一个功能正常的 RabbitMQ 集群!? 但是,在 Kubernetes 上拥有 RabbitMQ 集群只有在客户端可以连接到它时才有用。

现在是时候创建一个 Service 来使集群可供客户端连接访问了。

Service 的类型取决于您的用例。 the Kubernetes API 参考提供了可用 Service 类型的良好概述。

client-service.yaml 示例文件中,我们选择了 LoadBalancer Service。这为我们提供了一个可用于访问 RabbitMQ 集群的外部 IP。

例如,这应该可以让您通过访问 {external-ip}:15672 来访问 RabbitMQ 管理 UI,并进行登录。客户端应用程序可以连接到端点,如 {external-ip}:5672 (AMQP 0-9-1, AMQP 1.0) 或 {external-ip}:1883 (MQTT)。请参阅入门指南以了解如何使用 RabbitMQ。

如果跟随示例,请运行

kubectl apply -f client-service.yaml

以创建具有外部 IP 地址的 LoadBalancer 类型 Service。要查找外部 IP 地址,请使用 kubectl get svc

kubectl get svc
# => NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# => service/rabbitmq-client LoadBalancer 10.59.244.70 34.105.135.216 15672:30902/TCP,15692:30605/TCP,5672:31210/TCP 2m19s

资源使用和限制

容器资源管理是一个值得专门发文的主题。容量规划建议完全取决于工作负载、环境和系统。最佳值通常是通过对系统进行广泛监控、试用和错误来找到的。但是,在选择限制和资源分配设置时,请考虑一些 RabbitMQ 特定的事项。

使用最新的主要 Erlang 版本

RabbitMQ 运行在 Erlang 运行时上。最近的 Erlang/OTP 版本引入了许多对在 Kubernetes 上运行 RabbitMQ 的用户高度相关的改进。

撰写本文时,RabbitMQ 的 Docker 社区镜像附带 Erlang 23。强烈建议用户使用自定义 Docker 镜像的也置备 Erlang 23。

CPU 资源使用

RabbitMQ 被设计用于涉及多个队列且节点同时服务多个客户端的工作负载。节点通常会利用所有允许的 CPU 核心,无需任何显式配置。随着核心数量的增加,可能需要进行一些调整以减少CPU 上下文切换

CPU 时间的消耗可以通过运行时线程活动指标进行监控,这些指标也通过RabbitMQ Prometheus 插件暴露。

如果 RabbitMQ Pod 接近其 CPU 资源配额并在具有大量相对空闲客户端的环境中遇到节流,那么可以通过少量配置减少负载

内存限制

RabbitMQ 使用运行时内存高水位线的概念。默认情况下,节点将使用检测到的(可用)内存的 40% 作为高水位线。当超过高水位线时,整个集群中的发布者将被阻塞,并启动更积极的页面交换到磁盘。高水位线值最初可能看起来像 Kubernetes 上的内存配额,但有一个重要区别:RabbitMQ 资源警报假定节点通常可以从该状态恢复。例如,大量的消息积压最终会被消耗。

Kubernetes 内存限制由 OOM killer 强制执行:不期望恢复。这意味着 RabbitMQ 节点的内存高水位线必须低于施加在节点容器上的内存限制。Kubernetes 部署应使用推荐范围中的相对高水位线值。

应该使用内存使用情况明细数据来确定节点上消耗内存最多的部分。

磁盘使用

我们强烈建议过度配置 RabbitMQ 容器可用的磁盘空间。磁盘空间不足的节点可能无法从该事件中恢复。必须对这些节点进行退役和替换。

最后,请考虑用于节点间通信的网络链接和 Kubernetes 网络选项的类型。网络链路拥塞可能是系统吞吐量的显着限制因素,并影响其可用性。

下面是一个非常简单的公式,用于计算工作负载所需的带宽(以比特为单位)

# peak message rate * bits per message * 110% to account for metadata and protocol framing
PeakMessageRate * AverageMessagePayloadSizeInBytes * 8 * 1.1

因此,平均消息大小为 3 kiB 且预期峰值消息率为每秒 20K 条消息的工作负载可能消耗高达

3 kiB * 20000/second * 8 * 1.1 = 528 megabits/second

的带宽。

Team RabbitMQ 维护着一个用于节点间通信链路指标的Grafana 仪表板

使用 rabbitmq-perf-test 对集群运行功能和负载测试

RabbitMQ 随附一个负载模拟工具PerfTest,可以从集群外部执行,也可以使用 perf-test 公共docker 镜像部署到 Kubernetes。以下是如何将该镜像部署到 Kubernetes 集群的示例

kubectl run perf-test --image=pivotalrabbitmq/perf-test -- --uri amqp://{username}:{password}@{service}

这里的 {username}{password} 是用户凭证,例如在 rabbitmq-admin Secret 中设置的凭证。{serivce} 是要连接的主机名。我们使用客户端服务的名称,当部署时,该名称将解析为主机名。

上面的 kubectl run 命令将启动一个 PerfTest Pod,可以在以下位置观察到

kubectl get pods

对于功能正常的 RabbitMQ 集群,运行 kubectl logs -f {perf-test-pod-name}(其中 {perf-test-pod-name}kubectl get pods 报告的 Pod 名称)将产生类似以下的输出

id: test-110102-976, time: 263.100s, sent: 21098 msg/s, received: 21425 msg/s, min/median/75th/95th/99th consumer latency: 1481452/1600817/1636996/1674410/1682972 ?s
id: test-110102-976, time: 264.100s, sent: 17314 msg/s, received: 17856 msg/s, min/median/75th/95th/99th consumer latency: 1509657/1600942/1636253/1695525/1718537 ?s
id: test-110102-976, time: 265.100s, sent: 18597 msg/s, received: 17707 msg/s, min/median/75th/95th/99th consumer latency: 1573151/1716519/1756060/1813985/1846490 ?s

要了解更多关于 PerfTest、其设置、功能和输出的信息,请参阅PerfTest 文档指南

PerfTest 不会永久运行。要删除 perf-test Pod,请使用

kubectl delete pod perf-test

监控集群

监控是任何生产部署的关键部分。

RabbitMQ 带有内建的 Prometheus 支持。要启用它,请启用 rabbitmq_prometheus 插件。这可以通过将 rabbitmq_prometheus 添加到 enabled_plugins ConfigMap 来实现,如上所述。

Prometheus 抓取端口 15972 必须在 Pod 和客户端 Service 上都打开。

可以使用Grafana可视化节点和集群指标。

替代选项:RabbitMQ 的 Kubernetes Cluster Operator

正如本文所示,在 Kubernetes 上托管像 RabbitMQ 这样的状态化数据服务涉及相当多的部分。这可能看起来是一项艰巨的任务。本文演示的这种 DIY 部署有几种替代方法。

VMware 的 Team RabbitMQ 开源了 RabbitMQ 的Kubernetes Operator 模式实现。截至 2020 年 8 月,这是一个处于积极开发中的早期项目。尽管它目前存在局限性,但它是我们推荐的选项,优于本文演示的手动 DIY 设置。

请参阅RabbitMQ Cluster Operator for Kubernetes 以了解更多信息。该项目在 rabbitmq/cluster-operator on GitHub 上公开开发。请尝试一下,让我们知道结果如何。除了 GitHub,向 Operator 背后的团队提供反馈的两个绝佳场所是RabbitMQ 邮件列表RabbitMQ 社区 Slack 中的 #kubernetes 频道

© . This site is unofficial and not affiliated with VMware.