# クラスター機能

> **注意**
>
> この機能を利用する場合は事前にサポートまでご連絡ください

> **警告**
>
> この機能は [実験的機能](EXPERIMENTAL.html) のため、正式版では仕様が変更される可能性があります

## 概要

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



## 目的

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

## 用語

**ノード**
: クラスターに参加している Sora です



## 特徴

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

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

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

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

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

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



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

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

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

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

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

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

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

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


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

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

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

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

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

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

クラスターを手動で構築する機能です。既存のクラスターに参加する際に、クラスターに参加させるノードで [JoinCluster](EXPERIMENTAL_API_CLUSTER.html#17f3f5) API を実行します。

すでクラスターに参加しているいずれかのノードの名前を `contact_node_name` に指定してください。
クラスターに参加していて、初期化済みであれば、どのノードでもかまいません。

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

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

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

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

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

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

### 録画情報の共有

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












































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

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

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

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

たとえば、5 ノードのクラスターが 3 ノードからなるグループ A と
残りの 2 ノードからなるグループ B の 2 グループに分断されるということが起こりえます。
それぞれのグループ内では通信ができるものの、他グループへの通信ができないという状態です。

この状態ですべてのノードが接続を受け付けると、同じチャネル ID を指定していても、
あるクライアントはグループ A に繋がり、別のクライアントは グルーブ B につながる可能性があり、
それぞれのクライアント間で音声と映像が届かなくなります。

このような状況を防止するため、Sora は自分が半数以下のグループに属した場合に、新規接続の受け付けを停止します。
また、ノード数が半数以下のクラスターに参加しているノードは、接続中のすべての接続を切断し、その後は接続を受け入れません。

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

#### 全ノードが半数以下のグループに所属した場合の挙動

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

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

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

#### 録画情報の共有

録画の情報はクラスター内部で共有されます。
たとえばレガシー録画の [StartRecording](API_LEGACY_RECORDING.html#c5b527) API をあるノードまたに対して実行した後にそのノードが停止しても
録画情報は他のノードに残り続けます。
また指定したチャネル ID に接続がない状態であれば、 [StopRecording](API_RECORDING.html#fd0de5) API はどのノードに対しても実行できます。

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

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

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

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

1. [レガシー録画機能](LEGACY_RECORDING.html) でチャネル ID を指定して録画開始
2. ノード A に指定したチャネル ID の接続が来てしばらくして切断、録画ファイルがノード A に作られる
3. ノード A が停止
4. ノード B に指定したチャネル ID に接続が来てしばらくして切断、録画ファイルがノード B に作られる

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

あるチャネルに対して、 [StopRecording](API_LEGACY_RECORDING.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_LEGACY_RECORDING.html#fd0de5) API を
実行すると `report-<recording_id>.json` ファイルが出力されます。

#### `report-<recording_id>.json` ファイルの archives の内容と実際に出力される録画ファイルが一致しない場合

録画中のノードがクラスターから分離された場合に `report-<recording_id>.json` ファイルの `archives` に全ての録画ファイルが含まれないケースがあります。

録画中のノードがクラスターから分離された場合は別のノードが `report-<recording_id>.json` ファイルを出力しますが、クラスター分離前に共有された録画情報の内容で出力し、
分離直後に作成された録画ファイルは含まれないためです。

## 設定

### cluster

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

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

> **重要**
>
> cluster = true にして Sora を起動した場合、
> Sora は [InitCluster](EXPERIMENTAL_API_CLUSTER.html#621990) API を実行するか、クラスターに参加して過半数のグループになったタイミングでのみ利用可能になります。

```ini
cluster = true
```

### node_name

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

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

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

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

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

```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](SORA_CONF.html#a3d0fd) が指定されていた場合、
起動時に自分以外のノード名に対して自動でクラスターの参加を試みます。

> **注釈**
>
> 自分自身のノード名が `contact_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
```

例えば、上記の設定の場合は、シグナリングのリダイレクトが必要な際に Sora から `{"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/api
```

### cluster_listen_{min,max}_port

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

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

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


## API

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

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

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

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

API の詳細は [InitCluster](EXPERIMENTAL_API_CLUSTER.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@192.0.2.8", "node-01@192.0.2.5"]' \
    -vvv
POST / HTTP/1.1
Accept: application/json, */*;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 83
Content-Type: application/json
Host: 127.0.0.1:3000
User-Agent: HTTPie/3.2.2
x-sora-target: Sora_20221221.InitCluster

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


HTTP/1.1 200 OK
access-control-allow-headers: Origin, X-Requested-With, Content-Type, Accept, x-sora-target
access-control-allow-methods: POST, OPTIONS
access-control-allow-origin: *
access-control-max-age: 1000
content-length: 80
content-type: application/json
date: Wed, 06 Dec 2023 06:52:47 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_CLUSTER.html#17f3f5) API をご確認ください。

下記は、これから参加する既存クラスター内の node-01@192.0.2.5 ノードを contact_node_name　に指定して、新規に参加させるノードで [JoinCluster](EXPERIMENTAL_API_CLUSTER.html#17f3f5) APIを実行しています。

```console
$ http POST 127.0.0.1:3000/ x-sora-target:Sora_20211215.JoinCluster \
    contact_node_name=node-01@192.0.2.5
HTTP/1.1 200 OK
content-length: 80
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

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

`20220629.PurgeClusterNode` API は、クラスターに参加している破棄するノード以外のどのノードでもかまわないので 1 回だけ実行します。
この API で完全消去した結果はクラスター内部で共有されます。

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

```console
$ http POST 127.0.0.1:3000/ x-sora-target:Sora_20220629.PurgeClusterNode \
    target_node_name=node-01@192.0.2.5
HTTP/1.1 200 OK
access-control-allow-headers: Origin, X-Requested-With, Content-Type, Accept, x-sora-target
access-control-allow-methods: POST, OPTIONS
access-control-allow-origin: *
access-control-max-age: 1000
content-length: 40
content-type: application/json
date: Wed, 06 Dec 2023 07:16:06 GMT
server: Cowboy

{
    "target_node_name": "node-01@192.0.2.5"
}
```

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

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

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

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

[
    {
        "external_api_url": "http://192.0.2.5:3000/",
        "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": "2023.2.0"
    },
    {
        "external_api_url": "http://192.0.2.7:3000/",
        "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": "2023.2.0"
    },
    {
        "external_api_url": "http://192.0.2.8:3000/",
        "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": "2023.2.0"
    }
]
```

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

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

```console
$ http POST 127.0.0.1:3000/ x-sora-target:Sora_20211215.ListClusterChannels
HTTP/1.1 200 OK
content-length: 461
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",
               "connected": true
           }
        ]
    },
    {
        "channel_id": "lemon",
        "owners": [
           {
               "node_name": "node-01@192.0.2.5",
               "connected": true
           }
        ]
    },
    {
        "channel_id": "hisui",
        "owners": [
           {
               "node_name": "node-03@192.0.2.5",
               "connected": true
           }
        ]
    },
    {
        "channel_id": "zakuro",
        "owners": [
           {
               "node_name": "node-03@192.0.2.5",
               "connected": true
           }
        ]
    }
]
```













## クラスター運用

### 推奨ノード数

Sora のクラスター構築ノード数は 3 ノードが推奨となっております。

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

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

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

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

[ListClusterNodes](EXPERIMENTAL_API_CLUSTER.html#a70901) API では現在クラスターに参加しているノードの一覧が返ります。

> **注釈**
>
> 結果に一時的に離脱したノードを含めない場合には、 `include_all_known_nodes` に `false` を指定して API を実行します。

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

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

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

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

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

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


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

> **重要**
>
> ローリングアップデートを行ってください。
> ここでのローリングアップデートとは 1 台ずつノードのアップデートを行っていく事です。
> 1 台のアップデートが終わり次第、次のアップデートを行ってください。
> 同時に複数のノードのアップデートは行わないでください。

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

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

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

### Sora のバージョンダウン

[クラスターのアップデート](CLUSTER.html#c99fc7) の仕組みを使って、Sora のバージョンを下げることはできません。

そのため、クラスターのアップデート後に何らかの問題が発生し、
以前のバージョンに戻したい場合には、クラスターの再構築を行う必要があります。

再構築の際には、事前に全ノードを停止し、 `data/` ディレクトリを削除した上で、
以前のバージョンの Sora を使用してクラスターの構築を行ってください。


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

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

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

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

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

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

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

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


### ノード名変更手順

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

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

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

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

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

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

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

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

状況:

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

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

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

- 最初に sora1 を入れ替える際には、残り2ノードは現状のノード `sora2@192.0.2.102`, `sora3@192.0.2.103` です
- 途中で sora2 を入れ替える際には、残り2ノードは現状のノード `sora3@192.0.2.103` と新規`sora1@198.51.100.1` が混在しています
- 最後に sora3 を入れ替える際には、残り2ノードは新規のノード `sora1@198.51.100.1`, `sora2@198.51.100.2` です

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

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

### ロードバランサーの導入

クラスター機能を利用した場合は、シグナリング URL に対してロードバランサーが利用できます。

ただし、ロードバランス自体は Sora が行うため、
ロードバランサーの導入はシグナリング URL の 1 本化が目的となります。

- ロードバランサーは WebSocket に対応している必要があります
- ロードバランサーで TLS 終端はせず TLS で Sora へ接続する必要があります
- Sora はロードバランサー経由以外にクライアントと直接接続できる必要があります- これはリダイレクトが発生し、ロードバランサーを経由せずに接続する可能性があるためです
- Sora の WebSocket 通信は 30 秒に 1 回通信を発生させるため、ロードバランサーのタイムアウトは 30 秒以上にする必要があります

```mermaid
sequenceDiagram
    participant C as クライアント
    participant LB as ロードバランサー
    participant S1 as Sora1
    participant S2 as Sora2
    participant A as アプリケーションサーバー
    C->>LB: wss://sora.example.com/signaling
    LB->>S1: wss://0001.sora.example.com/signaling
    note over B,S1: WebSocket 確立
    C->>+LB: "type": "connect"
    LB->>+S1: "type": "connect"
    S1-->>-LB: "type": "redirect"<br>"location": "wss://0002.sora.example.com/signaling"
    LB->>-C: "type": "redirect"<br>"location": "wss://0002.sora.example.com/signaling"
    note over B,S1: WebSocket 切断
    C->>S2: wss://0002.sora.example.com/signaling
    note over B,S1: WebSocket 確立
    C->>+S2: "type": "connect"
    S2->>+A: 認証ウェブフック
    A-->>-S2: "allowed": true
    S2->>-C: "type": "offer"
    C->>S2: "type": "answer"
    note over B,S2: WebRTC 確立
```

もしシグナリング URL の 1 本化を検討している方で、不明点がある場合はサポートまでご連絡ください。

### ログの出力

クラスターに関するログは `log/cluster.jsonl` に生成されます。

ログの詳細については [ログファイル](LOG.html) をご確認ください。

## 注意

### ライセンス

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

> **注釈**
>
> [最大ノード数ライセンス](LICENSE.html#aee259) を利用することで、同一ライセンスを複数ノードで利用できるようになります。
> クラスターを利用する場合は [最大ノード数ライセンス](LICENSE.html#aee259) を利用することを推奨します。


### ポートの開放

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

- epmd（Erlang Port Mapper Daemon）で利用する TCP ポート- 4369
- ノード間通信に利用する TCP ポート- 49010 - 49020
  - `sora.conf` の `cluster_listen_{min,max}_port` にて指定できます

### ノード間通信の暗号化

Sora のノード間通信は暗号化されておりません。
そのため、安全なプライベートネットワークの利用を推奨します。

もし、パブリックネットワークを利用してクラスターを構築する場合は、ノード間の通信を暗号化する必要があります。その場合は Tailscale の利用をお勧めします。

[Tailscale · Best VPN Service for Secure Networks](https://tailscale.com/)

参考までに、下記は弊社の Tailscale 利用事例です。

[ベアメタルサーバーを利用したクラウドサービスで発生する課題を Tailscale で解決する · Tailscale](https://tailscale.com/ja/customers/shiguredo/)

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

#### 最低ノード数

Sora は最低 3 ノードのクラスターを構築してください。

- 1 ノードで構築されたクラスターはクラスターを構築していないのと同様です
- 2 ノードで構築されたクラスターは片方のノードが停止すると、正常なノードも過半数の条件を満たさなくなるため接続を受け付けなくなってしまいます

#### 最大ノード数

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

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

> **危険**
>
> このファイルは Sora が内部で利用するためのファイルのため、指示がある場合以外は触らないでください。

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

#### Sora のバージョンアップ時

Sora のバージョンアップ時に `data/` ディレクトリをコピーする必要はありません。
このファイルが無い場合、 Sora の起動の際に生成されます。

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

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

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

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

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

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

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

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

この状態になったノードは新規ノードですので、 [JoinCluster](EXPERIMENTAL_API_CLUSTER.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/` ディレクトリ内のデータ

そのため、再利用する場合には下記の手順で、クラスター A に関する情報を無くす必要があります。

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

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

上記の手順の後は Sora を起動し、クラスター参加の手順に従って、ノードをクラスターに参加させます。

## シーケンス図

### Sora から別ノードへのリダイレクトを要求される

```mermaid
sequenceDiagram
    participant C as クライアント
    participant S1 as Sora1
    participant S2 as Sora2
    participant S3 as Sora3
    participant A as アプリケーションサーバー
    C->>S1: "type": "connect"
    note over S1: このノードは同時接続が多かったので、<br>Sora2 へリダイレクト提案
    S1->>C: "type": "redirect"
    C->>S2: "type": "connect", "redirect": true
    S2->>+A: 認証ウェブフック
    A-->>-S2: 200 OK<br />{"allowed": true}
    S2->>C: "type": "offer"
    C->>S2: "type": "answer"
    note over C, A: WebRTC 確立
    S2->>+A: イベントウェブフック<br />"type": "connection.created"
    A-->>-S2: 200 OK
```



































