LGTM

Looks Good To Me

local-as と as-override を併用したときのルーティング

大抵のルーターには、local-as / as-override という機能があります。

  • local-as
    • 一部のneighborに対し、自分自身のオリジナルAS番号とは別のAS番号としてふるまう
  • as-override
    • 一部のneighborに対し、AS_PATH中の該当peer ASを自分のAS番号に置換して経路広告する

それぞれ単独ではそこそこ見るものの、コンボで使う機会はありませんでした。実際使ってみるとよくわからない動きをします。 as-overrideはAS番号を置換しますが、local-asも指定した場合「オリジナルAS番号」「local-as番号」どちらを採用するか曖昧そうですよね。オプションによってもふるまいが変わりそうです。

「公式ドキュメントを読んで筋が通っているとは思えないので全数チェックしました」がこの記事です。

試したこと

今回試したのはJuniperです。

platform version
VMx 21.1R3.11
local-as autonomous-system <loops number> <private | alias> <no-prepend-global-as>;

local-as にはいくつかオプションがありますが、その全パターン x as-override あり / なしを試しました。

👇 はデフォルト ( local-as なし as-override なし) の状態です。 ここから AS200 の AS100 向けpeerの設定を変え、各ASの送受信経路を記録します。

参考までに Cisco でも

local-as number [no-prepend [replace-as [dual-as]]]

のようなオプションは存在します。今回調べていませんが、似たような事情があるかもしれません。

結果

# がリンクになっています。

# local-as XXX loop private alias no-prepend-global-as as-override
0
1 無関係なAS
2 無関係なAS ⭕️ -
3 無関係なAS - ⭕️ -
4 無関係なAS - ⭕️
5 無関係なAS ⭕️ - ⭕️
6 背後のAS
7 背後のAS ⭕️ -
8 背後のAS - ⭕️ -
9 背後のAS - ⭕️
10 背後のAS ⭕️ - ⭕️
無関係なAS ⭕️
無関係なAS ⭕️ ⭕️ -
無関係なAS ⭕️ - ⭕️ -
無関係なAS ⭕️ - ⭕️
無関係なAS ⭕️ ⭕️ - ⭕️
11 背後のAS ⭕️
12 背後のAS ⭕️ ⭕️ -
13 背後のAS ⭕️ - ⭕️ -
14 背後のAS ⭕️ - ⭕️
15 背後のAS ⭕️ ⭕️ - ⭕️
16 ⭕️
17 無関係なAS ⭕️
18 無関係なAS ⭕️ - ⭕️
19 無関係なAS - ⭕️ - ⭕️
20 無関係なAS - ⭕️ ⭕️
21 無関係なAS ⭕️ - ⭕️ ⭕️
22 背後のAS ⭕️
23 背後のAS ⭕️ - ⭕️
24 背後のAS - ⭕️ - ⭕️
25 背後のAS - ⭕️ ⭕️
26 背後のAS ⭕️ - ⭕️ ⭕️
無関係なAS ⭕️ ⭕️
無関係なAS ⭕️ ⭕️ - ⭕️
無関係なAS ⭕️ - ⭕️ - ⭕️
無関係なAS ⭕️ - ⭕️ ⭕️
無関係なAS ⭕️ ⭕️ - ⭕️ ⭕️
27 背後のAS ⭕️ ⭕️
28 背後のAS ⭕️ ⭕️ - ⭕️
29 背後のAS ⭕️ - ⭕️ - ⭕️
30 背後のAS ⭕️ - ⭕️ ⭕️
31 背後のAS ⭕️ ⭕️ - ⭕️ ⭕️
  • privatealias は排他です
  • aliasno-prepend-global-as は排他です

0. local-as なし as-override なし (デフォルト)

1. local-as <無関係なAS>

2. local-as <無関係なAS> private

3. local-as <無関係なAS> alias

4. local-as <無関係なAS> no-prepend-global-as

5. local-as <無関係なAS> private no-prepend-global-as

6. local-as <背後のAS>

7. local-as <背後のAS> private

8. local-as <背後のAS> alias

9. local-as <背後のAS> no-prepend-global-as

10. local-as <背後のAS> private no-prepend-global-as

11. local-as <背後のAS> loop 2

12. local-as <背後のAS> loop 2 private

13. local-as <背後のAS> loop 2 alias

14. local-as <背後のAS> loop 2 no-prepend-global-as

15. local-as <背後のAS> loop 2 private no-prepend-global-as

16. as-override

17.local-as <無関係なAS> / as-override

18. local-as <無関係なAS> private / as-override

19. local-as <無関係なAS> alias / as-override

20. local-as <無関係なAS> no-prepend-global-as / as-override

21. local-as <無関係なAS> private no-prepend-global-as / as-override

22. local-as <背後のAS> / as-override

23. local-as <背後のAS> private / as-override

24. local-as <背後のAS> alias / as-override

25. local-as <背後のAS> no-prepend-global-as / as-override

26. local-as <背後のAS> private no-prepend-global-as / as-override

27. local-as <背後のAS> loop 2 / as-override

28. local-as <背後のAS> loop 2 private / as-override

29. local-as <背後のAS> loop 2 alias / as-override

30. local-as <背後のAS> loop 2 no-prepend-global-as / as-override

31. local-as <背後のAS> loop 2 private no-prepend-global-as / as-override

オプション変更時、BGPセッションがどうなるか

  • ⭕️ 落ちません
  • ❌ 落ちます。どちらが落とすかは省略します

