情報畑でつかまえてロゴ
本サイトは NTTテクノクロスが旬の IT をキーワードに
IT 部門が今知っておきたい最新テクノロジーに関する情報をお届けするサイトです

SONiCとTelemetry, AIで作るクローズドループ基盤(3) ~監視基盤編~

本シリーズではSONiCとTelemetry, AIを用いたクローズドループ基盤について紹介します。本記事ではTelegraf、Prometheus、Grafanaを用いた監視/可視化基盤について扱います。

はじめに

こんにちは、NTTテクノクロスの山口です。

本連載では、SONiCとTelemetry、AIを活用した簡易なクローズドループ基盤の構築とその動作例について紹介します。
第3回となる今回は「監視/可視化基盤」の構築を行い、NW機器(SONiC)からTelemetryで収集した情報をGUI上で確認できるところまでを説明します。

nw_ad1-1-1.png

図0-1 本記事の範囲概要

構築手順については手順だけでなく、関連技術の説明やポイントなどの補足も紹介します。
補足は、本編の内容から多少それるような「参考情報」もある為、気になる方は確認いただければ、と思います。

記事
第1回 クローズドループとは - -
本連載で作る環境と検証シナリオ - -
構築環境と手順に関する前提 - -
完成した環境での検証結果概要 - -
第2回 環境の作り方
(各サーバ設定)
1. 全体像 -
2. SONiC -
3. 試験トラヒック用サーバ -
4. 監視/制御サーバ -
第3回
(今回)
環境の作り方
(監視/可視化基盤)
1. 全体像と全体設定 -
2. Telegraf A. 構築・起動手順
B. 動作確認
3. Prometheus A. 構築・起動手順
B. 動作確認
4. Grafana A. 構築・起動手順
B. 動作確認
【補足】
5. Prometheusによる
   ルールベース異常判定の準備
A. 設計・方針整理
B-1. 構築手順(ルール定義)
B-2. 構築手順(Prometheus)
B-3. 構築手順(Alertmanager)
C. 起動と動作確認
D. その他
第4回 環境の作り方
(AIによる判断と制御基盤)
1. 全体像 -
2. AI利用に関する設計と事前準備 ① AIに関する設計
② データの収集
③ AIの学習用データの選定/抽出
3. 学習ツール ④ データの加工(前処理)~
⑥ 精度確認(モデルの評価)
4. 推論ツール ⑦ AIモデルの利用(推論)
5. 経路切り替えツール -
6. 定期実行の実現 -
第5回 環境の作り方
(LLMによる問い合わせ支援基盤)
1. 全体像 -
2. チャット機能 A. 構築・起動手順
B. 動作確認
検証シナリオの実施と結果 1. AIを用いたクローズドループ環境検証 Ⅰ. 手順概要
Ⅱ. 手順実施と結果
【補足】
2. ルールベース異常判定の検証
Ⅰ. 手順概要
Ⅱ. 手順実施と結果

※ 本記事の内容を用いた開発・運用は、必ずご自身の責任と判断によって行ってください。
  開発・運用の結果について、いかなる責任も負いません。

環境の作り方(監視/可視化基盤)

1. 全体像と全体設定

ここから「監視/可視化基盤」の構築となりますが、前提として「第2回」のサーバ設定が済んでいることとします。
その他の前提事項に関しては、第1回をご確認ください。

構築の前に改めて「監視/可視化基盤」の構成についておさらいをしましょう。

nw_ad1-25.png

図1-1 本項の対象

上の図のように「Telegraf→Prometheus→Grafana」という構成で今回は環境を作ります。
それぞれで節を分けて説明します。
また、本連載ではAIによる制御がメインテーマではありますが、実運用では「AI検知」と「ルールベース検知」を組み合わせて利用することが考えられる為、補足としてルールベースの異常検知の方法についても示したいと思います。

