k8s で RabbitMQ を構築したいみなさんこんにちは。
LAPRAS Advent Calendar 2020 の2日目の記事です。
重要なことなので最初と最後で二度言いますが、今からRabbitMQを運用したい方はAWSのマネージドサービスを使うことをおすすめします。
2020年11に発表されました。https://aws.amazon.com/jp/about-aws/whats-new/2020/11/announcing-amazon-mq-rabbitmq/
k8s で helm を使って RabbitMQ を動かすときに、ユーザとパスワード設定で2箇所ハマったので、マネージドサービスを使うのが嫌いな人のためにそれについて書いていきます。
これ以外にもいろいろ苦労してたくさん調べものをしたのですが、ほとんど日本語の情報がなくて、皆あまり k8s で RabbitMQ の管理してないのかな…と思ったり思わなかったりしています。
まず注意しないといけないのが helm/charts の rabbitmq が名前からすると一番支持されているように見えますが、こちらは Helm ver 2 から 3 へのバージョンアップに伴い deplicated になっていて、bitnami/charts がメインストリームになっています。(2020年12月現在)
YAMLの書き方もそれぞれで違います。以降 bitnami/rabbitmq の helm chart を使う前提で記述しています。
Load Definitions せずに初期ユーザ/パスワードを設定する
Load Definitions といって、JSON形式で吐き出した RabbitMQ の設定ファイル(definitions)を起動時に読み込む機能があるのですが、それを使わないのであれば README に書かれている通り auth.username
と auth.password
でデフォルトのユーザ名とパスワードを作成できます。
helmfile を使う場合こんな感じのYAMLになります。
- name: rabbitmq
chart: bitnami/rabbitmq
values:
- auth:
username: your_name
password: raw_password
これは素直なので特にハマることはないです。
ハマリポイント1: Load Definitions したら初期ユーザが作られない
起動時に vhost の作成や、その権限設定をしたいと思いこんな感じでパラメータを設定します。
showwin
というユーザを作って、/
や /hello
の権限を付与したい訳です。
- name: rabbitmq
chart: bitnami/rabbitmq
values:
- auth:
username: showwin
password: showwin_password
- extraSecrets:
load-definition:
load_definition.json: |
{
"vhosts": [
{
"name": "/"
},
{
"name": "/hello"
}
],
"permissions": [
{
"user": "showwin",
"vhost": "/",
"configure": ".*",
"read": ".*",
"write": ".*"
},
{
"user": "showwin",
"vhost": "/hello",
"configure": ".*",
"read": ".*",
"write": ".*"
}
]
}
- loadDefinition:
enabled: true
existingSecret: load-definition
- extraConfiguration: |
load_definitions = /app/load_definition.json
これで $ helmfile sync
すると失敗して、showwin なんてユーザいないぜ!ってエラーがでます。
(エラーメッセージちゃんと残してなくてごめんなさい)
先に答えを書くと
- name: rabbitmq
chart: bitnami/rabbitmq
values:
- extraSecrets:
load-definition:
load_definition.json: |
{
"users": [
{
"name": "showwin",
"password_hash": "hcT66xoYpmmZQqzjH934IWfEdSdBZYLhIwP0oULa7DyecDFI",
"hashing_algorithm": "rabbit_password_hashing_sha256",
"tags": ""
}
],
"vhosts": [
{
"name": "/"
},
{
"name": "/hello"
}
],
"permissions": [
{
"user": "showwin",
"vhost": "/",
"configure": ".*",
"read": ".*",
"write": ".*"
},
{
"user": "showwin",
"vhost": "/hello",
"configure": ".*",
"read": ".*",
"write": ".*"
}
]
}
- loadDefinition:
enabled: true
existingSecret: load-definition
- extraConfiguration: |
load_definitions = /app/load_definition.json
このように definitions.json の中にユーザの情報を書かないと作成されません。
その理由を探るためにコードを追いかけてみます。
auth.username
や auth.password
は ここ で secret config にされて、 statefulset で RABBITMQ_USERNAME
と RABBITMQ_PASSWORD
という環境変数に設定されます。
(それらの環境変数が設定されていない場合は Docker の entrypoint.sh の中で呼ばれる librabbitmq.sh で user
, bitnami
というユーザ名、パスワードにデフォルトでなるようです。)
RABBITMQ_USERNAME
は ここ で config file に default_user
として設定され、RabbitMQ server 起動時に ここ でそれが読まれてユーザが作成されるはずなのですが、すぐ上のコードを読むと、読み込む definitions がない場合にそのデフォルトユーザを作成して、 definitions がある場合にはデフォルトユーザ(やvhost)を作成しないような動きになっています。
2020年6月の Import definitions in the post-launch phase のPRで入った挙動のようですが、公式のドキュメントも更新されていて
The definitions in the file will not overwrite anything already in the broker. However, if a blank (uninitialised) node imports a definition file, it will not create the default virtual host and user.
ときちんと書かれているので、ちゃんと読めという話でした。
ハマリポイント2: password_hash が毎回変わる問題
先程の設定でこのような部分があります。
- extraSecrets:
load-definition:
load_definition.json: |
{
"users": [
{
"name": "showwin",
"password_hash": "hcT66xoYpmmZQqzjH934IWfEdSdBZYLhIwP0oULa7DyecDFI",
"hashing_algorithm": "rabbit_password_hashing_sha256",
"tags": ""
}
],
ここの password_hash
ですが、どうパスワードをハッシュ化すればよいのかわからなかったので、適当に立ち上げたRabbitMQコンテナの中に入って
$ rabbitmqctl change_password showwin raw_password
$ rabbitmqctl export_definitions definitions.json
$ cat definitions.json
をして、吐き出された definitions ファイルにかかれている password_hash
をコピペして設定しました。
ところが、これで立ち上げると showwin
/ raw_password
で認証が通りません。
おかしいなと思い、上の手順を再度実行すると、異なる password_hash が得られます。毎回saltが変わっているようですね。
どうも load_definition.json
に password_hash
で定義するのは悪手のようで、
- extraSecrets:
load-definition:
load_definition.json: |
{
"users": [
{
"name": "showwin",
"password": "raw_password",
"hashing_algorithm": "rabbit_password_hashing_sha256",
"tags": ""
}
],
と password
のキーを使うとそのパスワードが設定できるようでした。
これは bitnami/rabbitmq の REAMDE の例も password
を使っていたので簡単に気づけたかもしれませんが、rabbitmqctl が吐き出す definitions はハッシュ化されたものだったのでそちらに気を取られてしまいました。
(生パスワードを定義する場合はきちんと secret 化しましょう)
以上、ぼくがユーザ、パスワード設定できない…と5,6時間格闘した結果でした。
おまけ: RabbitMQ運用辛い話
高可用性な構成にしたく、replica数3にしてRabbitMQクラスタを組んでいたのですが、1つpodが死ぬと再起動時に Error while waiting for Mnesia tables
というログを吐いて正常に立ち上がらなくなります。
https://github.com/bitnami/charts/issues/2868 など複数箇所で同様の報告が上がっており、それに対するPRもマージされているのですが自分の環境では未だに発生し続けています。
bitnami/rabbitmq だけではなくて、その前に使っていた stable/rabbitmq でも同じことが起きていました。
正しく理解できている自信はないですが、起動時にクラスタの他のpodの設定を読んで同期する処理があり、そのときにエラーになっているようです。
AWSのスポットインスタンスで構成されたnode群の上で動かしているので、割と頻繁にnodeが死ぬのですが、そこにRabbitMQのpodが乗っていると、再起動時に立ち上がらなくなり、3,4日に1回RabbitMQが死んでしまう状況でした。
(手動で全podを消して同時に立ち上げれば正常に起動します)
最終的に今は(起動時に同期処理が走らない)1podで動かしており、なんとクラスタを組むよりも高い可用性で運用ができている状況です…クラスタ化とは…
とはいえ使う場所によっては、1podな構成ではさすがに信頼性に欠けると思うので、高い可用性が必要であればAWSのマネージドサービスを使いましょう。
負けた感じもしますが…