9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LLDP と AWS Neptune でネットワークの物理構成を DB化・可視化する

9
Last updated at Posted at 2025-12-09

はじめに

突然ですがネットワークエンジニアの皆さん、ネットワークの物理構成情報はどう管理していますか?
様々な管理方法があるかと思いますが、人力で資料を作成して管理されている方が多いのではないでしょうか。その場合、次のようなお悩みを抱えているかと思います。

  • 資料作成・修正が手間
  • 最新の情報が反映されておらず、資料が信用できない

そのお悩み、LLDP と AWS Neptune を組み合わせることで解決できるかもしれません。
LLDP で機器から物理構成情報を収集し Neptune に登録することで、正しい情報をデータベースに保持することができます。また、Neptune に登録された情報から簡単に物理構成図を描画することができます。
定期実行する仕組みを整えれば、自動で最新の状態を反映させることもできます。

本記事では、その方法についてご紹介します。

本記事で説明すること

  • LLDP を用いて取得した情報を Neptune に登録する方法
  • Neptune に登録した情報を可視化する方法

本記事で説明しないこと

  • LLDP を用いて情報を取得する方法
  • 情報の取得および DB への登録を定期実行する方法

前提知識

LLDP とは

LLDP (Link Layer Discovery Protocol) は、IEEE802.1AB で標準化されている L2 の情報を隣接機器と交換するプロトコルです。
情報を取得したい機器で LLDP を有効化し、showコマンドを実行することで隣接機器の情報が得られます。マルチベンダ構成のネットワークにも適用することが可能です。

今回は、複数の機器から正しい情報を得るために、ベンダに関係なく機器から直接情報を取得できる LLDP を使用しました。

AWS Neptune とは

AWS Neptuneとは、AWS フルマネージドのグラフデータベースサービスです。
グラフデータベースは、ノードとエッジという形でデータを保持し、リレーショナルデータベースのような表形式ではなくネットワーク構造でデータの関係を表します。
グラフデータベースについての詳細な説明は、以下を参考にしてみてください。

上記 URL で紹介されている例のように、ネットワーク構造を持つデータの管理やクエリには、グラフデータベースを用いるのが最適です。
普段我々が扱う "ネットワーク" は当然ネットワーク構造を持っており、グラフデータベースはまさしく "ネットワーク" の情報を保持するのに適していると言えます。

今回は、簡単にデータを可視化できるグラフデータベースとして Neptune を採用しました。
代表的なグラフデータベースとしてよく名前が挙がる Neo4j も可視化機能を提供していますが、将来的には定期実行の仕組みを AWS で実装する予定のため親和性の観点から Neptune を採用しました。

検証環境

AWS の構成

今回の検証では、以下のような構成をとりました。
EC2 から Neptune へ接続する際には、IAM認証を使用しました。

aws.png

本来、Neptune データベースを作成する際に AWS が提供している Jupyter Notebook を作成することができ、これがあるとすぐに Neptune に対してクエリを投げられるようになるのですが、今回は作成せず EC2 を立てて代用しました。
理由は、ありがちな社内ネットワークによる制約です。

DB 化するネットワークの構成図

LLDP で情報を取得し、Neptune にその情報を登録するネットワークの構成は以下の通りです。

network.png

典型的な spine-leaf の構成です。
ここでは、以下のような前提を置いています。

  • 全て Cisco 機器 (Cisco-IOS)
  • LLDP は全ての機器で事前に有効化されている
  • show lldp neighbors コマンドの結果を、テキストファイル形式で別の場所に保存できる仕組みがある
  • 上記テキストファイルは、EC2 からアクセスできる場所に保存されている (今回は EC2 のローカルに保存)

spine-01 で実行した show lldp neighbors コマンドの結果は以下のようになります (OS やバージョンによって結果は異なります) 。

spine-01_show_lldp_neighbors.txt
spine-01# show lldp neighbors
Capability codes:
    (R) Router, (B) Bridge, (T) Telephone, (C) DOCSIS Cable Device
    (W) WLAN Access Point, (P) Repeater, (S) Station, (O) Other

Device ID               Local Intf      Hold-time  Capability  Port ID
leaf-01.cisco           Eth1/1          120        BR          Eth1/1
leaf-02.cisco           Eth1/2          120        BR          Eth1/1
leaf-03.cisco           Eth1/3          120        BR          Eth1/1
leaf-04.cisco           Eth1/4          120        BR          Eth1/1
edge-rtr-01.cisco       Eth1/10         120        R           Gi0/0/1
edge-rtr-02.cisco       Eth1/11         120        R           Gi0/0/1

