# クラスター機能

> **警告**
>
> クラスター機能は実験的機能のため、正式版では仕様が変更される可能性があります

## 概要

これは Sora でクラスターを構成し、冗長化、負荷分散を行うための仕組みです。

クラスター内では、ひとつのチャネル ID への接続は、担当するひとつの Sora ノードにすべて割り当てられます。
クライアントがクラスターを構成しているいずれかの Sora に接続した際には、
そのチャネル ID で、接続すべき Sora の `Signaling URL` を提示する仕組みです。

## 目的

可用性を高めるため Sora 複数台でクラスターを組み、冗長化やロードバランシングを実現します。

## 仕組み

Sora のクラスター機能の仕組みは、ひとつのチャネル ID への接続は、
担当するひとつの Sora ノードにすべて割り当てるものです。
これを実現するために、Sora のクラスターに参加しているそれぞれの Sora ノードは、
どのノードがどのチャネルを担当するべきかを把握しています。
そして、自分が担当でなかったら担当者にリダイレクトすることにより、
クライアントは担当ノードを意識することなく、任意のノードにアクセスするだけで担当ノードに接続できます。

## 特徴

> **注釈**
>
> これらの仕組みは `sora.conf` にてクラスター機能を有効にした場合に使用できます

### クライアントへの複数シグナリング設定

Sora SDK にシグナリング URL を複数指定することで、いずれかの Sora ノードが正常に動作していれば Sora に繋がります。

### Sora ノード選択 / ロードバランス機能

クラスターを構築している Sora ノードの中で `現在の同時接続数` を
`ライセンスの最大同時接続数` で割った値が一番小さい Sora ノードに新規チャネル ID の担当を割り当てます。
値が全て同じ場合は接続しに行ったノードが担当します。

すでにそのチャネル ID がクラスター内部で利用されている場合は、
そのチャネル ID を担当している Sora ノードへ割り当てられます。

### シグナリングのリダイレクト機能

> **重要**
>
> Sora の SDK を利用している場合は、基本的にここに書かれているリダイレクトの
> 細かい仕様を把握する必要はありません。

`"type": "connect"` を送った際、認証に入る前に `{"type": "redirect", "location": "wss://sora1.example.com/signaling"}` が戻ってくる場合があります。

この場合は送られてきたシグナリング URL に切り替えて再度 `type: connect` を行ってください。
その際は `{type: connect, redirect: true}` のように `redirect: true` を追加情報として入れてください。

### HTTP API のリダイレクト機能

チャネル ID を指定する HTTP API を利用する場合、リダイレクトが発生することがあります。
指定されたチャネル ID を他ノードが担当している場合に、ステータスコード 307 (Temporary Redirect)の
HTTP 応答により、クライアントにそのノードへのリダイレクトを要求します。

ステータス 307 の処理は HTTP のクライアントとするツールやライブラリにより異なります。
必要に応じて、リダイレクト応答の Location ヘッダーへアクセスを継続するようにしてください。

たとえば、`curl` コマンドの場合は、 `-L` オプションを指定することでリダイレクト先への再要求を行います。


### クラスター自動参加機能

クラスターにノードが自動で参加する機能です。

