APRESIA Technical Blog

SONiCの物理インタフェースのカウンタをPrometheusで監視するスクリプトを書いてみた

はじめに

ホワイトボックススイッチのOSSのNOSであるSONiCに対して、監視ツールであるPrometheusにて物理インタフェースのカウンタをモニタリングするサンプルスクリプトを作成しました。
import time
import redis
from prometheus_client import start_http_server, Gauge

if_rx_octets = Gauge('SONIC_RX_OCTETS', 'Rx Octets', ['ifName'])
if_tx_octets = Gauge('SONIC_TX_OCTETS', 'Tx Octets', ['ifName'])

ports = ['Ethernet0', 'Ethernet4']
r = redis.Redis(host='localhost', port=6379, db=2)

def get_counters(port):
    oid = r.hget('COUNTERS_PORT_NAME_MAP', port).decode()
    rx_octets = int(r.hget('COUNTERS:' + oid, 'SAI_PORT_STAT_IF_IN_OCTETS').decode())
    tx_octets = int(r.hget('COUNTERS:' + oid, 'SAI_PORT_STAT_IF_OUT_OCTETS').decode())
    return rx_octets, tx_octets

def set_prometheus_data(port, rx, tx):
    if_rx_octets.labels(port).set(rx)
    if_tx_octets.labels(port).set(tx)

if __name__ == '__main__':
    start_http_server(8888)

    while True:
        for port in ports:
            rx, tx = get_counters(port)
            set_prometheus_data(port, rx, tx)
            
        time.sleep(20)
このスクリプトによって、どのようにPrometheusからSONiCの物理インタフェースのカウンタを監視できるのか説明いたします。

用意する環境

今回の実験に用意した環境は以下です。

図1 実験構成

ホワイトボックススイッチはWedge100BF-32X(Barefoot Tofinoを搭載したP4プログラマブルスイッチ)を使い、SONiCのOSSコミュニティにてデイリービルドされているSONiC 202012ブランチをインストールしました。SONiCのデイリービルド環境は最近変わりまして、例えばBarefoot用のSONiC 202012ブランチのビルド済バイナリはこちらから入手可能です。今回の実験では、BuildId=24002のSONiCのインストールイメージ(sonic-barefoot.bin)を使用しました。SONiCはホワイトボックススイッチ上で動作しますが、DebianベースのLinuxですので、Linuxサーバと同じ感覚でツールを追加することが可能です。 Prometheusはこちらからダウンロードしました。本実験では、監視用サーバにはprometheus-2.27.1.linux-amd64.tar.gzを、SONiCにはnode_exporter-1.1.2.linux-amd64.tar.gzをインストールしました。SONiCにてnode_exporterを起動して、Prometheusから監視すると、それだけでもCPU使用率や、メモリ、温度センサーなどをモニタリングすることが可能です。以下は、Wedge100BF-32XにインストールしたSONiCにてnode_exporterを起動し、外部サーバのprometheusにてモニタリングした結果です。

図2 node_exporterによるWedge100BF-32X・SONiC環境のモニタリング

SONiCのRedis DBのカウンタ情報を取得

今回の実験では以下のように二つの100Gポート(Ethernet0, Ethernet4)を用意しました。この二つの物理ポートの送受信カウンタをPrometheusにてモニタリングできるようにすることが本記事のゴールです。
admin@sonic1:~$ show interfaces status
  Interface       Lanes    Speed    MTU    FEC      Alias    Vlan    Oper    Admin             Type    Asym PFC
-----------  ----------  -------  -----  -----  ---------  ------  ------  -------  ---------------  ----------
  Ethernet0  0, 1, 2, 3     100G   9100   none  Ethernet0   trunk      up       up  QSFP28 or later         N/A
  Ethernet4  4, 5, 6, 7     100G   9100   none  Ethernet4   trunk      up       up  QSFP28 or later         N/A
admin@sonic1:~$
SONiCは構成情報やアプリケーション間で交換する情報、SAIを通してデバイスに設定する内容などをデータベースとしてRedisに登録しています。SONiCにログインしてredis-cliを実行することで、CLIベースでデーターベースの内容を確認することが可能です。以下はRedisに登録されているカウンタ関連のキーを確認した例です。
admin@sonic1:~$ redis-cli
127.0.0.1:6379> select 2
OK
127.0.0.1:6379[2]> keys COUNTER*
 1) "COUNTERS:oid:0x1a00000000007c"
 2) "COUNTERS:oid:0x1a000000000079"
 3) "COUNTERS_PG_NAME_MAP"
 4) "COUNTERS:oid:0x15000000000075"
 5) "COUNTERS:oid:0x1a00000000007d"
 6) "COUNTERS:oid:0x1a000000000061"
 7) "COUNTERS_PORT_NAME_MAP"
 8) "COUNTERS_QUEUE_NAME_MAP"
 9) "COUNTERS:oid:0x1a000000000065"
