運用と安全
SENCA のノードを本番で動かすときに、何を監視し、止めるときや更新するときに何をすればよいかをまとめたページです。
このページの要点:
- 死活監視は
/healthz(動いているか)と/readyz(受け付けられるか)で行います。クラスターでは、過半数と通信できないノードは/readyzが 503 になります。 - 強制終了や停電の後は、起動時に録画を自動で復旧します(書きかけの録画は
interruptedとして閉じ、それまでの分は再生できます)。 - バックアップは
control/と、組にしたrecordings/・segments/が中心です。秘密情報を含むので保護してください。 - 保守の前には、ドレインで新しい処理を他のノードへ移します。メトリクスのエンドポイントは今のところ無いため、監視用の API を定期的に取得して監視します。
起動と終了コード
| 終了コード | 意味 |
|---|---|
| 0 | 正常終了(SIGTERM・SIGINT を受けた後) |
| 1 | 起動中に失敗した(ログに fatal: ...。ファイルが読めない、ポートで待ち受けできない、コントロールプレーンのログが途中で壊れている、クラスターシークレットが短いなど) |
| 2 | 設定に誤りがある(未知のオプション、検査に通らない値) |
| 70 | クラスターで Raft のデータを保存できなかった(書き込めない状態のまま動き続けることはしません) |
ヘルスチェック
| パス | 認証 | 応答 |
|---|---|---|
GET /healthz | 不要 | HTTP で応答できれば、常に 200 ok を返します |
GET /readyz | 不要 | リクエストを受け付ける準備ができていれば 200 ready、そうでなければ 503 を返します |
単一ノードは、起動時の処理(コントロールプレーンのデータの読み込み、録画の復旧、待ち受けの開始)が終わると準備完了(ready)になり、その後は終了するまで 200 のままです。
終了のシグナルを受けると 503 に変わります。ドレイン(下の「ノードのドレイン」)は /readyz の結果に影響しません。
クラスターのノードが準備完了になるのは、リーダーが決まり、自ノードの登録(最初のノードなら初期化も)が合意された後です。起動直後も、リーダーと通信できるまでは 503 です。
クラスターでは、過半数のノードとの通信が途絶えたときにも 503 になります。 リーダーなら過半数の投票者からの応答、リーダー以外のノード(フォロワー)ならリーダーからのメッセージが、
最後に届いてから 4 秒を超えると、そのノードは自分に割り当てられたワークロード(pull の入力・push の出力・パイプライン)を停止します。
これをフェンシングと呼び、ログには grid: fenced と出ます。この間は /readyz も書き込みの API も 503 を返します。
通信が戻れば 200 に戻り、割り当てられたワークロードを再開します(ログに quorum contact restored)。
curl の入っていないコンテナイメージでも使えるように、ヘルスチェック用のコマンドを用意しています。
senca-node --healthcheck http://127.0.0.1:8080/readyz
--healthcheck は最初の引数に書き、他のオプションとは組み合わせられません。URL を省略すると http://127.0.0.1:8080/readyz を確認します。
使えるのは平文の http:// だけです。終了コードは、HTTP/1.1 200 が返れば 0、それ以外は 1、URL の形式が誤っていれば 2 です。
Docker イメージの HEALTHCHECK は、このコマンドを 10 秒ごとに実行しています。
ログ
ノードのログ
ログは既定で、標準エラー出力に「RFC 3339 の時刻、空白、本文」の形で出ます。
ログ収集の仕組みに渡すなら --log-json を指定してください。標準出力に 1 行 1 JSON({"ts": "...", "node": "<ノード ID>", "msg": "..."})で出るようになります。
{"msg":"senca-node 0.0.0 started (node f1be5f27675d35f8dd72b88676be8754)","node":"f1be5f27675d35f8dd72b88676be8754","ts":"2026-11-03T01:11:38.915Z"}
--log-json のときは、ノードのログがすべて JSON になります。最初の管理者トークンの作成、
codec worker unavailable: ...、fatal: ...(起動の失敗。ノード ID が決まる前は node が null)、設定の誤りも例外ではありません。
コーデックワーカーとキャプチャワーカー(デバイスからのキャプチャを担当する別のプロセス)の標準エラー出力も、ノードが 1 行ずつ読み取って msg に入れます(1 行 4096 バイトまで)。
--log-json を指定しなければ、ワーカーの出力は標準エラー出力にそのまま出ます。
ログに出る主な内容は次のとおりです。ストリームキーやトークンの値は出しません。
- 待ち受けの開始、配信者の接続(
publisher attached)と切断 - 録画の失敗、復旧の処理(
recovery: ...)、保持期間による削除(retention: deleting recording ...) - 認証の失敗の理由と request_id
- 入力・出力の再接続時のエラー
- クラスターとグリッドの出来事(
grid: node ... is downなど)
監査ログ
data_dir/audit.log に、状態を変更する API の操作を 1 行 1 JSON で追記します(形式は セキュリティ の「監査ログ」)。
ノード自身はローテーションしないので、ファイルの大きさは運用側で管理してください。
ノードは書き込むたびにファイルを開き直すため、mv で切り替えるだけでローテーションできます(logrotate なら create 方式)。
書き込めなかったときはノードのログに audit log write failed: ... と出ますが、API の操作自体は失敗しません。
監視用の API
capabilities 以外は node:admin の権限が必要です。capabilities は、認証さえされていれば権限を問わず使えます。
| パス | 内容 |
|---|---|
GET /api/v1/nodes | 登録されたノードの一覧です。id・name・draining・registered_at・labels・live_streams(応答したノード自身の分だけを数えます)・self・relay(自ノードの中継の状態)が入ります |
GET /api/v1/nodes/{id} | 1 つのノードの情報です |
GET /api/v1/grid | グリッドの状態です。scheduler(配置を決めているノード。リーダーに問い合わせたときだけ入ります)と、ノードごとの up・draining・roles・labels・capacity・reserved・gpus(セッション数と予約)・capture・usage(ハートビートで報告された実際の使用量)・last_heartbeat・workloads が入ります。ワークロードごとには state(running・degraded・pending・unschedulable)・node・device・level・reason が入ります |
GET /api/v1/devices | このノードで使えるコーデックの実装(使えるかどうかと、その理由)、サンドボックスの状態、キャプチャデバイスです |
GET /api/v1/capabilities | このノードが対応する受信方式・出力・コーデック(codecs.transcode は読み込めたもの)・機能の一覧です |
ストリームごとの状態は、GET /api/v1/streams/{id} の observed_state(live・waiting_input・stopped)と live
(開始時刻・ビットレート・トラック)で確認します。入力・出力の状態は、status(state・last_error・接続回数)で確認します。
Prometheus などで使うメトリクスのエンドポイントは、今のところありません。監視するときは、上の API を定期的に取得してください。
イベント(SSE)による自動化
GET /api/v1/events(stream:read)は、合意されて適用された変更を Server-Sent Events(サーバーから送り続ける HTTP の通知)で配信します。
curl -sN -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8080/api/v1/events
event: node.updated
id: 10
data: {"data":{"draining":true},"occurred_at":"2026-11-03T01:12:07.603Z","resource_id":"f1be5f27675d35f8dd72b88676be8754","type":"node.updated"}
自動化の基本は、接続したときと resync を受けたときに REST API で一覧を取り直し、それ以降の変化をイベントで追うことです。
届くのは自分のテナントのイベントだけです。ノード・デバイス・グリッドのイベント(node.*・device.*・grid.decisions・stream.owner)は、node:admin を持つ場合だけ届きます。
接続を保つため、15 秒ごとに空のメッセージ(keep-alive)が送られます。
id は、変更の記録(制御ログ)の中での位置です。Last-Event-ID を付けて再接続すると、ノードが残している直近のイベント(既定 4096 件、--events-retain)から続きが届きます。
それより古い位置を指定すると resync が届きます。id はクラスターのどのノードでも同じなので、別のノードへ再接続しても続きから受け取れます。
受信側の読み取りが遅れてイベントを取りこぼし、ノードが残している分にも無くなったときは、resync を送って接続を閉じます。
ルームのイベント(room.started・room.connection.created など)は id を持たないので、取りこぼしても再送されません。
イベントの種類の一覧は 管理 API と SDK にあります。
障害からの復旧
ノードは起動のたびに、録画の復旧処理を行います。
- 削除の途中だった録画の削除を完了します。
recordings/*/index.jsonlを読みます。end行が無い(書き込み途中の)インデックスは、末尾の書きかけの行を切り捨て、end行(state: "interrupted"、終了時刻は最後のセグメントの終わり)を追記して閉じます。コントロールプレーン上の録画の記録もinterruptedにします。ログにはrecovery: recording <ID> closed as interrupted (<N> segments)と出力されます。- それでもコントロールプレーンで
recordingのままになっている、このノードの録画(インデックスが無いものなど)をinterruptedにします。 - どのインデックスからも参照されていないセグメントを
segments/{stream}/orphans/{run}/へ移します(削除はしません)。 読めないインデックスが 1 つでもあると、この隔離は行いません(参照を失わないためです)。読めないインデックス自体には手を加えません。 --verify-recordingsを指定すると、インデックスにあるすべてのセグメントの SHA-256 を照合し、一致しないものをログに出力します。
たとえば録画中に SIGKILL で止めて起動し直すと、録画は interrupted になります。それでも、それまでのセグメントは残り、VOD として再生・エクスポートできます。
SIGTERM で止めた場合は、録画は finalized で正しく閉じられます。
コントロールプレーンのデータ(単一ノードの control/log)は、末尾の書きかけのレコードを切り捨てて読み込みます。ただし途中のレコードが壊れていたら、黙って捨てずに起動を止めます。
バックアップ
| 対象 | 重要度 | 内容 |
|---|---|---|
control/(単一ノード: log・snapshot.json) | 必須 | コントロールプレーンのデータすべて(ストリーム・トークン・録画の記録・予約・入力・出力・シークレット・再生用の鍵) |
control/secret-key | 必須(単一ノード) | シークレットを復号する鍵。失うと、登録したシークレットは復元できません。クラスターでは、シークレットをクラスター共通の鍵(クラスターシークレットで暗号化してコントロールプレーンで複製)で暗号化します。そのため、クラスターシークレット(secret_file)を別に安全に保管してください(セキュリティ) |
recordings/ と segments/ | 録画を守るなら必須 | インデックスとセグメントは組にしてバックアップします(インデックスがセグメントを参照するためです) |
snapshots/ | 任意 | 定期的に保存した静止画 |
node-id | 推奨 | ノードの ID。変わると別のノードとして登録されます |
audit.log | 運用方針しだい | 監査ログ |
バックアップは、ノードを停止してからコピーするか、ファイルシステムのスナップショット(LVM・ZFS・btrfs など)で取ってください。
動いているノードの control/ をそのままコピーすると、スナップショットの作成(snapshot.json を書き出してログを空にする処理)と重なって、変更を取りこぼすことがあります。
復元するときは、ノードを停止し、データディレクトリをまとめて戻してから起動します。書き込み途中だった録画は、起動時の復旧処理で閉じられます。
バックアップには、再生用の鍵・暗号化したシークレット・トークンのハッシュが入っています。データディレクトリと同じように守ってください (セキュリティ の「データディレクトリの保護」)。
クラスターでは、コントロールプレーンのデータは全ノードに複製されていますが、録画は録画したノードのディスクにしかありません。録画のバックアップは各ノードで取ってください。
また、動いているクラスターの 1 台に、Raft の保存データ(control/raft/)を古いバックアップから戻してはいけません。
「投票者は保存した状態を失わない」という Raft の前提が崩れるためです。クラスターの構成は クラスターとグリッド を見てください。
バージョンアップ
今のところ、バージョン間でデータの互換性は保証していません。更新の前に、次の点を確認してください。
- コントロールプレーンのログは、変更の内容を
opという名前で表した JSON です。コントロールプレーンの項目の多くは、省略されていれば既定値で読み込みます。 ただし、古いバージョンは未知のopを読めず、起動しません(control log record N: ...)。ダウングレードはできないものと考えてください。 - 録画のインデックスは、
begin行にschema(現在は 1)を持ちます。未知のschemaのインデックスには、復旧処理で手を加えません(left untouched)。 その場合は、どこからも参照されていないセグメントの隔離も行いません。 - 実行ファイルの
senca-nodeとsenca-codec-workerは、バージョン付きのプロトコルで通信します。この 2 つは必ず同じバージョンにそろえて入れ替えてください。 - クラスターで、バージョンの異なるノードを混在させる運用には対応していません。
単一ノードでは、次の手順で更新します。
- バックアップを取ります(上の「バックアップ」)。万一のときに元に戻せるようにするためです。
- ノードを SIGTERM で停止します。録画は
finalizedで正しく閉じられます。 - 実行ファイルの
senca-node・senca-codec-worker(使っていればsenca-capture-workerも)を新しいものに入れ替えます。 - 起動し、
/readyzとログ(recovery: ...・fatal: ...)を見て、正常に動いていることを確かめます。
ノードのドレイン
ドレインは、ノードを保守などで止める前に、新しい処理を受け付けないようにして、処理を他のノードへ移す操作です。
curl -s -X POST -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8080/api/v1/nodes/$NODE/drain # 202
curl -s -X POST -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8080/api/v1/nodes/$NODE/undrain # 202
ドレインには node:admin の権限が必要です。実行するとノードの draining が true になり、node.updated イベントが送られます。
以後グリッドは、このノードに新しいワークロード(pull の入力・push の出力・パイプライン・ルーム・メディアパイプライン)を配置せず、
配置済みのもののうち移せるものを他のノードへ移します(クラスターとグリッド)。
ドレイン中のノードは、新しい配信者の接続を拒否します。 RTMP は NetStream.Publish.Rejected(説明は this node is draining; publish to another node)、
SRT はハンドシェイクの段階(理由 1503)で拒否します。
WHIP の POST /whip/{stream} は、ドレイン中でない別のノードの同じパス(そのノードの peers[].api)へ 307 でリダイレクトし、
そうしたノードが無ければ 503 と Retry-After: 10 を返します。WHIP クライアントはこのリダイレクトに従います。
RTMP・SRT の配信者には、ノードをロードバランサーや DNS から外したうえで、別のノードへ接続し直してもらってください。
すでに接続している配信者と視聴者は切断しません。 /readyz も 200 のままなので、ロードバランサーからは別に外す必要があります。
単一ノードでは移動先が無いので、ワークロードはそのまま残ります。ドレイン後も pull の入力は running、/readyz は 200 です。新しい配信者だけが拒否されます(WHIP は 503)。
デバイス(GPU・キャプチャデバイス)だけを止めるときは、POST /api/v1/nodes/{node}/devices/{device}/disable を使います(戻すときは enable)。
停止のしかた
SIGTERM または SIGINT(Ctrl-C)を受けると、ノードは次の順に終了します。
- ログに
shutting downを出力し、/readyzが 503 になります。 - クラスターでは、ノード間の中継の接続に終了の通知(GoAway)を送り、このノードが終了することを相手のノードに伝えます。
- ストリームごとに作成途中のセグメントを閉じ、録画中のものをすべて
finalizedで閉じます(インデックスにendを書き、ディスクへ確実に書き出します)。 - コーデックワーカーを停止します。
- ログに
byeを出力し、終了コード 0 で終了します。
配信者と視聴者の接続は、プロセスの終了とともに切れます。
終了処理に時間の上限はありませんが、ふつうはすぐに終わります。なお Docker の docker stop は、既定で 10 秒後に SIGKILL を送ります。
SIGKILL や停電で止まった場合も、次の起動時に復旧処理が行われます(上の「障害からの復旧」)。
容量の見積もり
ノードが登録する容量
| オプション | 既定 | 意味 |
|---|---|---|
--capacity-cpu-millis | CPU 数 × 1000 × 90% | グリッドがワークロードに割り当てる CPU(OS やノード自身の管理処理の分を残します) |
--capacity-memory-mb | MemTotal の 85% | ワークロードに割り当てるメモリ |
--net-mbps | 10000 | 送信・受信それぞれの帯域 |
--label KEY=VALUE | なし | 配置の条件に使うラベル(zone=a など) |
GPU は、コーデックワーカーが検出したデバイスごとに、エンコード・デコードのセッション数として登録されます(エンコードは上限が分からなければ 8、
デコードは 16 以上)。登録された容量は GET /api/v1/grid の capacity・gpus で確認できます。
グリッドが数える量
グリッドは、ワークロードごとに次の**見積もり(予約量)**を合計して配置を決めます。実際に測った使用量ではありません。
| ワークロード | CPU | メモリ | 帯域 |
|---|---|---|---|
| pull の入力 | 150 m | 64 MB | 受信 10 Mbit/s |
| キャプチャ入力 | 500 m | 64 MB | —(H.264/H.265 でエンコードするなら GPU のエンコード 1) |
| push の出力 | 100 m | 64 MB | 送信 10 Mbit/s |
| パイプライン | 処理ノードの数から計算(デコード 400 m、GPU でのデコードなら 100 m、合成・板書補正・拡大縮小・切り抜き 300 m など) | 処理ノードの数から計算 | 送受信 10 Mbit/s |
| ルーム(参加者の上限 N、既定 50) | 200 + 20N m | 64 + 2N MB | 受信 2N、送信 2N × min(N, 8) Mbit/s |
(m はミリコア。1000 m で CPU 1 個分です)
注意したいのは、RTMP・SRT・WHIP の配信者(push での受信)と、HLS などの視聴者が、グリッドの予約に入らないことです。
この分の余裕は、--capacity-*・--net-mbps を実際の性能より小さく登録して確保してください。
反対に、ルームは既定の 50 人で送信 800 Mbit/s を予約します。--net-mbps を小さくしすぎるとルームを配置できなくなる(unschedulable)ので、
ルームの max_participants を実際の人数に合わせてください。
コーデックワーカーのプロセス数は --codec-workers(既定は CPU 数)で決まり、パイプラインはこれとは別に 1 つにつき 1 プロセスを使います。
録画の書き込みを待つ列(キュー)の長さは --record-queue で指定します(設定)。
ディスクが遅くてキューがあふれると、録画に欠落(gap)が記録されます。ライブ配信は止まりません。
参考値
開発用のマシン 1 台(8 スレッド、ループバック)で、配信者 4 本 × 視聴者 200 人(各約 4 Mbit/s)の HLS を 60 秒間配信したところ、 合計 877 Mbit/s、セグメント取得の 99 パーセンタイル(p99)は 11.9 ms、エラーは 0 でした。 これは傾向を確認するための参考値で、性能を保証する値ではありません。
関連するページ
この文書の原文は、リポジトリの docs/guide/ja/13-operations.md にあります。内容は現在の版(v0.0.1)に合わせています。