まとめ

Juniperでlocal-as と as-override を併用したとき、どのようなルーティングになるか試しました。

理屈が通っているようには見えないのですが…納得できたら追記します。

PeeringDBの過去データを読む

インターネットルーティングに関わっていると、まれに PeeringDB の過去データを集計したくなります。

https://www.peeringdb.com から現在の情報は取れますが、「過去に遡りたい」「特定の国・地域・事業者に注目して時間推移を見たい」といった要望を満たせません。そこで CAIDAが公開してくれている PeeringDB の daily snapshot を読み、集計し、下のようなグラフを描いてみます。東京・大阪の事業者数の推移です。

CAIDA PeeringDB Dataset

CAIDA Acceptable Use Agreement に同意し コンタクト情報と利用用途をsubmitすれば、PeeringDB アーカイブをダウンロードできます。

PeeringDBは 2016-03-15 にv2になり、スキーマが新しくなりました。バージョンごとに利用可能なアーカイブ種別も異なります。

時期 PeeringDB v1 PeeringDB v2
2010-07-29 ~ 2015-12-31 .sql, .sqlite
2016-01-01 ~ 2016-03-14 .sql, .sqlite .sql, .sqlite
2016-05-27 ~ 2018-03-10 .sqlite
2018-03-11 ~ .json
  • v2 リリース直後はアーカイブがありません
  • 2016-03-14 以前のv2データはβリリース版のものです

アーカイブをどう読むか

SQLで好きに集計できそうなのですが、最近の v2 データはAPIダンプになっていてDBに書き戻せず、 JOIN するのが厳しいです。また、古い v1 データは DBダンプがありますが 対応するappが公開されていません。

  • 試行錯誤しつつ検索条件・集計方法・出力を変えたいので、言語は何でもいいがプログラム処理したい
  • ORM があると便利

なので、ORMが使えるフレームワークを使い 最低限アーカイブを読めるappをでっちあげ、集計プログラムを書くのがよさそうです。

アーカイブを読むためのapp

DBスキーマからよしなにモデルを生成してくれる ( DBにある情報はプログラム側に書かなくてもよい ) フレームワークのひとつにRailsがあります。PeeringDB v2は Django 製ですが、v1 流用を考えた場合アーカイブを読むだけならRailsのほうがたぶんラクです。

v2 期間だけでよければ、 公式Django モデル 向けloaderを書くのが早いと思います。

Railsで読む場合、たぶんこんな感じになります。

github.com

v2 データを読む例を記載しますが、もし興味があれば使い方は README を見てください。

$ git switch peeringdb-v2
$ bundle install

$ sqlite3 db/development.sqlite3 < db/598a658.sql

# Download any json file of v2 as archive.json for example, then

$ rails runner script/load.rb archive.json

v2 データをロードするとDBスキーマはこうなります。公式Django PeeringDB モデルと同じはずです。

データをロードできたら、集計プログラムを書きます。たとえば「日本の都市ごとに、POPがある or IX接続がある事業者を数える」場合はこんな感じになると思います。

require 'json'

ixs = Hash[Ixlan.joins(:ix).includes(:networks).where(ix: { country: 'JP' }).group_by { |i| i.ix.city }.map { |c, ixlans|
  [c, ixlans.map(&:networks).flatten.uniq]
}]

privates = Hash[Facility.includes(:networks).where(country: 'JP').group_by(&:city).map { |c, facilities|
  [c, facilities.map(&:networks).flatten.uniq]
}]

cities = (ixs.keys + privates.keys).uniq
stats = Hash[cities.map { |c|
  [c, {
    ix: ixs[c]&.count || 0,
    private: privates[c]&.count || 0,
    total: ((ixs[c] || []) + (privates[c] || [])).uniq.count
  }]
}]

def upcase_keys(hash)
  normalized = {}

  hash.each do |k1, v|
    if normalized.has_key?(k1.upcase)
      v.keys.each do |k2|
        normalized[k1.upcase][k2] += v[k2]
      end
    else
      normalized[k1.upcase] = v.dup
    end
  end

  normalized
end

print JSON.dump(upcase_keys(stats))

v1 データを読みたい場合は、GitHubレポジトリに スキーマサンプル があります。

グラフを描画する

ここでは省略しますが、PeeringDBアーカイブから任意の時点・検索条件・集計方法でjson出力を得られるので、好きな方法でグラフ化してください。

参考

packer で vEOS Vagrant Box を作る

Arista vEOS 4.19 までは Software Download ページから .box をダウンロードできましたが、 現時点で 4.20 ~ 4.27 にはありません。

そこで jerearista/vagrant-veos を使って .vmdk から .box を作ります。

環境

Software version
VirtualBox 6.1.26
packer 1.7.6

作り方

vEOS-lab-4.27.0F をベースに作る例です。

1 . Software Download ページからファイルをダウンロードします。

  • Aboot-veos-8.0.0.iso
  • vEOS-lab-4.27.0F.vmdk

後述しますが Aboot-veos-serial-8.0.0.iso ではうまく動きません。 .vmdk は 64bit 版でも OK です。

2 . vagrant-veos を実行します。

git clone https://github.com/codeout/vagrant-veos.git
cd vagrant-veos/packer

cp <path to Aboot-veos-8.0.0.iso> source/Aboot-vEOS.iso
cp <path to vEOS-lab-4.27.0F.vmdk> packer/source/vEOS.vmdk

