クラスタリング

LXD はクラスタリングモードで実行できます。クラスタリングモードでは複数台の LXD サーバが同じ分散データベースを共有し、REST API や lxc クライアントで統合管理できます。

この機能は API 拡張の "clustering" の一部として導入しました。

クラスターの形成

まず、ブートストラップノードを選択する必要があります。既存の LXD サーバでも新しいインスタンスでもブートストラップノードになれます。ブートストラップノードとなるサーバを決めた後は、ブートストラップノードを初期化し、それからクラスターへ追加ノードを参加させます。この処理はインタラクティブに行えますし、前もって定義ファイルを作成しても行えます。

クラスターに追加するノードはすべて、ストーレージプールとネットワークについて、ブートストラップノードと同じ構成を持たなければなりません。ノード特有の設定として持てる唯一の設定は、ストレージプールに対する sourcesize、ネットワークに対する bridge.external_interface です。

クラスター内のノード数としては 3 以上を強く推奨します。これは少なくとも 1 ノードが落ちても分散状態のクオラムを確立できるからです(分散状態は Raft アルゴリズムを使ってレプリケーションされている SQLite データベースに保管されています)。ノード数が 3 より小さくなるとクラスター内のただ 1 つのノードだけが SQLite データベースを保管します。第 3 のノードがクラスターに参加したときに、第 2 と第 3 のノードがデータベースの複製を受け取ります。

インタラクティブに行う方法

lxd init を実行し、最初の質問("Would you like to use LXD clustering?")に yes と答えます。そして、そのノードを特定する名前、他のノードが接続するための IP もしくは DNS アドレスを選択します。そして、既存のクラスターに加わるかどうかの質問には no と答えます。最後に、オプショナルでストレージプールとネットワークブリッジを作成できます。これで、最初のクラスターノードが起動し、ネットワークが利用できるようになります。

更に追加のノードをクラスターに追加できます。しかし、追加ノードの既存データはすべて失われるため、追加のノードは完全に新しい LXD サーバであるか、追加前にすべての情報をクリアしたノードである必要があります。

ノードを追加するために、lxd init を実行し、クラスタリングを使うかどうかの質問に yes と答えます。ブートストラップノード、それまでに参加したノードとは異なる名前を指定します。IP もしくは DNS アドレスを指定し、既存のクラスターに加わるかどうかの質問には yes と答えます。クラスター内の既存のノードのアドレスを指定し、表示されたフィンガープリントを確認します。

事前に定義して行う方法

事前にブートストラップノードの設定内容を書いた定義ファイルを作成できます。例えば:

config:
  core.trust_password: sekret
  core.https_address: 10.55.60.171:8443
  images.auto_update_interval: 15
storage_pools:
- name: default
  driver: dir
networks:
- name: lxdbr0
  type: bridge
  config:
    ipv4.address: 192.168.100.14/24
    ipv6.address: none
profiles:
- name: default
  devices:
    root:
      path: /
      pool: default
      type: disk
    eth0:
      name: eth0
      nictype: bridged
      parent: lxdbr0
      type: nic
cluster:
  server_name: node1
  enabled: true

定義ファイルを作成したあと、cat <preseed-file> | lxd init --preseed を実行し、最初のノードを作成します。

次に、他のノードのブートストラップファイルを作成します。cluster セクションに、追加するノード固有のデータと設定値を指定するだけです。

ターゲットとなるブートストラップノードのアドレスと証明書を必ず含めてください。cluster_certificate に対する YAML 互換のエントリーを作成するには、sed ':a;N;$!ba;s/\n/\n\n/g' /var/lib/lxd/server.crt のようにコマンドを実行します。このコマンドはブートストラップノードで実行する必要があります。

例えば:

cluster:
  enabled: true
  server_name: node2
  server_address: 10.55.60.155:8443
  cluster_address: 10.55.60.171:8443
  cluster_certificate: "-----BEGIN CERTIFICATE-----

opyQ1VRpAg2sV2C4W8irbNqeUsTeZZxhLqp4vNOXXBBrSqUCdPu1JXADV0kavg1l

2sXYoMobyV3K+RaJgsr1OiHjacGiGCQT3YyNGGY/n5zgT/8xI0Dquvja0bNkaf6f

...

-----END CERTIFICATE-----
"
  cluster_password: sekret
  member_config:
  - entity: storage-pool
    name: default
    key: source
    value: ""

クラスターの管理

クラスターが形成されると、lxc cluster list を実行して、ノードのリストと状態を見ることができます。ノードそれぞれのもっと詳細な情報は lxc cluster show <node name> を実行して取得できます。

投票 (voting) メンバーとスタンバイメンバー

クラスターは状態を保管するために分散 データベース を使用します。 クラスター内の全てのノードはユーザーのリクエストに応えるためにそのような分散データベースにアクセスする必要があります。

