Cumulus Linux - 基本的な L3 設定(FRR, Static) - 1
Cumulus Linux に興味が湧いたので触っています。ここでは頭の整理として基本的な部分をまとめました。
また、長くなったので記事を分割しました。
- Cumulus Linux - 基本的な L3 設定(FRR, Static) - 1
- Cumulus Linux - 基本的な L3 設定(OSPF) - 2
- Cumulus Linux - 基本的な L3 設定(BGP) - 3
ここでは FRR と Static について扱います。
FreeRangeRouting
OSPF, BGP など一般的に求められる機能がサポートされていますが、そのドキュメントを読むと随所にFRR
という単語が出てきます。
プロトコルの前にまずはFRR
について整理します。
概要
FRRとはQuagga(ソフトウェアルータ)
をルーツとした LinuxFoundation が管理している Linux/Unix のプラットフォームに向けた IP Routing Protocol Suite です。簡単にいうとライブラリです。
名前の通り Free のためGitHubにソースが公開されています。
サポートしている RFC をみたいときにはFeatureMatrixが参考になるかもしれません。
このことから各種プロトコルの設定は FRR
の設定ファイルに当たる /etc/frr/frr.conf
に設定されます。
アーキテクチャ
bgpd
, ospfd
, ospf6d
といったようにプロトコル単位に daemon があり、それらをzebra
が差分を吸収し、Kernel Routing Table
に経路情報を設定します。
利用している daemon は yes
となります。
cumulus@leaf01:mgmt:~$ sudo cat /etc/frr/daemons ... bgpd=yes ospfd=yes ospf6d=yes ripd=no ripngd=no isisd=no fabricd=no pimd=no ldpd=no nhrpd=no eigrpd=no babeld=no sharpd=no pbrd=no fabricd=no vrrpd=no
このことから設定の変更を反映させるには各 daemon を再起動する必要がありますが、frr.service
を再起動することで、結果的に依存している各種サービスも再起動することができます。
詳細はArchitectureを参照ください。
設定の変更について
Cumulus Linux のユーザーには3つの設定方法が用意されています。そのため、各種マニュアルには3つの方法が記載されています。
net(NCLU)
コマンドを通してfrr.conf
に設定vtsh(FRR)
コマンドを通してfrr.conf
に設定vi /etc/frr/frr.conf
で直接編集
net
コマンドと、vtsh
コマンドの比較はココに書いてあります。
個人的な意見ではvtsh
の方が慣れればシンプルなので高速に思いますが、他の操作もあるのでnet
コマンドでまずは慣れるほうがいいと感じています。
ちなみに、/etc/network/interfaces
からインターフェース情報をルーティングテーブルに継承する仕組みのため、vtsh
やfrr.conf
ではプロトコル"のみ"変更を行います。
ログについて
以上のことからプロトコルのログは/var/log/frr/frr.log
で確認できます。出力レベルはinfo
となり、debug
に変更したい場合はマニュアルを参照してください。
Route Entries
設計にあたって重要なスペックですが、Broadcom など汎用チップと共に利用するため、その仕様に合わせる必要があります。
詳細はマニュアルを参照ください。
簡単にいうと MAC アドレス、IPv4、IPv6、Neighbor 数に応じてメモリの割り当て量をセットしたパターンから1つ選択します。例えば L2 スイッチで使うなら、L3 関連のリソースは不要なのでl2-heavy-1
を選ぶ、、といった考え方です。
ECMP
ECMP は次の仕様となります。
- デフォルトで有効
- 同じプロトコルで等コストであること
- フローベーすハッシュ
- Hash*は IP protocol, 入力 IF, src IP, dst IP, src TCP/UDP, dst TCP/UDP
* Meallnox などの Spectrum ASIC 搭載の L2 では src/dst MAC, Etye Type, VLAN ID も評価
hash-seed の変更は次の通りです。範囲が広いでの loopback から求めるような仕組みにしてもいいかもしれません。
cumulus@spine01:mgmt:~$ net add forwarding ecmp hash-seed <0-4294967295> : An integer from 0 to 4294967295
ハッシュのカスタマイズは Mellanox のスイッチでサポートされています。詳細はマニュアルを参照ください。
Static Routing
Static Routing です。一般的な方法かと思います。
net add routing route 0.0.0.0/0 192.168.0.2 net add routing route 192.168.1.0/24 192.168.0.2
route に追加されます。
cumulus@spine01:mgmt:~$ net show route static RIB entry for static ==================== ... S>* 0.0.0.0/0 [1/0] via 192.168.0.2, swp1, weight 1, 00:01:29 S>* 192.168.1.0/24 [1/0] via 192.168.0.2, swp1, weight 1, 00:08:46
また next-hop には IP だけではなく、blackhole
やreject
があるので、再配信の時に利用するかもしれませんね。
cumulus@leaf01:mgmt:~$ net add routing route 192.168.200.0/24 <interface> : An interface name "swp1" or glob "swp1-4,6,10-12" <ipv4> : an IPv4 address lo : interface eth0 : LLDP peer oob-mgmt-switch swp1 : interface swp2 : interface swp3 : interface swp49 : LLDP peer leaf02 swp50 : LLDP peer leaf02 swp51 : LLDP peer spine01 swp52 : LLDP peer spine02 swp53 : LLDP peer spine03 swp54 : LLDP peer spine04 Null0 : Null interface blackhole : Silently discard pkts when matched g-ebgp : interface group-ibgp : interface mgmt : interface reject : Emit an ICMP unreachable when matched
IPv6 は同じ方法なので省略します。
まとめ
ざっと最低限使う機能を整理しました。他のプロトコルも触っていこうと思います。
よければ他の記事も参照ください。
Cumulus Linux - 基本的なL2設定方法
Cumulus Linuxに興味が湧いたので触っています。ここでは頭の整理として基本的な部分をまとめました。
概要
この記事はCumulus Linux 4.2
でテストしています。
Interface Type
次の Type があり、各ベンダがサポートしている内容と同じかと思います。
種類 | 概要 |
---|---|
Access | アクセスポート |
Trunk | トランクポート |
SVI | VLAN 内の Layer3 Interface |
Bond | Port Channel / Link Aggregation |
Bridge | ブリッジドメイン |
Layer2 Mode
Cumulus Linux には 2 種類の設定 Mode がありまます。
- VLAN-aware Bridge
- Traditional Bridge
簡単に比較表を作りました。
手法 | 概念 | ポイント |
---|---|---|
VLAN-aware | 1 ブリッジ内で VLAN グループを作成してポートを所属 | - 2000 VLAN まで利用可能 - シンプルな設定でスケールしやすい |
Traditional | ブリッジ単位が VLAN グループとしてポートを所属 | - 200VLAN まで利用可能 - Cumulus Linux 3.1 以前で VXLAN を利用している - PVSTP+を利用している - VLAN で LAN を指定するというよりブリッジで LAN を決めるイメージ |
上記のことから、そもそもの使い方してVLAN-aware
でどーんっと VLAN を通してあげることが推奨された使い方に感じました。
詳細はココを参照してください。
設定
比較の結果、VLAN-Aware
のみで実施しています。興味のある方はTraditional Bride Modeを参照してください。
Access/Trunk
bridge を作成します。ここで所属させた port が全てTrunk
で動作します。
net add bridge bridge ports swp49,51 net add bridge bridge vids 100,200 net add bridge bridge vlan-aware
swp51
をAccess
に変更する場合は、上記に加えて次のように指定します。
net add interface swp51 bridge access 100
以下のように状態が正しくセットされたことが確認できます。
cumulus@leaf01:mgmt:~$ net show bridge vlan Interface VLAN Flags --------- ---- --------------------- swp49 1 PVID, Egress Untagged 100 200 swp51 100 PVID, Egress Untagged
続いてswp49
でTrunk
の範囲をVLAN100に絞り込みます。
net add interface swp49 bridge vids 100
これによって、先ほどswp49
は VLAN100, 200 でしたが、VLAN100 のみ通るようになりました。
ncumulus@leaf01:mgmt:~$ net show bridge vlan Interface VLAN Flags --------- ---- --------------------- swp49 1 PVID, Egress Untagged 100 swp51 100 PVID, Egress Untagged
また、VLAN の利用にはデフォルトで 3600-3999 が予約済み(変更可能)のようなので注意が必要です。
Native-VLAN
デフォルトで Native VLAN は 1 が利用されるようになっており、自動的に設定されます。明示的に設定する場合は次のようにします。
net add bridge bridge pvid 1
もし通したくない場合は次のようにします。
net add interface swp49 bridge allow-untagged no
次のように swp49
から VLAN 1 が削除されました。
cumulus@leaf01:mgmt:~$ net show bridge vlan Interface VLAN Flags --------- ---- --------------------- swp49 100 swp51 100 PVID, Egress Untagged
SVI
VLAN 上に Interface を設定します。
net add vlan 100 ip add 192.168.0.100/24
route に追加されました。
cumulus@tor01:mgmt:~$ net show route 192.168.0.0/24 RIB entry for 192.168.0.0/24 ============================ Routing entry for 192.168.0.0/24 Known via "connected", distance 0, metric 0, best Last update 00:03:39 ago * directly connected, vlan100 FIB entry for 192.168.0.0/24 ============================ 192.168.0.0/24 dev vlan100 proto kernel scope link src 192.168.0.100
Storm-Control
ループしないように設計を行っても、Storm-Control は念の為入れておくかと思います。
/etc/cumulus/switchd.conf
の設定項目を編集して機能を利用します。
cumulus@leaf01:mgmt:~$ cat /etc/cumulus/switchd.conf | grep storm interface.swp1.storm_control.broadcast = 400 interface.swp1.storm_control.multicast = 3000 interface.swp1.storm_control.unknown_unicast = 2000
編集後はswitchd
の restart を行いますが、通信断が発生するため注意が必要です。
cumulus@leaf01:mgmt:~$ sudo systemctl restart switchd
一応 ping で試してみましたが、loss にはなりませんでしたね。
cumulus@spine01:mgmt:~$ ping 192.168.0.2 vrf-wrapper.sh: switching to vrf "default"; use '--no-vrf-switch' to disable PING 192.168.0.2 (192.168.0.2) 56(84) bytes of data. ... 64 bytes from 192.168.0.2: icmp_seq=8 ttl=64 time=1.35 ms 64 bytes from 192.168.0.2: icmp_seq=9 ttl=64 time=1.75 ms 64 bytes from 192.168.0.2: icmp_seq=10 ttl=64 time=2.14 ms <----- ここ 64 bytes from 192.168.0.2: icmp_seq=11 ttl=64 time=1.34 ms 64 bytes from 192.168.0.2: icmp_seq=12 ttl=64 time=1.49 ms ... ^C --- 192.168.0.2 ping statistics --- 14 packets transmitted, 14 received, 0% packet loss, time 36ms rtt min/avg/max/mdev = 1.341/1.722/2.135/0.222 ms
Bond(LAG)
リンク冗長の定番は LAG かと思います。早速、swp49
とswp50
で設定してみましょう。
net add bond bond0 bond slaves swp49-50
Bond Details
を見ると、Bond で変更できるオプションがわかりますね。
Bond Mode
は LACP に当たるIEEE802.3ad
が基本ですが、対向装置が使えない場合はbalance-xor
で静的接続に変更が必要です。
cumulus@leaf01:mgmt:~$ net show interface | grep bond UP swp49 1G 9216 BondMember leaf02 (swp49) Master: bond0(UP) UP swp50 1G 9216 BondMember leaf02 (swp50) Master: bond0(UP) UP bond0 2G 9216 802.3ad Bond Members: swp49(UP) bond0 Bond Members: swp50(UP) cumulus@leaf01:mgmt:~$ net show interface bond0 Name MAC Speed MTU Mode -- ----- ----------------- ----- ---- ------- UP bond0 76:ed:2a:8a:67:24 2G 9216 802.3ad Bond Details ------------------ -------- Bond Mode: 802.3ad Load Balancing: layer3+4 Minimum Links: 1 LACP Sys Priority: LACP Rate: 1 LACP Bypass: Inactive All VLANs on L2 Port -------------------- 1,100,200 Untagged -------- 1 cl-netstat counters ------------------- RX_OK RX_ERR RX_DRP RX_OVR TX_OK TX_ERR TX_DRP TX_OVR ----- ------ ------ ------ ----- ------ ------ ------ 701 0 20 0 697 0 0 0 LLDP Details ------------ LocalPort RemotePort(RemoteHost) --------- ---------------------- swp49 swp49(leaf02) swp50 swp50(leaf02) Routing ------- Interface bond0 is up, line protocol is up Link ups: 3 last: 2020/12/19 07:23:19.93 Link downs: 6 last: 2020/12/19 07:17:33.85 PTM status: disabled vrf: default index 15 metric 0 mtu 9216 speed 2000 flags: <UP,BROADCAST,RUNNING,MULTICAST> Type: Unknown HWaddr: 76:ed:2a:8a:67:24 Interface Type bond Master interface: bridge PVID: 1 protodown: off (n/a) cumulus@leaf01:mgmt:~$ ip link ... 6: swp49: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 9216 qdisc pfifo_fast master bond0 state UP mode DEFAULT group default qlen 1000 link/ether 76:ed:2a:8a:67:24 brd ff:ff:ff:ff:ff:ff 7: swp50: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 9216 qdisc pfifo_fast master bond0 state UP mode DEFAULT group default qlen 1000 link/ether 76:ed:2a:8a:67:24 brd ff:ff:ff:ff:ff:ff ... 15: bond0: <BROADCAST,MULTICAST,MASTER,UP,LOWER_UP> mtu 9216 qdisc noqueue master bridge state UP mode DEFAULT group default qlen 1000 link/ether 76:ed:2a:8a:67:24 brd ff:ff:ff:ff:ff:ff
また、デフォルトで利用できるハッシュは次の通りです。
Meallnox
など利用する HW によってはカスタマイズが可能のようなので、環境に応じてドキュメントの確認が必要です。
LLDP
デフォルトで Linux のlldpd
が起動します。そのため、設定を変えたい場合は/etc/lldpd.conf
を作成します。詳細はマニュアルを参照ください。
net
コマンドでも確認できるように次の内容が用意されています。
cumulus@tor01:mgmt:~$ net show lldp LocalPort Speed Mode RemoteHost RemotePort --------- ----- ---------- --------------- ---------- eth0 1G Mgmt oob-mgmt-switch swp10 swp49 1G BondMember leaf02 swp49 swp50 1G BondMember leaf02 swp50 swp51 1G Access/L2 spine01 swp1 swp52 1G Default spine02 swp1 swp53 1G Default spine03 swp1 swp54 1G Default spine04 swp1
まとめ
ざっと最低限使いそうな機能を整理しました。まだnet
操作に慣れていませんが、全体的にシンプルでいいですね。
そのほかにも以下の機能がありますので、興味ある方はマニュアルを読んでみてください。
- STP
- MLAG
- LACP bypass
- IGMP/MLD snooping
Juniper/go-netconfをざっと触る
Junos の自動化に Python ライブラリの PyEZ があることはご存知(ネットワークエンジニアの世界)の方が多いと思います。最近は Go 言語を触る機会が多いので、ふと調べたらJuniper/go-netconfが出てきたのでざっと触ってみました。
試した際のログはsourjp/lab-networkに置きました。
go-netonf?
This library is a simple NETCONF client based on RFC6241 and RFC6242 (although not fully compliant yet). Note: this is currently pre-alpha release. API and features may and probably will change. Suggestions and pull requests are welcome.
Juniper 公式パッケージで、netconf 接続を go 言語で実装されています。簡単に言えば PyEZ の Go 言語版に位置しますが、実装は比べると少なく、記載の通り pre-alpha です。
基本的な使い方
基本的な使い方は PyEZ と同じ感覚です。
- Connection を作成
- XML メッセージを投げる
とりあえずサンプルを書きました。
package main import ( "fmt" "log" "github.com/Juniper/go-netconf/netconf" "golang.org/x/crypto/ssh" ) func main() { sshConfig := &ssh.ClientConfig{ User: "root", Auth: []ssh.AuthMethod{ssh.Password("Juniper")}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } s, err := netconf.DialSSH("localhost:2830", sshConfig) if err != nil { log.Fatal(err) } defer s.Close() r, err := s.Exec(netconf.RawMethod("<get-chassis-inventory/>")) if err != nil { log.Fatal(err) } fmt.Printf("Reply: %+v", r) } """ $ go run main.go [] 0 Reply: &{XMLName:{Space:urn:ietf:params:xml:ns:netconf:base:1.0 Local:rpc-reply} Errors:[] Data: <chassis-inventory xmlns="http://xml.juniper.net/junos/12.1X47/junos-chassis"> <chassis junos:style="inventory"> <name>Chassis</name> ... """
まず、netconf.DialSSH()
でコネクションを作成します。
作成したコネクションのExec()
により RPC を通して XML メッセージを送信します。
このExec()
で受け取る型はRPCMethod
で、実体はstring
です。
そしてこの型を作成する方法は次の 4 種類です。
// RawMethod defines how a raw text request will be responded to type RawMethod string // MethodLock files a NETCONF lock target request with the remote host func MethodLock(target string) RawMethod { return RawMethod(fmt.Sprintf("<lock><target><%s/></target></lock>", target)) } // MethodUnlock files a NETCONF unlock target request with the remote host func MethodUnlock(target string) RawMethod { return RawMethod(fmt.Sprintf("<unlock><target><%s/></target></unlock>", target)) } // MethodGetConfig files a NETCONF get-config source request with the remote host func MethodGetConfig(source string) RawMethod { return RawMethod(fmt.Sprintf("<get-config><source><%s/></source></get-config>", source)) }
このことから次の使い分けをして情報を取得するようです。
メソッド | 用途 |
---|---|
netconf.RawMethod() | show コマンドなど情報を取得したい場合 |
netconf.MethodLock() | 設定を変える場合 |
netconf.MethodUnlock() | MethodLock()をしたい場合は忘れずに |
netconf.MethodGetConfig() | config を取得する場合 |
RPC の調べ方
先ほどのサンプルのように情報を取得したい場合は、特定のコマンドが Junos XML API のどれに当たるのかを調べる必要があります。
Junos XML API Explorerで、コマンドで検索すれば一発です。(昔からありましたっけ?)
ただ古い OS を利用しているとマトリクスに存在しないので、(きっと新しい ver と同じでしょうが)調べる方法があります。次のように CMD | display xml rpc
とつけることで、何を使っているかがわかります。
root@vsrx1> show interfaces terse | display xml rpc <rpc-reply xmlns:junos="http://xml.juniper.net/junos/12.1X47/junos"> <rpc> <get-interface-information> <terse/> </get-interface-information> </rpc> <cli> <banner></banner> </cli> </rpc-reply>
上記の場合であれば、次のようにすることで show interface terse
の結果が XML で得ることができます。
r, err := s.Exec(netconf.RawMethod("<get-interface-information><terse/></get-interface-information>"))
応用的な使い方
レポジトリにもいくつかサンプルがあります。
その中で面白かったのが CLI の作成で、ちょっとしたサンプルを書いてみました。
引数で接続先(port 番号)を与えて、取りたい情報を-chassis
や-if
で指定しています。
$ ./main -chassis -port=2830 Chassis : 340603e5d1fa $ ./main -chassis -port=2831 Chassis : 8c7c5874f721 $ ./main -if -port=2830 ge-0/0/0 : up ge-0/0/1 : up ge-0/0/2 : up
長いので chassis の serial number 取得する部分だけ抜粋します。
func main() { target := flag.String("target", "localhost", "target") port := flag.String("port", "2830", "port") chassisCmd := flag.Bool("chassis", false, "get chassis serial number from 'show chassis hardware'") ifCmd := flag.Bool("if", false, "get if admin from 'show interface terse'") flag.Parse() c, err := NewConn(*target, *port) if err != nil { log.Fatalln(err) } defer c.conn.Close() if *chassisCmd { if err := c.GetChassisSN(); err != nil { log.Fatal(err) } } if *ifCmd { if err := c.GetIfAdmin(); err != nil { log.Fatal(err) } } } // Conn netconf.Session type Conn struct { conn *netconf.Session } // NewConn Entrypoint of netconf.Session func NewConn(target, port string) (Conn, error) { conf := &ssh.ClientConfig{ User: "root", Auth: []ssh.AuthMethod{ssh.Password("Juniper")}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } d := fmt.Sprintf("%v:%v", target, port) s, err := netconf.DialSSH(d, conf) if err != nil { return Conn{}, err } return Conn{conn: s}, nil } // GetChassisSN get chassis serial number from "show chassis hardware" func (c *Conn) GetChassisSN() error { out, err := c.conn.Exec(netconf.RawMethod("<get-chassis-inventory/>")) if err != nil { return err } var ci ChassisInventory if err = xml.Unmarshal([]byte(out.Data), &ci); err != nil { return err } chassisName := strings.ReplaceAll(ci.Chassis.Name, "\n", "") chassisSN := strings.ReplaceAll(ci.Chassis.SerialNumber, "\n", "") fmt.Printf("%v : %v\n", chassisName, chassisSN) return nil } // ChassisInventory struct for "show chassis hardware" type ChassisInventory struct { XMLName xml.Name `xml:"chassis-inventory"` Text string `xml:",chardata"` Xmlns string `xml:"xmlns,attr"` Chassis struct { Text string `xml:",chardata"` Style string `xml:"style,attr"` Name string `xml:"name"` SerialNumber string `xml:"serial-number"` Description string `xml:"description"` ChassisModule []struct { Text string `xml:",chardata"` Name string `xml:"name"` Description string `xml:"description"` ChassisSubModule struct { Text string `xml:",chardata"` Name string `xml:"name"` Description string `xml:"description"` } `xml:"chassis-sub-module"` } `xml:"chassis-module"` } `xml:"chassis"` }
このように Go であれば簡単に CLI ツールを作成できますし、Go 言語の特徴としてクロスコンパイルができるので、作成した運用コマンド集を CLI として配布して、Windows も MAC ユーザーも同じように叩けるっていうのは一つかも、、しれません。 あとは API の作成も簡単ですし、xml の構造体を json に変換してもいいかもしれません。
一番手間がかかるのは Go で構造体を用意することですが、Junos から XML のレスポンスを受けて、XML to Go structに貼り付ければ簡単に作れます。
まとめ
いかがでしょうか?シンプルな実装で使いやすいですね。ただ commit 状況やその他ベンダのサポートも考えると Python ライブラリによる実装が今もメインかもしれません。
しかしながら他の言語で使えるようになると選択肢が増えるのでとてもいいことですね。gobgp
もありますし、個人的には Go の方が好きだったりします。
接続が失敗する場合
次のように接続できない場合についてです。
$ go run main.go 2020/12/17 15:27:55 dial tcp [::1]:830: connect: connection refused exit status 1
PyEZ でもそうですが接続方法が netconf 接続であるため、port 830
を利用します。
Junos には次の設定を忘れずにしましょう。
set system services netconf ssh
そして Vagrantfile を利用している方は次の設定を行ってください。
vsrx.vm.network :forwarded_port, id: "ssh", guest: 22, host: 2201 # for SSH vsrx.vm.network :forwarded_port, id: "netconf", guest: 830, host: 2830 # for NETCONF