packer build -var "version=4.27.0F" vEOS-4-i386.json

オリジナルの https://github.com/jerearista/vagrant-veos最新の VirtualBox + packer で動かないため、パッチが必要でした。

https://github.com/codeout/vagrant-veos がパッチ済みのものです。
( オリジナルがメンテされてなさそうで…PR してもレビューしてもらえない気がする😭 )

パッチ中身

  • vagrant up 後に Management1 にIP アドレスが振られませんでした。明示的に DHCP enable にします
  • /mnt/flash 配下のファイルが admin オーナーの場合 EOS がうまく読んでくれません。 root にします
  • VirtualBox 6.1.20 から、 VBoxManage export --iso オプションは動きません。*1 --options iso に変えます
  • 最新の VirtualBox + packer では COM1 経由の boot command 入力がうまくいきません = Aboot-veos-serial-8.0.0.iso では動作しません。 ふつうの key input を使います
  • VM 作成後 export する前に shutdown しますが ACPI を使っていないため、直前に file provisioner で送ったファイルが保存されない場合があります。 ACPI enable にします

*1:Document Bug がある様子

日本はアジアのハブになれたのか?

通信キャリアや IX に勤務していたころ、よく「日本をアジアのハブにしよう」と言っていました。

データを整理する機会があったので「実際のところどうなの?」をまとめてみます。

話の背景は文末に回しました。もしご興味あればどうぞ。

パブリックデータから、国際ハブとしての大きさを探る

通信のために事業者が相互接続するとき、検討段階ではどんな事業者がどこにいるかを知っておく必要があります。 接続時・接続後も、たとえば経路数やPOPなど、継続的に情報インプットが必要です。 各種情報がまとまったデータベースとして PeeringDB が有名ですが、そこに登録されている

  • 事業者(AS) 数
  • 事業者(AS) が拠点を持つ国
  • 事業者(AS) が接続するIX がある国

から、「アジア各国で、どれくらいの事業者と接続できそうか」を推測してみます。
( ある事業者がその国に拠点を持っている or その国のIX に接続していれば、「接続できそう」とします )

アジア各国の事業者数 (国際 + 国内)

f:id:codeout:20210613231255p:plain

見づらいですが、現時点で次のようになっています。 ここには各国内に閉じる事業者も含まれている点に注意ですが、以降、この5ヵ国に注目します。

インド・インドネシアの伸びがすごいですね。

# 事業者数
1 インド 647
2 日本 437
3 シンガポール 427
4 インドネシア 418
5 香港 383

アジア5ヵ国の事業者数 (国際)

ここが本題です。シンガポール・香港は古くからアジアのハブとして機能していました。「これに追いつこう」というのが日本のスタンスでしたが、このデータでは残念ながら、差は縮まってないようですね。

一方、インドへの国際事業者への参入が目立ちます。

f:id:codeout:20210613231050p:plain

ちなみに、インターネットの中心とされる US を含めると これくらいのスケールになります。

f:id:codeout:20210613230803p:plain

アジア5ヵ国の事業者数 (国内)

参考までに、国内事業者だけをプロットしてみます。

インド・インドネシアがすごく伸びていますが、増えたのは国内事業者であることがよく分かります。

f:id:codeout:20210613230956p:plain

「日本をアジアのハブに」の話

企業の枠を超えた、業界全体のスローガンのようなものです。一般に言われているかは分かりませんが、 たとえば US・EU の通信事業者がアジアと接続したいときに「日本につなごう」「日本に拠点を持とう」と 思ってもらえるようなエコシステムを作ろう、という意味です。

逆もしかりで、アジアの通信事業者がUS・EU と接続したいときに 「日本には事業者が集まっているので、足を伸ばす・拠点を持つ価値がある」と思ってもらいたい。

インターネットな BGP を運用している事業者が言う「接続したい」は、「物理的に直接つないでトラフィック交換したい」です。 Gbps ~ Tbps のトラフィックを持つ事業者にとって、間に別の事業者が挟まっているのは割に合いません。 特定の事業者と大量トラフィックを交換したい場合、直接接続することで

  • トータル、コストが下がる
  • ビジネス面での交渉・技術的な調整・トラフィック制御がしやすくなる
  • 遅延の解消

のようなメリットが見込めます。

そう考える国際事業者に対して 日本がアジアのハブとして機能することは、国内事業者にとってもメリットがあります。 「少しの投資で国際事業者と接続できる」などです。日本であれば簡単にクラウド事業者と接続できますが、もし国内で接続できなかったらどうでしょう? 影響は通信業界だけではなく、自国に国際ハブがあることは日本全体にとっても経済面・ 国際競争の面で有利にはたらきます。

このような背景があって通信事業者のひとたちは「日本をアジアのハブにしよう」と言っていました。 検索してみると、かれこれ20年 にもなるようです。

立地的に有利とされていたはずが…

この文脈でよく言われるのが海底ケーブルの陸揚げです。「USから見てアジアへの経路上にあるので、中継点としては最適」とされていました。

f:id:codeout:20210614015315p:plain https://www.submarinecablemap.com/

しかし長年国際ハブになりきれない点について

  • 言語バリアーがあって、消費者は英語コンテンツをあまり見ない
  • シンガポール・香港に比べ、通信政策・法制度・税制度が行き届いていない
  • 国内キャリアの競争戦略

いろんな意見を聞きますが、個人的にはよく分かっていません。