10) "COUNTERS:oid:0x15000000000072"
11) "COUNTERS_QUEUE_PORT_MAP"
12) "COUNTERS:oid:0x1a000000000064"
13) "COUNTERS_GLOBAL_NAT:Values"
14) "COUNTERS:oid:0x1500000000005b"
15) "COUNTERS:oid:0x6000000000098"
16) "COUNTERS:oid:0x1a00000000007b"
17) "COUNTERS:oid:0x15000000000077"
18) "COUNTERS:oid:0x15000000000074"
...
上記を見ていただくと、カウンタはoid(object id)をキーとして登録されていることが分かります。物理ポートのカウンタを取得するためには、まず物理ポートカウンタのoidを調べて、そのoidからカウンタを取得する二つのステップが必要になります。物理ポートとoidの紐づけは以下のCOUNTERS_PORT_NAME_MAPに登録されています。
127.0.0.1:6379[2]> HKEYS COUNTERS_PORT_NAME_MAP
1) "Ethernet0"
2) "Ethernet4"
以下は、Ethernet0をキーとしてoidを取得する場合の例です。
127.0.0.1:6379[2]> HGET COUNTERS_PORT_NAME_MAP Ethernet0
"oid:0x100000000004f"
127.0.0.1:6379[2]>
上記のoidをキーにして、登録されているカウンタの種類を確認します。
127.0.0.1:6379[2]> HKEYS COUNTERS:oid:0x100000000004f
 1) "SAI_PORT_STAT_ETHER_STATS_FRAGMENTS"
 2) "SAI_PORT_STAT_ETHER_STATS_TX_NO_ERRORS"
 3) "SAI_PORT_STAT_PFC_5_RX_PKTS"
 4) "SAI_PORT_STAT_PFC_4_RX_PKTS"
 5) "SAI_PORT_STAT_PFC_2_RX_PKTS"
 6) "SAI_PORT_STAT_PFC_7_RX_PKTS"
 7) "SAI_PORT_STAT_PFC_0_RX_PKTS"
 8) "SAI_PORT_STAT_PFC_4_TX_PKTS"
 9) "SAI_PORT_STAT_PFC_2_TX_PKTS"
10) "SAI_PORT_STAT_PFC_1_TX_PKTS"
11) "SAI_PORT_STAT_PFC_0_TX_PKTS"
12) "SAI_PORT_STAT_ETHER_OUT_PKTS_9217_TO_16383_OCTETS"
13) "SAI_PORT_STAT_ETHER_STATS_UNDERSIZE_PKTS"
14) "SAI_PORT_STAT_ETHER_OUT_PKTS_4096_TO_9216_OCTETS"
15) "SAI_PORT_STAT_ETHER_IN_PKTS_1024_TO_1518_OCTETS"
16) "SAI_PORT_STAT_PFC_3_RX_PKTS"
17) "SAI_PORT_STAT_IF_IN_BROADCAST_PKTS"
...
多数のカウンタのキーが登録されていることが分かりました。今回は物理ポートの送受信カウンタをモニタリングするため、送信カウンタとしてSAI_PORT_STAT_IF_OUT_OCTETS、受信カウンタとしてSAI_PORT_STAT_IF_IN_OCTETSを、以下のようにそれぞれ取得することにします。
127.0.0.1:6379[2]> hget COUNTERS:oid:0x100000000004f SAI_PORT_STAT_IF_OUT_OCTETS
"51614495415"
127.0.0.1:6379[2]> hget COUNTERS:oid:0x100000000004f SAI_PORT_STAT_IF_IN_OCTETS
"51676894202"
127.0.0.1:6379[2]>
ここからは、上記と同様の操作をPython3で実施することを考えます。Python3にてRedisにアクセスする場合、redisのPython3ライブラリを使用します。以下のPython3スクリプトのように事前にRedisのDB = 2にアクセスできる状態にしておきます。
import redis
r = redis.Redis(host='localhost', port=6379, db=2)
上記にてRedisにアクセスする準備が完了すれば、以下のメソッドにて物理ポートの送受信カウンタを取得可能です。引数のportには'Ethernet0'のように物理インタフェースの名前が入ります。
def get_counters(port):
    oid = r.hget('COUNTERS_PORT_NAME_MAP', port).decode()
    rx_octets = int(r.hget('COUNTERS:' + oid, 'SAI_PORT_STAT_IF_IN_OCTETS').decode())
    tx_octets = int(r.hget('COUNTERS:' + oid, 'SAI_PORT_STAT_IF_OUT_OCTETS').decode())
    return rx_octets, tx_octets