Sora はクラスターが有効な際、起動時に `sora.conf` の [contact_node_name_list](CLUSTER.html#a3d0fd) に存在する
ノード名に対してクラスターの参加を自動で試みます。

そのノードが一度でもクラスターに参加したことがある場合は、そのクラスターに参加していた時の
ノード一覧を利用して、クラスターへの参加を自動で試みます。

新規にクラスターを作成する場合には事前に [InitCluster](EXPERIMENTAL_API.html#621990) API を実行して、
クラスターを初期化しておく必要があります。

### クラスター手動参加機能

> **重要**
>
> `sora.conf` の [contact_node_name_list](CLUSTER.html#a3d0fd) を利用した自動参加の利用を推奨しています。

複数の Sora でクラスターを手動で組む機能です。クラスターに参加する際に [JoinCluster](EXPERIMENTAL_API.html#17f3f5) API を叩きます。

すでにクラスターが存在している場合には、そこに参加しているノードのいずれかを引数の `contact_node_name` で
指定してください。クラスターに参加していて、初期化済みであれば、どのノードでもかまいません。

新規にクラスターを作成する場合には事前に [InitCluster](EXPERIMENTAL_API.html#621990) API を実行して、
クラスターを初期化しておく必要があります。

### クラスター自動復旧

クラスターで利用しているネットワークに障害が発生した際、個々のノードはクラスターを構成する他のノードへの接続を試み、
復旧処理を行います。

### ネットワーク分断時の接続受付の停止

ネットワーク分断時には Sora のノード間で通信ができず、クラスター内の情報の整合性が取れなくなる可能性があります。
不整合を回避するために、Sora は自分が過半数未満のグループに属した場合に、既存の接続を切断した上で、
新規接続の受付を停止します。

### 特定ノードへの新規チャネル ID の割り当て停止

[モード機能](MODE.html) の [イニシャルモード](MODE.html#74fada) 、
[新規コネクションブロックモード](MODE.html#0b67d3) または [新規セッションブロックモード](MODE.html#041e1c) になっている場合は、
そのノードに対して新規チャネル ID の割り当てを行いません。

### 録画情報の共有

録画の情報はクラスター内部で共有されます。
たとえば [StartRecording](API.html#c5b527) API をあるノードに対して実行した後にそのノードが停止しても
録画情報は他のノードに残り続けます。

## ポート番号の開放

以下のポート番号を開放して下さい

- epmd（Erlang Port Mapper Daemon）で利用する TCP/IP ポート番号- 4369
- ノード間通信に利用する TCP/IP ポート番号- 49010 - 49020
  - `sora.conf` の `cluster_listen_(min|max)_port` にて指定可能です

## 注意

### ライセンス

クラスターの 1 Sora ノードにつき 1 ライセンスが必要になります。
3 台の Sora ノードでクラスターを組む場合は 3 ライセンス必要になります。

> **注釈**
>
> [最大ノード数ライセンス](LICENSE.html#aee259) を利用することで、同一ライセンスを複数 Sora ノードで利用することが可能になります。

### クラスターのノード数

クラスター機能を有効にしている場合は 1 ノードだけでは利用できず、必ず 2 ノード以上である必要があります。

> **注釈**
>
> 1 ノードの場合は接続を受け付けません

3 ノード以上での運用を強く推奨します。

> **注釈**
>
> 2 ノードのクラスターでは片方のノードが停止すると、生きているノードは過半数未満となるため接続を受け付けません。
> そのため 1 ノードの場合よりも可用性が落ちます。

### クラスターの最大ノード数

クラスターを構築するノードの最大数は 10 ノードを想定しています。
これ以上を検討されている場合はサポートまでご連絡ください。

### クラスター構成情報ファイル

> **危険**
>
> このファイルは Sora が内部で利用するためのファイルなので触らないでください。

Sora でクラスターを利用する場合、クラスター参加後のノード情報を永続化する
`data/` ディレクトリ以下にファイルを生成します。

Sora のバージョンアップ時にこのファイルをコピーする必要はありません。
ファイルが無い場合、クラスターに参加した際に生成されます。
バージョンアップ以外の場合は `data/` ディレクトリを消さないでください。

### クラスターからのノードの完全消去について

クラスターからノードを完全に消去させる場合は `bin/sora stop` で停止させた後に、
クラスターを構成してる実行中のノードのいずれかに対し、 [PurgeClusterNode](EXPERIMENTAL_API.html#13b35a) API を実行します。

> **警告**
>
> [PurgeClusterNode](EXPERIMENTAL_API.html#13b35a) API はクラスターからノードを完全に消去するための API です。
> 再参加するノードに対しては基本的に使用しないでください。

### PurgeClusterNode API で完全消去したノードの再参加

> **警告**
>
> [PurgeClusterNode](EXPERIMENTAL_API.html#13b35a) API はクラスターからノードを完全に消去するための API です。
> 通常は、再参加するノードに対しては使用しないでください。
> ここの説明は、長期間に渡ってクラスターに参加できないため、苦肉の策として
> [PurgeClusterNode](EXPERIMENTAL_API.html#13b35a) API を使った場合の説明です。

> **警告**
>
> [PurgeClusterNode](EXPERIMENTAL_API.html#13b35a) APIでクラスターから消去したノードは、
> 以下に示す手順を経ずに再起動してはいけません。

クラスターからノードを完全消去した場合、そのまま再度参加をすることはできません。
再度クラスターに参加をする場合は `data/` ディレクトリを削除する必要があります。

PurgeClusterNode API を実行して、さらにそのノードの `data/` ディレクトリを削除した場合、そのノードは
完全に新規のノードとして取り扱われます。

この状態になったノードは新規ノードですので、 [JoinCluster](EXPERIMENTAL_API.html#17f3f5) API をつかうか、
`sora.conf` の [contact_node_name_list](CLUSTER.html#a3d0fd) を利用してクラスターに参加できます。

### クラスターから消去されたノードを別クラスターに参加させたい場合

あるクラスター A に参加していたノードを、A から削除して別のクラスター B に参加させたい状況を考えます。

> **警告**
>
> PurgeClusterNode API はクラスターからノードを完全に消去するための API です。
> 通常は、再利用するノードに対しては使用しないでください。
> Sora が稼働するサーバーを再利用して、別のクラスター B に参加する新規ノードが欲しい場合には、
> Sora リリースの tar.gz を展開して、まっさらな状態からの新規ノード構築を推奨します。
> ここの説明は、どうしても既存の tar.gz 展開ディレクトリを再利用したい場合の説明です。

PurgeClusterNode API で消去されたノードは以下の 2 点で以前参加していたクラスター A のノード群を覚えています。

- sora.conf の [contact_node_name_list](CLUSTER.html#a3d0fd)
- sora 起動ディレクトリ以下の `data/` ディレクトリ内のデータ

よって、再利用する場合にはこれらの情報を無くす必要があります。

- [contact_node_name_list](CLUSTER.html#a3d0fd) は新しい別クラスター B のノードのリストにするか空にする必要があります
- `data/` ディレクトリは削除する必要があります

> **警告**
>
> 以上の手順を踏まずに別クラスター B に参加させようとすると、最悪の場合には A と B がひとつになった
> クラスターになってしまう場合があります。

## クラスター機能有効時のモード

クラスター機能を有効にした場合、 Sora は [イニシャルモード](MODE.html#74fada) という
クラスター機能だけで利用される特殊なモードで起動します。

- [イニシャルモード](MODE.html#74fada) はすべての接続を受け入れません
- [イニシャルモード](MODE.html#74fada) はクラスターに参加後、過半数以上のグループになったタイミングで[ノーマルモード](MODE.html#920e82) へ自動で切り替わります
- [イニシャルモード](MODE.html#74fada) はモード変更 API では指定できません

## ネットワーク障害やノード障害

Sora のクラスター機能はネットワーク障害やノード障害が発生した際に、
自動で新規接続の受付の停止とそこからの復旧を試みます。

### 発生しうる問題と新規接続の受付停止

ネットワーク障害等が発生した場合には、
Sora のノード間で通信ができず、クラスター内の情報の整合性が取れなくなる可能性があります。

たとえば、5 ノードのクラスターが 3 ノードからなるグループ A と
残りの 2 ノードからなるグループ B の 2 グループに分断されるということが起こりえます。
それぞれのグループ内では通信ができるものの、他グループへの通信ができないという状態です。
この状態ですべてのノードが接続を受け付けると、同じチャネル ID を指定していても、
あるクライアントはグループ A に繋がり、別のクライアントは グルーブ B につながる可能性があります。
その場合はクライアント間で音声と映像が届かなくなります。

このような状況を防止するため、Sora は自分が過半数未満のグループに属した場合に、新規接続の受付を停止します。
特に、自分が孤立した場合もこれに含まれます。

その後、クラスター自動復旧により、通信できるノードが過半数以上となった場合には、自動で新規接続の受付を再開します。

#### 新規接続の受付がまったくできなくなる場合

障害の状況によっては新規接続の受付がまったくできなくなる場合もあります。
例えばネットワークの分断によって、5 ノードのクラスターが 2 ノード、2 ノード、1 ノードという
3 つのグループに分断された状況を考えます。
この場合にはどのノードも過半数未満となるため、新規接続の受付はまったくできなくなります。

ネットワーク障害ではなくても、5 ノードクラスターで、3 ノードが停止してしまった場合には、残った 2 ノードは
過半数未満となるため、新規接続の受付はできません。

### 通信できるノードが過半数未満となった場合の挙動

ノード数が過半数未満のクラスターに参加しているノードは、接続中のすべての接続を切断し、その後は接続を受け入れません。

また、その時のモードによってモードが変わることがあります。

- [ノーマルモード](MODE.html#920e82) の場合には [イニシャルモード](MODE.html#74fada) に状態を移行します
- [ノーマルモード](MODE.html#920e82) 以外のモードであった場合はモードは変わりません

その後はクラスター自動復旧を試みます。

### 通信できるノードが過半数以上となった場合の挙動

通信できるノードが過半数未満から過半数以上に変わったタイミングで、そのときのモードによってモードが変わることがあります。

- [イニシャルモード](MODE.html#74fada) の場合には [ノーマルモード](MODE.html#920e82) に状態を移行します
- [イニシャルモード](MODE.html#74fada) 以外のモードであった場合はモードは変わりません

遷移後のモードに従って、新規接続を受け付けます。

### 全ノードが過半数未満に所属した場合の挙動

ネットワーク障害等が発生したことで、いずれのノードも通信できるノードが過半数未満になった場合、
新規接続はまったく受け付けられない状態になります。

この場合でも、永続化しておいたノード一覧を利用し自動でクラスターの復旧を試みます。
復旧して過半数以上のグループができた場合は新規接続が可能になります。

## クラスター環境での録画

### 録画情報の共有

録画の情報はクラスター内部で共有されます。
たとえば [StartRecording](API.html#c5b527) API をあるノードに対して実行した後にそのノードが停止しても
録画情報は他のノードに残り続けます。
また [StopRecording](API.html#fd0de5) API はどのノードに対しても実行することができます。

たとえば、あるチャネルで [StartRecording](API.html#c5b527) API が実行された後に、
ノード A に接続がきて録画が開始したとします。
このノード A がノード障害等で終了した後に、同じチャネルで別の接続がノード B に来た場合には
ノード B で録画が進行します。

### 録画ファイルの出力ノード

`archive-<connection_id>.json`, `archive-<connection_id>.webm` の録画ファイルは、接続を担当するノードで
出力されます。

次のような場合には同一の `recording_id` に属する録画ファイルが、
複数ノードにまたがって出力されることがありますのでご注意ください。

1. 録画開始
2. ノード A に接続が来てしばらくして終了、録画ファイルがノード A に作られる
3. ノード A が停止
4. ノード B に接続が来てしばらくして終了、録画ファイルがノード B に作られる

### `report-<recording_id>.json` ファイルの出力ノード

あるチャネルに対して、 [StopRecording](API.html#fd0de5) API を実行した場合には録画が終了し、
`report-<recording_id>.json` ファイルが出力されます。

このファイルの出力ノードは次のとおりです。

- そのチャネルに接続したクライアントがひとつもなかった場合はどのノードで出力されるかは未定です- ただし、クラスター内のいずれかのノードで出力されます
- そのチャネルに接続中のクライアントがいる場合は、チャネルの担当ノードで出力されます
- そのチャネルに接続したクライアントが過去にいた場合は 2 パターンがありえます- クライアントが接続したノードが生存し続けている場合はそのノードで出力されます
  - ノードが落ちている、または停止した後に再起動した場合にはどのノードで出力されるかは未定です- ただし、クラスター内のいずれかのノードで出力されます

### `report-<recording_id>.json` ファイルが一時的に出力されない場合

全ノードが過半数未満に所属した場合には `report-<recording_id>.json` ファイルが出力されません。

5 ノードのクラスターがあるとして、いくつか例を示します。

- ネットワーク障害が発生し、2 + 2 + 1 に分断された場合
- ノード障害(運用での停止も含む)が発生し、3 ノードが停止した場合
- ノード障害で 1 ノードが停止した状態でネットワーク障害が発生し、 2 + 2 に分断された場合

このあとに分断が解消し、過半数以上のグループができたあとに [StopRecording](API.html#fd0de5) API を
実行すると `report-<recording_id>.json` ファイルが出力されます。

## 設定

### cluster

クラスター機能を利用する場合、 `sora.conf` にて [cluster](SORA_CONF.html#b2cd99) を `true` を設定する必要があります。

デフォルトでは `cluster` は有効になっていません。

> **重要**
>
> cluster = true にして Sora を起動した場合、
> Sora はすべての接続を受け付けない [イニシャルモード](MODE.html#74fada) モードで起動します。
>
> 自動または手動でクラスターに参加した後、過半数以上のグループになった時点で
> [ノーマルモード](MODE.html#920e82) へ自動で切り替わります。

```ini
cluster = true
```

### node_name

クラスター機能を利用する際のノード名を指定して下さい。

この名前はクラスター内のノードの識別に使われます。
具体的には、クラスターノード間の通信相手の特定や、
`sora.conf` の [contact_node_name_list](CLUSTER.html#a3d0fd) 、 [JoinCluster](EXPERIMENTAL_API.html#17f3f5) API、
[PurgeClusterNode](EXPERIMENTAL_API.html#13b35a) API で指定する名前として利用します。

> **重要**
>
> `node_name` はクラスター内のすべてのノードでユニークである必要があります

ノード名の @ の前には、正規表現 `[0-9A-Za-z_\\-]+` にマッチする文字列を指定してください。
また @ の後ろには、サーバーのドメイン名（FQDN）や、IP アドレスを指定してください。

> **重要**
>
> クラスターどうしの通信は暗号化されていないため、プライベートネットワークを利用するか
> 暗号化されたネットワークを利用してください。

> **重要**
>
> @ の後ろにホスト名（"." を含まない文字列）を指定すると、ノード間で通信が行えなくなってしまいます。

> **重要**
>
> クラスター参加後にノード名を変更する場合には、古い方の名前は
> [PurgeClusterNode](EXPERIMENTAL_API.html#13b35a) API を用いて削除してください。
> また `data/` のディレクトリ削除も必ずおこなってください。
>
> 詳しくは [ノード名変更手順](CLUSTER.html#1ab2c5) を参照してください。

```ini
# ドメイン名を指定する例
node_name = node01@node01.example.com
```

```ini
# IP アドレスを指定する例
node_name = node02@192.0.2.1
```


### contact_node_name_list

クラスター機能が有効な場合で、 `sora.conf` の [contact_node_name_list](CLUSTER.html#a3d0fd) が指定されていた場合、
起動時に自分以外のノード名に対して自動でクラスターの参加を試みます。

> **注釈**
>
> 自分自身のノード名が `concact_node_name_list` に含まれていても無視します。

```ini
node_name_list = sora1@192.0.2.1, sora2@192.0.2.2, sora3@192.0.2.3
```


### cluster_auto_reconnect

クラスター機能が有効な場合で、 `sora.conf` の `cluster_auto_reconnect` が `true` の場合、
ネットワーク障害やノード障害の発生時に自動で再接続を試みます。

この設定はデフォルトで有効です。

```ini
cluster_auto_reconnect = true
```

### external_signaling_url

`sora.conf` にてシグナリングの URL を指定して下さい。
この URL はクラスター機能を利用した際に `"type": "redirect"` の `"location"` の値として払い出されます。

```ini
external_signaling_url = wss://node1.example.com/signaling
```

上記の設定をした場合は `{"type": "redirect", "location": "wss://node1.example.com/signaling"}` として払い出されます。

### external_api_url

`sora.conf` にて API の URL を指定して下さい。
この URL はクラスター機能を利用した際に API を適切なノードに HTTP 307 でリダイレクトする際、
HTTP ヘッダー `"location"` の値として払い出されます。

```ini
external_api_url = https://node1.example.com/
```

### cluster_listen_(min|max)_port

`sora.conf` にてクラスター情報の同期に利用する TCP/IP の受信ポートの範囲を指定してください。

デフォルトでは 49010 - 49020 が指定されています。

```ini
cluster_listen_min_port = 49010
cluster_listen_max_port = 49020
```


## API

### クラスター初期化 API

クラスターを構築するときは、まずクラスターの初期化を行います。

> **重要**
>
> クラスターの初期化はクラスターを初めて構築する際と、クラスターが破綻した際に行う必要があります。

クラスターの初期化を実行する場合は `Sora_20221221.InitCluster` API をクラスター構築するノードで実行します。
`node_name_list` にはクラスターを構成するノードを、 API を実行するノードを含め指定してください。

1 ノードだけを指定した場合には、そのノードは initial モードに留まります。
その後に [JoinCluster](EXPERIMENTAL_API.html#17f3f5) API を実行すると normal モードに遷移します。

API の詳細は [InitCluster](EXPERIMENTAL_API.html#621990) API をご確認ください。

```console
$ http POST 127.0.0.1:3000/ x-sora-target:Sora_20221221.InitCluster \
    node_name_list:='["node-02@192.0.2.7", "node-03@10.99.0.8", "node-01@192.0.2.5"]' \
    -vvv
HTTP/1.1 200 OK
content-length: 646
content-type: application/json
date: Tue, 16 Nov 2021 08:42:25 GMT
server: Cowboy

{
    "node_name_list": [
        "node-02@192.0.2.7",
        "node-03@192.0.2.8",
        "node-01@192.0.2.5"
    ]
}
```

> **重要**
>
> クラスターのノードが恒久的に破損し、過半数以下になった場合には、
> 再度クラスターの初期化を行う必要があります。
>
> 詳しくは [クラスター破綻からの再構築手順](CLUSTER.html#7c3099) を参照してください。

### クラスター参加 API

> **ヒント**
>
> `sora.conf` の [contact_node_name_list](CLUSTER.html#a3d0fd) を利用したクラスターへの自動参加の利用を推奨しています。

参加するノードに対して、すでにクラスターに参加しているノードを指定してクラスターへ参加します。

API の詳細は [JoinCluster](EXPERIMENTAL_API.html#17f3f5) API をご確認ください。

```console
http POST 127.0.0.1:3000/ x-sora-target:Sora_20211215.JoinCluster \
    contact_node_name=sora1@192.0.2.5
HTTP/1.1 200 OK
content-length: 646
content-type: application/json
date: Tue, 16 Nov 2021 08:42:25 GMT
server: Cowboy

{
    "node_name_list": [
        "node-01@192.0.2.5",
        "node-02@192.0.2.7",
        "node-03@192.0.2.8"
    ]
}
```

### クラスターノード完全消去 API

ある Sora ノードが障害で再参加が難しい、またはスケールインのためにノード破棄する場合は
[PurgeClusterNode](EXPERIMENTAL_API.html#13b35a) API を利用して、そのノード情報を完全消去してください。

これはクラスターのどのノードでもかまわないので 1 回だけ実行すれば、完全消去した結果はクラスター内部で共有されます。

> **警告**
>
> PurgeClusterNode API はクラスターからノードを完全に消去するための API です。
> 再参加するノードに対しては基本的に使用しないでください。
> この API を含めた運用の手順は [クラスター運用](CLUSTER.html#0233aa) をご確認ください。

```console
$ http POST 127.0.0.1:3000/ x-sora-target:Sora_20220629.PurgeClusterNode \
    target_node_name=sora1@192.0.2.5
```

詳細は [PurgeClusterNode](EXPERIMENTAL_API.html#13b35a) API をご確認ください。

### クラスターノード一覧 API

詳細は [ListClusterNodes](EXPERIMENTAL_API.html#a70901) API をご確認ください。

```console
$ http POST 127.0.0.1:3000/ x-sora-target:Sora_20211215.ListClusterNodes
HTTP/1.1 200 OK
content-length: 1051
content-type: application/json
date: Tue, 16 Nov 2021 08:36:25 GMT
server: Cowboy

[
    {
        "external_api_url": "http://192.0.2.5:3000/",
        "epoch": 1,
        "license_max_connections": 600,
        "license_max_nodes": 10,
        "license_serial_code": "ABCDEF-SRA-E001-203801-400",
        "license_type": "Experimental",
        "node_name": "node-01@192.0.2.5",
        "connected": true,
        "mode": "normal",
        "external_signaling_url": "wss://node-01.example.com/signaling",
        "version": "2022.1.0"
    },
    {
        "external_api_url": "http://192.0.2.7:3000/",
        "epoch": 1,
        "license_max_connections": 600,
        "license_max_nodes": 10,
        "license_serial_code": "ABCDEF-SRA-E002-203801-400",
        "license_type": "Experimental",
        "node_name": "node-02@192.0.2.7",
        "connected": true,
        "mode": "normal",
        "external_signaling_url": "wss://node-02.example.com/signaling",
        "version": "2022.1.0"
    },
    {
        "external_api_url": "http://192.0.2.8:3000/",
        "epoch": 1,
        "license_max_connections": 600,
        "license_max_nodes": 10,
        "license_serial_code": "ABCDEF-SRA-E003-203801-500",
        "license_type": "Experimental",
        "node_name": "node-03@192.0.2.8",
        "connected": true,
        "mode": "normal",
        "external_signaling_url": "wss://node-03.example.com/signaling",
        "version": "2022.1.0"
    }
]
```

### クラスターチャネル割り当て一覧 API

詳細は [ListClusterChannels](EXPERIMENTAL_API.html#0a4459) API をご確認ください。

```console
$ http POST 127.0.0.1:3000/ x-sora-target:Sora_20211215.ListClusterChannels
HTTP/1.1 200 OK
content-length: 646
content-type: application/json
date: Tue, 16 Nov 2021 08:42:25 GMT
server: Cowboy

[
    {
        "channel_id": "sora",
        "owners": [
           {
               "node_name": "node-01@192.0.2.5",
               "epoch": 1,
               "epoch_latest": true,
               "connected": true
           }
        ]
    },
    {
        "channel_id": "lemon",
        "owners": [
           {
               "node_name": "node-01@192.0.2.5",
               "epoch": 1,
               "epoch_latest": true,
               "connected": true
           }
        ]
    },
    {
        "channel_id": "hisui",
        "owners": [
           {
               "node_name": "node-03@192.0.2.5",
               "epoch": 2,
               "epoch_latest": true,
               "connected": true
           }
        ]
    },
    {
        "channel_id": "zakuro",
        "owners": [
           {
               "node_name": "node-03@192.0.2.5",
               "epoch": 2,
               "epoch_latest": true,
               "connected": true
           }
        ]
    }
]
```













## クラスター構築

> **重要**
>
> `node_name` はクラスター内のすべてのノードでユニークである必要があります

### ライセンス

Sora ノード数分のライセンスが必要になります。
Sora 3 台でクラスターを組む場合は通常のライセンスが 3 つ、もしくは 3 ノード以上に対応した
最大ノード数ライセンスが 1 つ必要です。


### ノードの未初期化、初期化済み状態

クラスターの場合、各ノードは「未初期化」または「初期化済み」の状態を持ちます。

`bin/sora daemon` や `bin/sora foreground` コマンドで最初に起動したとき、そのノードは「未初期化」の
状態で起動します。

この状態のノードが「初期化済み」の状態に遷移するのは以下のどれかのイベントが発生した場合です。

- [InitCluster](EXPERIMENTAL_API.html#621990) の `node_name_list` に指定され、クラスターの初期化が成功した場合- このとき、 `node_name_list` に指定された他のノードも同時に「初期化済み」に遷移します
- sora.conf の [contact_node_name_list](CLUSTER.html#a3d0fd) を利用した自動参加が成功した場合
- そのノードに [JoinCluster](EXPERIMENTAL_API.html#17f3f5) API が発行され、成功した場合

sora.conf の [contact_node_name_list](CLUSTER.html#a3d0fd) での参加も、[JoinCluster](EXPERIMENTAL_API.html#17f3f5) API での参加も、
相手が「初期化済み」である必要があります。
そのため、その前に一度だけ、 [InitCluster](EXPERIMENTAL_API.html#621990) API を実行し、「初期化済み」ノードを
作る必要があります。

一度「初期化済み」に遷移したノードは、その後も「初期化済み」の状態にとどまります。

「初期化済み」から「未初期化」に遷移する手順は通常の運用では発生しませんが、
`bin/sora stop` で停止して、 `data/` ディレクトリを削除した場合には「未初期化」状態になります。

> **注釈**
>
> Sora の新しいリリースバージョンに更新する場合、 tar.gz を展開した場合にも `data/` ディレクトリを
> 古いバージョンからコピーしない場合は「未初期化」状態から始まります。

### 3 台での構築

ここでは、3 ノードの Sora クラスターを構築する手順を示します。

クラスター特有ではない Sora の設定や起動方法については [チュートリアル](TUTORIAL.html#e103da) を参照してください。

> **注釈**
>
> クラスターを構築する際、クラスター間の通信に使うネットワークは、
> 可能な限りプライベートネットワークを利用してください。
>
> クラスタ間の通信に使う IP アドレスは `node_name` の @ 以下の IP アドレスまたはドメイン名で指定します。

以下の手順の前提として、3 台のサーバーがそれぞれ TCP/IP で通信できる状態になっていること、
そして Sora の tar.gz が展開されて `bin/sora` が実行可能になっていることが必要です。

1. クラスター間の通信に必要な TCP/IP のポートをすべて開放する- TCP/IP 4369- ポート番号の変更はできません、必ずこのポートを開放してください
   - TCP/IP 49010-49020- `sora.conf` にて変更可能ですがデフォルトをお勧めします
2. 最初のノード(sora1)の `sora.conf` の設定をクラスター対応にする```ini
   node_name = sora1@192.0.2.101
   cluster = true
   external_signaling_url = wss://sora-node1.example.com/signlaing
   external_api_url = https://sora-node1.example.com/api
   contact_node_name_list = sora1@192.0.2.101, sora2@192.168.0.102, sora3@192.168.0.103
   ```
3. 最初のノード(sora1)の Sora を起動する
4. 次のノード(sora2)の `sora.conf` の設定をクラスター対応にする```ini
   node_name = sora2@192.168.0.102
   cluster = true
   external_signaling_url = wss://sora-node2.example.com/signlaing
   external_api_url = https://sora-node2.example.com/api
   contact_node_name_list = sora1@192.0.2.101, sora2@192.168.0.102, sora3@192.168.0.103
   ```
5. 次のノード(sora2)の Sora を起動する
6. 最後のノード(sora3)の `sora.conf` の設定をクラスター対応にする```ini
   node_name = sora3@192.168.0.103
   cluster = true
   external_signaling_url = wss://sora-node3.example.com/signlaing
   external_api_url = https://sora-node3.example.com/api
   contact_node_name_list = sora1@192.0.2.101, sora2@192.168.0.102, sora3@192.168.0.103
   ```
7. 最後のノード(sora3)の Sora を起動する
8. 最初のノード(sora1)の Sora に [InitCluster](EXPERIMENTAL_API.html#621990) APIを実行し、3 ノードのクラスターとして初期化する```console
   $ http POST 127.0.0.1:3000/ x-sora-target:Sora_20221221.InitCluster \
       node_name_list:='["sora1@192.0.2.101", "sora2@192.168.0.102", "sora3@192.168.0.103"]'
   ```
9. 最初のノード(sora1)の Sora に [ListClusterNodes](EXPERIMENTAL_API.html#a70901) APIを実行し、ノード一覧を取得する```console
   $ http POST 127.0.0.1:3000/ x-sora-target:Sora_20211215.ListClusterNodes
   ```
   - 結果として、sora1 から sora3 の 3 ノードの一覧が取得できれば、クラスターの構築は完了です


## クラスター運用

### ノード数

Sora は 2 ノードからクラスターを構築できますが、 2 ノードの場合は可用性が 1 ノードの場合より低下するため、
3 ノード以上でのクラスター構築をお勧めしています。

### クラスターからの一時的な離脱

モード切り替え API を利用し、 [新規コネクションブロックモード](MODE.html#0b67d3) または [新規セッションブロックモード](MODE.html#041e1c) に
切り替えます。
その後、すべての接続がいなくなったタイミングで Sora ノードを終了してください。

一時的に離脱したノードは、再起動するとそのままクラスターに参加して動作します。

### 一時的に離脱したノードを含めるクラスターノード一覧

[ListClusterNodes](EXPERIMENTAL_API.html#a70901) API では現在クラスターに参加しているノード一覧が返ります。
結果に一時的に離脱したノードを含めるには、 `include_all_known_nodes` を `true` に指定し
API を実行すると、そのノードが知っているすべてのノードを返します。

### クラスターからノード情報の完全消去

> **重要**
>
> 恒久的にノードを破棄する場合には、必ず、この作業を行ってください。

特定のクラスターノードが障害が起きた後、復旧が難しい場合は [PurgeClusterNode](EXPERIMENTAL_API.html#13b35a) API を利用して、
クラスターに参加しているノードから、障害が起きたノードの情報を完全消去してください。

また、スケールインのためにノードを破棄する場合も同様に [PurgeClusterNode](EXPERIMENTAL_API.html#13b35a) API を
利用して、破棄したノードの情報を完全消去してください。

この作業はクラスターの新鮮さを保つために必要な作業となります。
この作業を行わないと存在していないノードが残り続けることで過半数かどうかの判断を誤る場合があります。

> **警告**
>
> PurgeClusterNode API はクラスターからノードを完全に消去するための API です。
> 近い将来に再び参加するノードに対しては基本的に使用しないでください。

### クラスターのアップデート

> **注釈**
>
> ローリングアップデートを推奨しています。

1. アップデート対象の Sora ノードを [ChangeMode](MODE.html#3af05f) API で [新規コネクションブロックモード](MODE.html#0b67d3)または [新規セッションブロックモード](MODE.html#041e1c) に切り替えます- 必要があれば [ListChannels](EXPERIMENTAL_API.html#b27c42) API と [DisconnectChannel](API.html#a87366) API を利用して既存接続を切断します
2. すべての接続がなくなったら Sora を停止させます
3. 新しいバージョンの Sora へ入れ替えます
4. 新しいバージョンの Sora を起動します
5. [JoinCluster](EXPERIMENTAL_API.html#17f3f5) API を叩いて起動ノードをクラスターに参加させます（ `sora.conf` で[contact_node_name_list](CLUSTER.html#a3d0fd) が指定されている場合にはこの手順は不要です）
6. [ListClusterNodes](EXPERIMENTAL_API.html#a70901) API で新しいバージョンの Sora がクラスターに参加できているかを確認します

> **警告**
>
> 自動でクラスターに参加する処理がエラーになる可能性もあるため、
> [ListClusterNodes](EXPERIMENTAL_API.html#a70901) API で新しいバージョンの Sora がクラスターに参加できているかを必ず確認してください。
> 確認せずに他ノードのローリングアップデートを行った場合、クラスターに参加できていないままのノードが出てしまう
> 可能性があります。
> さらに、複数のノードが参加できずに過半数を割ってしまうと、クラスターがまったく稼働しなくなる可能性もあります。

上の手順を 1 ノードごとに繰り返してください。


### クラスター破綻からの再構築手順

クラスターのうち、過半数のノードが停止した場合、クラスターのすべての Sora ノードは接続を受け付けなくなります。
この状態からでも、停止したノードを再開させ、過半数のノードが正常に戻ったタイミングで、接続を受け付け始めます。
その場合は、ここで書かれている手順は必要ありません。

ただし、停止したノードがディスク障害などで `data/` ディレクトリを失ってしまったときや、使っていた
IP アドレスが使えなくなってしまうなどの理由で、再開できないこともあります。

ここでは、3 ノードクラスターを組んでいたものの、うち 2 つのサーバーがディスク障害で
`data/` ディレクトリを失った状態からの再構築手順を示します。

> **注釈**
>
> `data/` ディレクトリを失ってしまうと、「未初期化」状態になるため、再度クラスターへの参加手順が必要です。

> **注釈**
>
> `node_name` に IP アドレスを使う場合、IP アドレスが変わると `node_name` も変える必要があります。
> その際は `data/` ディレクトリを削除する必要があります。

- まずすべての Sora プロセスを停止状態にします
- ディスク障害がおきたサーバーでは- Sora をインストールしなおします
  - 必要に応じて sora.conf 設定を行います
- ディスク障害がおきなかったサーバーでは- `data/` ディレクトリを削除します
  - これを忘れると正しい再構築ができません、必ず削除してください
- ここまでで、完全にまっさらの Sora クラスターを組む準備ができたことになります
- すべての Sora ノードを起動します
- [InitCluster](EXPERIMENTAL_API.html#621990) API を発行し、クラスターを初期化します

以上の手順で、あたらしい 3 ノードのクラスターが動き始めます。


### ノード名変更手順

sora.conf の `node_name` を変更する際の手順を示します。

この手順は、例えば Sora が動作するサーバーの IP アドレスが変わり、 `node_name` の IP アドレス部分を
変更する場合に必要になります。

- 対象の Sora ノードを停止します
- `data/` ディレクトリを削除します- これを忘れるとノード名変更が正しく実行できません、必ず削除してください
- クラスターの起動しているノードのいずれかに対し、 [PurgeClusterNode](EXPERIMENTAL_API.html#13b35a) API を発行し、対象のノードをクラスターから削除します
- sora.conf の `node_name` を変更します
- 対象の Sora ノードを起動します
- [JoinCluster](EXPERIMENTAL_API.html#17f3f5) API でクラスターにあらたに参加します- [contact_node_name_list](CLUSTER.html#a3d0fd) を利用している場合にはこの手順は必要ありません

> **警告**
>
> この手順では、 `data/` ディレクトリの削除を含むため、対象のノードは「未初期化」状態になります。
> 複数のノードの名前変更が必要な場合には、1 ノードずつこの手順を実行することを推奨します。
> 1 ノードの変更のたびに、 [ListClusterNodes](EXPERIMENTAL_API.html#a70901) を使ってクラスターに正しく
> 参加したことを確認して次のノードの手順に取り掛かってください。

#### ノード名変更をする場合の [contact_node_name_list](CLUSTER.html#a3d0fd) の扱い

ノード名変更をするときに、クラスター自動参加を利用する方法を示します。
クラスター自動参加を使わずに [JoinCluster](EXPERIMENTAL_API.html#17f3f5) API を使う場合は
この説明は関係しません。

関連する sora.conf の設定は [contact_node_name_list](CLUSTER.html#a3d0fd) です。
この設定は sora 起動時のときのみ利用されます。
また、一度クラスターに参加して初期化済みとなった場合には、正しいノードリストを知っているため、
この設定は無視されます。

以下、具体的に、3 ノードクラスターがあり、全ノードの IP アドレスが変わる状況を考えます。

状況:

- 現状のノード名: `sora1@192.0.2.101`, `sora2@192.168.0.102`, `sora3@192.168.0.103`- 既にクラスターを組んでいる
- それぞれを新規のノード名 `sora1@10.0.0.1`, `sora2@10.0.0.2`, `sora3@10.0.0.3` に変更する- まず sora1 を入れ替える
  - それが成功した後に sora2 を入れ替える
  - それが成功した後に sora3 を入れ替えと進める

現状のノードでは、既にクラスターを組んでいるため `concact_node_name_list` は利用されません。
そのため、sora.conf の設定を変更する必要はありません。

新しいノードでは、起動時に [contact_node_name_list](CLUSTER.html#a3d0fd) が利用されます。
入れ替えは順番に実行する必要があるため、現状のノードのみが起動中である状態や
新規ノードのみの状態、または混在している状態がありえます。

- 最初に sora1 を入れ替える際には、残り2ノードは現状のノード `sora2@192.168.0.102`, `sora3@192.168.0.103` である
- 途中で sora2 を入れ替える際には、残り2ノードは現状のノード `sora3@192.168.0.103` と新規`sora1@10.0.0.1` が混在している
- 最後に sora3 を入れ替える際には、残り2ノードは新規のノード `sora1@10.0.0.1`, `sora2@10.0.0.2` である

このパターンに対して、ノード個別に [contact_node_name_list](CLUSTER.html#a3d0fd) を設定するのは複雑になってしまいます。
しかし、 [contact_node_name_list](CLUSTER.html#a3d0fd) に指定したノードのうち、起動していないノードは無視されますので、
既存ノードと新規ノードをすべて `sora1@192.0.2.101`, `sora2@192.168.0.102`, `sora3@192.168.0.103`
`sora1@10.0.0.1`, `sora2@10.0.0.2`, `sora3@10.0.0.3` 指定することで、全ノードの入れ替えに対応できます。

> **注釈**
>
> 既存と新規の対応するノードが同時に起動することがないようにしてください。
> たとえば、 `sora1@10.0.0.1` を起動する前には、かならず `sora2@192.168.0.102` を停止した上で、
> `sora2@192.168.0.102` 再び起動することがないようにしてください。

## シーケンス図

### type: redirect

seqdiag {
    default_fontsize = 14;
    edge_length = 200;

    クライアント ->> Sora1 [label = '"type": "connect"'];
    クライアント <<- Sora1 [label = '"type": "redirect"', rightnote='sora2 のシグナリング URL を返す'];
    クライアント ->> Sora2 [label = '"type": "connect"'];
        Sora2 -> アプリケーションサーバー [label = '認証ウェブフック'];
        Sora2 <-- アプリケーションサーバー [label = '200 OK\n{"allowed": true}'];
    クライアント <<- Sora2 [label = '"type": "offer"'];
    クライアント ->> Sora2 [label = '"type": "answer"', noactivate];
    クライアント ->> Sora2 [label = '"type": "candidate"', noactivate];

    === WebRTC 確立 ===

        Sora2 -> アプリケーションサーバー [label = 'イベントウェブフック\n"type": "connection.created"'];
        Sora2 <-- アプリケーションサーバー [label = '200 OK'];
}
