佐藤です。
先日、私が主に担当しているプロダクトでRabbitMQのリプレースを行いました。
リプレースにあたり、RabbitMQのKubernetes(K8s)オペレーターであるCluster OperatorとMessaging Topology Operatorを使用したので共有します。
RabbitMQ概要
まず始めに、今回リプレースの対象であるRabbitMQについて紹介します。
RabbitMQは、VMwareが開発しているオープンソースのメッセージキューのミドルウェア(メッセージブローカー)です。
AMQPやMQTTなどのプロトコルに対応しており、StarlarkやErlangで実装されています。
2023年11月2日時点の最新バージョンは3.12.8です。
また、各言語に対応したクライアントライブラリも数多くサポートされています。
その他の詳細については、以下のソースを参照してください。
リプレースにあたり検討したポイント
ミドルウェア
リプレースを行うにあたり、このままRabbitMQを使い続けるかどうかを検討しました。
メッセージキューのミドルウェアは様々ありますが、私が担当しているプロダクトではファンアウトパターンで使用していたため、MQからコンシューマーにメッセージを送るプッシュ型が望ましいと思われました。
いくつかミドルウェアを調べていたところ、NATSも今の使い方にマッチしそうだと考えられました。
NATSはシンプルな設定・高速な起動・軽量なコンテナイメージなどインフラ観点で見るとメリットがありました。
しかし、アプリケーション側でクライアントライブラリの検証・改修が必要になるため、チームで相談して今回はRabbitMQを使い続けることとしました。
インフラストラクチャー
ミドルウェアはRabbitMQを使い続けることが決まったところで、次はインフラストラクチャーをどうするか検討しました。
リプレース前は、RabbitMQはプライベートクラウド上のVMで稼働していましたが、以下の理由からRabbitMQをK8s上で管理することにしました。
- AMoAdのアプリケーションはほぼK8s上で稼働していること
- MQはファンアウトパターンで、パブリッシャーから受信したメッセージは即座にコンシューマーへ渡されるため、ほぼステートレスであること
- インフラの管理をなるべく楽にしたい
- 公式がK8s Operatorをサポートしている
RabbitMQ Operatorの詳細
上記に記載している通り、RabbitMQは公式がK8s Operatorを管理しているため、こちらを検証してみることにしました。
K8sオペレーターについて
K8sのオペレーターパターンは、1つ以上のカスタムリソースにカスタムコントローラーをリンクすることで、K8sクラスターの振る舞いを拡張して、運用やタスクを自動化することです。
オペレーターはOperatorHubなどで探すことができ、RabbitMQ OperatorもOperatorHubに記載されています。
Cluster OperatorとMessaging Topology Operator
最初に軽く触れましたが、RabbitMQ Operatorは以下の2種類のオペレーターが存在しており、それぞれ管理するリソースが異なるようでした。
以下に違いを記載します。
オペレーター名 | 役割 |
---|---|
Cluster Operator | RabbitMQクラスターの管理 (Stateful SetsやServiceなど、K8sのリソースを操作) |
Messaging Topology Operator | RabbitMQ上のリソースを管理 (キューやエクスチェンジなど、RabbitMQのリソースを操作) |
HelmチャートとRabbitMQ Operatorの種類
RabbitMQ OperatorをK8sにインストールするにあたり、今回はBitnamiが提供しているHelmチャートrabbitmq-cluster-operator を使用しました。
公式が提供しているHelmチャートはCluster OperatorとMessaging Topology Operatorが分かれていますが、こちらは2つのオペレーターを一括で導入出来るためです。
BitnamiがRabbitMQの主要開発元であるVMWareの関連会社であることも選んだポイントの1つです。
実際に作成してみる
ではK8sにRabbitMQ Operatorをデプロイし、その後カスタムリソースを使用してRabbitMQクラスターやRabbitMQ上のリソースを作成してみます。
RabbitMQ Operator
Helmを使用し、カスタムリソース定義(CRD)やカスタムコントローラーを作成します。
リリース名は適当にrmqope
とし、チャートのバージョンは3.2.10
を使用しました。
helm repo add bitnami <https://charts.bitnami.com/bitnami>
helm install rmqope bitnami/rabbitmq-cluster-operator \
--version 3.2.10 \
--values values.yaml \
--namespace rabbitmq-system \
--create-namespace
❯ helm list -n rabbitmq-system
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
rmqope rabbitmq-system 1 YYYY-mm-dd HH:MM:dd.xxxxxxxx +0900 JST deployed rabbitmq-cluster-operator-3.2.10 2.2.0
values.yaml
はこんな感じです。
基本的には指定しなくても動きますが、今回はプロダクション環境で動作することを想定し、イメージやリソースを明示的に指定しました。
credentialUpdaterImage:
registry: docker.io
repository: bitnami/rmq-default-credential-updater
tag: 1.0.2-scratch-r20
clusterOperator:
image:
registry: docker.io
repository: bitnami/rabbitmq-cluster-operator
tag: 2.2.0-scratch-r3
resources:
requests:
cpu: 50m
memory: 100Mi
limits:
cpu: 50m
memory: 100Mi
msgTopologyOperator:
image:
registry: docker.io
repository: bitnami/rmq-messaging-topology-operator
tag: 1.10.3-scratch-r0
resources:
requests:
cpu: 50m
memory: 100Mi
limits:
cpu: 50m
memory: 100Mi
作成されたCRDは以下の通りです。
❯ kubectl get crd | grep 'rabbitmq.com'
bindings.rabbitmq.com YYYY-mm-ddTHH:MM:ddZ
exchanges.rabbitmq.com YYYY-mm-ddTHH:MM:ddZ
federations.rabbitmq.com YYYY-mm-ddTHH:MM:ddZ
permissions.rabbitmq.com YYYY-mm-ddTHH:MM:ddZ
policies.rabbitmq.com YYYY-mm-ddTHH:MM:ddZ
queues.rabbitmq.com YYYY-mm-ddTHH:MM:ddZ
rabbitmqclusters.rabbitmq.com YYYY-mm-ddTHH:MM:ddZ
schemareplications.rabbitmq.com YYYY-mm-ddTHH:MM:ddZ
shovels.rabbitmq.com YYYY-mm-ddTHH:MM:ddZ
superstreams.rabbitmq.com YYYY-mm-ddTHH:MM:ddZ
topicpermissions.rabbitmq.com YYYY-mm-ddTHH:MM:ddZ
users.rabbitmq.com YYYY-mm-ddTHH:MM:ddZ
vhosts.rabbitmq.com YYYY-mm-ddTHH:MM:ddZ
カスタムコントローラーの実態は以下の通りです。
❯ kubectl get all -n rabbitmq-system -l app.kubernetes.io/name=rabbitmq-cluster-operator --show-labels
NAME READY STATUS RESTARTS AGE LABELS
pod/rmqope-rabbitmq-cluster-operator-65458b66b8-wvsvm 1/1 Running 1 70d app.kubernetes.io/component=rabbitmq-operator,app.kubernetes.io/instance=rmqope,app.kubernetes.io/managed-by=Helm,app.kubernetes.io/name=rabbitmq-cluster-operator,app.kubernetes.io/part-of=rabbitmq,helm.sh/chart=rabbitmq-cluster-operator-3.2.10,pod-template-hash=65458b66b8
pod/rmqope-rabbitmq-messaging-topology-operator-67b9b57b85-zpjgb 1/1 Running 1 54d app.kubernetes.io/component=messaging-topology-operator,app.kubernetes.io/instance=rmqope,app.kubernetes.io/managed-by=Helm,app.kubernetes.io/name=rabbitmq-cluster-operator,app.kubernetes.io/part-of=rabbitmq,helm.sh/chart=rabbitmq-cluster-operator-3.2.10,pod-template-hash=67b9b57b85
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE LABELS
service/rmqope-rabbitmq-messaging-topology-operator-webhook ClusterIP xx.xxx.xx.x <none> 443/TCP 109d app.kubernetes.io/component=messaging-topology-operator,app.kubernetes.io/instance=rmqope,app.kubernetes.io/managed-by=Helm,app.kubernetes.io/name=rabbitmq-cluster-operator,app.kubernetes.io/part-of=rabbitmq,helm.sh/chart=rabbitmq-cluster-operator-3.2.10
NAME READY UP-TO-DATE AVAILABLE AGE LABELS
deployment.apps/rmqope-rabbitmq-cluster-operator 1/1 1 1 109d app.kubernetes.io/component=rabbitmq-operator,app.kubernetes.io/instance=rmqope,app.kubernetes.io/managed-by=Helm,app.kubernetes.io/name=rabbitmq-cluster-operator,app.kubernetes.io/part-of=rabbitmq,helm.sh/chart=rabbitmq-cluster-operator-3.2.10
deployment.apps/rmqope-rabbitmq-messaging-topology-operator 1/1 1 1 109d app.kubernetes.io/component=messaging-topology-operator,app.kubernetes.io/instance=rmqope,app.kubernetes.io/managed-by=Helm,app.kubernetes.io/name=rabbitmq-cluster-operator,app.kubernetes.io/part-of=rabbitmq,helm.sh/chart=rabbitmq-cluster-operator-3.2.10
RabbitMQクラスターとRabbitMQ上のリソース
次に、カスタムリソースを使用してRabbitMQクラスターとRabbitMQ上のリソースを作成してみます。
まずRabbitMQクラスターですが、マニフェストの内容はDeploymentやStatefuleSetに似ています。
以下にシンプルなマニフェストの例を記載します。
apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
name: rabbitmq
spec:
image: bitnami/rabbitmq:3.11.13-debian-11-r2
replicas: 3
resources:
requests:
cpu: 1000m
memory: 2Gi
limits:
cpu: 500m
memory: 1Gi
persistence:
storage: 5Gi
storageClassName: hoge-storage-class
service:
type: ClusterIP
rabbitmq:
additionalConfig: |
log.console = true
特徴的なのはRabbitMQのコンフィグの項目があることでしょうか。
詳細な設定項目は公式ドキュメントに記載されていますので、よろしければご参照ください。
上記のマニフェストを使用して、RabbitMQクラスターをデプロイしてみます。
kubectl apply -f rabbitmq-cluster.yaml
rabbitmqcluster
カスタムリソースが作成されます。
❯ kubectl get rabbitmqcluster.rabbitmq.com
NAME ALLREPLICASREADY RECONCILESUCCESS AGE
rabbitmq True True 1d
実態としては、Cluster Operatorのカスタムコントローラーがカスタムリソースの設定を基に、StatefuleSetやServiceを作成してくれているようです。
❯ kubectl get all -l app.kubernetes.io/name=rabbitmq
NAME READY STATUS RESTARTS AGE
pod/rabbitmq-server-0 1/1 Running 0 1d
pod/rabbitmq-server-1 1/1 Running 0 1d
pod/rabbitmq-server-2 1/1 Running 0 1d
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/rabbitmq ClusterIP 10.96.207.38 <none> 5672/TCP,15672/TCP,15692/TCP 1d
service/rabbitmq-nodes ClusterIP None <none> 4369/TCP,25672/TCP 1d
NAME READY AGE
statefulset.apps/rabbitmq-server 3/3 1d
次に、RabbitMQ上のリソースであるエクスチェンジやユーザーを作成してみます。
apiVersion: rabbitmq.com/v1beta1
kind: Exchange
metadata:
name: fanout-exchange
spec:
name: 'fanout.exchange'
type: fanout
autoDelete: false
durable: true
rabbitmqClusterReference:
name: rabbitmq
---
apiVersion: rabbitmq.com/v1beta1
kind: User
metadata:
name: user
spec:
tags:
- administrator
rabbitmqClusterReference:
name: rabbitmq
# Secretは事前に作成しておき、参照するようにする
importCredentialsSecret:
name: rabbitmq-user
kubectl apply -f exchange-and-user.yaml
以下のようなリソースが作成されました。
❯ kubectl get exchange.rabbitmq.com
NAME AGE
fanout-exchange 1d
❯ kubectl get user.rabbitmq.com
NAME AGE
user 1d
こちらは、Messaging Topology Operatorのカスタムコントローラーが上記のカスタムリソースを基に、該当のRabbitMQクラスターのAPIに対してリクエストを行っているようです。
では、実際にRabbitMQの管理画面にアクセスして、エクスチェンジやユーザーが作成されているか確認します。
Serviceをローカル環境にポートフォワードし、ブラウザで接続します。
kubectl port-forward service/rabbitmq 15672:15672
http://localhost:15672/ にアクセスします。
kind: User
で参照したSecretに記載してあるユーザー名とパスワードを使用し、ログインします。
無事ログインすることが出来ました。
画像は割愛しますが、エクスチェンジの画面を表示すると先程定義したエクスチェンジが作成されています。
構築・運用してみての感想
記事執筆時点でリプレースをしてから3ヶ月程度運用した状況ですので、どうだったかを振り返ってみます。
やはり、別途インフラのリソースをプロビジョニング・管理する必要がないのはとても楽だと思いました。
今まではTerraformでインスタンスやボリュームをプロビジョニングし、Ansibleで構成管理をするといった手法を取ることが多くありました。
今回のように、K8sのリソースが十分であればマニフェストをapplyするだけで良いというのは運用負担が少ないと感じました。
また、スケールイン・アウトやバージョンアップがしやすくなったというのも、コンテナ化・K8s管理のメリットだと感じました。
懸念事項としては、K8sクラスターのアップデート時に考慮する点が増えてしまったことです。
今までK8sクラスターをアップデートする際は、新しいバージョンのK8sクラスターを作成するブルーグリーンデプロイ方式で行っていました。
今までのようにRabbitMQがK8sクラスター外にある場合は、パブリッシャーとコンシューマーは別々に移行しても問題ありませんでした。
今回のリプレースにより、パブリッシャー・RabbitMQ・コンシューマーが全てK8sクラスター内にあるため、K8sクラスターアップデート時はこれらを一括で移動する必要が出てきてしまいました。
とはいえ、総じてメリットの方が大きいと感じています。
まとめ
今回は、RabbitMQをCluster OperatorとMessaging Topology Operatorを使用してK8s上で管理する方法を共有しました。
懸念事項はあるものの構築・運用してみての所感は良好で、リプレース前に比べると負担が減っていると感じます。
最近はオペレーターを使用してK8sでシステムを管理する事例が増えてきていますので、これからも情報をキャッチアップしていきます。