監視/可視化基盤の各コンポーネント(Telegraf,Prometheus,Grafana」は拡張性を加味し、コンテナで管理するのが理想でしょう。
そこで今回はコンテナオーケストレーションツール(コンテナ管理ツール)であるKubernetes(k8s)の簡易版であるk3sを利用します。
監視VM1台でControl-plane(制御処理。コンテナ作成依頼を受け付け、k3sクラスタのどのノード(マシン)にPod(コンテナ)を配置するのか決めたりする)とWorker処理(Control-planeの内容に応じてPodを起動/運用する)を担う形で試していきます。

nw_ad1-26.png

図1-2 Kubernetesとマシン(ノード)の構成例

まずはk3sのインストールから行います。

#インストール $ curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable=traefik --node-ip=172.16.200.10 --flannel-iface=enp0s10" sh -
# k3sサービス起動確認(activeならOK) $ sudo systemctl status k3s --no-pager --- ● k3s.service - Lightweight Kubernetes      Loaded: loaded (/etc/systemd/system/k3s.service; enabled; preset: enabled)      Active: active (running) since ~略 ---
# k3sコマンドの動作確認(バージョン表示されればOK) $ k3s --version --- k3s version v1.36.2+k3s1 (01b6f04a) go version go1.26.4 ---
# kubectlコマンドの動作確認(k3sクラスタのノード一覧取得. 自VMがノードとして出力されればOK) $ sudo k3s kubectl get nodes --- NAME STATUS ROLES AGE VERSION monitor Ready control-plane 1d v1.36.2+k3s1 ---
# k3sなしにkubectlを使えるようにする $ mkdir -p ~/.kube $ sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config $ sudo chown "$USER:$USER" ~/.kube/config
$ echo 'export KUBECONFIG=$HOME/.kube/config' >> ~/.bashrc $ source ~/.bashrc
# 動作確認(kubectlだけで実行してk3sクラスタのノード一覧取得. 自VMがノードとして出力されればOK) $ kubectl get nodes --- NAME STATUS ROLES AGE VERSION monitor Ready control-plane 1d v1.36.2+k3s1 ---

これでk3sが利用できるようになったので、ここからはSONiCからTelemetryデータを収集する仕組みを整えていきます。
前述の通り、Telemetry収集用のコレクタにはTelegrafを用意しますが、その前に監視/制御サーバで以下のポートフォワードを実行しておいてください。

$ ssh -N -L 18080:127.0.0.1:8080 admin@172.16.100.11

TelemetryでNW機器からデータ取得をする際には認証が利用できます。(SONiCのID/PWで認証します)
あわせてTLSによる通信暗号化も対応できますが、今回はTLSなしで実行したいと思います。
今回NW機器として利用するSONiC(VS版)では、TLSなしでTelemetry取得をする際はループバックアドレスからのみに絞っているため、そのままだと監視/制御サーバからSONiCに対してTelemetry収集がかけられません。
この対策として上のポートフォワード設定を入れておきます。

[補足1] Kubernetes固有のリソース/概念について

kubernetesにはPodやDeployment、Serviceなど、コンテナを管理する為の固有のリソースや概念があります。
これらは主にYAMLファイルで定義します。
今回環境を作るにあたり利用するもの・触れておきたいものは以下の通りです。
Kubernetesに慣れていない方は、それぞれの役割も押さえながらYAMLファイルの定義を眺めてみてください。

リソース/概念 概要
Pod コンテナを動かす最小単位。コンテナ1つ、あるいは複数のコンテナで構成される。
Deployment Podを指定した数だけ起動し、正常な状態に保つ仕組み。
Service Podにアクセスする為の窓口を定義する仕組み。Serviceの方式にもいくつか種類があるが、NodePortを指定するとKubernetes環境の外部からアクセスできる。
ConfigMap コンテナ化されたアプリケーションの設定を保存/反映する仕組み。
Secret パスワードやAPIキーのような機密情報を保存する仕組み。

2. Telegraf

2-A. 構築・起動手順/Telegraf

ここからはTelegrafコンテナ(正確にはPod)の用意に進みます

まずは、監視基盤用のネームスペースを構築し、SONiCの認証情報をシークレットとして登録します。

# namespace作成、エラーとなれば無視 $ kubectl create namespace telemetry
# 認証情報はシークレットで管理する。シークレット作成。 $ kubectl -n telemetry create secret generic sonic-auth \    --from-literal=username='admin' \    --from-literal=password='YourPaSsWoRd'
# 確認(sonic-authが確認できればOK) $ kubectl -n telemetry get secret sonic-auth --- NAME TYPE DATA AGE sonic-auth Opaque 2 1d ---

telegrafのConfigをConfigMapとして定義します。

$ vim telegraf-sonic-config.yaml
apiVersion: v1 kind: ConfigMap metadata:   name: telegraf-sonic-config   namespace: telemetry data:   telegraf.conf: |     [agent]       # inputs.gnmiからのデータ収集間隔やoutputs向けの反映間隔       interval = "10s"       flush_interval = "10s"       omit_hostname = false     [[inputs.gnmi]]       # 宛先情報など       addresses = ["127.0.0.1:18080"]       username = "${SONIC_USERNAME}"       password = "${SONIC_PASSWORD}"
      # TLS無効設定       tls_enable = false
      # OC-YANG(OpenConfig)も指定できるが、今回はCOUNTERS_DBを指定       target = "COUNTERS_DB"
      # 接続失敗時のリトライ間隔       redial = "10s"       path_guessing_strategy = "subscription"       tags = { device = "sonic-101" }
      # Telemetryで取得する情報や取得頻度       [[inputs.gnmi.subscription]]         name = "sonic_counters_ethernet4"         path = "/COUNTERS/Ethernet4"         subscription_mode = "sample"         sample_interval = "10s"
      [[inputs.gnmi.subscription]]         name = "sonic_counters_ethernet8"         path = "/COUNTERS/Ethernet8"         subscription_mode = "sample"         sample_interval = "10s"
      [[inputs.gnmi.subscription]]         name = "sonic_counters_ethernet12"         path = "/COUNTERS/Ethernet12"         subscription_mode = "sample"         sample_interval = "10s"
    # 文字列カウンターを数値化する     [[processors.converter]]       namepass = ["sonic_counters_*"]
      [processors.converter.fields]         unsigned = ["SAI_PORT_STAT_*"]
    # デバッグ用。     [[outputs.file]]       files = ["stdout"]       data_format = "influx"
    # Prometheus向け     [[outputs.prometheus_client]]       listen = ":9273"       path = "/metrics"       metric_version = 2       expiration_interval = "60s"

今回はDial-In方式で値を取得する為、コレクタからNW機器に指示を出して、Telemetryデータを定期取得します。
したがってコレクタ側にターゲットとなる機器のIPアドレスやほしい情報、取得間隔などを指定します。
上のConfig定義をみるとinputs.gnmiプラグインを活用し、宛先や取得したい情報、取得間隔などを指定していることがわかるかと思います。
SONiCに対してEthernet4, 8, 12のIFカウンター情報を10秒ごとに送信させる設定になっています。

今回取得対象のNW機器は1台ですが、全く同じ情報をとりたければaddressesにアドレスを追加することもできますし、別の値をとりたければinputs.gnmiブロックをわけて記載/設定することも可能です。
また、NW機器情報の判別にはTelemetryで上がってくるIPアドレスやポート番号で見分けるでも良いですし、上のConfigのようにtagsを入れてデバイス名(sonic-101)がわかるようにしても良いかと思います。

processorsプラグインはデータ書き換えを行っています。
IFカウンター情報は文字列として取得されますが数値形式で扱いたいため、数値型へ変換しています。

[補足2-1] Telemetryの動作確認とgNMI、gnmicについて
Telegrafの設定内で「input.gnmi」という名前のプラグインを使用していますが、SONiC内にもgnmiコンテナが立ち上がっていました。(第2回参照)
これらに共通する「gNMIとは何なのか」をここで紹介したいと思います。

gNMIとは通信基盤にgRPCを利用したNW管理IFです。
イメージがつかない方は、まずはDial-Inでよく使われるTelemetryの方式のようなものと考えていただくと良いかと思います。

gNMIの動作確認には、gnmicを利用すると良いかと思います。
本編ではTelemetry利用をするためにそのままTelegrafを用意していますが、TelegrafはConfigで設定した条件にあわせて収集が行われます。
一方、gnmicはコマンドベースでgNMI経由のTelemetry収集に関する動作確認ができる為、動作確認をする上ではgnmicの方が手軽です。

gnmicのインストールは以下のように行います。

# インストール $ bash -c "$(curl -sL https://get-gnmic.openconfig.net)"
# 動作確認・バージョン確認 $ gnmic version --- version : 0.46.0 (以下略) ---

gNMIではNW機器側(今回SONiC)が対応しているgNMIバージョンやモデル(センサーパス)などを見ることができるcapabilities確認ができます。
まずはそれを試してみます。

# ポートフォワード設定をした18080に対してTLSなし(--insecure)でcapabilities取得 $ gnmic -a 127.0.0.1:18080 -u admin -p 'YourPaSsWoRd' --insecure capabilities --- gNMI version: 0.7.0 supported models:   - openconfig-acl, OpenConfig working group, 1.0.2   - openconfig-interfaces, OpenConfig working group,   - openconfig-mclag, OpenConfig working group,   - openconfig-sampling-sflow, OpenConfig working group,   - openconfig-acl, OpenConfig working group,   - openconfig-lldp, OpenConfig working group, 1.0.2   - openconfig-platform, OpenConfig working group, 1.0.2   - openconfig-system, OpenConfig working group, 1.0.2   - ietf-yang-library, IETF NETCONF (Network Configuration) Working Group, 2016-06-21   - sonic-db, SONiC, 0.1.0 supported encodings:   - JSON   - JSON_IETF   - PROTO ---

結果としてマルチベンダーで対応可能なOpenConfigモデルもいくつか対応していそう、ということがわかります。
SONiC側の試験トラヒックサーバからの入力IF(eth2 = Ethernet4)に関するIFカウンター情報を「openconfig-interfaces」で取得してみましょう。

# mode onceのため、1回だけ値を取得する。 $ gnmic -a 127.0.0.1:18080 -u admin -p 'YourPaSsWoRd' --insecure sub --target OC-YANG --path '/openconfig-interfaces:interfaces/interface[name=Ethernet4]/state/counters' --mode once --- {   "source": "127.0.0.1:18080",   "subscription-name": "default-1784560251",   "timestamp": 1784174172580085427,   "time": "2026-06-30T10:56:12.580085427Z",   "prefix": "openconfig-interfaces:interfaces/interface[name=Ethernet4]/state/counters",   "target": "OC-YANG",   "updates": [     {       "Path": "in-unicast-pkts",       "values": {         "in-unicast-pkts": 8       }     },     {       "Path": "out-broadcast-pkts",       "values": {         "out-broadcast-pkts": 0       }     },     {       "Path": "out-discards",       "values": {         "out-discards": 0       }     },     {       "Path": "out-errors",       "values": {         "out-errors": 0       }     },     {       "Path": "out-multicast-pkts",       "values": {         "out-multicast-pkts": 0       }     },     {       "Path": "out-octets",       "values": {         "out-octets": 5532       }     },     {       "Path": "out-unicast-pkts",       "values": {         "out-unicast-pkts": 74       }     },     {       "Path": "in-broadcast-pkts",       "values": {         "in-broadcast-pkts": 0       }     },     {       "Path": "in-discards",       "values": {         "in-discards": 0       }     },     {       "Path": "in-errors",       "values": {         "in-errors": 0       }     },     {       "Path": "in-multicast-pkts",       "values": {         "in-multicast-pkts": 0       }     },     {       "Path": "in-octets",       "values": {         "in-octets": 956       }     },     {       "Path": "in-pkts",       "values": {         "in-pkts": 8       }     },     {       "Path": "out-pkts",       "values": {         "out-pkts": 74       }     }   ] } {   "sync-response": true } ---

実際に取得できる値をみるとTelemetryのイメージが少しわいたのではないでしょうか。
上の例では[name=Ethernet4]と入れているため、IF名がEthernet4のものしか取得していませんが、何も指定しない場合は全IFの情報が取得されます。
また、今回mode once指定のため、1回しか取得されませんが、Telemetryらしい定期的な取得であったり、ON_CHANGEと呼ばれるステータスが切り替わったときだけ取得する(例えばIFがUP→DOWNに切り替わった場合などに利用します)といったモードもあります。

ちなみにgNMIおよびgnmicは設定値の取得や設定投入ができる場合もあります。(NW機器/NOSによります)
つまり用途はTelemetry(データ取得)だけに限らないものとなります。
また、NW機器の再起動やOS更新に利用するgNOIと呼ばれるIFも存在します。
gNMI、gNOIともにgRPCを基盤としたものです。


[補足2-2] OpenConfigとベンダーネイティブ、COUNTERS_DBについて
Telegrafのtargetにて「COUNTERS_DB」を指定していますが、これはSONiC内のDBで管理している情報から取得する、という意味合いです。
言ってしまえばSONiC独自の仕組みとなるため、OpenConfigというよりは「ベンダーネイティブ(SONiCネイティブ)」といえます。

OpenConfigは多くのベンダー機器で共通的に利用できる一方で、そのモデル/センサーパスでどのメトリクスまで取れるかはNW機器のバージョンやベンダの対応状況によります。
ベンダーネイティブはベンダーごとに扱いは異なるものの、OpenConfigでは取れないような値が取れることもあります。

「[補足2-1] Telemetryの動作確認とgNMI、gnmicについて」ではOpenConfigの例を示したので、本文ではベンダーネイティブであるCOUNTERS_DBの例でみていきたいと思います。

なお、SONiC内のDBで管理しているものはCOUNTERS_DB以外にもあります。(例えば設定関連の情報を扱うCONFIG_DBや各種状態情報を保持するSTATE_DBなど)
COUNTERS_DBはシステム内のIFに関するカウンターや統計情報を保存する役割のDBです。
今回はIFカウンターをとりたい為、このDBを指定しています。


次にdeploymentを作ります。

$ vim telegraf-sonic-deployment.yaml
apiVersion: apps/v1 kind: Deployment metadata:   name: telegraf-sonic   namespace: telemetry spec:   replicas: 1   selector:     matchLabels:       app: telegraf-sonic   template:     metadata:       labels:         app: telegraf-sonic     spec:       hostNetwork: true       dnsPolicy: ClusterFirstWithHostNet
      containers:         - name: telegraf           image: telegraf:latest           imagePullPolicy: IfNotPresent           args:             - "--config"             - "/etc/telegraf/telegraf.conf"             - "--debug"
          env:             - name: SONIC_USERNAME               valueFrom:                secretKeyRef:                  name: sonic-auth                  key: username             - name: SONIC_PASSWORD               valueFrom:                secretKeyRef:                  name: sonic-auth                  key: password
          volumeMounts:             - name: telegraf-config               mountPath: /etc/telegraf/telegraf.conf               subPath: telegraf.conf               readOnly: true
      volumes:         - name: telegraf-config           configMap:             name: telegraf-sonic-config

Deploymentで、これまで作ってきたsecret(sonic-auth)やConfigMap(telegraf-sonic-config)を紐づけ、どういう条件でコンテナを立ち上げるか指定しています。

Deploymentで特に重要なのがreplicasで、Kubernetesはここで指定された数だけPodを起動するように動きます。
何か問題があってPodが停止した場合、そのPodは停止し、別のPodが起動します。
この例では1を指定しているので、極力1Pod起動するように動きます。

利用するPodやコンテナ化するツールにもよりますが、スケール対応などを考える際には複数指定しておけるとよいでしょう。
なお、Telegrafの場合はSONiCに対する設定が紐づいているため、例えば2つTelegrafコンテナを立ち上げると、SONiCへのTelemetry取得が重複します。
そのため、replicasは1としています。

異なるNW機器の取得をする場合は前述の通りConfigMapに加筆し、1Telegrafでは性能的な限界を感じた場合は別にConfigMapやDeploymentを用意の上、別定義のTelegrafとして起動(デプロイ)する、という運用もありかと思います。

設定ファイルが用意できたら起動/適用しましょう。

# 起動/適用 $ kubectl apply -f telegraf-sonic-config.yaml $ kubectl apply -f telegraf-sonic-deployment.yaml
# 作成したPod(コンテナ)確認 $ kubectl -n telemetry get pods --- NAME READY STATUS RESTARTS AGE telegraf-sonic-7b4ff98bcd-rdx9l 1/1 Running 0 46s ---
# 作成したPodがどのノードに配置されたかも含めた確認(現在は1VMで実行しているので自VM内に配置されていることがわかる) $ kubectl get pods -n telemetry -o wide --- NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES telegraf-sonic-7b4ff98bcd-rdx9l 1/1 Running 0 55s 172.16.200.10 monitor <none> <none> ---
# Telegrafのバージョン確認 $ kubectl -n telemetry exec deploy/telegraf-sonic -- telegraf --version --- Telegraf 1.39.1 (git: HEAD@9a8b2022) ---

最後に、Telegrafと次の構成であるPrometheusが連携できるようにするためのサービスを作ります。
PrometheusはPull型でTelegrafからデータ取得をする(PrometheusからTelegrafに定期的にデータを取得する)為、Prometheusがアクセスできるための口をTelegraf側に用意します。

nw_ad1-28.png

図2-1 Pull型/Push型イメージ

vim telegraf-sonic-metrics-service.yaml
apiVersion: v1 kind: Service metadata:   name: telegraf-sonic-metrics   namespace: telemetry spec:   selector:     app: telegraf-sonic   ports:     - name: metrics       port: 9273       targetPort: 9273

用意したら起動/適用します。

# 起動/適用 $ kubectl apply -f telegraf-sonic-metrics-service.yaml

2-B. 動作確認/Telegraf

Telegraf Podの起動ができたら、想定通りに動いているか確認をしましょう。

Telegraf Podは起動すると、自動でSONiCに接続しにいきTelemetry収集が開始します。
Telegraf Podのログをみて想定通り収集ができているか確認します。

# telegraf Podのログ確認 $ kubectl -n telemetry logs -f deploy/telegraf-sonic

デバッグ用に標準出力設定を入れていますので、以下のような形式の情報が出ていれば問題なく収集できています。

2026-06-30T10:24:16Z D! [inputs.gnmi] Connection to gNMI device 127.0.0.1:18080 established sonic_counters_ethernet12,device=sonic-101,host=Monitor,path=/COUNTERS/Ethernet12,source=127.0.0.1 SAI_PORT_STAT_IF_IN_DISCARDS=8i,SAI_PORT_STAT_IF_IN_UNKNOWN_PROTOS=0i,SAI_PORT_STAT_PFC_5_TX_PKTS=0i,SAI_PORT_STAT_IF_IN_BROADCAST_PKTS=0i,SAI_PORT_STAT_IF_IN_ERRORS=0i,SAI_PORT_STAT_IF_IN_NON_UCAST_PKTS=0i,SAI_PORT_STAT_PFC_6_RX_PKTS=0i,SAI_PORT_STAT_IF_OUT_UCAST_PKTS=26i,SAI_PORT_STAT_PFC_2_RX_PKTS=0i,SAI_PORT_STAT_PFC_4_RX_PKTS=0i,SAI_PORT_STAT_PFC_6_TX_PKTS=0i,SAI_PORT_STAT_DROPPED_TRIM_PACKETS=0i,SAI_PORT_STAT_ETHER_STATS_TX_NO_ERRORS=0i,SAI_PORT_STAT_IF_OUT_BROADCAST_PKTS=0i,SAI_PORT_STAT_IF_OUT_NON_UCAST_PKTS=0i,SAI_PORT_STAT_TX_TRIM_PACKETS=0i,SAI_PORT_STAT_ETHER_RX_OVERSIZE_PKTS=0i,SAI_PORT_STAT_ETHER_STATS_FRAGMENTS=0i,SAI_PORT_STAT_ETHER_STATS_JABBERS=0i,SAI_PORT_STAT_IF_IN_MULTICAST_PKTS=0i,SAI_PORT_STAT_PFC_0_RX_PKTS=0i,SAI_PORT_STAT_PFC_3_TX_PKTS=0i,SAI_PORT_STAT_PFC_5_RX_PKTS=0i,SAI_PORT_STAT_IF_IN_OCTETS=866i,SAI_PORT_STAT_IF_OUT_ERRORS=0i,SAI_PORT_STAT_IF_OUT_MULTICAST_PKTS=0i,SAI_PORT_STAT_PFC_1_RX_PKTS=0i,SAI_PORT_STAT_PFC_2_TX_PKTS=0i,SAI_PORT_STAT_PFC_3_RX_PKTS=0i,SAI_PORT_STAT_PFC_7_RX_PKTS=0i,SAI_PORT_STAT_ETHER_STATS_UNDERSIZE_PKTS=0i,SAI_PORT_STAT_IF_OUT_OCTETS=2137i,SAI_PORT_STAT_PFC_1_TX_PKTS=0i,SAI_PORT_STAT_PFC_4_TX_PKTS=0i,SAI_PORT_STAT_TRIM_PACKETS=0i,SAI_PORT_STAT_ETHER_TX_OVERSIZE_PKTS=0i,SAI_PORT_STAT_IF_IN_UCAST_PKTS=7i,SAI_PORT_STAT_IF_OUT_DISCARDS=0i,SAI_PORT_STAT_IF_OUT_QLEN=0i,SAI_PORT_STAT_PFC_0_TX_PKTS=0i,SAI_PORT_STAT_PFC_7_TX_PKTS=0i 1784179055625461399 ・・・(略)

1行目はTelegrafとSONiCで接続が成功し、gNMI用のコネクションが張れた事を示しています。
以降は定期的に値を受け取るため、2行目のような値表示がされていきます。
このことから問題なく収集ができているということがわかります。

ちなみに上のログはInfluxDBとの連携用の取得値の表示形式となりますが、構成は以下の図の通りです。

nw_ad1-27.png

図2-2 連携先による出力フォーマットの違い

今回利用するPrometheusが扱う形式とは少し異なります。
連携先とするツールによって成形されるフォーマットが少し異なるということがわかります。

Prometheusと連携用に用意したサービスの動作確認も行いましょう。

# 動確(以下のような表示がされればSONiCから上がったTelemetry情報がTelegrafからとれている) $ curl -s http://127.0.0.1:9273/metrics | grep -E 'SAI_PORT_STAT_IF_IN_OCTETS|SAI_PORT_STAT_IF_OUT_OCTETS' --- # HELP sonic_counters_ethernet4_SAI_PORT_STAT_IF_IN_OCTETS Telegraf collected metric # TYPE sonic_counters_ethernet4_SAI_PORT_STAT_IF_IN_OCTETS untyped sonic_counters_ethernet4_SAI_PORT_STAT_IF_IN_OCTETS{device="sonic-101",host="Monitor",path="/COUNTERS/Ethernet4",source="127.0.0.1"} 169397 # HELP sonic_counters_ethernet4_SAI_PORT_STAT_IF_OUT_OCTETS Telegraf collected metric # TYPE sonic_counters_ethernet4_SAI_PORT_STAT_IF_OUT_OCTETS untyped sonic_counters_ethernet4_SAI_PORT_STAT_IF_OUT_OCTETS{device="sonic-101",host="Monitor",path="/COUNTERS/Ethernet4",source="127.0.0.1"} 10236 ---

問題なく値が取れていることがわかります。
curlで取得した値を見ると、先ほど図で説明した形式になっていることがわかるかと思います。

3. Prometheus

3-A. 構築・起動手順/Prometheus

次に収集したデータを管理する部分を構築します。
この部分は「本連載で作る環境と検証シナリオ」章(第1回)で説明した通り、Prometheusを利用します。

Prometheusは時間の経過にあわせて変動する値である「時系列データ」の管理を得意とするOSSで、アラート機能も有する点も特徴といえます。
データの検索や集計には「PromQL」と呼ばれる独自のクエリ言語(問い合わせ言語)を利用します。
このことから広義にはNoSQLに該当し、時系列データの管理が得意であることから「時系列データベース」として扱われることが多いかと思います。

[補足3] InfluxDBとPrometheus

時系列データベースというと、先ほどデバッグ用ログの確認でも登場した「InfluxDB」も有名です。
[参考] RDBとNoSQLの比較,InfluxDBについては【RDBとNoSQL比較 前編】【同 後編】もご参照ください。前述のmeasurementやtag, fieldに関する説明もあります。

【監視入門 第1回】でも軽く触れましたが、TelegrafはInfluxDBとも相性が極めて高いといえ、可視化だけであればInfluxDBを扱うのも手かと思います。
一方、特定の条件下でアラートを出したい、という要件まで含む場合にはPrometheusを利用できるとよいかと思います。

今回の記事では「全体構成とシナリオ」章に記載した通りPrometheusのアラート発報(ルールベースの異常検知)は本題に含まず「補足」として扱っていきます。
また、記事の長さの関係でAIによる異常検知をした場合のPrometheusとの連動(=アラートの一元管理)は含めていません。
しかしながら監視をする上ではアラート通知も重要、かつ将来的にはルールベースだけでなく、AIによる異常検知もアラートとして、アラートを統合管理していくのが理想と考えます。
この将来性/拡張性から本記事ではPrometheusを採用しています。


では、Prometheus Podの構築手順を進めていきましょう。
TelegrafではConfigMapとDeployment、Serviceを分けてYAMLで定義しましたが、これらはまとめて1ファイルで定義することも可能です。
今回はまとめて定義してみます。

$ vim prometheus.yaml
apiVersion: v1 kind: ConfigMap metadata:   name: prometheus-config   namespace: telemetry data:   prometheus.yaml: |     global:       scrape_interval: 10s       evaluation_interval: 10s     scrape_configs:       - job_name: "telegraf-sonic"         scrape_interval: 10s         static_configs:           - targets:             - "telegraf-sonic-metrics.telemetry.svc.cluster.local:9273" --- apiVersion: v1 kind: PersistentVolumeClaim metadata:   name: prometheus-data   namespace: telemetry spec:   accessModes:     - ReadWriteOnce   resources:     requests:       storage: 5Gi --- apiVersion: apps/v1 kind: Deployment metadata:   name: prometheus   namespace: telemetry spec:   replicas: 1   selector:     matchLabels:       app: prometheus   template:     metadata:       labels:         app: prometheus     spec:       containers:         - name: prometheus           image: prom/prometheus:latest           imagePullPolicy: IfNotPresent           args:             - "--config.file=/etc/prometheus/prometheus.yaml"             - "--storage.tsdb.path=/prometheus"             - "--storage.tsdb.retention.time=3d"             - "--web.enable-lifecycle"           ports:             - containerPort: 9090               name: web           volumeMounts:             - name: prometheus-config               mountPath: /etc/prometheus/prometheus.yaml               subPath: prometheus.yaml               readOnly: true             - name: prometheus-data               mountPath: /prometheus       volumes:         - name: prometheus-config           configMap:             name: prometheus-config         - name: prometheus-data           persistentVolumeClaim:             claimName: prometheus-data --- apiVersion: v1 kind: Service metadata:   name: prometheus   namespace: telemetry spec:   type: NodePort   selector:     app: prometheus   ports:     - name: web       port: 9090       targetPort: 9090       nodePort: 30090

上の定義上重要な点は3点あります。

1点目は、ConfigMapに記載している「scrape_interval」と「evaluation_interval」についてです。
前述の通り、Prometheusのデータ取得方法はPull型となりますが、その間隔を指定するのがscrape_intervalです。
Telegrafに設定したデータ収集頻度とあわせて10秒としています。
また、「evaluation_interval」についてはPrometheusでのルールベースの異常判定の頻度などに利用されます。

2点目は、Deploymentで指定している「--storage.tsdb.retention.time=3d」という箇所です。
これは3日を超えたデータを自動的に削除する設定となります。
特にTelemetryは頻度高くデータを取得することもあり、ストレージ容量をひっ迫する可能性があるため、データ保持期間を定めて定期的に削除する仕組みが重要です。
今回は検証用環境なので少なめに設定しています。

3点目はNodePortを利用している点です。
ホストマシンからアクセスできるように、監視VMのポート番号30090をPodの9090へ転送するように設定しています。
このような設定はこの後説明するGrafanaでも同様にしています。

続いて起動を行います。

# 起動 $ kubectl apply -f prometheus.yaml
# 作成したPod確認(Prometheus PodのSTATUSがRunningになっていることを確認する) $ kubectl -n telemetry get pods --- NAME READY STATUS RESTARTS AGE telegraf-sonic-7b4ff98bcd-rdx9l 1/1 Running 0 10m prometheus-7f6588bb6d-hbtbh 1/1 Running 0 30s ---
# Prometheusのバージョン確認 $ kubectl -n telemetry exec deploy/prometheus -- prometheus --version --- prometheus, version 3.13.0 (branch: HEAD, revision: 40af9c2cdc0eda00f3622e867a27f6359f7295f3) (以下略) ---

3-B. 動作確認/Prometheus

Pod起動後、ホストマシンから「http://{監視VMのIPアドレス}:30090」でアクセスしてみましょう。
問題なく設定できていればPrometheusにアクセスできるはずです。
併せて、ブラウザでTelegrafから値が取れているかも確認します。
Status > Target health で telegraf-sonic が UP になっていればうまくいっています。

nw_ad1-29.png

図3-1 確認するPrometheus画面

4. Grafana

4-A. 構築・起動手順/Grafana

続いてGrafanaも用意します。
Grafanaは様々なデータソースにあるデータを可視化したりアラート設定ができるOSSです。
今回はPrometheusにあるデータをGrafanaで可視化します。
Prometheusは時系列データを扱うことに長けたツールでしたが、Grafanaは時系列グラフ以外にも様々な表示が可能です。(例えばTable表示など。今回は時系列グラフを利用します)

Prometheusと同様、ConfigMap/Deployment/Serviceなどの条件をまとめてyamlファイルに定義します。

$ vim grafana.yaml
apiVersion: v1 kind: ConfigMap metadata:   name: grafana-datasources   namespace: telemetry data:   datasource.yaml: |     apiVersion: 1     datasources:       - name: Prometheus         type: prometheus         access: proxy         url: http://prometheus.telemetry.svc.cluster.local:9090         isDefault: true --- apiVersion: v1 kind: PersistentVolumeClaim metadata:   name: grafana-data   namespace: telemetry spec:   accessModes:     - ReadWriteOnce   resources:     requests:       storage: 2Gi --- apiVersion: apps/v1 kind: Deployment metadata:   name: grafana   namespace: telemetry spec:   replicas: 1   selector:     matchLabels:       app: grafana   template:     metadata:       labels:         app: grafana     spec:       containers:         - name: grafana           image: grafana/grafana:latest           imagePullPolicy: IfNotPresent           env:             - name: GF_SECURITY_ADMIN_USER               value: "admin"             - name: GF_SECURITY_ADMIN_PASSWORD               value: "admin"           ports:             - containerPort: 3000               name: web           volumeMounts:             - name: grafana-data               mountPath: /var/lib/grafana             - name: grafana-datasources               mountPath: /etc/grafana/provisioning/datasources/datasource.yaml               subPath: datasource.yaml               readOnly: true       volumes:         - name: grafana-data           persistentVolumeClaim:             claimName: grafana-data         - name: grafana-datasources           configMap:             name: grafana-datasources --- apiVersion: v1 kind: Service metadata:   name: grafana   namespace: telemetry spec:   type: NodePort   selector:     app: grafana   ports:     - name: web       port: 3000       targetPort: 3000       nodePort: 30300

上の定義で触れておきたい点は2点あります。

1点目はConfigMap内にデータソース(どこを参照するか=今回はPrometheus)を指定している点です。
Grafanaは前述の通り様々なデータソースと連携できます。
将来的に別の方法でデータ管理をする場合(例えばOpenSearchやPostgreSQLなど)はここを設定することで簡単に拡張が可能です。

もう1点はDeployment内で管理者ID/PWを直接指定している点です。
本来はsecretsで管理すべきものですが、手順簡略化のために今回はこのような記載としています。

Prometheusと同様にGrafanaも以下のように起動します。

# 起動 kubectl apply -f grafana.yaml
# 作成したPod確認(grafana PodのSTATEがRunningになっていることを確認する) $ kubectl -n telemetry get pods --- NAME READY STATUS RESTARTS AGE telegraf-sonic-7b4ff98bcd-rdx9l 1/1 Running 0 20m prometheus-7f6588bb6d-hbtbh 1/1 Running 0 10m grafana-6b55bf5664-8nqvq 1/1 Running 0 25s ---
# Grafanaのバージョン確認 $ kubectl -n telemetry exec deploy/grafana -- grafana -v --- grafana version 13.1.0 ---

4-B. 動作確認/Grafana

Pod起動後、ブラウザから「http://{監視VMのIP}:30300」にアクセスします。
ログイン画面が最初は開かれるかと思いますが、初期はID/PWはどちらもadminとなります。

ログイン後、早速値が取れているか確認してみましょう。
一度登録したものが後からも確認できるようにダッシュボード作成と合わせて確認を行います。

まずは左側にあるサイドメニュからDashboardsを選択し、次に右に表示される「New」から「New dashboard」を選択します。

nw_ad1-30.png

図4-1 GrafanaでのDashboard作成①

次に「Panel」欄にある「+」を押し、その後、Configure visualization を押下します。

nw_ad1-31.png

図4-2 GrafanaでのDashboard作成②

Data sourceが「Prometheus」になっていることを確認し、Codeモードに変更します。 凡例を増やしたい場合は下の「Add Query」を押下します。

nw_ad1-32.png

図4-3 GrafanaでのDashboard作成③

今回はSONiCのEthernet4(eth2), Ethernet8(eth3), Ethernet12(eth4)をみたい為、3つの枠を用意しましょう。

それぞれのクエリ欄にPromQLで以下を入れます。

# 1つ目 rate(sonic_counters_ethernet4_SAI_PORT_STAT_IF_IN_OCTETS[30s]) * 8
# 2つ目 rate(sonic_counters_ethernet8_SAI_PORT_STAT_IF_IN_OCTETS[30s]) * 8
# 3つ目 rate(sonic_counters_ethernet12_SAI_PORT_STAT_IF_IN_OCTETS[30s]) * 8

それぞれ直近30秒間におけるIFカウンターの増加量から、平均bpsを算出して表示します。
rate()は指定した期間におけるカウンター値の増加量から、1秒あたりの平均増加量を算出する関数です。
Telemetryで取得したIFカウンターはoctetsで来る為、bpsレベルで見るため、bit化(×8)しています。

ちなみにEthernet8とEthernet12はSONiCから試験トラヒック用サーバ(ns-serv-a/ns-serv-b)にそれぞれパケットを出力するIFの為、OUT_OCTETSを指定するべきなのでは、と思われる方もいるかもしれません。
この点はその通りなのですが、検証したSONiC VS版ではOUT_OCTETSカウンターが上がらずIN_OCTETSカウンターが上昇したため、今回の検証ではIN_OCTETSを指定しています。

設定後、「Run queries」を押下します。

nw_ad1-33.png

図4-4 GrafanaでのDashboard作成④

Run queriesを実行すると、実際に取得した値が可視化されます。
IFごとの流量がわかりやすいようにPanel Stylesから積み重なりグラフを選択しておきましょう。
上手く値が取れていれば上の画面のように実際にグラフ上にトラヒック量の波形が表示されます。

あとは毎回queryを登録しなくて良いように保存しておきましょう。
右上のSaveを押下します。

その後、dashboardのタイトルを入れて、Saveを押します。

nw_ad1-34.png

図4-5 GrafanaでのDashboard作成⑤

これでDashboardとして保存が完了しました。
以降はサイドメニュのDashboardsから保存したダッシュボードを選択することで、先ほどのグラフを再設定不要で確認できます。

Dashboardでの確認画面の参考が以下の図となります。

nw_ad1-35.png

図4-6 作成したDashboard確認(Grafanaによる可視化画面確認)

ちなみにDashboard作成で利用したPromQLですが、今回はTelemetry取得対象機器(SONiC)が1台しかいない為、機器での絞り込みをせずとも対象機器の情報のみが表示されます。
台数が増えた場合は、上のままだと全台数の表示がされますが、それでは見づらい場合は前述の通り付与したtagであったりIPアドレスで判定しましょう。
以下に機器絞り込みをした例を記載します。

# tagのdevice名で絞り込む場合 rate(sonic_counters_ethernet4_SAI_PORT_STAT_IF_IN_OCTETS{device="sonic-101"}[30s]) * 8
# IPアドレスで絞り込む場合(ポートフォワードしている為、127.0.0.1で取得される) rate(sonic_counters_ethernet8_SAI_PORT_STAT_IF_IN_OCTETS{source = "127.0.0.1"}[30s]) * 8

本記事では汎用化した例としてデバイス絞り込みをしないパターンで記載します。

最後にこれまでの設定から各コンポーネント間の連携概要図を以下に示します。

nw_ad1-36.png

図4-7 各コンポーネントの連携図

これで監視/可視化基盤の構築は完了です。
本編としての準備は今回の記事ではここまでとなりますが、参考としてルールベースでの異常検知の準備/構築に関して以降で説明します。
気になる方はあわせてご確認ください。

【補足】5. Prometheusによるルールベース異常判定の準備

5-A. 設計・方針整理/ルールベース検知準備

ここからは本題に+αする補足です。
長くなるのでこの補足は節と項にわけて紹介します。

テーマはPrometheusを用いたルールベースの異常検知の実現ですが、ここでは事前準備や環境の作り方について説明します。
実際の動作確認は第5回で紹介します。

前提として、ここまでの本文の内容は実施できているものとします。

作る環境の構成は「全体構成とシナリオ章」でも紹介した通り、Prometheusでルールベースの異常判断を行い、その後のアラート通知はAlertmanagerと呼ばれる別のツールが担当します。
Alertmanagerはメール通知のほか、Webhook(イベントが起きたら特定の宛先にPostリクエストを送る)などにも対応しており高い汎用性がありますが、今回はAlertmanagerまでアラートが流れることまでを確認したいと思います。

nw_ad1-37.png

図5-1 ルールベース異常判定も含めた各コンポーネントの連携図

なお、条件に合致したら出力するアラートですが、Prometheusから出力するアラートとAlertmanagerから出力するアラートがあります。
ここで便宜上、前者をシンプルにアラート、後者をアラート通知と呼びます。

nw_ad1-38.png

図5-2 アラートとアラート通知について

Prometheusの異常判定条件はPromQLで記載でき、様々な条件をある程度柔軟に設定できます。
今回は条件指定の柔軟性が伝わるようにAnd条件を踏まえた検知にしてみましょう。
以下の条件を設定したいと思います。
   ・アラートA: SONiCのEthernet4が5Mbps以上通信している and SONiCのEthernet8が5Mbps以上通信している
   ・アラートB:SONiCのEthernet4が5Mbps以上通信している and SONiCのEthernet12が5Mbps以上通信している

要は経路1(ns-serv-a)と経路2(ns-serv-b)のアラートをそれぞれ用意している、ということです。
AI異常検知も含めた使い分けを考えると、通常時はおおむね1Mbpsしか流れていない、かつ最大6Mbpsしか通せないNWがあるとして、5Mbps使われていたら枯渇が近いため、ルールベースで異常を通知し、普段と傾向が変わったら(1Mbpsから3Mbpsになる)AI異常検知で判定される、というようなイメージです。

とはいえ、普段1Mbpsしか流れていないところから5Mbps以上を流すとAI異常検知側でも「異常」と判定され、経路が切り替わるかと思います。
「全体構成とシナリオ」章でもふれたとおり、経路切り替えは大量トラヒック流入の解決案になりえない場合があります(冗長化されているルートに経路切り替えにより意図的に大量通信を仕向ける場合などもあるため、必ずしも解決しないというわけではありません。)
特に今回の構成では経路を変えることで解決はしません。

したがって5Mbpsの流入が続くと、アラートAが発生した後、アラートBも発生します。
これは同一事象(アラートAとBは関係している)、かつ今回の対処(経路切り替え)ではこの事象が解決できないことを示すものとも言えます。
関連しているアラートは、【監視入門 第2回】でも触れたとおり、連携させられるとより良いかと思います。

そこでアラートAとBを関連させたアラートCも用意します。
   ・アラートC: 直近5分以内にアラートAとアラートBが発生する

これでアラート同士の関連付けのイメージもつけることができれば、と思います。

Prometheusのアラート条件もyamlで記載します。
アラート条件そのものをConfigMapとしてprometheus.yamlにまとめて書くこともできますが、実運用ではアラートは様々なものを用意したり運用の結果を踏まえて条件が少し変わったりする場合もあるかと思うので、prometheus本体の動作を定義したprometheus.yamlから切り離してアラート条件のyamlを記載します。
各アラート.yamlは、本体yamlから参照される形とします。

またアラートAとアラートBは、Ethernet4を見る点は共通ですし、アラートCもアラートA/Bを前提とする為、アラートAとBのロジックは再利用可能なようにRecording Rule(計算結果を再利用できる仕組み)として別yamlに記載します。
したがってアラートAとBの定義はあくまで条件にヒットしたときにアラートを上げるものとします。
この辺は言葉だけだとイメージがしづらいかと思いますので、この後説明するConfigを見てイメージを掴んでもらえれば、と思います。

nw_ad1-39.png

図5-3 今回用意するYAMLファイルの関連図

なお、Prometheusについては保守者が利用する画面にもなるため、設定変更をした際に極力ダウンタイムが起きないことが理想でしょう。
最もシンプルな例はアラート用のConfigMapを作り、prometheus.yamlのDeploymentに追加マウントするケースですが、これではprometheus.yamlのapplyが必要になり、Prometheus Podの再作成が起きます。
そこでアラートルールの変更があってもprometheus.yamlのDeploymentを変更しない(prometheus.yamlを更新しない)形で反映し、アラートルールの最新状態の反映にダウンタイムが起きないような構成/手順を用意したいと思います。(ただし初回/アラートルールに対応した追加設定の反映だけはPod再作成が起きます)

5-B-1. 構築手順(ルール定義)/ルールベース検知準備

では手順について紹介します。
まずはアラートルール用のディレクトリを切っておきましょう。
このフォルダ配下でアラートルールを追加したり変更したりする運用とし、ルール変更があればprometheus.yamlの変更なしに反映できるように作っていきたいと思います。

# 異常検知ルール用のフォルダ作成 $ mkdir prometheus_rules

まずアラートAとBのロジックを記載するRecording Ruleを定義します。

$ vim prometheus_rules/sonic-traffic-recording.yaml
groups:   - name: sonic-traffic-recording     interval: 10s     rules:       - record: sonic:ethernet4:in_bps         expr: |           rate(sonic_counters_ethernet4_SAI_PORT_STAT_IF_IN_OCTETS[30s]) * 8       - record: sonic:traffic:path_a_high         expr: |           (             sonic:ethernet4:in_bps >= bool 5000000           )           * ignoring(path)           (             rate(sonic_counters_ethernet8_SAI_PORT_STAT_IF_IN_OCTETS[30s]) * 8 >= bool 5000000           )       - record: sonic:traffic:path_b_high         expr: |           (             sonic:ethernet4:in_bps >= bool 5000000           )           * ignoring(path)           (             rate(sonic_counters_ethernet12_SAI_PORT_STAT_IF_IN_OCTETS[30s]) * 8 >= bool 5000000           )

ポイントは3つあります。
1点目は、この後の他アラートの定義も同様ですが、まだk3sのConfigMapとして書かず、あくまでPrometheusのルールのみ記載する点です。
ConfigMap化は全ルールファイルができた後にまとめて対応します。

2点目はEthernet4の計算結果を「sonic:ethernet4:in_bps」として定義し、経路1および経路2の異常判定ロジック(sonic:traffic:path_a_high,sonic:traffic:path_b_high)に組み込んでいる点です。
経路1および経路2の異常判定ロジックのなかで、それぞれEthernet4の判定ロジックを記載する案もありますが、このほうが再利用ができてシンプルに見えるかと思います。

3点目はAnd条件の指定についてです。
Prometheusの処理ではラベル(系列)が一致しているもの同士が計算対象となります。
今回の式ではEthernet4とEthernet8, あるいは12を指定する為、系列が異なるもの同士のAndとなっており、そのままでは計算されません。
そこでignoringで系列情報を持つpathを指定することで、path(ラベル)不一致があっても計算されるように指定しています。
一方、今回はSONiC1台しかないですが、複数のSONiC機器があったとしても、ignoringでデバイス名(device)やIPアドレス(instance)を指定していない為、異なる機器同士の計算(SONiC1のEthernet4とSONiC2のEthernet8のようなパターン)はされません。
したがって、上の設定は「同じ機器の、異なるIF(path)で計算する」という式になっています。

計算式の内容は (1つ目の条件) × (2つ目の条件)となっています。
中身はBoolを指定しているため、条件を満たす場合は1、そうでない場合は0が返ります。
つまり 1×1 (どちらも満たしている場合)のみ 1が返ります。

続いて、アラートA/アラートB/アラートCの定義ファイルを用意します。

# アラートA用の定義ファイル用意 $ vim prometheus_rules/traffic-alert-a.yaml
# アラートB用の定義ファイル用意 $ vim prometheus_rules/traffic-alert-b.yaml
# アラートC用の定義ファイル用意 $ vim prometheus_rules/traffic-alert-c-correlation.yaml
# アラートA定義 groups:   - name: sonic-traffic-alert-a     interval: 10s     rules:       - alert: SonicTrafficPathAHigh         expr: |           sonic:traffic:path_a_high == 1         for: 0s         labels:           severity: warning           incident: sonic-high-traffic-demo           alert_stage: first_report           traffic_path: path-a         annotations:           summary: "経路A側で高トラヒックを検知しました"           description: |             直近30秒の平均bpsにおいて、Ethernet4とEthernet8がともに5Mbps以上となりました。
# アラートB定義 groups:   - name: sonic-traffic-alert-b     interval: 10s     rules:       - alert: SonicTrafficPathBHigh         expr: |           sonic:traffic:path_b_high == 1         for: 0s         labels:           severity: warning           incident: sonic-high-traffic-demo           alert_stage: first_report           traffic_path: path-b         annotations:           summary: "経路B側で高トラヒックを検知しました"           description: |             直近30秒の平均bpsにおいて、Ethernet4とEthernet12がともに5Mbps以上となりました。
# アラートC定義 groups:   - name: sonic-traffic-alert-c-correlation     interval: 10s     rules:       - alert: SonicTrafficFailoverIneffective         expr: |           (             max_over_time(sonic:traffic:path_a_high[5m]) == 1           )           and           (             max_over_time(sonic:traffic:path_b_high[5m]) == 1           )         for: 0s         labels:           severity: critical           incident: sonic-high-traffic-demo           alert_stage: escalation           traffic_path: multiple         annotations:           summary: "経路切替では高トラヒックが解消していない可能性があります"           description: |             過去5分以内に、経路A側条件と経路B側条件の両方で高トラヒックを検知しました。             既存の単純なIF切替では対処できていない可能性があるため、原因調査や別の対処が必要です。

アラートAとBについては、それぞれRecording Ruleで指定したロジック(sonic:traffic:path_a_high, sonic:traffic:path_b_high)を読み込み、それを満たす際の動作を設定しています。
どちらも1が返ってきた場合(条件を満たした場合)を指定しています。

nw_ad1-40.png

図5-4 Recording Ruleとアラートファイルの連動イメージ

アラートCもRecording Ruleで指定したロジックを利用しており、加えてAnd条件も指定しています。
「sonic:traffic:path_a_high」と「sonic:traffic:path_b_high」という異なる条件を組み合わせている一方、今回はignoringを使っていません。
これはどちらもアラートA・Bのロジックで系列が異なるものを計算した結果(0 or 1)を受け取っており、再度のignoringは不要の為です。

各アラートintervalを10秒としている為、10秒ごとに直近30秒の値から異常の有無を判定します。
prometheus.yamlでもevaluation_intervalを10秒としているので、interval指定がなくても10秒ごとの判定処理となりますが、alert定義に明示することができます。
evaluation_intervalはアラートファイルにintervalが書かれていなかったときのデフォルトの動きとなるため、今回のように両方書かれていた場合はアラートファイル側の起動間隔が優先されます。(今回は同じ10秒なので変わりません)

また「アラートA/B」と「アラートC」の扱いは変えています。
アラートAとBは「高トラヒック」を検知したもののIF切り替えで対処できる可能性があるため、「warning」レベルとしています。
一方、「アラートC」は「IF切り替えで対処できない(別途対処が必要)」となるため、アラートの温度感はアラートA/Bよりも上がる想定です。
したがって、アラートCのレベルはwarningより高いcriticalとしています。
その他に付与すべき情報(問題のグループ名(incident)やアラートのステージなど)もラベルとして追加付与しています。

これでルールの設定準備は完了です。
適用する際に、これらのファイルを一括でまとめる処理を行います。

5-B-2. 構築手順(Prometheus)/ルールベース検知準備

次にPrometheus本体の設定も加筆します。
以下に追加内容も含めた全文を記載します。(変更点はこの後説明します)

apiVersion: v1 kind: ConfigMap metadata:   name: prometheus-config   namespace: telemetry data:   prometheus.yaml: |     global:       scrape_interval: 10s       evaluation_interval: 10s     rule_files:       - /etc/prometheus/rules/*.yaml
    alerting:       alertmanagers:         - static_configs:           - targets:             - "alertmanager.telemetry.svc.cluster.local:9093"
    scrape_configs:       - job_name: "telegraf-sonic"         scrape_interval: 10s         static_configs:           - targets:             - "telegraf-sonic-metrics.telemetry.svc.cluster.local:9273" --- apiVersion: v1 kind: PersistentVolumeClaim metadata:   name: prometheus-data   namespace: telemetry spec:   accessModes:     - ReadWriteOnce   resources:     requests:       storage: 5Gi --- apiVersion: apps/v1 kind: Deployment metadata:   name: prometheus   namespace: telemetry spec:   replicas: 1   selector:     matchLabels:       app: prometheus   template:     metadata:       labels:         app: prometheus     spec:       containers:         - name: prometheus           image: prom/prometheus:latest           imagePullPolicy: IfNotPresent           args:             - "--config.file=/etc/prometheus/prometheus.yaml"             - "--storage.tsdb.path=/prometheus"             - "--storage.tsdb.retention.time=3d"             - "--web.enable-lifecycle"           ports:             - containerPort: 9090               name: web           volumeMounts:             - name: prometheus-config               mountPath: /etc/prometheus/prometheus.yaml               subPath: prometheus.yaml               readOnly: true
            - name: prometheus-rules               mountPath: /etc/prometheus/rules               readOnly: true
            - name: prometheus-data               mountPath: /prometheus
      volumes:         - name: prometheus-config           configMap:             name: prometheus-config
        - name: prometheus-rules           configMap:             name: prometheus-rules
        - name: prometheus-data           persistentVolumeClaim:             claimName: prometheus-data --- apiVersion: v1 kind: Service metadata:   name: prometheus   namespace: telemetry spec:   type: NodePort   selector:     app: prometheus   ports:     - name: web       port: 9090       targetPort: 9090       nodePort: 30090

変更箇所はConfigMapに「rule_files」と「alerting」の設定を加えた事と、Deploymentの「volumeMounts」「Volumes」欄に「prometheus-rules」(各アラートルールをConfigMap化したもの)に関する設定を加えた事です。

内容としては「prometheus-rules」をConfigMapとして紐づけ、かつそのConfigMapに記載された各ルール.yamlをPod内の「/etc/prometheus/rules」内に配置、かつ当該フォルダ内のすべてのyamlファイルを読み込む、という設定です。
prometheus-rulesファイルの詳細は後述します。

5-B-3. 構築手順(Alertmanager)/ルールベース検知準備

最後にAlertmanagerを定義しましょう。

$ vim alertmanager.yaml
apiVersion: v1 kind: ConfigMap metadata:   name: alertmanager-config   namespace: telemetry data:   alertmanager.yaml: |     global:       resolve_timeout: 5m     route:       receiver: "ui-only"       group_by:         - device         - incident
    inhibit_rules:       - source_matchers:           - severity="critical"           - incident="sonic-high-traffic-demo"         target_matchers:           - severity="warning"           - incident="sonic-high-traffic-demo"         equal:           - device           - incident
    receivers:       - name: "ui-only" --- apiVersion: apps/v1 kind: Deployment metadata:   name: alertmanager   namespace: telemetry spec:   replicas: 1   selector:     matchLabels:       app: alertmanager   template:     metadata:       labels:         app: alertmanager     spec:       containers:         - name: alertmanager           image: prom/alertmanager:latest           imagePullPolicy: IfNotPresent           args:             - "--config.file=/etc/alertmanager/alertmanager.yaml"             - "--storage.path=/alertmanager"           ports:             - containerPort: 9093               name: web           volumeMounts:             - name: alertmanager-config               mountPath: /etc/alertmanager/alertmanager.yaml               subPath: alertmanager.yaml               readOnly: true       volumes:         - name: alertmanager-config           configMap:             name: alertmanager-config --- apiVersion: v1 kind: Service metadata:   name: alertmanager   namespace: telemetry spec:   type: NodePort   selector:     app: alertmanager   ports:     - name: web       port: 9093       targetPort: 9093       nodePort: 30093

Alertmanagerの設定について大きく3点触れておきたいポイントがあります。
まず1点目は、ConfigMapにて「receiver: "ui-only"」としている点です。
前述の通りAlertmanagerはメール通知なども可能ですが、今回は検証の為、AlertmanagerのUI表示までとしています。

2点目はアラートのグループ化です。
アラートの内容(incident)と、発生した機器(device)でアラートをグループ化しています。
deviceはTelegrafがtagとして埋め込んだ情報をそのまま使います。

3点目は「inhibit_rules」についてです。
これは「もし同一incidentかつ同一deviceにてcriticalレベルの通知がきたら、warningの表示や通知を抑止する」という設定です。
warningはwarningで追えるようにしておきたい、というケースもあるかと思いますが、多くのアラートを飛ばすと重要なアラートが埋もれる可能性も上がります。
このあたりは設計/運用次第かとも思いますが、今回はシンプルな見た目、またこのような設定もあると紹介する為、あえて設定しています。

これで設定ファイルの準備は完了です。

5-C. 起動と動作確認/ルールベース検知準備

必要なファイルがそろったので、起動/適用をしていきましょう。
まずはAlertmanagerを起動と起動確認をします。

# Alertmanager起動 $ kubectl apply -f alertmanager.yaml
# Alertmanager動確/バージョン確認 $ kubectl -n telemetry exec deploy/alertmanager -- alertmanager --version --- alertmanager, version 0.33.1 (branch: HEAD, revision: 2c8da51e03f3dbbed24f9711ca2d76aab4eef9c5) (以下略) ---

次にPrometheusの各ルールファイルをまとめます。

# prometheus-rules.yamlを自動定義(prometheus_rulesフォルダ以下をまとめる) # dry-runで実際にConfigMapを作らず、作成内容だけyaml形式で表示する。それをリダイレクトで出力する。 $ kubectl -n telemetry create configmap prometheus-rules \   --from-file=./prometheus_rules \   --dry-run=client -o yaml > prometheus-rules.yaml
# ConfigMap反映 $ kubectl apply -f prometheus-rules.yaml

1点目のコマンドはdry-runとリダイレクト指定なしの場合、k3sクラスタにConfigMapは反映されるものの、どのような設定となったかファイルがローカルに残りません。
prometheus_rulesフォルダ配下にルールファイルはあるものの、運用を経て更新される場合などもあります。
前の記録を保持する為にも各ルールファイルをまとめたものを作成しておく形としています。

ちなみに作成した「prometheus-rules.yaml」の中身は以下のようになります。

apiVersion: v1 data:   sonic-traffic-recording.yaml: |     groups:       - name: sonic-traffic-recording         interval: 10s         rules:           - record: sonic:ethernet4:in_bps             expr: |               rate(sonic_counters_ethernet4_SAI_PORT_STAT_IF_IN_OCTETS[30s]) * 8           - record: sonic:traffic:path_a_high             expr: |               (                 sonic:ethernet4:in_bps >= bool 5000000               )               * ignoring(path)               (                 rate(sonic_counters_ethernet8_SAI_PORT_STAT_IF_IN_OCTETS[30s]) * 8 >= bool 5000000               )           - record: sonic:traffic:path_b_high             expr: |               (                 sonic:ethernet4:in_bps >= bool 5000000               )               * ignoring(path)               (                 rate(sonic_counters_ethernet12_SAI_PORT_STAT_IF_IN_OCTETS[30s]) * 8 >= bool 5000000               )   traffic-alert-a.yaml: |     groups:       - name: sonic-traffic-alert-a         interval: 10s         rules:           - alert: SonicTrafficPathAHigh             expr: |               sonic:traffic:path_a_high == 1             for: 0s             labels:               severity: warning               incident: sonic-high-traffic-demo               alert_stage: first_report               traffic_path: path-a             annotations:               summary: "経路A側で高トラヒックを検知しました"               description: |                 直近30秒の平均bpsにおいて、Ethernet4とEthernet8がともに5Mbps以上となりました。   traffic-alert-b.yaml: |     groups:       - name: sonic-traffic-alert-b         interval: 10s         rules:           - alert: SonicTrafficPathBHigh             expr: |               sonic:traffic:path_b_high == 1             for: 0s             labels:               severity: warning               incident: sonic-high-traffic-demo               alert_stage: first_report               traffic_path: path-b             annotations:               summary: "経路B側で高トラヒックを検知しました"               description: |                 直近30秒の平均bpsにおいて、Ethernet4とEthernet12がともに5Mbps以上となりました。   traffic-alert-c-correlation.yaml: |     groups:       - name: sonic-traffic-alert-c-correlation         interval: 10s         rules:           - alert: SonicTrafficFailoverIneffective             expr: |               (                 max_over_time(sonic:traffic:path_a_high[5m]) == 1               )               and               (                 max_over_time(sonic:traffic:path_b_high[5m]) == 1               )             for: 0s             labels:               severity: critical               incident: sonic-high-traffic-demo               alert_stage: escalation               traffic_path: multiple             annotations:               summary: "経路切替では高トラヒックが解消していない可能性があります"               description: |                 過去5分以内に、経路A側条件と経路B側条件の両方で高トラヒックを検知しました。                 既存の単純なIF切替では対処できていない可能性があるため、原因調査や別の対処が必要です。 kind: ConfigMap metadata:   name: prometheus-rules   namespace: telemetry

dataキーで定義した各ファイルを、prometheus.yamlで定義した/etc/prometheus/rules配下に配置する、という動作となります。
ConfigMapは「こういうルールファイルをPod内のフォルダに配置してね」という指定のみで、ルールそのものの読み取りはConfigMapではなく、Prometheus Pod内でファイル読み取りにて行います。
各ルール.yamlを読み取らせる為、prometheus.yamlではルールファイルの読み取りを「*.yaml」としています。

最後に適用を行います。

# k3s PodとしてのPrometheus設定反映 $ kubectl apply -f prometheus.yaml
# Prometheus自体のルール設定再読み込み(apply後一定時間待ってから実施) $ curl -X POST http://127.0.0.1:30090/-/reload

これで反映が完了したので、GUI上でも動作確認をしてみましょう。

まずPrometheus側の確認です。
本文と同様に「http://{監視VMのIP}:30090」にホストマシンからブラウザでアクセスし、Status > Rule healthにてRecording RuleやAlert Ruleが表示されるか確認しましょう。

nw_ad1-41.png

図5-5 Prometheusの確認画面(Rule health)

また、Alertmanagerとうまく連動できているかも確認します。
Status > Alertmanager discovery でActive表示が出ているか確認しましょう。

nw_ad1-42.png

図5-6 Prometheusの確認画面(Alertmanager discovery)

最後にAlertmanager側の画面をみてみましょう。
「http://{監視VMのIP}:30093」でアクセスし、以下のような画面がみれれば確認完了です。

nw_ad1-43.png

図5-7 Alertmanagerの確認画面

5-D. その他/ルールベース検知準備

最後に、ルールファイルが変更された場合の操作についても説明しておきます。
prometheus_rulesフォルダ配下でルールファイルが変更された場合は以下のように設定反映をします。

# ConfigMapの再作成 $ kubectl -n telemetry create configmap prometheus-rules \   --from-file=./prometheus_rules \   --dry-run=client -o yaml > prometheus-rules.yaml
# ConfigMap再反映。ApplyされてPrometheus Podから更新版定義ファイルが見えるようになるまで少し時間がかかる為、本コマンド実行後少し待つ。 $ kubectl apply -f prometheus-rules.yaml
# 一定時間経過後、反映コマンド実施 $ curl -X POST http://127.0.0.1:30090/-/reload

prometheus.yaml(Prometheus本体の設定)には「prometheus-rules ConfigMapを参照する」と定義されている為、当該ファイルの変更とApplyは不要です。
したがってPrometheusの再起動など挟まず、変更されたConfigの反映が可能となります。

これでほぼ準備は完了なのですが、最後に検証用に以下を試験トラヒック用サーバにて作成しておきます。

## 試験トラヒック用サーバで実施 # ルールベースの異常検知用トラヒック流入シェルスクリプトを作成 $ vim loop_iperf_rulebase.sh
#!/usr/bin/env bash
while true; do     # 6000Kbpsで出力し続ける。     sudo ip netns exec ns-cli iperf3 -u -b 6000k -c 10.10.99.1 -B 10.10.1.1 -t 10     sleep 0.5 done
$ sudo chmod +x ./loop_iperf_rulebase.sh

以上でルールベース異常検知の準備は完了です。
実際の動作については前述の通り第5回の「検証シナリオの実施と結果」にて説明します。


おわりに

今回は監視/可視化基盤の構築手順/動作を紹介し、GrafanaでSONiCのIF流量が可視化できることまで確認できました。
Telemetry、Kubernetes、Telegraf、Prometheus、Grafanaなど、扱う技術範囲が広かったかと思いますが、実施できることだけでなく、各技術についても本記事で理解が進んだのであれば幸いです。

次回は収集したデータをAI活用する「AIによる判断と制御基盤」の構築と関連する技術の紹介をしていきたいと思います。

今回も最後までご覧いただきありがとうございました。
もし何かご意見・コメント・ご質問などがあれば、以下からお問い合わせください。

本件に関するお問い合わせ

NTTテクノクロス
フューチャーネットワーク事業部

山口 佳輝

お問い合わせ

連載シリーズ
テクノロジーコラム
著者プロフィール
山口 佳輝
山口 佳輝

<<<<著者プロフィール>>>>

フューチャーネットワーク事業部
 第一ビジネスユニット
  山口 佳輝(YAMAGUCHI YOSHIKI)

NWに関係したシステム開発を担当しています

>>>>>>>>><<<<<<<<<