# クラスター機能チュートリアル

## 概要

このチュートリアルでは、Sora のクラスター機能をひととおり使ってみるところまでを説明します。

## 前提

クラスター特有ではない Sora の設定や起動方法、シグナリングで使用する NGINX の設定等については [チュートリアル](TUTORIAL.html) をご確認ください。

### ライセンス

ノード数分のライセンスが必要になります。

Sora 3 台でクラスターを構築する場合は通常のライセンスが 3 つ、もしくは 3 ノード以上に対応した
[最大ノード数ライセンス](LICENSE.html#aee259) が 1 つ必要です。

> **注釈**
>
> クラスターを構築する場合はリレー機能やライセンスで決められた最大同時接続数の合計を維持する機能が利用できる [最大ノード数ライセンス](LICENSE.html#aee259) の利用をお勧めします。

### リレー機能

どのノードに繋いでも同一チャネルに接続できるようになるリレー機能を利用する場合は、
[最大ノード数ライセンス](LICENSE.html#aee259) を利用する必要があります。

[最大ノード数ライセンス](LICENSE.html#aee259) を利用している場合、リレー機能はデフォルトで有効です。

### アフィニティ機能

リレー機能を利用した際でも、できるだけ同一セッションは同一ノードへ接続することで、
ノード間の通信を最小限に抑えるアフィニティ機能を利用する場合は、
[最大ノード数ライセンス](LICENSE.html#aee259) を利用する必要があります。

[最大ノード数ライセンス](LICENSE.html#aee259) を利用している場合、アフィニティ機能はデフォルトで有効です。

### ライセンスで決められた最大同時接続数の合計を維持する機能

クラスターのノードで障害が発生した際、
他のノードが同時接続数を引き受けるライセンスで決められた最大同時接続数の合計を維持する機能は、
[最大ノード数ライセンス](LICENSE.html#aee259) を利用する必要があります。

[最大ノード数ライセンス](LICENSE.html#aee259) を利用している場合、ライセンスで決められた最大同時接続数の合計を維持する機能はデフォルトで有効です。

### テンポラリーノード機能

クラスター機能を利用した際に、クラスターの維持を目的とせず、
一時的なスケールアウトを目的にノードを追加するテンポラリーノード機能を利用する場合は、
[最大ノード数ライセンス](LICENSE.html#aee259) を利用する必要があります。


## クラスターの構築

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

それぞれのノードのノード名は **sora1@192.0.2.101**, **sora2@192.0.2.102**, **sora3@192.0.2.103** とします。

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

`external_api_url` は HTTP API を叩いたノードにはそのセッションが存在しない場合に、
別のノードに転送するために `location` ヘッダーに指定する URL です。
そのためプライベートなアドレスでも問題ありません。

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

ここでのクラスター構築では、3 台のサーバーがそれぞれ TCP/IP で通信できる状態になっていること、
そして Sora の tar.gz が展開されて `bin/sora` が実行できるようになっていることを前提としています。

1. クラスター間の通信に必要な TCP のポートをすべて開放します- TCP 4369- ポート番号の変更はできません、必ずこのポートを開放してください
   - TCP 49010-49020- `sora.conf` にて変更できますがデフォルトをお勧めします
2. 最初のノード(**sora1@192.0.2.101**)の `sora.conf` にクラスターの設定をします```ini
   node_name = sora1@192.0.2.101
   cluster = true
   external_signaling_url = wss://sora-node1.example.com/signaling
   external_api_url = http://192.0.2.101:3000/
   ```
3. 最初のノード(**sora1@192.0.2.101**)の Sora を `bin/sora daemon` で起動します
4. 次のノード(**sora2@192.0.2.102**)の `sora.conf` も最初のノードと同様にクラスターの設定をします```ini
   node_name = sora2@192.0.2.102
   cluster = true
   external_signaling_url = wss://sora-node2.example.com/signaling
   external_api_url = http://192.0.2.102:3000/
   ```
5. 次のノード(**sora2@192.0.2.102**)の Sora を `bin/sora daemon` で起動します
6. 最後のノード(**sora3@192.0.2.103**)の `sora.conf` にもクラスターの設定をします```ini
   node_name = sora3@192.0.2.103
   cluster = true
   external_signaling_url = wss://sora-node3.example.com/signaling
   external_api_url = http://192.0.2.103:3000/
   ```
7. 最後のノード(**sora3@192.0.2.103**)の Sora を `bin/sora daemon` で起動します
8. 最初のノード(**sora1@192.0.2.101**)の Sora に [InitCluster](API_CLUSTER.html#621990) API を実行し、3 ノードのクラスターとして初期化します```console
   $ curl -sS \
       -X POST \
       http://127.0.0.1:3001/ \
       -H "x-sora-target: Sora_20221221.InitCluster" \
       --json '{"node_name_list":["sora1@192.0.2.101","sora2@192.0.2.102","sora3@192.0.2.103"]}' \
       | jq .
   ```
9. 最初のノード(**sora1@192.0.2.101**)の Sora に [ListClusterNodes](API_CLUSTER.html#a70901) API を実行し、ノード一覧を取得します```console
   $ curl -sS \
       -X POST \
       http://127.0.0.1:3001/ \
       -H "x-sora-target: Sora_20211215.ListClusterNodes" \
       | jq .
   [
       {
           "connected": true,
           "external_api_url": "https://sora-node1.example.com/api",
           "external_signaling_url": "wss://sora-node1.example.com/signaling",
           "license_max_connections": 100,
           "license_max_nodes": 3,
           "license_serial_code": "DOC123-SRA-E002-201303-N3-100",
           "license_type": "Experimental",
           "mode": "normal",
           "node_name": "sora1@192.0.2.101",
           "temporary_node": false,
           "version": "2024.1.0"
       },
       {
           "connected": true,
           "external_api_url": "https://sora-node2.example.com/api",
           "external_signaling_url": "wss://sora-node2.example.com/signaling",
           "license_max_connections": 100,
           "license_max_nodes": 3,
           "license_serial_code": "DOC123-SRA-E002-201303-N3-100",
           "license_type": "Experimental",
           "mode": "normal",
           "node_name": "sora2@192.0.2.102",
           "temporary_node": false,
           "version": "2024.1.0"
       },
       {
           "connected": true,
           "external_api_url": "https://sora-node3.example.com/api",
           "external_signaling_url": "wss://sora-node3.example.com/signaling",
           "license_max_connections": 100,
           "license_max_nodes": 3,
           "license_serial_code": "DOC123-SRA-E002-201303-N3-100",
           "license_type": "Experimental",
           "mode": "normal",
           "node_name": "sora3@192.0.2.103",
           "temporary_node": false,
           "version": "2024.1.0"
       }
   ]
   ```

結果として、 **sora1@192.0.2.101** から **sora3@192.0.2.103** の 3 ノードの一覧が取得できれば、クラスターの構築は完了です。

### クラスター構築のトラブルシューティング

クラスター構築時に躓きやすいポイントと確認事項を示します。

- ノード間通信ができない場合- Sora はノード間通信を行うため、ファイアウォールの開放が必要です
  - [クラスター構成情報ファイル](CLUSTER_OPS.html#9bed3b) にあるとおりに、EPMD と sora の Listen ポートの開放を確認してください
- ライセンス違反のエラーが出る場合- [最大ノード数ライセンス](LICENSE.html#aee259) ではないライセンスを使っている場合には、すべてのノードで異なるライセンスが設定されている必要があります
  - [最大ノード数ライセンス](LICENSE.html#aee259) のライセンスを使っている場合には、指定されたノード数まで同じライセンスを利用できます
  - [最大ノード数ライセンス](LICENSE.html#aee259) とそうではないライセンスを混在させて利用することはできません
- [RegisterClusterNode](API_CLUSTER.html#09ed96) API 実行時に `CONTACT-NODE-NOT-INITIALIZED` エラーが出る場合- 未初期化のノードを `contact_node_name` に指定した場合に発生します
  - `contact_node_name` に指定するノードは [InitCluster](API_CLUSTER.html#621990) で初期化したノードか、[RegisterClusterNode](API_CLUSTER.html#09ed96) API でクラスターに参加したノードを指定する必要があります

## クラスター操作

上記のクラスターの構築の手順で、クラスターの構築と初期化が完了していることを前提とします。

### クラスターのノード一覧の取得

構築済みのクラスター内のノード名 **sora1@192.0.2.101**、 **sora2@192.0.2.102**、 **sora3@192.0.2.103** のいずれかで [ListClusterNodes](API_CLUSTER.html#a70901) API を実行して、
クラスター内のノード一覧を取得します。

```console
$ curl -sS \
    -X POST \
    http://127.0.0.1:3000/ \
    -H "x-sora-target: Sora_20211215.ListClusterNodes" \
    | jq .
[
    {
        "connected": true,
        "external_api_url": "https://sora-node1.example.com/api",
        "external_signaling_url": "wss://sora-node1.example.com/signaling",
        "license_max_connections": 100,
        "license_max_nodes": 3,
        "license_serial_code": "DOC123-SRA-E002-201303-N3-100",
        "license_type": "Experimental",
        "mode": "normal",
        "node_name": "sora1@192.0.2.101",
        "temporary_node": false,
        "version": "2024.1.0"
    },
    {
        "connected": true,
        "external_api_url": "https://sora-node2.example.com/api",
        "external_signaling_url": "wss://sora-node2.example.com/signaling",
        "license_max_connections": 100,
        "license_max_nodes": 3,
        "license_serial_code": "DOC123-SRA-E002-201303-N3-100",
        "license_type": "Experimental",
        "mode": "normal",
        "node_name": "sora2@192.0.2.102",
        "temporary_node": false,
        "version": "2024.1.0"
    },
    {
        "connected": true,
        "external_api_url": "https://sora-node3.example.com/api",
        "external_signaling_url": "wss://sora-node3.example.com/signaling",
        "license_max_connections": 100,
        "license_max_nodes": 3,
        "license_serial_code": "DOC123-SRA-E002-201303-N3-100",
        "license_type": "Experimental",
        "mode": "normal",
        "node_name": "sora3@192.0.2.103",
        "temporary_node": false,
        "version": "2024.1.0"
    }
]
```

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

接続済みのチャネルのノードへの割り当てを確認するために、[ListClusterChannels](API_CLUSTER.html#0a4459) API を実行します。

まず、構築済みのクラスターに複数のチャネルで接続しておきます。

次に、クラスター内のいずれかのノードで [ListClusterChannels](API_CLUSTER.html#0a4459) API を実行して、クラスターのチャネル割り当ての一覧を取得し、チャネルと割り当てられているノードを確認します。

```console
$ curl -sS \
    -X POST \
    http://127.0.0.1:3000/ \
    -H "x-sora-target: Sora_20211215.ListClusterChannels" \
    | jq .
[
    {
        "channel_id": "hisui",
        "owners": [
            {
                "connected": true,
                "node_name": "sora1@192.0.2.101"
            }
        ]
    },
    {
        "channel_id": "kohaku",
        "owners": [
            {
                "connected": true,
                "node_name": "sora2@192.0.2.102"
            }
        ]
    },
    {
        "channel_id": "sora",
        "owners": [
            {
                "connected": true,
                "node_name": "sora3@192.0.2.103"
            }
        ]
    },
    {
        "channel_id": "zakuro",
        "owners": [
            {
                "connected": true,
                "node_name": "sora3@192.0.2.103"
            }
        ]
    }
]
```

### クラスターからノードを消去する

構築済みのクラスター内からノードを消去してみます。

ここでは、ノード名 **sora1@192.0.2.101**、 **sora2@192.0.2.102**、 **sora3@192.0.2.103** の 3 ノードで構築されているクラスターから、
**sora3@192.0.2.103** のノードを消去します。

ノードの消去には [PurgeClusterNode](API_CLUSTER.html#13b35a) API を使用します。

初めに、 **sora3@192.0.2.103** の Sora を `bin/sora stop` で停止します。

次に、 **sora3@192.0.2.103** ノードの `data/` ディレクトリを削除します。

**sora1@192.0.2.101** のノードで [ListClusterNodes](API_CLUSTER.html#a70901) API を実行して、
**sora3@192.0.2.103** のノードについて `"connected": false` になっていることを確認します。

```console
$ curl -sS \
    -X POST \
    http://127.0.0.1:3000/ \
    -H "x-sora-target: Sora_20211215.ListClusterNodes" \
    | jq .
[
    {
        "connected": true,
        "external_api_url": "https://sora-node1.example.com/api",
        "external_signaling_url": "wss://sora-node1.example.com/signaling",
        "license_max_connections": 100,
        "license_max_nodes": 3,
        "license_serial_code": "DOC123-SRA-E002-201303-N3-100",
        "mode": "normal",
        "node_name": "sora1@192.0.2.101",
        "temporary_node": false,
        "version": "2024.1.0"
    },
    {
        "connected": true,
        "external_api_url": "https://sora-node2.example.com/api",
        "external_signaling_url": "wss://sora-node2.example.com/signaling",
        "license_max_connections": 100,
        "license_max_nodes": 3,
        "license_serial_code": "DOC123-SRA-E002-201303-N3-100",
        "license_type": "Experimental",
        "mode": "normal",
        "node_name": "sora2@192.0.2.102",
        "temporary_node": false,
        "version": "2024.1.0"
    },
    {
        "connected": false,
        "license_max_connections": 100,
        "license_max_nodes": 3,
        "license_serial_code": "DOC123-SRA-E002-201303-N3-100",
        "license_type": "Experimental",
        "node_name": "sora3@192.0.2.103",
        "temporary_node": false
    }
]
```

クラスターから **sora3@192.0.2.103** ノードの情報を消去するために、 **sora1@192.0.2.101** または **sora2@192.0.2.102** ノードのいずれかで [PurgeClusterNode](API_CLUSTER.html#13b35a) API を実行します。
この API を実行する際の `target_node_name` には消去するノード名を指定します。
今回指定するノード名は、 **sora3@192.0.2.103** です。

```console
$ curl -sS \
    -X POST \
    http://127.0.0.1:3000/ \
    -H "x-sora-target: Sora_20220629.PurgeClusterNode" \
    --json '{"target_node_name":"sora3@192.0.2.103"}' \
    | jq .
{
    "target_node_name": "sora3@192.0.2.103"
}
```

再度 **sora1@192.0.2.101** のノードで [ListClusterNodes](API_CLUSTER.html#a70901) API を実行して、
クラスターのノードから **sora3@192.0.2.103** のノードが見えなくなっていることを確認します。

```console
$ curl -sS \
    -X POST \
    http://127.0.0.1:3000/ \
    -H "x-sora-target: Sora_20211215.ListClusterNodes" \
    | jq .
[
    {
        "connected": true,
        "external_api_url": "https://sora-node1.example.com/api",
        "external_signaling_url": "wss://sora-node1.example.com/signaling",
        "license_max_connections": 100,
        "license_max_nodes": 3,
        "license_serial_code": "DOC123-SRA-E002-201303-N3-100",
        "mode": "normal",
        "node_name": "sora1@192.0.2.101",
        "temporary_node": false,
        "version": "2024.1.0"
    },
    {
        "connected": true,
        "external_api_url": "https://sora-node2.example.com/api",
        "external_signaling_url": "wss://sora-node2.example.com/signaling",
        "license_max_connections": 100,
        "license_max_nodes": 3,
        "license_serial_code": "DOC123-SRA-E002-201303-N3-100",
        "license_type": "Experimental",
        "mode": "normal",
        "node_name": "sora2@192.0.2.102",
        "temporary_node": false,
        "version": "2024.1.0"
    }
]
```

### クラスターにノードを追加する

構築済みのクラスターにノードを追加してみます。

ノード名 **sora1@192.0.2.101**、 **sora2@192.0.2.102** の 2 ノードで構築されているクラスターへ、
API を使用して **sora4@192.0.2.104** ノードを追加します。

ノードの追加には [RegisterClusterNode](API_CLUSTER.html#09ed96) API を使用します。

クラスターに **sora4@192.0.2.104** ノードを追加するために、 **sora4@192.0.2.104** ノードで [RegisterClusterNode](API_CLUSTER.html#09ed96) API を実行します。
この API を実行する際の `contact_node_name` には既存のクラスターのノード名を指定します。

今回指定するノード名は、 **sora1@192.0.2.101** または **sora2@192.0.2.102** のいずれかです。

```console
$ curl -sS \
    -X POST \
    http://127.0.0.1:3000/ \
    -H "x-sora-target: Sora_20211215.RegisterClusterNode" \
    --json '{"contact_node_name":"sora1@192.0.2.101"}' \
    | jq .
{
    "node_name_list": [
        "sora1@192.0.2.101",
        "sora2@192.0.2.102",
        "sora4@192.0.2.104"
    ]
}
```

[ListClusterNodes](API_CLUSTER.html#a70901) API を使用して、クラスターに追加されていることを確認します。

```console
$ curl -sS \
    -X POST \
    http://127.0.0.1:3000/ \
    -H "x-sora-target: Sora_20211215.ListClusterNodes" \
    | jq .
[
    {
        "connected": true,
        "external_api_url": "https://sora-node1.example.com/api",
        "external_signaling_url": "wss://sora-node1.example.com/signaling",
        "license_max_connections": 100,
        "license_max_nodes": 3,
        "license_serial_code": "DOC123-SRA-E002-201303-N3-100",
        "license_type": "Experimental",
        "mode": "normal",
        "node_name": "sora1@192.0.2.101",
        "temporary_node": false,
        "version": "2024.1.0"
    },
    {
        "connected": true,
        "external_api_url": "https://sora-node2.example.com/api",
        "external_signaling_url": "wss://sora-node2.example.com/signaling",
        "license_max_connections": 100,
        "license_max_nodes": 3,
        "license_serial_code": "DOC123-SRA-E002-201303-N3-100",
        "license_type": "Experimental",
        "mode": "normal",
        "node_name": "sora2@192.0.2.102",
        "temporary_node": false,
        "version": "2024.1.0"
    },
    {
        "connected": true,
        "external_api_url": "https://sora-node4.example.com/api",
        "external_signaling_url": "wss://sora-node4.example.com/signaling",
        "license_max_connections": 100,
        "license_max_nodes": 3,
        "license_serial_code": "DOC123-SRA-E002-201303-N3-100",
        "license_type": "Experimental",
        "mode": "normal",
        "node_name": "sora4@192.0.2.104",
        "temporary_node": false,
        "version": "2024.1.0"
    }
]
```