上記のメソッドでRedisから物理インタフェースの送受信カウンタを取得できるようになりました。次に、この値をPrometheusからモニタリングできる状態にする方法を考えます。

送受信カウンタをPrometheusにてモニタリング

Prometheusから送受信カウンタをモニタリングできるようにするためには、node_exporterと同様にPrometheusからのHTTP queryに対して、送受信カウンタをメトリクスとして応答できるように簡易的なHTTPサーバを用意する必要があります。そのために、prometheus_clientというPython3のライブラリが用意されていますので、SONiCにて以下のようにインストールしておきます。
sudo pip3 install  prometheus_client
送受信カウンタをメトリクスに登録するためには、以下の事前準備をしておきます。
# ライブラリのインポート
from prometheus_client import start_http_server, Gauge

# 送受信カウンタをメトリクスとして登録するためのインスタンスの準備
if_rx_octets = Gauge('SONIC_RX_OCTETS', 'Rx Octets', ['ifName'])
if_tx_octets = Gauge('SONIC_TX_OCTETS', 'Tx Octets', ['ifName'])

# ポート番号8888としてHTTPサーバを起動
start_http_server(8888)
上記の準備が完了すれば、あとは定期的に送受信カウンタのメトリクスを以下のメソッドにて更新すれば、Prometheusからモニタリングができるようになります。以下のメソッドのportの引数には'Ethernet0'のような物理インタフェースの名前が、rxとtxには送受信オクテットカウンタが入ります。
def set_prometheus_data(port, rx, tx):
    if_rx_octets.labels(port).set(rx)
    if_tx_octets.labels(port).set(tx)
以上の一連の処理を一つにまとめると、冒頭のサンプルスクリプトになります。

サンプルスクリプトの動作確認

ここから冒頭に示したサンプルスクリプトの動作を確認していきます。このスクリプトをredis2prometheus.pyのファイル名でSONiCにて保存し、以下のように実行します。
python3 redis2prometheus.py
これによって、HTTPサーバが起動しますので、"http://<SONiCのIPアドレス>:8888"にwebブラウザでアクセスします。すると以下のようにHTTPサーバの応答が表示されますが、下部にサンプルスクリプトで登録したSONIC_RX_OCTETSとSONIC_TX_OCTETSがメトリクスとして取得できていることが分かります。

図3 サンプルスクリプトのHTTPサーバの応答

上記のメトリクスをPrometheusから取得できるように、Prometheus側の監視設定を変更します。例えば、prometheus.ymlに以下を追加します。(下部のSONiC_Interfaceが追加部分)
scrape_configs:
  # The job name is added as a label `job=` to any timeseries scraped from this config.
  - job_name: 'prometheus'

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
    - targets: ['localhost:9090']

  - job_name: 'node'
    static_configs:
      - targets: ['10.249.29.106:9100']

  - job_name: 'SONiC_Interface'
    static_configs:
      - targets: ['10.249.29.106:8888']
上記の変更をした上で、Prometheusをリロードして、Prometheus側でSONiCの物理インタフェースのカウンタをモニタリングができるか確認します。まずはPrometheusのGUI側でメトリクスが登録されていることを確認します。PrometheusのGUIは"http://<PrometheusのIPアドレス>:9090"にてアクセス可能です。以下のようにSONIC_RX_OCTETSとSONIC_TX_OCTETSが登録されていることが確認できます。

図4 Prometheusにて取得したサンプルスクリプトのメトリクスの確認

上記のPrometheusのメトリクスを使えば、以下のようにGrafanaにて可視化することができます。今回の実験では、ある一定の帯域をトラフィックジェネレータから送信していますので、送受信のオクテットカウンタが右肩上がりに増加していることが可視化されています。

図5 PrometheusによるSONiCとWedge100BF-32Xの物理インタフェースのモニタリング

最後に

今回は、SONiCのRedisにアクセスしてPrometheusにて物理インタフェースのカウンタをモニタリングするサンプルスクリプトをご紹介させていただきました。SONiCはマルチベンダ対応(異なるスイッチベンダ、異なるチップベンダをサポート)しておりますが、今回のサンプルスクリプトはベンダ依存が隠蔽されたRedis DBの情報にアクセスしているため、異なるハードウェアスイッチでも動作できると考えています。RedisもPrometheusもアクセスするためのクライアントライブラリが充実しているため、少ない労力でSONiCの物理インタフェースを監視する仕組みを作ることができました。SONiCは内部で多くのOSSを活用していますので、その良さを生かしたテクニックを今後もご紹介していく予定です。