運用

ヘルスチェック、ログ、監視用の API、イベントによる自動化、障害からの復旧、バックアップ、バージョンアップ、ノードのドレイン、停止のしかた、容量の見積もりなど、SENCA のノードを運用するための機能と手順を説明します。

運用と安全

このページの見出し
  1. 起動と終了コード
  2. ヘルスチェック
  3. ログ
  4. 監視用の API
  5. イベント(SSE)による自動化
  6. 障害からの復旧
  7. バックアップ
  8. バージョンアップ
  9. ノードのドレイン
  10. 停止のしかた
  11. 容量の見積もり
  12. 関連するページ

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 にあります。

障害からの復旧

ノードは起動のたびに、録画の復旧処理を行います。

  1. 削除の途中だった録画の削除を完了します。
  2. recordings/*/index.jsonl を読みます。end 行が無い(書き込み途中の)インデックスは、末尾の書きかけの行を切り捨て、 end 行(state: "interrupted"、終了時刻は最後のセグメントの終わり)を追記して閉じます。コントロールプレーン上の録画の記録も interrupted にします。ログには recovery: recording <ID> closed as interrupted (<N> segments) と出力されます。
  3. それでもコントロールプレーンで recording のままになっている、このノードの録画(インデックスが無いものなど)を interrupted にします。
  4. どのインデックスからも参照されていないセグメントを segments/{stream}/orphans/{run}/ へ移します(削除はしません)。 読めないインデックスが 1 つでもあると、この隔離は行いません(参照を失わないためです)。読めないインデックス自体には手を加えません。
  5. --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 つは必ず同じバージョンにそろえて入れ替えてください。
  • クラスターで、バージョンの異なるノードを混在させる運用には対応していません。

単一ノードでは、次の手順で更新します。

  1. バックアップを取ります(上の「バックアップ」)。万一のときに元に戻せるようにするためです。
  2. ノードを SIGTERM で停止します。録画は finalized で正しく閉じられます。
  3. 実行ファイルの senca-node・senca-codec-worker(使っていれば senca-capture-worker も)を新しいものに入れ替えます。
  4. 起動し、/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)を受けると、ノードは次の順に終了します。

  1. ログに shutting down を出力し、/readyz が 503 になります。
  2. クラスターでは、ノード間の中継の接続に終了の通知(GoAway)を送り、このノードが終了することを相手のノードに伝えます。
  3. ストリームごとに作成途中のセグメントを閉じ、録画中のものをすべて finalized で閉じます(インデックスに end を書き、ディスクへ確実に書き出します)。
  4. コーデックワーカーを停止します。
  5. ログに bye を出力し、終了コード 0 で終了します。

配信者と視聴者の接続は、プロセスの終了とともに切れます。

終了処理に時間の上限はありませんが、ふつうはすぐに終わります。なお Docker の docker stop は、既定で 10 秒後に SIGKILL を送ります。 SIGKILL や停電で止まった場合も、次の起動時に復旧処理が行われます(上の「障害からの復旧」)。

容量の見積もり

ノードが登録する容量

オプション既定意味
--capacity-cpu-millisCPU 数 × 1000 × 90%グリッドがワークロードに割り当てる CPU(OS やノード自身の管理処理の分を残します)
--capacity-memory-mbMemTotal の 85%ワークロードに割り当てるメモリ
--net-mbps10000送信・受信それぞれの帯域
--label KEY=VALUEなし配置の条件に使うラベル(zone=a など)

GPU は、コーデックワーカーが検出したデバイスごとに、エンコード・デコードのセッション数として登録されます(エンコードは上限が分からなければ 8、 デコードは 16 以上)。登録された容量は GET /api/v1/grid の capacity・gpus で確認できます。

グリッドが数える量

グリッドは、ワークロードごとに次の**見積もり(予約量)**を合計して配置を決めます。実際に測った使用量ではありません。

ワークロードCPUメモリ帯域
pull の入力150 m64 MB受信 10 Mbit/s
キャプチャ入力500 m64 MB—(H.264/H.265 でエンコードするなら GPU のエンコード 1)
push の出力100 m64 MB送信 10 Mbit/s
パイプライン処理ノードの数から計算(デコード 400 m、GPU でのデコードなら 100 m、合成・板書補正・拡大縮小・切り抜き 300 m など)処理ノードの数から計算送受信 10 Mbit/s
ルーム(参加者の上限 N、既定 50)200 + 20N m64 + 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)に合わせています。