跳到主要内容

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

·22 分钟阅读

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

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

这篇文章解释了在 Kubernetes 上 DIY 部署 RabbitMQ 的基础知识:哪些 Kubernetes 资源是必要的,如何确保 RabbitMQ 节点使用持久存储,如何处理敏感值的配置等等。

2024 年的更新

提示

与其自行部署 RabbitMQ 到 Kubernetes,不如考虑使用 RabbitMQ 核心团队构建的 Kubernetes Operator。它融合了所有最佳实践,是强烈推荐的选择。

简介

在不使用 Kubernetes Operator的情况下,将 RabbitMQ 这样的有状态数据服务部署到 Kubernetes,有点像组装拼图。

涉及多个部分

在这篇文章中,我们将尝试涵盖关键部分,并提及一些在 Kubernetes 上运行 RabbitMQ 不是技术上必需的步骤,但每个生产系统运维人员迟早都必须担心

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

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

可执行示例

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

要跟随示例,请准备:

这篇文章假设读者熟悉kubectl 基本用法,并且该工具已设置为与 GKE 集群一起工作

RabbitMQ Docker 镜像

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

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

Kubernetes 命名空间和权限 (RBAC)

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

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

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

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

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

指定一个角色、角色绑定和服务帐户来配置此访问权限。

命名空间示例以及 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:

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

使用有状态集

RabbitMQ 要求使用有状态集将 RabbitMQ 集群部署到 Kubernetes。有状态集确保 RabbitMQ 节点按顺序一次部署一个。这避免了在部署多节点 RabbitMQ 集群时遇到潜在的对等发现竞争条件

还有其他同样重要的原因需要使用有状态集而不是部署:粘性身份、简单的网络标识符、稳定的持久存储以及执行有序滚动升级的能力。

有状态集定义文件包含配置挂载、凭据挂载、打开端口等详细信息,这些将在以下部分按主题进行解释。

最终的有状态集文件可以在gke 目录下找到。

为集群和 CLI 工具创建服务

有状态集定义可以引用一个服务,该服务为有状态集的 Pod 提供其网络身份。在这里,我们指的是v1.StatefulSet.Spec.serviceName 属性

RabbitMQ 集群需要这样做,并且如 Kubernetes 文档中所述,必须在有状态集之前创建。

RabbitMQ 使用端口 4369 进行节点发现,使用端口 25672 进行节点间通信。由于此服务在内部使用,不需要公开,因此我们创建一个无头服务。它可以在example headless-service.yaml 文件中找到。

如果按照示例进行操作,请运行以下命令为节点间和 CLI 工具流量创建无头服务

kubectl apply -f rabbitmq-headless.yaml

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

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 示例中,我们创建一个持久卷声明来配置持久卷。

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

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

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

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

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

这将创建一个 Secret,其中包含一个键 cookie(取自文件名),并将文件内容作为其值。

管理员凭据

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

这将创建一个 Secret,其中包含两个键 userpass(取自文件名),并将文件内容作为其各自的值。

用户也可以使用 CLI 工具显式创建。有关更多信息,请参阅RabbitMQ 文档中关于用户管理的部分

节点配置

几种方法可以配置 RabbitMQ 节点。推荐的方法是使用配置文件。

配置文件可以表示为Config Map,并作为卷挂载到 RabbitMQ Pod 上。

要使用 RabbitMQ 配置创建 Config Map,请应用我们的minimal configmap.yaml 示例

kubectl apply -f configmap.yaml

使用 Init Container

自 Kubernetes 1.9.4 起,Config Map 作为只读卷挂载到 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 镜像以 uid 为 999 的 rabbitmq 用户身份运行,并写入 rabbitmq.conf 文件。因此,rabbitmq.conf 文件的权限必须允许这样做。可以向有状态集定义添加Pod 安全上下文来实现此目的。在安全上下文中将runAsUserrunAsGroupfsGroup 设置为 999。

请参阅有状态集定义文件中的安全上下文

导入定义

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

根据 RabbitMQ 文档,可以使用以下步骤完成此操作

  1. 从您希望复制的 RabbitMQ 集群导出定义并保存文件
  2. 创建一个 Config Map,键为文件名,值为文件内容(请参阅 rabbitmq.conf Config Map 示例)
  3. 在有状态集定义中将 Config Map 作为卷挂载到 RabbitMQ Pod 上
  4. 使用 load_definitions = /path/to/definitions/file 更新 rabbitmq.conf Config Map