Total entries displayed: 6

実装手順

本記事で記載しているコードは、メイン部分のみを抜き出して記載したものです。
このままコピペしただけでは動作しないので、各自で編集・補完してください。

1. Neptune との接続

1-1. 事前準備

EC2 から Neptune へ接続するために、事前に必要な設定を行いましょう。

  1. EC2 や Neptune で、適切な IAMロールおよびセキュリティグループの設定を行う
  2. EC2 に、AWS が提供する graph-notebook という Neptune 操作用のソースコードをクローンする

 
3. 上記リポジトリの README を参考に、EC2 に必要なパッケージ等をインストールする
requirements.txt の内容をインストールします。
その後、README の Installation, JupyterLab 4.x (Recommended) に記載のある以下のコマンドを実行することで JupyterLab の実行環境が整います。

Dockerfile が用意されているので、そちらを利用しても良いですね。
 
4. JupyterLab を実行し Notebook を開く
以下のコマンドを実行します。

ブラウザで JupyterLab にアクセスし、01-Neptune-Database/01-Getting-Started/01-About-the-Neptune-Notebook.ipynb を開きます。
※ Notebook であればなんでも良いです。01-Neptune-Database 配下には、今回開いたファイルの他にもチュートリアル用の Notebook が複数あるので参考にしてみてください。

1-2. マジックコマンドで接続設定を行う

Notebook 上でセルを追加し、以下のコードを記載して実行します。

%graph_notebook_version

すると、結果が返ってきます。私の場合は 5.1.0 でした。

このコマンドは、名前の通り graph-notebook のバージョンを返すコマンドです。
このように、先頭に %%% がつくものをマジックコマンドと呼びます。
Neptune 操作用のマジックコマンドを AWS が提供してくれているのでそれを使用します。

では、マジックコマンドを使って Neptune への接続を確立してみましょう。
以下のマジックコマンドを実行します。

%graph_notebook_config

すると、以下のような結果が得られます。

{
 "host": "change-me",
 "neptune_service": "neptune-db",
 "port": 8182,
 "proxy_host": "",
 "proxy_port": 8182,
 "auth_mode": "default",
 "load_from_s3_arn": "",
 "ssl": true,
 "ssl_verify": true,
 "aws_region": "us-east-1",
 "sparql": {
     "path": "sparql"
 },
 "gremlin": {
     "connection_protocol": "websockets",
     "traversal_source": "g",
     "username": "",
     "password": "",
     "message_serializer": "GraphSONMessageSerializerV3"
 },
 "neo4j": {
     "username": "neo4j",
     "password": "password",
     "auth": true,
     "database": null
 }
}

自分の環境に合わせて値を書き換えます。
私の場合、"host", "port", "auth_model", "aws_region" を変更しました。
書き換え方法はとても簡単です。以下のように、先ほどのコマンドの先頭を % から %% に変更して、出力結果をコマンドの下にコピペし値を書き換えて実行するだけです。

%%graph_notebook_config
{
 "host": "YOUR-NEPTUNE-ENDPOINT",
 "neptune_service": "neptune-db",
 "port": YOUR-NEPTUNE-PORT,
 "proxy_host": "",
 "proxy_port": 8182,
 "auth_mode": "IAM",
 "load_from_s3_arn": "",
 "ssl": true,
 "ssl_verify": true,
 "aws_region": "YOUR-NEPTUNE-REGION",
 "sparql": {
     "path": "sparql"
 },
 "gremlin": {
     "connection_protocol": "websockets",
     "traversal_source": "g",
     "username": "",
     "password": "",
     "message_serializer": "GraphSONMessageSerializerV3"
 },
 "neo4j": {
     "username": "neo4j",
     "password": "password",
     "auth": true,
     "database": null
 }
}

1-3. Neptune との接続性を確認する

Neptune と接続できるかを確認します。以下のマジックコマンドを実行します。

%status

すると、以下のような結果が得られます。
'status' が 'healthy' になっていれば無事に接続することができます。
'unhealthy' の場合は、もう一度 graph_notebook_config や IAMロール、セキュリティグループの設定を見直してみてください。

