🎨

NW図 as Code(1) PlantUMLを試す

2022/06/14に公開

はじめに

ネットワーク図ってどうやって書いていますか?
Visioですか? Excel? パワポで頑張る? diagrams.net(旧draw.io)?

ちょっとポートを追加したいとか、ノードを追加したいと思ったときに、隙間がなくて図をずらしたりするのが面倒くさいんですよね。

IaaSとかSaaS、PaaS[1] IaC(Infrastructure as Code)みたいな感じでコード(テキスト形式)でNW図を描きたいなと思って試してみました。

物理ネットワーク図

ネットワーク図は大きく分けると物理ネットワーク図と論理ネットワーク図があります。
最近は仮想化によってどこまで物理?どこから論理?の分界があいまいになってきているんですが、ざっくりいうと以下を記載します。

  • 物理ネットワーク図
    • 筐体
    • ネットワークインターフェイス(NIC)
    • ケーブル
    • ホスト名
  • 論理ネットワーク図
    • ネットワークセグメント
    • IPアドレス
    • ホスト名

まずは物理ネットワーク図を描いてみます。Googleさんに聞いてみたところ、以下のツールが使えそうです。

PlantUML

PlantUMLで書いてみましょう。
PlantUMLはテキスト形式の入力ファイルをもとに以下の図を作成できます。

  • シーケンス図
  • ユースケース図
  • クラス図
  • オブジェクト図
  • アクティビティ図
  • コンポーネント図
  • 配置図
  • 状態遷移図(ステートマシン図)
  • タイミング図

物理ネットワーク図に使えそうなのはコンポーネント図です。
以下のコードを書いて

@startuml
node router0 {
  rectangle e0 as router0_e0
  rectangle e2 as router0_e2
}
node router1 {
  rectangle e0 as router1_e0
  rectangle e2 as router1_e2
}
rectangle  firewall0 {
  rectangle e0 as firewall0_e0
}
rectangle  firewall1 {
  rectangle e0 as firewall1_e0
}
router0_e0 - router1_e0
router0_e2 -- firewall0_e0
router1_e2 -- firewall1_e0
@enduml

変換すると次の図が得られます。

PlantUMLはオンラインのコンバーターが提供されており、ここをクリックすると先の図形をカスタマイズできます

ノード

ノードは以下の形で定義できます。直方体で描画されます。

node オブジェクト名

ノードの内部に別のオブジェクトを内包したいときは以下の形で定義します。

node オブジェクト名 {
  別のオブジェクト定義  
}

「node」の代わりに「rectangle」とすると直方体ではなくて四角形になります。冒頭の図ではrouterを「node」で、firewallを「rectangle」で定義してみました。
正直どちらの図形を使うかは好みかと思います。

ネットワークインターフェイス

定義

ネットワークインターフェイスを表現してみます。
以下で「オブジェクト名」という文字列を持つ四角形が描画されます。

rectangle オブジェクト名

以下で「e0」という名前のネットワークインターフェイス(というかただの四角形)が描けます。

rectangle e0

ノード「router0」の中にネットワークインターフェイス「e0」、「e1」を描くには以下のようにします。

node router0 {
  rectangle e0
  rectangle e2
}

注意点

注意事項としてはオブジェクト名はファイルの中で一意である必要があります。
たとえば、以下のコードで同じ名前のネットワークインターフェイスを持つrouter0とroute1を描けそうな気はするのですが、

node router0 {
  rectangle e0
  rectangle e2
}
node router1 {
  rectangle e0
  rectangle e2
}

実際に上記を画像にすると以下のようになります。router1の中見が消えてしまっています。

回避策その1

オブジェクトの名前が一意であればよいので、以下はOKです。

node router0 {
  rectangle router0_e0
  rectangle router0_e2
}
node router1 {
  rectangle router1_e0
  rectangle router1_e2
}

ただ、上記コードを画像にすると以下の通りとなり、あんまりかっこよくないんですよね。

