Cassandra DBをDocker Composeで動かす¶
Cassandra DB のクラスタを、1台のPC上で構成します。主に開発・検証用です。
同じマシン上で複数のCassandraノードをDockerコンテナで実行するには、複数のDockerコンテナを連動して実行する Docker compose を使用するのが定番です。
まず、作業ディレクトリを作成し、そこに compose.yaml ファイルを定義します。
通常 各ノードのcassandra.yamlに設定を記述する方法は、同じDockerイメージから複数コンテナを起動する場合に適用が難しいので、環境変数から設定を各ノードに入れます。
方針¶
Cassandra DBのクラスタを新規に構成するには、シードノードと呼ぶノードをまず最初に起動します。シードノードが動作状態に達してから、2番目のノードを起動しクラスタに参加させます。2番目のノードが動作しクラスタに参加した後に3番目のノードを起動していきます。4つ以上ノードがある場合は同様に順番にノードを起動していきます。
クラスタの新規構成時に、もしノードを一斉に起動してしまうとトークンの分担がノード間で正しく行われないのでトークンのコンフリクトが発生します。
一度クラスタ構成ができてしまうと、以後はどの順序でノードを起動してもクラスタが維持されます。
ただし、同時に起動するとうまくクラスタに参加できずエラー終了するノードが発生してしまいます。
なお、コンテナのデータ(Cassandra DBのデータ)を削除してしまうと、次の起動は新規クラスタの構成となってしまいます。
クラスタを新規に構成する手順と、クラスタ構成後の各ノードの起動・終了は手順がことなるので、1つのcomposeで両方に対応することが難しいです。次の2案があります。案1が実装容易ですが、案2は難易度が上がります。
- 案1)クラスタを構成したのちに compose upですべてのノードを起動する。クラスタを新規構成(データを削除してからの起動)するときは、手動もしくは別のスクリプトで1ノードずつ時間をおいて、またはステータスを確認しクラスタに参加状態となってから次のノードを起動する
- 案2)compseの定義でhealthcheckにノードがクラスタに参加状態となっていることを確認する定義を記述
ステップ by ステップ¶
クラスタを組まないで3ノード実行¶
compose.yamlの記述¶
3ノードをDocker Composeで実行するので、サービスを3つ記述します。サービスの内容は、起動するだけの最低限の記述としてDockerイメージ名とコンテナ名を指定します。
services:
cassandra1:
image: cassandra:5.0.9
container_name: cassandra1
cassandra2:
image: cassandra:5.0.9
container_name: cassandra2
cassandra3:
image: cassandra:5.0.9
container_name: cassandra3
- 最低限 image の指定が必要
- container_nameを省略すると、デフォルトで <compose.yamlのあるディレクトリ名>-<サービス名> がコンテナ名となります。
単一コンテナの起動¶
まずはこの設定ファイルで単一のコンテナ(cassandra1)が起動できるか確認します。
~cassandra_docker$ docker compose up -d cassandra1
[+] Running 1/1
✔ Container cassandra1 Started 0.2s
~cassandra_docker$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f1a96b1273ef cassandra:5.0.9 "docker-entrypoint.s…" 4 seconds ago Up 4 seconds 7000-7001/tcp, 7199/tcp, 9042/tcp, 9160/tcp cassandra1
3ノードの起動¶
Docker composeで3つのコンテナが起動できることを確認します。
~cassandra_docker$ docker compose up -d
[+] Running 4/4
✔ Network docker2_default Created ✔ Container cassandra2 Started ✔ Container cassandra3 Started
✔ Container cassandra1 Started
この状態で、ノードの情報を参照するとクラスタにはなっておらず各コンテナが単独ノードとして動いています。
~cassandra_docker$ docker exec cassandra1 nodetool status
Datacenter: datacenter1
=======================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
-- Address Load Tokens Owns (effective) Host ID Rack
UN 172.19.0.4 172.12 KiB 16 100.0% cb9bc4d4-6ebb-4353-bbf3-1d6605abf508 rack1
クラスタを構成する¶
この3つのCassandraコンテナで同一のクラスタを組むには、シードノードの設定が必要です。
シード・ノードの定義¶
ノードが起動したときに、クラスタ構成を問い合わせるノードをシードとして指定します。同一データセンター内で最低1つのノードを指定しますが、運用では障害を考慮して2つないし3つをシードに指定するのが定番です。シードが正常に稼働していないと、新規参入するノードがクラスタに加わることができません。
新規にクラスタを構築するときは、まずシードノードが正常に起動したあとに、追加のノードを起動していきます。
cassandra2:
image: cassandra:5.0.9
container_name: cassandra2
+ environment:
+ - CASSANDRA_SEEDS: cassandra1
cassandra3:
image: cassandra:5.0.9
container_name: cassandra3
+ environment:
+ - CASSANDRA_SEEDS: cassandra1
シードノードとなるcassandra1にはシードの定義は不要です。
まず、cassandra1から手動で順番にコンテナを起動し、都度 nodetool でステータスを確認します。
- cassandra1の起動
docker compose up -d cassandra1 - cassandra1のnodetool status確認
$ docker exec cassandra1 nodetool status Datacenter: datacenter1 ======================= Status=Up/Down |/ State=Normal/Leaving/Joining/Moving -- Address Load Tokens Owns (effective) Host ID Rack UN 172.19.0.4 172.12 KiB 16 100.0% cb9bc4d4-6ebb-4353-bbf3-1d6605abf508 rack1
- cassandra1のステータスがUNになったことを確認し、cassandra2の起動
docker compose up -d cassandra2 - しばらく待ってから、nodetoolのsutatus確認
$ docker exec cassandra1 nodetool status Datacenter: datacenter1 ======================= Status=Up/Down |/ State=Normal/Leaving/Joining/Moving -- Address Load Tokens Owns (effective) Host ID Rack UN 172.19.0.2 119.83 KiB 16 100.0% f2a68fdc-c15d-445c-9a67-e6f0c81aefcc rack1 UN 172.19.0.3 119.67 KiB 16 100.0% e92cbc68-bd5f-4755-8f00-2d082c856a97 rack1
- cassandra2のステータスがUNになったことを確認し、 cassandra3の起動
docker compose up -d cassandra3 - しばらく待ってから、nodetoolのsutatus確認
$ docker exec cassandra1 nodetool status Datacenter: datacenter1 ======================= Status=Up/Down |/ State=Normal/Leaving/Joining/Moving -- Address Load Tokens Owns (effective) Host ID Rack UN 172.19.0.4 30.9 KiB 16 76.0% 03efb4bf-0722-4188-925d-d3fcc18a79ee rack1 UN 172.19.0.2 119.83 KiB 16 64.7% f2a68fdc-c15d-445c-9a67-e6f0c81aefcc rack1 UN 172.19.0.3 85.11 KiB 16 59.3% e92cbc68-bd5f-4755-8f00-2d082c856a97 rack1
- cluster_nameはデフォルトでTest Clusterとなるので、cluster_name未設定でも同じクラスタを組むことができます
docker compose down -vでデータを削除したあとにdocker compose up -dで起動すると、クラスタ構成の情報も削除されているのでクラスタの新規構成となります。すると、起動タイミングがほぼ同時となるため、シードノードが起動完了するまえにノードが起動してしまい、クラスタが構成できません
healthcheckの設定¶
Cassandraのノードが起動完了し、動作可能となっているかをチェックする設定を追加します。
手段は、nodetoolを実行して応答が返ってくるかの確認をします。
以下のhealthcheck設定を各ノードに記述します。
service:
cassandra1:
:
healthcheck:
test: ["CMD-SHELL", "cqlsh -e 'describe cluster' || exit 1"]
interval: 10s
timeout: 5s
retries: 20
- interval(デフォルト30秒)
コンテナ起動時点を0として、interval指定時間が経過する都度ヘルスチェックを開始します。 - timeout(デフォルト30秒)
チェックを実行してから timeout指定時間以内にチェックが終わらない場合チェックが失敗したものとみなします。 - retries(デフォルト3回)
チェックが retries指定回数失敗したらunhealthy状態に遷移します。 - start-period(デフォルト0)
起動後、start-periodで指定した時間が経過するまではstart-intervalで指定した周期でチェックを実行します。start-period期間内のチェック失敗回数には加算されません。 - start-interval(デフォルト5秒)
上述
起動順番の設定¶
クラスタ構成後においても、ノードの起動を複数同時にするとノード間通信がしばらく確立できずエラーになることもあります。そこで、クラスタ構成後の起動を順番付けします。
services:
cassandra1:
:
cassandra2:
:
depends_on:
cassandra1:
condition: service_healthy
:
cassandra3:
depends_on:
cassandra2:
condition: service_healthy
- cassandra2は、cassandra1のヘルスチェックがOKとなったら起動します。
- cassandra3は、cassandra2のヘルスチェックがOKとなったら起動します。
これでラグを付けて起動することができます。
- 注)新規クラスタ構成時は、このラグでは不十分です
使用メモリサイズの指定¶
同一PC上に複数Cassandra DBノードを実行するときは、各ノードで使用するメモリの合計をホストPCのメモリに応じて制限する必要があります。Cassandra DBは、JavaVM上で実行しますが、JavaVMはデフォルトでホストPCのメモリの1/4の容量をJavaヒープに確保します。3ノードを実行すると大半のメモリがとられてしまうので、設定で最大ヒープサイズを制限します。
services:
cassandra1:
:
environment:
:
MAX_HEAP_SIZE=1G
- JavaVM (JDK) 17 では、デフォルトのGC(ガベージコレクタ)がG1GCであり、G1GCではHEAP_NEWSIZEの指定は不要
データの永続化¶
データベースのデータを、Docker コンテナを破棄したときにも残して再利用できるように Volume としてコンテナ外部に保存します。
使用する volume の定義¶
トップレベルのvolumes項目に、使用する volume を定義します。
volumes:
cassandra-data-1:
cassandra-data-2:
cassandra-data-3:
ここに定義したボリュームが未作成のときは、composeを起動するときに、docker compose up -d で作成されます。事前に手動で docker volume create をする必要がありません。
ボリュームをコンテナにマウントする設定を、各servicesのサービス毎に記述します。
services:
cassandra1:
:
volumes:
cassandra-data-1:/var/lib/cassandra
ここまでのまとめ¶
これまでの compose.yaml の全体を示します。
services:
cassandra1:
image: cassandra:5.0.9
container_name: cassandra1
environment:
- MAX_HEAP_SIZE=1G
volumes:
- cassandra-data-1:/var/lib/cassandra
healthcheck:
test: ["CMD-SHELL", "cqlsh -e 'describe cluster' || exit 1"]
interval: 10s
timeout: 5s
retries: 20
cassandra2:
image: cassandra:5.0.9
container_name: cassandra2
environment:
- CASSANDRA_SEEDS=cassandra1
- MAX_HEAP_SIZE=1G
volumes:
- cassandra-data-2:/var/lib/cassandra
depends_on:
cassandra1:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "cqlsh -e 'describe cluster' || exit 1"]
interval: 10s
timeout: 5s
retries: 20
cassandra3:
image: cassandra:5.0.9
container_name: cassandra3
environment:
- CASSANDRA_SEEDS=cassandra1
- MAX_HEAP_SIZE=1G
volumes:
- cassandra-data-3:/var/lib/cassandra
depends_on:
cassandra2:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "cqlsh -e 'describe cluster' || exit 1"]
interval: 10s
timeout: 5s
retries: 20
volumes:
cassandra-data-1:
cassandra-data-2:
cassandra-data-3:
新規クラスタ構築対応¶
手動でクラスタ新規構成する手順をcomposeで実装します。healthcheckで、自身のノードがクラスタに参加状態(UN)となったことをもってhealth状態とします。
healthcheck:
test: ["CMD-SHELL", 'nodetool status | grep -qE "^UN[[:space:]]+$$(hostname -i)[[:space:]]"']
interval: 2m
start_period: 2m
timeout: 10s
retries: 3
- hostname -i でノード自身のIPアドレスを実行時に取得します。通常のLinux OS上ではhostname -iコマンドは複数のIPアドレスを返却し、最初の値はループバック(127.0.0.1)となることがありますが、Dockerコンテナ上ではループバックは返らず、接続しているネットワークの数に相当するIPアドレスが返ります。
これで、手動と同じ手順で複数ノードを順番に起動しクラスタを構成することができました。
compose定義の共通化¶
上述のhealthcheckは、どのコンテナでも共通です。同じ定義を複数個所に記述するのはメンテナンス上好ましくないので、設定を共通化します。
compse定義では、yamlのアンカー(&)、エイリアス(*)を使って共通化を図ることができます。
- cassandra1のhealthcheckに定義を記述、このhealthcheckにアンカーを設定
- cassandra2、3のhealthcheckは、エイリアスでcassandra1のhealthcheckを参照する定義を記述
cassandra1:
(中略)
healthcheck: &healthcheck
test: ["CMD-SHELL", 'nodetool status | grep -qE "^UN[[:space:]]+$$(hostname -i)[[:space:]]"']
interval: 2m
start_period: 2m
timeout: 10s
retries: 3
cassandra2:
(中略)
healthcheck: *healthcheck
cassandra3:
(中略)
healthcheck: *healthcheck
cassandra1のhealthcheckのキーに、&<名前> でアンカーを設定します。
cassandra2と3のhealthcheckのキーに、*<名前> でアンカー設定した項目のエイリアスを設定します。
environmentとhealthcheckの共通化¶
上述のhealthcheckに加え、environmentを共通化したcompose定義を次に示します。
services:
cassandra1:
image: cassandra:5.0.9
container_name: cassandra1
environment: &environment
- CASSANDRA_SEEDS=cassandra1
- MAX_HEAP_SIZE=1G
volumes:
- cassandra-data-1:/var/lib/cassandra
healthcheck: &healthcheck
test: ["CMD-SHELL", 'nodetool status | grep -qE "^UN[[:space:]]+$$(hostname -i)[[:space:]]"']
interval: 2m
start_period: 2m
timeout: 10s
retries: 3
cassandra2:
image: cassandra:5.0.9
container_name: cassandra2
environment: *environment
volumes:
- cassandra-data-2:/var/lib/cassandra
depends_on:
cassandra1:
condition: service_healthy
healthcheck: *healthcheck
cassandra3:
image: cassandra:5.0.9
container_name: cassandra3
environment: *environment
volumes:
- cassandra-data-3:/var/lib/cassandra
depends_on:
cassandra2:
condition: service_healthy
healthcheck: *healthcheck
volumes:
cassandra-data-1:
cassandra-data-2:
cassandra-data-3:
- cassandra1のenvironmentに、CASSANDRA_SEEDS=cassandra1を追記、これで3ノード共通定義となります。シードノードの設定に自身を参照する記述は問題ありません。
未整理メモ¶
- Snitchの設定
Rack、Data centerを意識しない構成なら SimpleSnitchでもよい
Rack、Data centerを考慮する構成なら GossipingPropertyFileSnitch - GossipingPropertyFileSnitchを設定したら、CASSANDRA_DCを明示的に指定
- cassandra.yaml の cluster_name設定、cassandra-rackdc.properties の dc設定と同等
資料¶
Running Apache Cassandra Single and Multi-Node Clusters on Docker with Docker Compose