{'status': 'healthy',
 'startTime': 'Wed Dec 10 10:00:00 UTC 2025',
 'dbEngineVersion': '1.4.5.1.R1',
 'role': 'writer',
 'dfeQueryEngine': 'viaQueryHint',
 'gremlin': {'version': 'tinkerpop-3.7.1'},
 'sparql': {'version': 'sparql-1.1'},
 'opencypher': {'version': 'Neptune-9.0.20190305-1.0'},
 'labMode': {'ObjectIndex': 'disabled',
  'ReadWriteConflictDetection': 'enabled'},
 'features': {'SlowQueryLogs': 'disabled',
  'InlineServerGeneratedEdgeId': 'disabled',
  'ResultCache': {'status': 'disabled'},
  'IAMAuthentication': 'enabled',
  'Streams': 'disabled',
  'AuditLog': 'disabled'},
 'settings': {'StrictTimeoutValidation': 'true',
  'clusterQueryTimeoutInMs': '120000',
  'SlowQueryLogsThreshold': '5000'}}

2. LLDP データの取得・パース

Neptune に接続できたら、実際にデータを登録します。
ここでは、show lldp neighbors コマンドの実行結果をパースする流れを簡単に説明します。
なお、以降のコードは将来の定期実行を見据えて Notebook ではなくスクリプト形式で記述しています。

2-1. TextFSM テンプレートファイルをダウンロードする

TextFSM テンプレートファイルをダウンロードします。
TextFSM は、Google が提供する Python ライブラリで、ネットワーク機器が出力するような半構造化されたテキストをパースする機能を提供します。
今回は、同じく Google が提供するテンプレートファイルを使用してパースを行います。

2-2. 結果をパースする

2-1. でダウンロードしたファイルを使って show lldp neighbors コマンドの結果をパースします。

from pathlib import Path
from typing import Dict, List
from textfsm import TextFSM

def parse_lldp_file(file_path: str, local_hostname: str) -> List[Dict]:
    """
    単一のshow lldp neighborsコマンド出力ファイルを解析してネイバー情報を抽出
    
    Args:
        file_path (str): 解析するLLDPファイルのフルパス
        local_hostname (str): show lldp neighborsコマンドを実行した機器のホスト名
    
    Returns:
        List[Dict]: 解析された隣接機器情報のリスト
    """
          
    # TextFSMテンプレートの読み取り
    with open(template_file, 'r') as template:
        fsm = TextFSM(template)

    # show lldp neighborsコマンドの出力結果ファイルの読み取り
    with open(file_path, 'r') as f:
        lldp_outputfile = f.read()

    # パースの実施
    results = fsm.ParseText(lldp_outputfile)

    # パースの結果を格納
    parsed_data = []
    for result in results:
        neighbor_data = {
            'local_hostname': local_hostname,
            'neighbor_hostname': result[0],
            'local_interface': result[1], 
            'capabilities': result[2],
            'neighbor_interface': result[3]
        }
        parsed_data.append(neighbor_data)
    return parsed_data
          
def parse_all_lldp_files(directory: str) -> List[Dict]:
    """
    指定ディレクトリ内のすべてのLLDPファイルを一括解析
    
    Args:
        directory (str): LLDPファイルが格納されているディレクトリパス
    
    Returns:
        List[Dict]: 全ファイルから解析された隣接機器情報の統合リスト
                    各辞書の構造は parse_lldp_file() の戻り値と同じ
    """
        
    all_data = []
    lldp_files = list(Path(directory).glob("*_show_lldp_neighbors.txt"))
        
    if not lldp_files:
        return all_data
        
    for file_path in lldp_files:
        # ファイル名から機器名を抽出
        filename = file_path.name
        local_hostname = filename.replace('_show_lldp_neighbors.txt', '')
            
        file_data = parse_lldp_file(str(file_path), local_hostname)
        all_data.extend(file_data)
            
    return all_data

'local_hostname' に関しては、show lldp neighbors コマンドの結果に記載がないためファイル名に埋め込んでおくなどの対応が別途必要です。今回の場合は、HOSTNAME_show_lldp_neighbors.txt という形式でホスト名が埋め込まれていると仮定しています。

3. Neptune にデータを登録する

3-1. Neptune との接続を確立する

スクリプトで Neptune にクエリを行う場合は、Neptune への接続を確立するコードが必要になります。AWS が サンプルコードを提供してくれているので参考にしてみてください。
Neptune は複数のクエリ言語をサポートしていますが、今回は Gremlin で Neptune への接続、クエリを行います。

3-2. データを Neptune に登録する

今回は show lldp neighbors コマンドで得られる情報を基に、以下の情報を登録しました。

  • ノード
    • ホスト名
    • デバイスのタイプ (L2SW, L3SW, ルータ)
  • エッジ
    • 送信元インターフェース
    • 宛先インターフェース

以下のような形で、ノードとエッジを登録することができます。