クラスター内に多くのノードがある場合、それらのうちいくつかだけがデータベースのデータを複製するために選ばれます。 選ばれた各オンードは投票者 (voter) としてあるいはスタンバイとしてデータを複製できます。 データベース(とそれに由来するクラスター)は投票者の過半数がオンラインである限り利用可能です。 別の投票者が正常にシャットダウンした時やオフラインであると検出された時はスタンバイノードが自動的に投票者に昇格されます。

投票ノードのデフォルト数は 3 で、スタンバイノードのデフォルト数は 2 です。 これは 1 度に最大で 1 つの投票ノードの電源を切る限りあなたのクラスターは稼働し続けることを意味します。

投票ノードとスタンバイノードの望ましい数は以下のように変更できます。

lxc config set cluster.max_voters <n>

そして

lxc config set cluster.max_standby <n>

投票者の最大数は奇数で最低でも 3 であるという制約があります。 一方、スタンバイノードは 0 から 5 の間でなければなりません。

ノードの削除

クラスターからノードをクリーンに削除するには、lxc cluster remove <node name> を使います。

オフラインノードとフォールトトレランス

都度、選出されたクラスターリーダーが存在し、そのリーダーが他のノードの健全性をモニタリングします。20 秒以上ノードがダウンした場合は、ステータスは OFFLINE とマークされ、そのノード上での操作はできなくなります。また、すべてのノードで状態の変更が必要な操作が可能です。

リーダーがオフラインに移行した場合、他のノードが新しいリーダーに選出されます。

オフラインノードがオンラインに戻るとすぐに、ふたたび操作できるようになります。

ノードをオンラインに戻せないとき、ノードをオンラインに戻したくないときは、lxc cluster remove --force <node name> を使ってクラスターからノードを削除できます。

反応しないノードがオフラインと認識されるまでの秒数は以下のようにして変更できます。

lxc config set cluster.offline_threshold <n seconds>

最小値は 10 秒です。

ノードのアップグレード

クラスターをアップグレードするためには、すべてのノードをアップグレードし、すべてが確実に同じバージョンの LXD にする必要があります。

単一のノードをアップグレードするには、単にホスト上で(snap や他のパッケージ管理システムを使って) lxd/lxc バイナリをアップグレードし、lxd デーモンを再起動します。

デーモンの新バージョンでデータベーススキーマや API が変更になった場合は、再起動したノードは Blocked 状態に移行する可能性があります。これは、クラスター内にまだアップグレードされていないノードが存在し、その上で古いバージョンが動作している場合に起こります。ノードが Blocked 状態にあるとき、このノードは LXD API リクエストを一切受け付けません(詳しく言うと、実行中のインスタンスは動き続けますが、ノード上の lxc コマンドは動きません)。

ブロックされていないノード上で lxc cluster list を実行すると、ノードがブロックされているかどうかを確認できます。

残りのノードのアップグレードを進めると、最後のノードをアップグレードするまでは、ノードはすべて Blocked 状態に移行します。その時点で、Blocked ノードは古いノードが残っていないかを確認し、再度操作できるようになります。

ディザスターリカバリー

各 LXD クラスターはデータベースノードとして機能するメンバーを最大 3 つまで持つことができます。 恒久的にデータベースノードとして機能するクラスターメンバーの過半数を失った場合 (例えば 3 メンバーのクラスターで 2 メンバーを失った場合)、 クラスターは利用不可能になります。しかし、 1 つでもデータベースノードが生き残っていれば、クラスターをリカバーすることができます。

クラスターメンバーがデータベースノードとして設定されているかどうかをチェックするには、クラスターのいずれかの生き残っているメンバーにログオンして以下のコマンドを実行します。

lxd cluster list-database

これは LXD デーモンが実行中でなくても実行できます。

一覧表示されたメンバーの中で、生き残っていてログインしたものを選びます (コマンドを実行したメンバーと異なる場合)。

LXD デーモンが実行していないことを確認したうえで次のコマンドを実行します。

lxd cluster recover-from-quorum-loss

この時点で LXD デーモンを再起動できるようになり、データベースはオンラインに復帰するはずです。

データベースからは何の情報も削除されていないことに注意してください。特に失われたクラスターメンバーに関する情報は、それらのインスタンスについてのメタデータも含めて、まだそこに残っています。 この情報は失われたインスタンスを再度作成する必要がある場合に、さらなるリカバーのステップで利用することができます。

失われたクラスターメンバーを恒久的に削除するためには、次のコマンドが利用できます。

lxc cluster remove <name> --force

