アーキテクチャ
単一ノード構成の Yagra は、数個のコンテナに同居して動きます。そこから分散ポーラーや高可用ストア へ広げるとき、作り直しは要りません。設定を変えるだけです。 そうできるように、各要素は最初から 互いに切り離してあります。
コンポーネント
Section titled “コンポーネント”| コンポーネント | 役割 | 動作場所 |
|---|---|---|
| Core | オーケストレーション、スケジューリング、ノースバウンド REST API | コア |
| Poller | ICMP / SNMP / API ポーリングとパッシブ受信 — ステートレスで水平スケール可能 | ポーラー |
| WebUI | ダッシュボードと可視化(React + TypeScript) | ブラウザ |
| Bus | NATS 上でのジョブ配信とポーラーへのファンアウト | コア + ポーラー |
| Transport | すべての機器 I/O が通る ICMP / SNMP / HTTP 抽象 | ポーラー |
| Topology | 抑制とネットワークマップを支える依存関係グラフ | コア |
| Discovery | 機器のディスカバリと分類 | コア + ポーラー |
| Alert | ステートマシン、ヒステリシス、依存関係抑制 | コア |
| Ingest | syslog と SNMP トラップの解析、フローのデコード、エッジでのレート制限 | ポーラー |
| Forward | 受信イベント/フローの外部コレクタへのフィルタ付きティー | コア |
| Secrets | 機器の認証情報をエンベロープ暗号で保護 — 鍵を持つのはコアだけ | コア |
| Telemetry | 構造化ログ、Prometheus メトリクス、OpenTelemetry トレース | コア + ポーラー |
デプロイする単位は、表の最初の 3 つだけです。 実体は、2 つの Rust プロセスと 1 つの静的な WebUI です。プロセスはコア(全体を制御する側)とポーラー(機器を叩く側)で、WebUI は nginx が配信します。
表の残りは、そのどちらか(または両方)に組み込まれるライブラリです。ingest コンテナも alert サービスも、独立した discovery デーモンもありません。「動作場所」の列は、それぞれがどのプロセス の中に入るかを示しています。
紛らわしい名前が 2 つあるので、先に区別しておきます。Bus は、コアと各ポーラーの両方に組み込ま れるクライアントのライブラリです。NATS は、その両者が経由するブローカー本体です。NATS は コンポーネントではなくバックエンドサービスなので、下のストア一覧に載っています。
コア ⇄ ポーラーの境界
Section titled “コア ⇄ ポーラーの境界”コアとポーラーは、バス経由でのみ通信します。直接呼び出すことは一切ありません。この形だから こそ、ポーラーは状態を持たずに済み、台数を増やすだけで規模を広げられます。NAT やファイア ウォールの向こう側にある拠点にも置けます。
リモートのポーラーは、接続を待ち受けません。自分から中央のバスへつなぎに行きます。ですから 拠点側に必要なのは、外向きの許可 1 本だけです。内向きの穴を開ける必要はありません。
ポーラーは、消えては困る状態を持ちません。データベースにも触れません。必要なもの — ジョブの 指示、デバイスの認証情報、作業セットの割当 — は、すべてバス経由で届きます。生み出すもの — 結果、イベント、フローのまとまり、ハートビート — も、すべて同じ道を通って出ていきます。
ジョブのメッセージはデバイスの認証情報を運ぶことがあります。ですからバスをリモート拠点へ 公開するには、同梱の TLS と認証の設定が必要です。詳しくは セキュリティを見てください。
ストアの分離
Section titled “ストアの分離”データは種類ごとに、それ専用のストアへ保存します。バックエンドサービスは 6 つで、3 つが必須、 残り 3 つは任意です。任意のものは、設定すると対応する機能が使えるようになります。 インストールガイドと同じ枠組みです:
| バックエンドサービス | 保持するもの | 必須? |
|---|---|---|
| PostgreSQL | メタデータ: ノード、設定、しきい値、ユーザー、アラート履歴 | 必須 |
| NATS | コア ⇄ ポーラーのバス: ジョブ、作業セット、結果、イベント | 必須 |
| VictoriaMetrics | 時系列メトリクス | 必須 |
| Redis | 一時的なポーラーの死活/割当ミラー — 再構築可能で、失っても安全 | 任意 |
| VictoriaLogs | パッシブイベントのログストア — syslog/トラップの全文検索 | 任意 |
| ClickHouse | トラフィックフローレコード | 任意 |
この表の背後には、2 つのルールがあります。種類の多い時系列データは、決して PostgreSQL に入れま せん。消えては困る設定は、決して Redis に入れません。
VictoriaLogs が無ければ、パッシブイベントはすべて PostgreSQL に留まります。ClickHouse が無ければ、 フロー監視はオフになります。
ポーラーが保存するのは、生の SNMP カウンタです。レートや利用率は、問い合わせたときに計算し ます。この方式なら、カウンタが一周したりリセットされたりしても正しく扱えます。そしてポーラーは、 前回の値を覚えておく必要がありません。
データの流れ
Section titled “データの流れ”ポーリングのライフサイクル
Section titled “ポーリングのライフサイクル”まずコアが、すべてのチェックの実行時刻を決めます。このとき時刻を少しずつずらします(ジッタ)。 数千のノードが同じ瞬間にプローブを撃たないようにするためです。決めた作業は、バスへ流します。
割り当てられたポーラーが、Transport(ICMP・SNMP・HTTP をまとめて扱うしくみ)を通してプローブを 実行し、結果を返します。
コアは受け取った結果から状態としきい値を評価し、アラートパイプライン を動かします。同時に、生のメトリクスを VictoriaMetrics へ書き込みます。
収集の時点では、レートの計算を一切しません。レートは、問い合わせたときと評価するときに、時系列 ストアから得ます。
パッシブイベント
Section titled “パッシブイベント”機器が、syslog や SNMP トラップをポーラーの UDP リスナーへ送ります。ポーラーはこれを解析します。 このとき、送信元ごとと全体の両方でレート制限を、拠点側でかけます。そのうえで、元のデータグラムの バイト列のコピーを添えて、イベントをバス経由で送ります。
コアは、送信元 IP を手がかりにイベントをノードへ対応付けます。次にイベントルールと突き合わせて、 アラートを上げるか解消します。最後に PostgreSQL へ保存します。VictoriaLogs を設定していれば、 全文検索のためにそちらへも保存します。
受信 Webhook だけは、ポーラーを経由しません。送信元ごとの Bearer トークンを添えて、コアの API へ直接届きます。詳しくはパッシブイベントを見てください。
トラフィックフロー
Section titled “トラフィックフロー”フローエクスポーターが、NetFlow・IPFIX・sFlow のデータグラムをポーラーのフローリスナーへ送ります。
ポーラーはこれをデコードし、拠点側でまとめます。まとめ方は、決まった長さの時間で区切り、 エクスポーターごとにバイト数の多い順に上位を残す形です。まとめた結果をコアへ送ります。あわせて、 原本のデータグラムもそのまま送ります。転送機能が、機器の送ったものをそのまま中継できるように するためです。
コアは受け取ったフローに AS 番号を補います(オフラインの IP→ASN データセットを使います)。 そして ClickHouse へ書き込みます。詳しくは トラフィックフローを見てください。
転送の外向き通信
Section titled “転送の外向き通信”転送は、受け取った syslog・トラップ・フローエクスポートを外部の コレクタへ中継するしくみです。
外へ出ていく通信は、ポーラーからではなくコアから出ます。ポーラーは、受け取った原本のバイト列 をバス経由でコアへ運ぶだけです。動いているコアが、転送先ごとのフィルタを適用し、中継も BigQuery へのストリーミングも、すべて行います。
ですから、いくつの拠点が流し込んでいても、ファイアウォールで面倒を見る出口は 1 か所だけです。
スケーリングモデル
Section titled “スケーリングモデル”ポーラープール。 すべてのノードは pool 属性を持ちます。各プールのノードは、コンシステント
ハッシュで、稼働中のポーラーへ配られます。プールにポーラーを足せば、コアが配り直します。1 台失え
ば、その担当ノードは残りのポーラーへ引き継がれます。
プールは拠点にそのまま対応させられます。東京プールのポーラーは、東京の機器をその場で監視します。 詳しくは分散ポーリングを見てください。
作業セット。 コアは各ポーラーへ、担当するノードの一覧をバス経由で渡します。渡し方は、全体の スナップショットと、そのあとの差分の組み合わせです。ポーラーは、自分のプローブの実行時刻を自分で 決めます。
この形なら、コアが 1 本 1 本のチェックを細かく管理しなくても、ポーリングの能力を台数で増やせます。 ポーラーが一時的に 0 台になったプールは、ジョブを 1 件ずつ配る昔のやり方に切り替わるので、監視が 止まるノードは出ません。
高可用性。 同じストア群に対して複数のコアを動かし、リーダーを自動で選ばせられます。実際に
作業をするのは、リーダー 1 台だけです。スタンバイは数秒のうちに引き継ぎます。どちらがリーダーか
は /readyz がロードバランサに伝えます。詳しくは
高可用性を見てください。
アップグレード
Section titled “アップグレード”バージョンアップは、手間をかけずに終わり、何も壊さないよう設計されています。それを支えているのは 次の 3 つです。
- データベースの形の変更は、コアの起動時に自動で走ります。 先に新しい形を足し、あとから古い 形を削る、2 段構えのやり方です。
- バスのメッセージは、版が 1 つずれていても通じます。 入れ替えの最中、新しいコアは 1 つ前の リリースのポーラーと動きます。ですからコアを先に上げ、そのあとポーラーを好きな順で上げて ください。
- 保存されたデータはそのまま残ります。 大きなアップグレードの前には、バックアップの手順が 用意されています。
手順はインストールガイドにあります。
コードの並べ方
Section titled “コードの並べ方”Yagra は AGPL-3.0 で、ソースは公開されています。ですから、どう並べてあるかを書いておきます。 v0.3.0 で何が変わったかも、あわせて書きます。
v0.2 系のあいだに、バックエンドにはとても大きな Rust のファイルがいくつか育ちました。無関係な 仕事が何本も並んで座っている状態です。コアの起動ファイルは 5,066 行、ポーラーは 1,097 行に なっていました。この形のファイルは、不便というより危険です。いま直している部分が、自分が一度も 開いたことのない何かからも読まれている、と誰も教えてくれないからです。
v0.3.0 は、それらを割りました。 大きさで割るのではなく、中身がすでに従っている線に沿って 割っています。
| 場所 | 何で割ったか |
|---|---|
analysis/ |
その Troubleshoot 解析が、どのストアを読むか |
scheduler/ |
動くために何が要るか — 純粋な側は一度も待たない |
events/ |
どのプログラムに属する部分か(検索・保存・照合・取り込み) |
repo/ |
そのメソッドの SQL が、どの表を名指しするか |
reports/ |
レポート 1 本ができるまでの、どの段階か |
config_bundle/ |
設定がどちら向きに運ばれるか |
mcp/tools/ |
どの REST のドメインに対応するツールか |
ポーラーの worker/ |
機器とどうやって話すチェックか |
それぞれの境目は、越えたときにビルドを落とすテストで押さえてあります。自分のファイルが宣言 していない表を名指しするクエリ、待ってしまうスケジューラの関数、トランスポートへ直接手を伸ばす ポーラーのチェック。規則が実行されるので、書いただけの約束事のようには腐りません。
コアの起動ファイルは 2,100 行になり、起動のことだけを持つようになりました。ポーラーは 450 行 です。これで Yagra の動きが変わったわけではありません。変わるのは、次の変更の値段です。