はじめに
Helmを利用してRabbitMQを稼動させていましたが、いろいろ問題が発生していました。
別クラスターを構築したタイミングで本家のドキュメントに従ってRabbitMQをK8s環境で稼動させたので、その際のメモを残します。
参考文書
- Deploying RabbitMQ to Kubernetes: What's Involved?
- Installing RabbitMQ Cluster Operator in a Kubernetes cluster
- Using RabbitMQ Cluster Kubernetes Operator
Operator v2.xへバージョンアップする際に参考にした資料
- Github - Pause Reconsiliation
- Github - RabbitMQ Operator v1.6.0 Release Notes
- RabbitMQ - Erlang Version Requirements
- RabbitMQ - v3.11.x Required feature flags
- RabbitMQ - Upgrading RabbitMQ (Main guide)
環境
K8sクラスターは以下のような環境です。
- Hardware: TX1320 M4 (富士通製 Xeon E-2234)
- 64GB Memory (DDR4-21300 ECC)
- 4TB HDD (/dev/sda)
- 500GB SSD (/dev/sdb)
- K8s: v1.19.7 (Kubesprayで構築) → v1.29.5
- Rook/Ceph v1.5.5 → v1.13.10
バージョンアップを続けて、現在はRabbitMQ v3.13.7を運用しています。
方針
今回はRabbitMQ Operatorを使用します。これはK8sのバージョンがv1.17以上、Dockerイメージが3.8.8以上という条件があって、以前構築したクラスターでは利用できないものでした。
本番に移行させることを目的に、テスト環境を構築したので、この手順を試してみることにします。
作業メモ
あらかじめRabbitMQ Cluster Operatorを導入しておきます。
$ sudo kubectl apply -f https://github.com/rabbitmq/cluster-operator/releases/latest/download/cluster-operator.yml
ここからの作業は参考文書に掲載したUsing RabbitMQ Cluster Kubernetes Operatorに従っていきます。
CRDsについて
Custom Resources (CRDs)を確認するよう指示があります。
$ sudo kubectl get crd/rabbitmqclusters.rabbitmq.com
NAME CREATED AT
rabbitmqclusters.rabbitmq.com 2021-01-26T05:59:59Z
確認するだけであればこれで十分ですが、どのようなCRDsが定義されているかは-o yaml
などの冗長出力によって確認することができます。
$ sudo kubectl get crd/rabbitmqclusters.rabbitmq.com -o yaml
長々と出力されますが定義されているリソースは、"RabbitmqCluster"だけのようです。
RabbitMQインスタンスの実行
書いてあるとおり、definition.yamlファイルを準備して適用します。
$ cat > 01.definition.yaml
apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
name: definition
$ sudo kubectl -n rabbitmq-system apply -f 01.definition.yaml
rabbitmqcluster.rabbitmq.com/definition created
$ sudo kubectl -n rabbitmq-system get all
NAME READY STATUS RESTARTS AGE
pod/definition-server-0 0/1 Pending 0 2s
pod/rabbitmq-cluster-operator-7bbbb8d559-884lc 1/1 Running 0 3h40m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/definition ClusterIP 10.233.7.222 <none> 5672/TCP,15672/TCP 2s
service/definition-nodes ClusterIP None <none> 4369/TCP,25672/TCP 2s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/rabbitmq-cluster-operator 1/1 1 1 3h40m
NAME DESIRED CURRENT READY AGE
replicaset.apps/rabbitmq-cluster-operator-7bbbb8d559 1 1 1 3h40m
NAME READY AGE
statefulset.apps/definition-server 0/1 2s
Podは現時点ではPendingのままです。原因についてはdescribeで確認します。
$ sudo kubectl -n rabbitmq-system describe pod definition-server-0
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 22h default-scheduler 0/3 nodes are available: 3 pod has unbound immediate PersistentVolumeClaims.
PVCを確認すると、次のようになっていました。
$ sudo kubectl -n rabbitmq-system get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistence-definition-server-0 Pending 10m
Pending状態の解消
PVはRook/Cephで構成しているために、PVC定義にstorageClassName: rook-ceph-block
を指定することが必要です。
RabbitMQのドキュメントにstorageClassNameを指定する方法が掲載されているので、これを参照し、01.definition.yamlを編集し、再度適用します。
$ cat 01.definition.yaml
apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
name: definition
spec:
persistence:
storageClassName: rook-ceph-block
storage: 20Gi
$ sudo kubectl -n rabbitmq-system delete -f 01.definition.yaml
$ sudo kubectl -n rabbitmq-system apply -f 01.definition.yaml
これで無事にサービスが起動するところまでは確認できました。
$ sudo kubectl -n rabbitmq-system get all
NAME READY STATUS RESTARTS AGE
pod/definition-server-0 0/1 Running 0 27s
pod/rabbitmq-cluster-operator-7bbbb8d559-884lc 1/1 Running 0 26h
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/definition ClusterIP 10.233.49.46 <none> 5672/TCP,15672/TCP 27s
service/definition-nodes ClusterIP None <none> 4369/TCP,25672/TCP 27s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/rabbitmq-cluster-operator 1/1 1 1 26h
NAME DESIRED CURRENT READY AGE
replicaset.apps/rabbitmq-cluster-operator-7bbbb8d559 1 1 1 26h
NAME READY AGE
statefulset.apps/definition-server 0/1 27s
$ sudo kubectl -n rabbitmq-system get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistence-definition-server-0 Bound pvc-5cf12c25-aeb0-45f5-9ce6-b9825ec0946d 20Gi RWO rook-ceph-block 40s
LoadBalancerの有効化
このままだとサービスにWebブラウザからアクセスすることが難しいので、LoadBalancerを有効にします。
$ cat 01.definition.yaml
apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
name: definition
spec:
persistence:
storageClassName: rook-ceph-block
storage: 20Gi
service:
type: LoadBalancer
$ sudo kubectl -n rabbitmq-system apply -f 01.definition.yaml
rabbitmqcluster.rabbitmq.com/definition configured
$ sudo kubectl -n rabbitmq-system get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
definition LoadBalancer 10.233.49.46 192.168.1.110 5672:30104/TCP,15672:30706/TCP 59m
definition-nodes ClusterIP None <none> 4369/TCP,25672/TCP 59m
ドキュメントを眺める限りはLBにIPを割り当てるための設定(loadBalancerIP)がないようだと思って初稿を書いたのですが、spec.override.service が定義可能だと分かって次のような設定を試しました。
apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
name: definition
spec:
replicas: 3
persistence:
storageClassName: rook-ceph-block
storage: 20Gi
service:
type: LoadBalancer
override:
service:
spec:
loadBalancerIP: 192.168.1.139
この設定を適用すると、EXTERNAL-IPが指定した値になります。
EXTERNAL-IPを指定するもう一つの方法
このセクションは初稿でLoadBalancerにExternal-IPを割り当てる唯一の方法として記述したものです。オンプレミスであれば直接IPを指定する前述の方法が楽だと思いますが、環境に応じて使い分けてください。
annotationsに項目を追加することはできるので、address-poolを指定する方法で通常とは違うip-rangeを指定することはできそうです。
address-poolから割り当てられると、再構築したタイミングで変更されてしまう可能性があります。
サービス用のIPアドレスは固定したいので、loadBalancerIPを指定する以外の方法を検討します。
あらかじめmetallbの設定で、割り当てたいIPを別のpoolとして定義しておきます。
下記の例では元々設定していたip-rangeの最後の1つ(192.168.1.139)を"rabbitmq-pool"として別に定義しました。
今回のk8sクラスターではmetallbはkubesprayのaddons.ymlから設定しているので、あらかじめkubesprayからaddress-poolを追加しています。
$ sudo kubectl -n metallb-system get cm -o yaml
apiVersion: v1
items:
- apiVersion: v1
data:
config: |
address-pools:
- name: loadbalanced
protocol: layer2
addresses:
- 192.168.1.110-192.168.1.138
- name: rabbitmq-pool
protocol: layer2
addresses:
- 192.168.1.139-192.168.1.139
auto-assign: False
kind: ConfigMap
...
この"rabbitmq-pool"をannotationsで指定するよう01.definition.yamlファイルを編集し、適用します。
$ cat 01.definition.yaml
apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
name: definition
spec:
persistence:
storageClassName: rook-ceph-block
storage: 20Gi
service:
type: LoadBalancer
annotations:
metallb.universe.tf/address-pool: rabbitmq-pool
$ sudo kubectl -n rabbitmq-system apply -f 01.definition.yaml
rabbitmqcluster.rabbitmq.com/definition configured
設定が反映されると、1つしかIPが定義されていないrabbitmq-poolからEXTERNAL-IPが割り当てられた事を確認します。
$ sudo kubectl -n rabbitmq-system get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
definition LoadBalancer 10.233.18.226 192.168.1.139 15672:31925/TCP,5672:30675/TCP 14h
definition-nodes ClusterIP None <none> 4369/TCP,25672/TCP 36h
直接loadBalancerIPが割り当てられると良いのですが、これでも同じような事ができたので、しばらく使ってみます。
Web UIにログインするためのID,Passwordの確認
自動的にランダムな文字列が割り当てられているので、ログインに必要なID, Passwordをドキュメントに書かれているように確認します。
手元では次のようなMakefileのタスクを設定しています。
show-adminuser:
(i=`sudo kubectl -n rabbitmq-system get secret definition-default-user -o jsonpath="{.data.username}"
| base64 --decode | tee /dev/null` ; echo user: $$i)
(i=`sudo kubectl -n rabbitmq-system get secret definition-default-user -o jsonpath="{.data.password}"
| base64 --decode | tee /dev/null` ; echo pass: $$i)
Replica数の変更
デフォルトではPodが1つのRabbitMQクラスターとなっているので、複数のPodを実行します。
$ cat 01.definition.yaml
apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
name: definition
spec:
replicas: 3
persistence:
storageClassName: rook-ceph-block
storage: 20Gi
service:
type: LoadBalancer
annotations:
metallb.universe.tf/address-pool: rabbitmq-pool
これを適用し、コンソールからNodeが増加したことを確認します。
RabbitMQ Operatorのバージョンアップについて
この記事で導入したRabbitMQ Operatorのバージョンはv1.4.0でした。これを出来るだけ最新にしていきたいと思います。
基本的にRabbitMQ自体のバージョンアップポリシーは、n+1世代にバージョンアップする(v3.8.x→v3.9.x、次にv3.9.x→v3.10.x) というものですので、これよりは細かくOperatorのバージョンを上げていきたいと思います。
## RabbitMQClustersのインスタンス名を確認
$ sudo kubectl -n rabbitmq-system get rabbitmqclusters
## v1.6.0のOperator YAMLファイルを適用
$ sudo kubectl -n rabbitmq-system apply -f https://github.com/rabbitmq/cluster-operator/releases/download/v1.6.0/cluster-operator.yml
## Operatorのログを確認し、"Finished reconciling"のメッセージを確認する
$ sudo kubectl -n rabbitmq-system logs -l app.kubernetes.io/component=rabbitmq-operator
## 無事に導入が終ったらRabbitMQClustersのインスタンス(e.g. "rabbitmq" or definition)から.spec.imageを削除する
$ sudo kubectl -n rabbitmq-system edit rabbitmqclusters rabbitmq
## "image: rabbitmq:3.8.9-management" 行を削除し、編集結果を保存してからEditorを終了する
ここまで操作が終わると自動的にRabbitMQClusterの各ノードが再起動し、RabbitMQ Operatorがサポートする最新のバージョンに更新されます。
Operatorの更新によるPodが再起動された時点ではRabbitMQ自体のバージョンは変化していませんRabbitMQのコンテナのバージョンを新しくするためにはrabbitmqclusters/rabbitmqの.spec.image行を削除する必要があります。
今回は再起動を伴う更新は順番に適用する方針で、"v1.4.0" → "v1.6.0" → "v1.8.3" → "v1.9.0" → "v1.11.1" → "v1.12.1" → "v1.14.0" → "v2.0.0" → "v2.1.0" → "v2.2.0" → "v2.3.0" → "v2.4.0" とOperatorを適用していき、デフォルトのバージョンが上がった場合は、その都度.spec.image行を削除しています。(v1.9.0とv1.11.1、v2.0.0、v2.1.0はRabbitMQのコンテナ・バージョンに変化がないためそのまま)
特に"v1.12.x"はv3.8.xからv3.9.xに切り替わるタイミングなので必ずimageは最新にする必要があります。RabbitMQのバージョンを順番に上げていけばErlangのバージョンは特に気にする必要はありません。
またYAMLファイルの適用を省略する場合でも、"v2.0.0"はRabbitMQv3.9.9以上を要求する点に注意が必要です。
"v2.4.0"はRabbitMQ v3.11.18以降を要求するため、適用前に必ず"v2.3.0"を適用する必要があります。
これらの挙動をみると全般的に"v2.0.0"以降はバージョンをスキップせずに順番に適用することが望ましいと思われます。
RabbitMQ v3.11.x(Opeartor v2.2.0)アップグレード前の必須作業
RabbitMQ Operator v1.14.0を適用した後からv2.2.0を適用してPodを再起動するまでの間に、RabbitMQ v3.11.xで必須になったfeature flagsの状況について、Pod内部で rabbitmqctl list_feature_flags
コマンドで確認する必要があります。
- quorum_queue
- implicit_default_bindings
- virtual_host_metadata
- maintenance_mode_status (ability to switch RabbitMQ to a maintenance mode)
- user_limits (ability to configure connection and channel limits for a user)
enabledに変更する場合には、rabbitmqctl enable_feature_flag <name>
を利用すること。
これを怠るとPodのRollingUpdateに失敗します。この場合は rabbitmqclusters の定義から images:行をそれまで使用していた image: rabbitmq:3.10.2-management
などとfallbackしてから問題のPodを再起動すれば元のバージョンで正常に稼動します。
テスト系では問題なかったものの、本番系ではこの確認をしていなかったため、問題が発生しました。
RabbitMQ v3.12.x(Operator v2.4.0)アップグレード前の必須作業
v3.11.xと同様にfeature_flagsについてですが、v3.12.xからは全てのfeature_flagsを更新することが求められています。
$ rabbitmqctl enable_feature_flag all
これをv3.11.18で実行してからRabbitMQ Operator v2.4.0を適用しRabbitMQのバージョンアップを実行します。
RabbitMQ v3.13.x(Operator v2.8.0-v2.11.0)アップグレード前の必須作業 (特になし)
v3.12.xで既に対応しているためenable_feature_flagについては事前に必要な変更はありません。
ただし2024年後半に予定されているv4.xへのアップグレードを見据えて、v3.13.xについては様々な変更が取り込まれています。feature_flagsへの項目追加はそれなりにあります。
v4.xへのバージョンアップのため必ず公式サイトのドキュメントを一読しておきましょう。
以下の作業では積極的に安定版のfeature flagsは有効にしています。
Operator v2.9.0〜v2.11.0 (RabbitMQ v3.13.x)の適用
Operator v2.8.0を適用した時点で、RabbitMQ v3.13.1が動作していますが、いくつかfeature flagsが追加されているので、v2.9.0を適用する前に安定版を全て有効にしておきます。
この作業は必須ではありませんが、将来のRabbitMQ v4.0.xへの更新時には全ての安定版(stable)feature flagsを有効にする必要があるとアナウンスされています。
## 現状の確認
$ sudo kubectl -n rabbitmq-system exec -it rabbitmq-server-0 -- rabbitmqctl list_feature_flags
## 有効化
$ sudo kubectl -n rabbitmq-system exec -it rabbitmq-server-0 -- rabbitmqctl enable_feature_flag all
all
を指定すると実験版のfeature flagとされているkhepri_db
だけがdisableのままで、他をenableに変更します。
次にURLに含まれるOperatorのバージョンを確認して、YAMLファイルを適用します。
sudo kubectl -n rabbitmq-system apply -f https://github.com/rabbitmq/cluster-operator/releases/download/v2.9.0/cluster-operator.yml
Operatorを更新するだけではバージョンは上がらないので、rabbitmqclusters
リソースのオブジェクト(rabbitmq
)のimage:行を削除して、PodのRabbitMQのバージョンをOperatorのデフォルトに更新します。
この作業をv2.9.0からv2.11.0まで繰り返します。
RabbitMQ v3.13.7 (Operator v2.10.0) 更新後のfeature flagsの変化
v3.13.7に更新すると安定板のfeature flagsの中にmessage_containers_deaths_v2
が追加されています。
これに対するドキュメントは全然ないのですが、v3.13.7のコードをみると次のような記述がありました。
-rabbit_feature_flag(
{message_containers_deaths_v2,
#{desc => "Bug fix for dead letter cycle detection",
doc_url => "https://github.com/rabbitmq/rabbitmq-server/issues/11159",
stability => stable,
depends_on => [message_containers]
}}).
これを有効にしても特に問題はなさそうなので、v2.10.0を適用してから再びenable_feature_flag all
を実行しています。
Operator v2.11.0の適用
RabbitMQのバージョンは3.13.7のままなので、Operatorを更新した後のeditコマンドによるimage:行の削除は行っていません。
さいごに
Operatorによる制御はかなり自然で、今後はHelmからRabbitMQをデプロイすることはないと思います。
MetalLBではloadBalancerIPによる制御を許可すると、(管理下の)任意のIPアドレスがアサインできてしまうので管理面からはあらかじめ準備しているaddress-poolからEXTERNAL-IPを割り当てる方が実際の場面では便利なのかなと感じています。
更新した記事では、spec.override.service によるloadBalancerIPを使用する方法も確認しました。両方確認しましたが、address-poolを利用する方法もそれなりに便利だと利用してみて思っているところなので、環境に応じて適切なものを選択していこうと思います。
RabbitMQをk8sで使い初めた当初はリスタートなどがあるとクラスターの起動に失敗していた事例がありJOBの滞留などをチェックする必要がありましたが、v2.12.xを稼動している現在ではそのようなトラブルもなく安定して稼動しています。クライアントのプログラミングに対するTipsも蓄積されてきたことも影響していると思いますが、MQが使えると便利です。
以上