就绪探针

Kubernetes 使用称为就绪探针的检查来确定 Pod 是否已准备好为客户端流量提供服务。这实际上是系统运维人员定义的专门的健康检查

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

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

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

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

rabbitmq-diagnostics ping

虽然此检查不够彻底,但它允许所有 Pod 启动并在一定时间段内重新加入集群,即使 Pod 是按顺序逐个重启的。

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

有状态集定义文件中的就绪探针部分演示了如何配置就绪探针。

存活探针

与上面描述的就绪探针类似,Kubernetes 允许使用不同的健康检查(称为存活探针)进行 Pod 健康检查。该检查确定是否必须重启 Pod。

与所有健康检查一样,没有适用于所有部署的单一解决方案。健康检查可能会产生误报,这意味着相当健康、可操作的节点将被重启甚至销毁并重新创建,而没有任何理由,从而降低系统可用性。

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

所有这些都是为了说明必须明智地选择存活探针,并考虑到误报和“通过重启的可恢复性”。存活探针还必须使用节点本地健康检查而不是集群范围的健康检查

RabbitMQ CLI 工具提供了许多预定义的健康检查,这些检查在彻底性、侵入性和在不同场景下(例如,当系统处于负载状态时)产生误报的可能性方面有所不同。这些检查是可组合的并且可以组合。正确的存活探针选择是特定于系统的决定。如有疑问,请从更简单、侵入性更小且不那么彻底的选项开始,例如

rabbitmq-diagnostics -q ping

以下检查可以是合理的存活探针候选者

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

但是请注意,它们对于被“暂停少数派”分区处理程序策略暂停的节点将失败。

有状态集定义文件中的存活探针部分演示了如何配置存活探针。

插件

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

rabbitmq_peer_discovery_k8s 插件是在 Kubernetes 上部署 RabbitMQ 所必需的。启用rabbitmq_management 插件以获得基于浏览器的管理 UI 和 HTTP API,以及rabbitmq_prometheus 用于监控也很常见。

可以在不同的方式启用插件。我们建议将插件文件 enabled_plugins 挂载到节点配置目录 /etc/rabbitmq。Config Map 可以用于表示 enabled_plugins 文件的值。然后,可以将其作为卷挂载到有状态集定义中的每个 RabbitMQ 容器。

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

端口

有状态集的最终考虑因素是在 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 抓取端点)

部署有状态集

这些是有状态集文件中的关键组件。请查看该文件,如果按照示例进行操作,请部署有状态集

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 集群!? 但是,只有客户端可以连接到 RabbitMQ 集群才有用。

现在是时候创建一个服务,使集群可以访问客户端连接

服务的类型取决于您的用例。Kubernetes API 参考给出了可用服务类型的良好概述。

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

例如,这应该可以通过访问 {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

以创建类型为 LoadBalancer 且具有外部 IP 地址的服务。要查找外部 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 的用户高度相关的改进

Docker 社区 RabbitMQ 镜像在撰写本文时附带 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 且预期峰值消息速率为每秒 2 万条消息的工作负载最多可能消耗

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

的带宽。

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_promethus 添加到如上所述的 enabled_plugins Config Map 来完成。

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

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

替代选项:RabbitMQ 的 Kubernetes 集群操作符

正如本文所示,在 Kubernetes 上托管 RabbitMQ 等有状态数据服务涉及很多部分。这可能看起来是一项艰巨的任务。对于本文中演示的这种 DIY 部署,有几种替代方案。

VMware 的 RabbitMQ 团队开源了一个用于 RabbitMQ 的 Kubernetes 操作符模式实现。截至 2020 年 8 月,这是一个处于积极开发中的年轻项目。虽然目前存在局限性,但相对于本文中演示的手动 DIY 设置,它是我们推荐的选项。

请参阅 RabbitMQ Kubernetes 集群操作符 以了解更多信息。该项目在 GitHub 上的 rabbitmq/cluster-operator 上开源开发。试用一下,让我们知道您的使用体验。除了 GitHub 之外,向操作符背后的团队提供反馈的两个好去处是 RabbitMQ 邮件列表RabbitMQ 社区 Slack 中的 #kubernetes 频道

© . All rights reserved.