ここでは ``lxdではなく通常のlxcコマンドを使う必要があることに注意してください。 <!-- Note that this time you have to use the regularlxccommand line tool, notlxd```. -->

インスタンス

クラスター上の任意のノード上でインスタンスを起動できます。例えば、node1 から:

lxc launch --target node2 ubuntu:18.04 bionic

のように実行すれば、node2 上で Ubuntu 18.04 コンテナーが起動します。

ターゲットを指定せずにインスタンスを起動したときは、インスタンスの数が一番少ないサーバ上でインスタンスが起動されます。全てのサーバが同じ数のインスタンスを持っている場合はランダムに選ばれます。

以下のように実行すると、インスタンス上のすべてのコンテナーをリストできます:

lxc list

NODE 列がコンテナーが実行中のノードを示します。

インスタンスが起動後、任意のノードからそのコンテナーを操作できます。例えば、node1 から:

lxc exec bionic ls /
lxc stop bionic
lxc delete bionic
lxc pull file bionic/etc/hosts .

のように操作できます。

イメージ

デフォルトではデータベースメンバを持っているのと同じ数のクラスターに LXD はイメージを複製します。これは通常はクラスター内で最大3つのコピーを 持つことを意味します。

耐障害性とイメージがローカルにある可能性を上げるためにこの数を増やす ことができます。

特別な値である "-1" は全てのノードにイメージをコピーするために使用できます。

この数を 1 に設定することでイメージの複製を無効にできます。

lxc config set cluster.images_minimal_replica 1

ストレージプール

先に述べたように、すべてのノードは同一のストレージプールを持たなければなりません。異なるノード上のプール間の違いは、設定項目、sourcesizezfs.pool\_name のみです。

新たにストレージプールを作成するためには、すべてのノードでストレージプールをを定義する必要があります。例えば:

lxc storage create --target node1 data zfs source=/dev/vdb1
lxc storage create --target node2 data zfs source=/dev/vdc1

のようにします。

新しいストレージプールをノード上に定義する際、ノード固有で与えることのできる設定項目は上記設定のみです。

この時点ではプールはまだ実際には作られていませんが、定義はされています(lxc storage list を実行すると、状態が Pending とマークされています)。

次のように実行しましょう:

lxc storage create data zfs

するとストレージがすべてのノードでインスタンス化されます。特定のノードで定義を行っていない場合、もしくはノードがダウンしている場合は、エラーが返ります。

この最後の storage create コマンドには、ノード固有ではない(上記参照)任意の設定項目を与えることができます。

ストレージボリューム

各ボリュームは特定のノード上に存在しています。lxc storage volume list は、特定のボリュームがどのノードにあるかを示す NODE 列を表示します。

異なるボリュームは、異なるノード(例えば image volumes)上に存在する限りは同じ名前を持てます。複数のノードが与えた名前のボリュームを持つ場合には、ボリュームコマンドに --target <node name> を与える必要がある点を除いて、ストレージボリュームはクラスター化されていない場合と同じ方法で管理できます。

例えば:

# Create a volume on the node this client is pointing at
lxc storage volume create default web

# Create a volume with the same node on another node
lxc storage volume create default web --target node2

# Show the two volumes defined
lxc storage volume show default web --target node1
lxc storage volume show default web --target node2

ネットワーク

先に述べたように、すべてのノードは同じネットワークを定義しなければなりません。異なるノード間のネットワークで異なっても良い設定は、bridge.external_interfaces というオプショナルの設定項目です(ネットワーク設定の文書を参照してください)

新しいネットワークを作成するには、最初にすべてのノードで以下のように定義を行う必要があります:

lxc network create --target node1 my-network
lxc network create --target node2 my-network

ノード上に新しいネットワークを定義する場合、先に述べたように bridge.external_interfaces のみ有効な設定として与えることができます。

この時点では、ネットワークはまだ実際には作成されていません。しかし定義はされています(lxc network list を実行すると、状態が Pending とマークされています)。

次のように実行しましょう:

lxc network create my-network

するとネットワークがすべてのノード上でインスタンス化されます。特定のノードで定義していない場合、もしくはノードがダウンしている場合は、エラーが返ります。

この最後の network create コマンドには、ノード固有ではない(上記参照)任意の設定項目を与えることができます。

分離した REST API とクラスターネットワーク

クライアントの REST API エンドポイントとクラスター内のノード間の内部的なトラフィック (例えば REST API に DNS ラウンドロビンとともに仮想 IP アドレスを使うために) で別のネットワークを設定できます。

このためには、クラスターの最初のノードを cluster.https_address 設定キーを 使ってブートストラップする必要があります。例えば以下の定義ファイルを使うと

config:
  core.trust_password: sekret
  core.https_address: my.lxd.cluster:8443
  cluster.https_address: 10.55.60.171:8443
...

(YAML 定義ファイルの残りは上記と同じ)。

新しいノードを参加させるには、まず REST API のアドレスを設定します。 例えば lxc クライアントを使って以下のように実行し

lxc config set core.https_address my.lxd.cluster:8443

そして通常通り PUT /1.0/cluster API エンドポイントを使って、 server_address フィールドで参加するノードのアドレスを設定します。 定義ファイルを使うなら YAML のペイロードは完全に上記のものと同じに なるでしょう。