別のグラフを出しますが、これは事業者の IX 接続帯域合計です。日本は、IX 先進国であるドイツ・オランダ・英国に近いレベルで伸びています。

f:id:codeout:20210614022953p:plain

次にヨーロッパ各国 + 日本の国際事業者数推移です。

f:id:codeout:20210614030239p:plain

通信インフラが成熟しているドイツ・オランダ・英国・日本、各国とも通信政策に力を入れている中、国際ハブの観点ではドイツだけが大きく伸びています。US から見て言語バリアーがなく大西洋ケーブルを多く終端している英国より、ドイツが伸びている点は面白いですね。

トラフィックは重力のように、大きなところに事業者が集まる傾向がありましたが、ドイツやシンガポールが国際ハブとして活発である点からすると、近年は

といういうことかもしれません。

参考

inet-henge for large scale network diagrams

inet-henge is a d3.js based network diagram generator that calculates node positions automatically. The input is a simple json like below, it requires no position information such as x-y coordinates.

{
  "nodes": [
    { "name": "A" },
    { "name": "B" }
  ],

  "links": [
    { "source": "A", "target": "B" }
  ]
}

github.com

The auto-layout works good and fast enough for small networks but it gets more complex and painfully slow when you have many nodes and links in a diagram. It'll take a couple of minutes to calculate the position of 300+ nodes and 1400 links for instance.

As a performance improvement, I've introduced a new option to tweak the auto-layout algorithm, especially for large scale network diagrams. When I tried my test data comprised of 800 nodes and 950 links, it only took <20 secs to determine node positions. I know it's still slow but acceptable hopefully, as it should be a one-shot calculation - inet-henge can cache the result for further rendering.

So why was it so slow?

Besides inet-henge calculates the node positions repeatedly in each time slice just like force layout of d3.js, extra constraints are added to iteration:

  1. Group nodes with bounding boxes
  2. Prevent nodes and groups from overlapping with each other

If you have huge groups having many nodes, it sometimes wastes ticks to pass by each other. In an example diagram below, it looks hard for a blue group to pass through a red one. It might get stuck for many ticks or never pass in the worst case.

The same thing will happen even in a no group diagram. Every node collides with each other many times and it prevents position calculation from fast convergence.

The new option initialTicks

For such a large scale network diagram, you can specify the number of initial "unconstrained" ticks.

const diagram = new Diagram('#diagram', 'data.json', {initialTicks: 100, ticks: 100});

With this option, inet-henge calculates the layout in two iteration phases:

  1. Initial iteration with no constraints. ( default: 0 tick )
    • Every group or node can transparently pass through each other in this phase.
  2. The main iteration with constraints that apply groups as bounding boxes, prevent nodes and groups from overlapping with each other, and so on. ( default: 1000 ticks )

Note: If you increase initialTicks, inet-henge calculates faster in exchange for network diagram precision so that you can decrease ticks which is the number of main iteration steps.

20 ~ 100 initialTicks and 70 ~ 100 ticks should be good start for 800 nodes with 950 links for example. It takes 20 seconds to render in my benchmark environment.

Draw your own diagram

The initialTicks option is very new and not tested enough due to a lack of actual network topology data. If you're interested in this project and drawing your network diagram, please reach out to me ( Twitter ) with your use case. Any feedback will be appreciated.

ネットワーク標準化ギプス

テキストやファイルで設定管理されるようなネットワークの、設定標準化・テンプレート化・Validation について書きます。 経験上、ISP・コンテンツプロバイダなどのネットワークでうまくいった手法ですが、よりよい方法があるかもしれません。ご指摘・コメント頂けるとうれしいです。

  • 現在自動化できていないが、自動化を目指したい
  • 移行過渡期は、もしくは移行後であっても運用上の都合で、手動投入は許容したい
  • ネットワークが正しく設定されているか? を常にチェックしたい

このようなネットワーク運用を想定します。

やりたいこと

ネットワークデバイスはシステム側からもオペレーターからも変更されうる という環境で、

  1. ネットワークポリシー・設定を標準化し、テンプレートとして実装する
    → テンプレートが強制力としてはたらき、ポリシーが維持できる
  2. 何かのトリガーで 構成管理DB + テンプレートから設定を生成し、自動 or 手動によりネットワークデバイスにロードする
    → オペレーターが手動投入した (= テンプレート化されていない) 設定との調整は よしなにやる
  3. オペレーターが投入した設定がポリシーに即しているかチェックする
  4. ネットワーク全体として、意図どおり設定されているかチェックする

👆 がやりたいことの概要です。

自動化によって運用コストを下げるのは当然として、手動オペレーションを許容しつつも ネットワークポリシー(標準設定) を強制することが目的です。

1. 手動オペレーションを許容する

やりたいことについて、いくつか補足します。 この項目は、従来のネットワーク運用から漸進的にシステム導入するために必要です。

手動オペレーションで運用している or 部分的に自動化している という状態から、ネットワークが標準化されていて テンプレートがあり、すべてをシステム経由で行う運用に一足で移行するのは、運用的にはかなり大きなチャレンジで、できれば避けたいところです。

2. 個々の業務を自動化するのではなく、完全な設定をテンプレート出力 & ロードする

こうする必要があるのは、手動オペレーションを許容しつつも、テンプレートにネットワークポリシー強制力を期待するからです。