回避策その2

「rectangle 表記ラベル as オブジェクト名」という形で定義するとオブジェクト名とは別のラベルをオブジェクトに表記することができます。

rectangle e0 as router1_e0

以下のコードを画像に変換すると

node router0 {
  rectangle e0 as router0_e0
  rectangle e2 as router0_e2
}
node router1 {
  rectangle e0 as router1_e0
  rectangle e2 as router1_e2
}

以下のようになります。いい感じですね。

ケーブル

定義

オブジェクトとオブジェクトを線で結ぶにはハイフンを使います。

router0_e0 -- router1_e0
router0_e2 -- firewall0_e0
router1_e2 -- firewall1_e0

これで最低限の四角形と線が描けるので、ノード、ネットワークインターフェイス、ケーブルが表現できるようになります。

オブジェクトの「緊密度」

先ほどの図はなんとなく違和感ありますよね。firewall0とfirewall1は同じ段に横に並んでいる方が自然だと思うんですよね。

線はハイフンで定義すると書いたのですが、「-」、「--」、「---」、「----」とハイフンの数を増やすごとにオブジェクト間の「緊密度」が離れていきます。

router0_e0 - router1_e0
router0_e2 -- firewall0_e0
router1_e2 --- firewall1_e0

「緊密度」というのは私が勝手に呼んでいるだけでPlantUMLの公式ドキュメントにはたぶん出てきません。

PlantUMLは「-」で結ぶと、それらのオブジェクトは「同ランク」という扱いになり、左右に並んで配置されます。

「--」で結ぶときはハイフンの左側に書かれたオブジェクトを上、右に書かれたオブジェクトを下に配置していきます。さっきの図は2行目の左右を入れ替えると違った図になります。

router0_e0 - router1_e0
firewall0_e0 -- router0_e2
router1_e2 --- firewall1_e0

基本的には「-」か「--」でオブジェクトを結び、「---」以上はあまり使わなくてもいいかなという気がします。この辺は完全に好みの問題かもしれません。

透明の線

オブジェクトの位置関係を細かく指定したい場合、透明の線を描き入れることができます。

例えば以下の図で「e0」、「e2」の順番に並んでほしいな、と思った場合

次のようにe0とe2を線で結ぶと、e0が左、e2が右になります。

@startuml
node router0 {
  rectangle e0 as router0_e0
  rectangle e2 as router0_e2
}
router0_e0 - router0_e2
@enduml

この線が実線だと見栄えが悪い…というかネットワーク図の意味が変わってきちゃいますよね。[hidden]を付け加えることで透明な線にすることができます。

@startuml
node router0 {
  rectangle e0 as router0_e0
  rectangle e2 as router0_e2
}
router0_e0 -[hidden] router0_e2
@enduml

ちょっと強引な例になりますが、router0の下にfirewall1が来るように「--」線で結んで、

router0 -- firewall1
router1 -- firewall0
router0_e0 - router1_e0
router0_e2 -- firewall0_e0
router1_e2 -- firewall1_e0

「-[hidden]-」で透明にすると、以下のようなたすき掛けが描けます。

router0 -[hidden]- firewall1
router1 -[hidden]- firewall0
router0_e0 - router1_e0
router0_e2 -- firewall0_e0
router1_e2 -- firewall1_e0

サンプル

もうちょっと複雑な例です。オンラインで試すにはこちら

線の太さとか背景色をカスタマイズしています。

@startuml
top to bottom direction

skinparam componentStyle rectangle
skinparam nodesep 10
skinparam ranksep 100 
skinparam ArrowThickness 3
skinparam ArrowColor Chocolate
skinparam node {
  BackgroundColor LemonChiffon
}
skinparam rectangle {
  BackgroundColor PeachPuff
}
skinparam interface {
  FontStyle bold
}

