コンテンツにスキップ

アーキテクチャ

単一ノード構成の Yagra は、数個のコンテナに同居して動きます。そこから分散ポーラーや高可用ストア へ広げるとき、作り直しは要りません。設定を変えるだけです。 そうできるように、各要素は最初から 互いに切り離してあります。

コンポーネント 役割 動作場所
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 は コンポーネントではなくバックエンドサービスなので、下のストア一覧に載っています。

コアとポーラーは、バス経由でのみ通信します。直接呼び出すことは一切ありません。この形だから こそ、ポーラーは状態を持たずに済み、台数を増やすだけで規模を広げられます。NAT やファイア ウォールの向こう側にある拠点にも置けます。

リモートのポーラーは、接続を待ち受けません。自分から中央のバスへつなぎに行きます。ですから 拠点側に必要なのは、外向きの許可 1 本だけです。内向きの穴を開ける必要はありません。

ポーラーは、消えては困る状態を持ちません。データベースにも触れません。必要なもの — ジョブの 指示、デバイスの認証情報、作業セットの割当 — は、すべてバス経由で届きます。生み出すもの — 結果、イベント、フローのまとまり、ハートビート — も、すべて同じ道を通って出ていきます。

ジョブのメッセージはデバイスの認証情報を運ぶことがあります。ですからバスをリモート拠点へ 公開するには、同梱の TLS と認証の設定が必要です。詳しくは セキュリティを見てください。

データは種類ごとに、それ専用のストアへ保存します。バックエンドサービスは 6 つで、3 つが必須、 残り 3 つは任意です。任意のものは、設定すると対応する機能が使えるようになります。 インストールガイドと同じ枠組みです:

バックエンドサービス 保持するもの 必須?
PostgreSQL メタデータ: ノード、設定、しきい値、ユーザー、アラート履歴 必須
NATS コア ⇄ ポーラーのバス: ジョブ、作業セット、結果、イベント 必須
VictoriaMetrics 時系列メトリクス 必須
Redis 一時的なポーラーの死活/割当ミラー — 再構築可能で、失っても安全 任意
VictoriaLogs パッシブイベントのログストア — syslog/トラップの全文検索 任意
ClickHouse トラフィックフローレコード 任意

この表の背後には、2 つのルールがあります。種類の多い時系列データは、決して PostgreSQL に入れま せん。消えては困る設定は、決して Redis に入れません。

VictoriaLogs が無ければ、パッシブイベントはすべて PostgreSQL に留まります。ClickHouse が無ければ、 フロー監視はオフになります。

ポーラーが保存するのは、生の SNMP カウンタです。レートや利用率は、問い合わせたときに計算し ます。この方式なら、カウンタが一周したりリセットされたりしても正しく扱えます。そしてポーラーは、 前回の値を覚えておく必要がありません。

まずコアが、すべてのチェックの実行時刻を決めます。このとき時刻を少しずつずらします(ジッタ)。 数千のノードが同じ瞬間にプローブを撃たないようにするためです。決めた作業は、バスへ流します。

割り当てられたポーラーが、Transport(ICMP・SNMP・HTTP をまとめて扱うしくみ)を通してプローブを 実行し、結果を返します。

コアは受け取った結果から状態としきい値を評価し、アラートパイプライン を動かします。同時に、生のメトリクスを VictoriaMetrics へ書き込みます。

収集の時点では、レートの計算を一切しません。レートは、問い合わせたときと評価するときに、時系列 ストアから得ます。

機器が、syslog や SNMP トラップをポーラーの UDP リスナーへ送ります。ポーラーはこれを解析します。 このとき、送信元ごとと全体の両方でレート制限を、拠点側でかけます。そのうえで、元のデータグラムの バイト列のコピーを添えて、イベントをバス経由で送ります。

コアは、送信元 IP を手がかりにイベントをノードへ対応付けます。次にイベントルールと突き合わせて、 アラートを上げるか解消します。最後に PostgreSQL へ保存します。VictoriaLogs を設定していれば、 全文検索のためにそちらへも保存します。

受信 Webhook だけは、ポーラーを経由しません。送信元ごとの Bearer トークンを添えて、コアの API へ直接届きます。詳しくはパッシブイベントを見てください。

フローエクスポーターが、NetFlow・IPFIX・sFlow のデータグラムをポーラーのフローリスナーへ送ります。

ポーラーはこれをデコードし、拠点側でまとめます。まとめ方は、決まった長さの時間で区切り、 エクスポーターごとにバイト数の多い順に上位を残す形です。まとめた結果をコアへ送ります。あわせて、 原本のデータグラムもそのまま送ります。転送機能が、機器の送ったものをそのまま中継できるように するためです。

コアは受け取ったフローに AS 番号を補います(オフラインの IP→ASN データセットを使います)。 そして ClickHouse へ書き込みます。詳しくは トラフィックフローを見てください。

転送は、受け取った syslog・トラップ・フローエクスポートを外部の コレクタへ中継するしくみです。

外へ出ていく通信は、ポーラーからではなくコアから出ます。ポーラーは、受け取った原本のバイト列 をバス経由でコアへ運ぶだけです。動いているコアが、転送先ごとのフィルタを適用し、中継も BigQuery へのストリーミングも、すべて行います。

ですから、いくつの拠点が流し込んでいても、ファイアウォールで面倒を見る出口は 1 か所だけです。

ポーラープール。 すべてのノードは pool 属性を持ちます。各プールのノードは、コンシステント ハッシュで、稼働中のポーラーへ配られます。プールにポーラーを足せば、コアが配り直します。1 台失え ば、その担当ノードは残りのポーラーへ引き継がれます。

プールは拠点にそのまま対応させられます。東京プールのポーラーは、東京の機器をその場で監視します。 詳しくは分散ポーリングを見てください。

作業セット。 コアは各ポーラーへ、担当するノードの一覧をバス経由で渡します。渡し方は、全体の スナップショットと、そのあとの差分の組み合わせです。ポーラーは、自分のプローブの実行時刻を自分で 決めます。

この形なら、コアが 1 本 1 本のチェックを細かく管理しなくても、ポーリングの能力を台数で増やせます。 ポーラーが一時的に 0 台になったプールは、ジョブを 1 件ずつ配る昔のやり方に切り替わるので、監視が 止まるノードは出ません。

高可用性。 同じストア群に対して複数のコアを動かし、リーダーを自動で選ばせられます。実際に 作業をするのは、リーダー 1 台だけです。スタンバイは数秒のうちに引き継ぎます。どちらがリーダーか は /readyz がロードバランサに伝えます。詳しくは 高可用性を見てください。

バージョンアップは、手間をかけずに終わり、何も壊さないよう設計されています。それを支えているのは 次の 3 つです。

  • データベースの形の変更は、コアの起動時に自動で走ります。 先に新しい形を足し、あとから古い 形を削る、2 段構えのやり方です。
  • バスのメッセージは、版が 1 つずれていても通じます。 入れ替えの最中、新しいコアは 1 つ前の リリースのポーラーと動きます。ですからコアを先に上げ、そのあとポーラーを好きな順で上げて ください。
  • 保存されたデータはそのまま残ります。 大きなアップグレードの前には、バックアップの手順が 用意されています。

手順はインストールガイドにあります。

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 の動きが変わったわけではありません。変わるのは、次の変更の値段です。