たとえば「よくやる業務はテンプレートで自動化されているが、まれな作業は手で設定する」ようなケースを想像してください。 この場合、手で入れた設定も含めてポリシーに準拠しているかを判定するのは大変です。

よくやる業務(下図グレー部分) はポリシー準拠ですが、手動設定時(下図ピンク部分) は何が設定されているか分かりません。手動設定の特定でさえ難易度が高いと思います。

  • アクセスできるのは、各業務のテンプレート + 現在の設定(= 各業務の累積) のみ
  • ポリシーは変化する

点にも注意してください。

今回の目的で言えば、「業務を積み重ねた結果である現在の設定」をチェックするほうがシンプルで、「各業務を自動化する」の延長では複雑になりそうです。 完全な設定をテンプレート出力したいのは、こういった理由からです。

実装のためのアプローチ

さて、テンプレートシステムを実装するにあたり、どのようなアプローチを取るかについて。 ここでは具体的な実装例について触れませんが、進め方の指針をいくつか記載します。

1. 標準化とテンプレート開発を同時進行する

「自動化の前に標準化しましょう」というのは基本ですが、個人的な経験でいえば実装前に標準化しきれたことはありません💦

ここでいう標準化とは、ネットワークポリシー・あるべき設定の標準形を制定するだけではなく、実機上の設定を統一するところまでを指します。 手動オペレーションな歴史のあるネットワークであれば、名前の表記揺れ (例: policy-statement / route-map) にはじまり、微妙な設定の揺れがおそらく多々残っていると思います。

これらの標準化に取り組む間もどんどん揺れは混入されますので、もぐらたたきになりがちです。 具体的にどう進めるかは後述しますが、標準化と実装は 同時進行すると良いと思っています。

2. ポリシー外の設定を許容する

別の重要なポイントとして、ビジネスを止めたくありません。 ネットワークポリシーを標準化し、実装し、できればキレイな状態で維持したいのですが、どんどん変わるビジネス要件に対応する必要があります。 「ユーザーの新たな要望に応えたい」だったり、「バグ対応で設定や運用を変えたい」だったり、要件は様々です。

そのたびに「ポリシーとして標準化して、ツール化しないとネットワークに入れられません」ではスピード感を損ないます。 これまでの「ネットワークポリシーを強制したい」と矛盾するのですが、「この設定はポリシー違反だけど例外としてオッケー」というステージを作るということです。

  • 一時的な設定
  • ビジネスとしてGO 判定されたが、ネットワークポリシー・標準とするか判断されていない設定

向けのステージですね。「この設定ブロックがそうですよ」と明記しつつ、一時的に許容します。

実装のヒント

多くのネットワークデバイスの設定は宣言的であり、それ自体設定ではないが設定を削除するコマンド ( no, delete など ) があることを利用し、

  • 標準化済みの設定を出力 (構成管理DB + テンプレートから)
  • その後に標準化されていない設定を出力 (自由記述)

とするだけで、結構うまくいきます。

標準化済みの設定 (ピンクの部分) をテンプレートが生成し、別管理してある標準化されていない自由記述設定 (白の部分) と結合するだけですね。結合してうまくいくように、白の部分は工夫して書く必要があります。

個人的には 自由記述枠のことを昔「hack」と呼んでいましたが、最近は「patch」と呼んでいます。

標準化・実装が間に合わない部分をパッチすることで 1. 標準化とテンプレート開発を同時進行する ことができ、2. ポリシー外の設定を管理する ことができます。当初はパッチが大半ですが、テンプレート実装と実機設定の標準化を進めながら、ピンク部分を100% にすることを目指します。

実機設定とのdiff

パッチのしくみで、テンプレート出力 (パッチ含む) は簡単に手に入りますが、これだけでは「手で入れた設定」が抽出できません。別途うまいことdiff するしくみが必要になります。

テンプレート出力(左) と実機設定(右) をdiff する際、パッチブロックをうまくマージしつつ、同一のものとして扱います。これによって「手で入れた設定」が抽出でき、 それをテンプレート実装する or パッチに入れるの判断が下せるようになります。

パッチには nodelete が混じっている可能性もあり、うまくdiff するには構文解析が必要になります。 ここはそれなりに手間がかかりますが、うまいことやってください。

なお、Juniper の構文解析機能は OSS として公開しています。興味があればご覧ください。

github.com

パッチの運用

手で入れた設定は、テンプレート出力(パッチ含む) をロードしてしまうと失われます。 なので定期的に、もしくは何か Event を hook してチェックし、テンプレート実装する or パッチに入れる 判断を促す必要があります。

手で入れた設定は、👇 のようなライフサイクルをたどるイメージになります。

  1. ビジネス判断により、ネットワークに入れる
  2. diff によって検知され、パッチする = 永続化される
  3. 標準として認められれば、テンプレート実装する

パッチを個別に管理することは「ポリシー外がこれだけありますよ」と定量評価できる点で重要だと思っています。

運用的な観点でいえば、例外は想定外なルーティングをし、事故の原因になるかもしれません。 一方で全てテンプレート化するのも毒で、パターンの多さはコードが複雑になるだけではなく、オペレーターを混乱させてしまいます。

何を標準とするか、どれくらいのパターンを許容するか、どのくらいの量のパッチを許容するかは 各社のビジネスや運用によるため「これ」という基準を提示できませんが、定期的に棚卸しする場を持つことは重要だと思います。

「このパッチは標準にいれるべきか」「このパッチは消すべきではないか」などを議論する場ですね。