' nodes
node router0 {
  rectangle e0 as router0_e0
  rectangle e2 as router0_e2
}
node router1 {
  rectangle e0 as router1_e0
  rectangle e2 as router1_e2
}
node firewall0 {
  rectangle e0 as firewall0_e0
}
node firewall1 {
  rectangle e0 as firewall1_e0
}
node lb0 {
  rectangle e0 as lb0_e0
}
node lb1 {
  rectangle e0 as lb1_e0
}
node l2sw0 {
  rectangle e0 as l2sw0_e0
  rectangle e1 as l2sw0_e1
  rectangle e2 as l2sw0_e2
  rectangle e3 as l2sw0_e3
  rectangle e4 as l2sw0_e4
}
node l2sw1 {
  rectangle e0 as l2sw1_e0
  rectangle e1 as l2sw1_e1
  rectangle e2 as l2sw1_e2
  rectangle e3 as l2sw1_e3
  rectangle e4 as l2sw1_e4
}
node l3sw0 {
  rectangle e0 as l3sw0_e0
  rectangle e1 as l3sw0_e1
  rectangle e2 as l3sw0_e2
  rectangle e3 as l3sw0_e3
  rectangle e4 as l3sw0_e4
  rectangle e5 as l3sw0_e5
}
node l3sw1 {
  rectangle e0 as l3sw1_e0
  rectangle e1 as l3sw1_e1
  rectangle e2 as l3sw1_e2
  rectangle e3 as l3sw1_e3
  rectangle e4 as l3sw1_e4
  rectangle e5 as l3sw1_e5
}
node host1 {
  rectangle eth0 as host1_eth0
  rectangle eth1 as host1_eth1
}
node host2 {
  rectangle eth0 as host2_eth0
  rectangle eth1 as host2_eth1
}

' cables
router0_e0 - router1_e0
router0_e2 -- l3sw0_e2
router1_e2 -- l3sw1_e2
firewall0_e0 -- l3sw0_e3
firewall1_e0 -- l3sw1_e3
l3sw0_e4 -- lb0_e0
l3sw1_e4 -- lb1_e0
l3sw0_e5 -- l2sw0_e2
l3sw1_e5 -- l2sw1_e2
l3sw0_e0 - l3sw1_e0
l3sw0_e1 - l3sw1_e1
l2sw0_e0 - l2sw1_e0
l2sw0_e1 - l2sw1_e1
l2sw0_e3 -- host1_eth0
l2sw1_e3 -- host1_eth1
l2sw0_e4 -- host2_eth0
l2sw1_e4 -- host2_eth1

' interface align information
router0_e0 -[hidden] router0_e2
router1_e0 -[hidden] router1_e2
l2sw0_e0 -[hidden] l2sw0_e1
l2sw0_e1 -[hidden] l2sw0_e2
l2sw0_e2 -[hidden] l2sw0_e3
l2sw0_e3 -[hidden] l2sw0_e4
l2sw1_e0 -[hidden] l2sw1_e1
l2sw1_e1 -[hidden] l2sw1_e2
l2sw1_e2 -[hidden] l2sw1_e3
l2sw1_e3 -[hidden] l2sw1_e4
l3sw0_e0 -[hidden] l3sw0_e1
l3sw0_e1 -[hidden] l3sw0_e2
l3sw0_e2 -[hidden] l3sw0_e3
l3sw0_e3 -[hidden] l3sw0_e4
l3sw0_e4 -[hidden] l3sw0_e5
l3sw1_e0 -[hidden] l3sw1_e1
l3sw1_e1 -[hidden] l3sw1_e2
l3sw1_e2 -[hidden] l3sw1_e3
l3sw1_e3 -[hidden] l3sw1_e4
l3sw1_e4 -[hidden] l3sw1_e5
host1_eth0 -[hidden] host1_eth1
host2_eth0 -[hidden] host2_eth1
@enduml

関連記事

脚注
  1. SはServiceでした。完全に呆けてました ↩︎

Discussion