# ノードの追加
g.addV("vertex_label").property('hostname', hostname).
                       property('type', device_type)
                       
# エッジの追加
g.V().has('hostname', source_device).addE("edge_label").
    to(__.V().has('hostname', target_device)).
    property('source_interface', source_interface).
    property('target_interface', target_interface)

ここまでの内容を参考にスクリプトを作成し、Neptune への接続とクエリを実行してください。

4. データの確認および可視化

4-1. Neptune 上のデータを確認する

Neptune にデータを登録できているか確認するため、Notebook 上で以下のクエリを実行します。
このクエリは、全てのノードとそのノードから出る全てのエッジを出力するクエリです。

%%gremlin -p v,e,v -l 30 -le 30
g.V().
    outE().
    otherV().
    path().
    by('hostname').
    by('source_interface')

all_query.png

出力結果の Graph タブを開いて確認してみると、正常に登録できていそうです。

別のクエリも試してみましょう。
例えば、edge-rtr-01 と接続されている機器と edge-rtr-01 側のインターフェース情報が知りたいときは、以下のクエリを実行します。

%%gremlin -p v,e,v -l 30 -le 30
g.V().has('hostname','edge-rtr-01').
      outE().
      otherV().
      path().
      by('hostname').
      by('source_interface')

edge-rtr-01_query.png

edge-rtr-01 の show lldp neighbors コマンドの結果は以下のようになっているので、正しく出力されていることが分かります。

edge-rtr-01_show_lldp_neighbors.txt
edge-rtr-01# show lldp neighbors
Capability codes:
    (R) Router, (B) Bridge, (T) Telephone, (C) DOCSIS Cable Device
    (W) WLAN Access Point, (P) Repeater, (S) Station, (O) Other

Device ID            Local Intf      Hold-time   Capability   Port ID
spine-01.cisco       Gi0/0/1         120         BR           Eth1/10
spine-02.cisco       Gi0/0/2         120         BR           Eth1/10

Total entries displayed: 2

クエリの雛形を用意しておけば、このように欲しい情報だけをすぐにピックアップして可視化することもできますね。

これで、ネットワーク構成をデータベース化することができました。
Neptune は前述したように、AWS のフルマネージドサービスでクエリ結果を簡単に可視化できるというメリットがあります。
show lldp neighbors コマンドとスクリプトを定期実行する仕組みをつくっておけば、自動で情報が Neptune に反映されるため、資料更新の手間や記載ミス、更新忘れも無くなります。

4-2. 可視化について

graph-notebook を用いることでクエリ結果を可視化するのは簡単ですが、このままだと物理構成図としては少し見づらいです。そこで、Cytoscape.js を用いて簡単な可視化アプリを作成してみました。
Cytoscape.js は、ノードとエッジという形式で情報を渡すことで簡単に可視化してくれる JavaScript ライブラリです。

cytoscape.png

試作段階なのでまだまだ改善の余地はありますが、全体構成の俯瞰にはこちらのほうが良さそうです。
ノードは Neptune に登録したデータをそのまま渡すだけですが、エッジに関しては少し工夫をしました。
例えば edge-rtr-01 - spine-01 間について、Neptune にエッジを登録した際は edge-rtr-01 → spine-01 のエッジと spine-01 → edge-rtr-01 のエッジの二つを登録しています。そのため、Notebook でクエリを実行した結果はノード間にエッジが二つ描画されています。しかし、Cytoscape.js で可視化を行った際には二つのエッジを一つのエッジにまとめてデータを登録しました。
また、今回は show lldp neighbors コマンドの結果だけを用いましたが、他のshowコマンドで VLAN や IPアドレスといった情報を取得して Neptune に登録することで、物理構成図だけでなく論理構成図も自動で描画できそうですね。

さいごに

LLDP を用いて取得したデータを AWS Neptune に登録することで、ネットワーク構成のデータベース化を行いました。Neptune にデータを登録すれば、高可用かつ安全な状態でデータを保持し高速なクエリでデータを取得することができます。また、コマンドやスクリプトを定期実行する仕組みをつくっておけば、自動で情報が Neptune に反映されるため、資料更新の手間や記載ミス・更新忘れが無くなります。
さらに、AWS が提供する graph-notebook で Neptune へのクエリ結果を簡単に可視化することができました。物理構成図としての機能を持たせるにはまだ少し手を加える必要がありそうですが、簡易な図で問題ない、図は不要でクエリによって欲しい情報を取得するだけでよい、という場合には graph-notebook が有効ですね。

9
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?