まとめ

手動オペレーションを許容しつつ、テンプレートシステムによってネットワークポリシーを強制するアプローチについて書きました。これは、従来のネットワーク運用から漸進的に自動運用に移行することを意識しています。

ここでは「全体として、意図どおり設定されているか」のチェックに留まっていますが、本来のNetwork Validation はふるまいの確認であり、さらに先にあります。それについてはまた別で書きたいと思います。

ネットワークは宣言的になりえるか

2020-07-08 追記

はじめに

Kubernetes などのコンテナオーケストレーターとの対比によって、ネットワークの世界でも同じように制御できないか注目されています。Cisco、Apstra、VMWare などが言う "Intent Based Networking" や "Closed Loop Automation" も同じものを指していると思われます。宣言的ネットワーキングは「あるべき状態の維持をプロトコルやソフトウェアに任せられるかもしれない」という点で運用上のメリットがあります。

以前所属していた国際Tier1 ISP *1 で、Kubernetes ほど洗練されてはないものの コンセプトとしてはこれを実践していたり、現在もネットワーク自動化の取り組みの中でゴールをここに設定したりしています。

このエントリーでは、

  • 宣言的ネットワーキングとは何なのか
  • 従来のネットワーク運用を宣言的ネットワーキングに移行できるのか
  • 移行できるとしたら、どういうステップを踏めばよいか

について自分の考えをまとめてみたいと思います。

宣言的ネットワーキングとは何なのか

ざっくりコンセプトとしてはKubernetes と同じで、「あるべき状態を宣言的に記述し API に渡すことで、システムが現在の状態を監視、必要に応じてあるべき状態に収束させてくれるネットワーク制御手法」のことです。

"宣言的" なのはあるべき状態の記述方法・APIの呼び出し方であって、内部的に実行される状態遷移プロセス自体は手続き的になりえます。Kubernetes に詳しくありませんが、おそらくそちらも同じであろうと想像しています。「システムに触れるユーザーが手続きを意識しない」であって「手続き的な何かは一切存在しない」ではない点に注意が必要ですが、これまでの議論 *2 を踏襲し、ここでは「宣言的ネットワーキング」と書くことにします。

実現のためのキーポイントはいくつかあって

  1. あるべき状態の記述方法と、その対象・抽象度の検討
  2. あるべき状態の抽象的な記述 (1) を、ネットワークデバイスが解釈できる設定・パラメーターに変換する機能
  3. 現在のネットワーク状態を取得する機能
  4. ネットワークデバイス上の表現を使って、あるべき状態 (2) と現在 (3) を比較する機能
  5. 差分 (4) を自動投入する機能

が必要だと考えています。それぞれについては、ネットワーキングならではの事情もふまえて後述します。

従来のネットワーク運用との対比

従来のネットワーク運用では、あるべきネットワーク状態を包括的には記述しません。代わりに 障害などのネットワークイベントを検知し、手続き的なアプローチによってあるべき状態に復旧させます。あるいは、あるべき状態が変化した際にその差分だけを反映させます。

従来のネットワーク運用でも 宣言的アプローチを取っている部分はあります。たとえばICMPによる到達性・遅延監視などです。許容できる latency を定義し 現在の状態と比較、超過した場合に通知するような運用はよく行われています。根本原因の自動特定 (Root Cause Analysis)・自動修復(Self Healing / Remediation) まで行えないものの アプローチとしては宣言的であり、部分的にではありますが 宣言的ネットワーキングに必要な要素を実装できていると言えそうです。

一方、宣言的ネットワーキングでは現在のネットワーク状態を取得、あるべき状態との比較を軸にします。比較した結果差分があれば状態に変化を加え、あるべき状態に自動収束させます。これをフィードバックループ的にぐるぐる回すことがポイントです。 (Reconciliation Loop)

もちろん、影響時間の短縮や Root Cause Analysis のために、障害などネットワークイベントを追加のトリガーとしたり、解析のヒントにすることはあると思います。

脱線しますが、ループを回す際、フィードバックが強すぎるなどシステムが発振して収束しないことも考えられますので、安定化のための何かしらの仕組みは別途必要になります。

コンテナオーケストレーションとの対比

Kubernetes をはじめとする宣言的コンテナオーケストレーションと宣言的ネットワーキングについて、若干乱暴ですが次のような違いがあると考えています。

コンテナオーケストレーション ネットワーキング
制御対象 コンテナ群 ネットワーク
あるべき状態は物理を含むか ⭕️
制御対象自体がロバスト ⭕️

両者を比較するために

1. あるべき状態の記述方法と、その対象・抽象度の検討

について触れておく必要があり すこし脱線しますが、「あるべき状態をどのレベルまで抽象化して記述できるか」はとても重要です。 あるべき状態を宣言的に書くと その状態を維持してくれるシステム があるとして、何を記述するでしょう? 究極的には

  • サービスを安価に安定的に提供できること

のように書きたいんじゃないでしょうか? これは残念ながら抽象的すぎて、今のところ現実的ではないため どんどん具体化します。

  • 秒間 1万 HTTPSリクエストを返す。10 ms 以内に。月額コストは1000万円以内
  • 国内ユーザー間のIPトラフィックをトランジットする。自社の網内で latency 20 ms 以内、jitter 0.1 ms 以内、loss rate 0.01% 以内。月額コストは1億円以内

のような、サービス品質を記述するレベルでも厳しいかもしれません。結局のところ、あるべき状態の抽象度として

  • 東京リージョンに、このスペックで動くapp AのPod が5コある
  • データセンターA にいるISP X からトランジット10G x 2 を買い、データセンターB に収容する

くらいまで具体化する必要があるんじゃないでしょうか。もともと宣言的に記述したい対象はビジネス・サービスレベルのものであるはずが 抽象度が高すぎて実現できず、具体化して「ビジネス・サービスを実現するために設計したコンテナ配置、ある相互接続」レベルまでブレイクダウンする必要があります。

さて 話を戻しますが、この抽象度まで下げる場合 ネットワークには物理的な制約が強く現れます。たとえばISP X からトランジットを買い 専用線を借りて相互接続する場合、その接続に替えはありません。「ISP X に繋がるこの専用線トラフィックを乗せる」があるべき状態になります。コンテナのように「すぐ廃棄してどこかに新規作成し、数があってればOK」とすることができません。残念なことに障害時には物理交換が発生するため、復旧プロセスも完全に自動化することは困難です。

このデメリットを補うため、ネットワークプロトコル自体にロバスト性が備わっています。 物理は替えがきかないものの、あらかじめ冗長性を持ちさえすれば、それらをうまく切り替えて自律分散的に動く仕組みがもともとあるわけです。 *3

この性質のため、ネットワーク制御システムはコンテナと比べておそらく薄くできます。コンテナ自体のロバスト性が低いため オーケストレーターが監視・操作する必要があるのに対し、「ネットワークをうまく設計して設定しておけば、あとは自律的に動いてくれる」というのは、おもしろい違いです。

個人的には、宣言的ネットワーキングにおける制御システムの主な役割は QoS と最適化だと考えています。乱暴に言えば、ネットワークは

  • 何かが壊れたら、自律的にそこを使わないようにして予備を使い始める
  • 壊れたものが直ったら、自律的に元に戻る

ので、残る問題としてまず思いつくのが

  • 壊れた判定が難しいもの
  • 計測しづらいもの
  • 自律的な調整が難しいもの

だからです。たとえば

  • ある回線の packet loss rate が許容できる閾値を超えたら、制御システムが自動的に切り離す
  • 10GE が輻輳しないレベルでギリギリまでトラフィックを流す

などです。

従来のネットワーク運用を宣言的ネットワーキングに移行できるのか

さきに

宣言的ネットワーキングにおける制御システムの主な役割は QoS と最適化だと考えています

と書きましたが、ここでは簡単のために「あるべき状態を宣言的に記述でき、何かコマンドを叩くとネットワークがその状態に収束する (ただし物理を除く)」を一歩目のゴールとしましょう。これはKubernetes チュートリアルに出てくるレベルと同じもので、簡単ですが宣言的ネットワーキングだと言っていいと思います。

図示してみると 👇 のような感じです。

一歩目のゴールまでのポイントは3点で、

  1. あるべきネットワークの状態を抽象的に・宣言的に記述する
  2. あるべき状態が変わったら、変化に追随する
  3. 故障したら、あるべき状態の一歩手前 = 冗長度は低いが品質低下のない状態 まで遷移する

しかも 3 はネットワークプロトコルに任せることができるため、宣言的ネットワーキングの一歩目を踏み出すために複雑な制御システムは不要だと考えています。ここでのフォーカスは、

1. あるべき状態の記述方法と、その対象・抽象度の検討
2. 抽象的なあるべき状態の記述 (1) を、ネットワークデバイスが解釈できる設定・パラメーターに変換する機能
5. 差分を自動投入する機能

です。

なお、図中では

  • 故障 (Outage) = ネットワーク自身が、自分で「壊れている」と判定できる状態
  • 品質低下 (Service Degradation) = ネットワーク自身は「壊れている」判定できないが、制御システムから見れば品質を満たせていない状態

を明示的に区別しています。

ネットワーク設定は冪等で宣言的だが、問題は抽象度

さきほど抽象度を下げた宣言的記述「データセンターA にいるISP X からトランジット10G x 2 を買い、データセンターB に収容する」を例にしてみましょう。

ネットワークデバイスへの入力を便宜的に config で書けば

protocols {
    bgp {
        group x-transit {
            type external;
            metric-out igp;
            export [ transit-out ];
            remove-private;
            neighbor 192.0.2.1 {
                description "Transit X AS65000";
                out-delay 1;
                import [ transit-in AS65000-in ];
                peer-as 65000;
            }               
        }
   }
}

policy-options {
    policy-statement transit-in {
        term default {
            then {
                community add transit;
                local-preference 80;
                next policy;
            }
        }
    }

    policy-statement transit-out {
        term peer-routes {
            from community [ customer ours ];
            then accept;
        }
    }


    policy-statement AS65000-in {
        term match-exact {
            from {
                route-filter 198.51.100.0/24 exact;
            }
            then {
                next policy;
            }
        }
        then reject;
    }
    community ours members XXX:100;
    community customer members XXX:110;
    community transit members XXX:120
}

たとえばこのようになります。中身は適当で特に意味はありませんが、この例のようなネットワークの設定自体、ある条件を満たす限り 宣言的だと考えています。ある条件とは

  1. NETCONF や RESTCONF でいう candidate config を、atomic にcommit できること
  2. 存在しない設定は消えること

で、これを満たす限り ほとんどのネットワークベンダーの設定はあるべき状態の宣言であって、変化を与えるための命令ではないように見えます。 多くのネットワークデバイスは、設定されている状態に収束するよう ネットワークプロトコルを使って自律分散的に動作するからです。 同じ設定を投入しても変化はなく、冪等でもあります。

一方、ネットワーク設定の問題点は抽象度が低いことです。無数にあるパラメーターごとに個別に宣言する必要がありますが、あるべき状態は可能な限り抽象的に記述するのが望ましく、たとえば

peers:
    - type: transit
      neighbor_as: 65000
      neighbor_address: 192.0.2.1   # これが物理と紐づいている
      priority: low

くらいまでは抽象化したいところです。 ここでは便宜的に yaml にしていますがたとえば RDBMS でもよく、データストアは本質ではありません。 「何を」「どのレベルの抽象度で記述するか」が本質です。これは各社のビジネスに深く依存するため、データ構造を共通化するのは困難であり、ビジネスの柔軟性のために自社で定義するのが望ましいと思っています。

さて、ここでは中間表現としてネットワークデバイス設定を例にしましたが、最終的に デバイスに設定を入れる = あるべき状態をAPIに入力する機能 とセットであり、必ずしも あるべき状態記述を設定に変換する必要はありません。 Ansible を使っている場合は あるべき状態を包括的に記述した playbook が中間表現になり、3rd party オーケストレーターを使っている場合は その API が期待する形式に変換する感じになります。

これらの中間表現は、特定業務を自動化するためだけの記述ではなく、ネットワークのあるべき状態を包括的に宣言したものである必要があります。 また、中間表現に冪等性があれば 投入時に差分計算する必要がなくなり、制御システムをより薄く保てます。

ここまでをまとめると、宣言的ネットワーキング (一歩目) に必要な

1. あるべき状態の記述方法と、その対象・抽象度の検討
2. 抽象的なあるべき状態の記述 (1) を、ネットワークデバイスが解釈できる設定・パラメーターに変換する機能
5. 差分を自動投入する機能

は、ざっくり言えば「なにかしらのテンプレートシステムを作りましょう」にすぎません。Reconciliation Loop を回す部分は、ネットワークプロトコルがやってくれます。

移行できるとしたら、どういうステップを踏めばよいか

まずは、抽象的な宣言からネットワークデバイスミドルウェアが解釈できる中間表現を生成するための、テンプレートシステムを作りましょう、ということを書きました。

障害時には物理交換が発生するため、復旧プロセスも完全に自動化することは困難です。
宣言的ネットワーキングにおける制御システムの主な役割は QoS と最適化だと考えています

ということも書きました。最後の2点はこれまで棚上げしていた項目です。両方それなりに複雑になりそうなのですが、前者は実現可能だと思われます。

ひとつの案としては、回線やモジュールごとにビジネスに応じたメトリックを計測します。許容できるサービスレベルを宣言的に記述しておいて、

  1. サービスレベルを下回った場合、制御システムは回線やモジュールをサービスから切り離す
  2. 物理的に復旧した場合、制御システムはメトリックとサービスレベルを比較する
  3. OK であればサービスに組み込む

というアプローチは実装できそうです。

メトリック収集が複雑になりそうですが、これは従来のネットワーク運用でよく見るプロセスで、「人が手順にのっとって実施できることは、システム化できるのでは?」という話です。状態が自動遷移するようなシステムが理想ですが、ネットワークポリシーにそぐわないかもしれません。間にオペレーターの確認アクションを挟んでもよいと思います。

QoS や最適化はより複雑で、具体的なアイデアを持っていません。たとえば、あるIPアドレス間の latency をメトリックとして計測しているとして、原因区間の特定 (Root Cause Analysis) をシステム化するのがまず複雑です。仮に特定でき 原因が回線の輻輳であったして、「解消のためにTEしたところ 移した先で輻輳した」というケースもかなり見ます。ネットワーク全体の評価をスコアリングして、より高いスコアに収束させるような制御システムになるだろうと個人的には想像しています。システムが発散しないような仕組みも必要になります。

まとめ

従来のネットワーク運用を宣言的ネットワーキングに移行するのは、比較的カンタンだと考えています。ネットワークプロトコル自身が持つロバスト性と物理への強い依存を考慮すれば、テンプレートシステムの実装が一歩目で、経験的には、この簡易実装でさえコスト削減効果を上げられます。

一歩目の問題は、宣言の抽象度が低いことです。本来 宣言的に記述したい対象はサービス品質やコスト、さらに高レベルなビジネスの文脈なのですが、従来のネットワーク運用から移行可能なレベルまで宣言の抽象度を下げたにすぎません。しかしながら、こうすることで宣言的ネットワーキング(簡易版) にいったん移行し、徐々に宣言の抽象度を上げるというアプローチを取れます。

抽象度の高いところからスタートしようとすれば、まったく新しい制御システムの導入はもちろん、運用の見直しや 最悪の場合 ネットワークデバイスのリプレースが必要になるかもしれません。費用対効果しだいで こちらのアプローチのほうが有利なケースはもちろんあると思いますが、ネットワークを止めずにマイグレーションを繰り返していくような運用の観点からすれば、変化の歩幅が大きすぎるのでないかと感じています。

2020-07-08 追記

@motonori_shindo さんからフィードバックをいただきました。JANOG45 での議論 に登壇されていた方で、

QoS や最適化はより複雑で、

のところを掘り下げたブログエントリーを書かれています。