APRESIA Technical Blog

SONiC ZTPを仮想マシン環境で試してみた

はじめに

 SONiC 201911 ブランチが作成されてから半年近く経ち、各機能が安定して動作するようになってきました。本ブランチの個々の機能の開発状況については、201911ブランチ のトラッキングにて管理されています。以下がSONiC 201911ブランチにて開発された主な機能となります。
  • VRF
  • ZTP
  • NAT
  • sFlow
 上記の中から、ZTP (Zero Touch Provisioning) についてGNS3の仮想化環境で動作確認をしましたので、その結果をご紹介いたします。

SONiC ZTPの紹介および本記事のシナリオ

SONiC ZTPのHigh Level Design(仕様および実装方法の概略)はこちらにて公開されています。SONiCのZTPは、SONiCを初期起動したときに、以下の初期設定・操作を行うことを可能にします。
  • 運用で使用するSONiCイメージの再インストール
  • 装置の設定(config_db.jsonのインストール)
  • shellスクリプトによる任意の初期設定・操作
  • pingによる起動確認
これらの初期設定の有無や順番はZTP JSONファイルにjson形式で定義することが可能です。そのZTP JSONファイルのダウンロード場所は、DHCP option 67を使ってSONiCを初期起動したホワイトボックススイッチに配布します。それによって、ホワイトボックススイッチはZTP JSONファイルを入手し、その記述内容に従って自身のSONiCの初期設定・操作を実行します。 本記事では、GNS3上に以下のようにSONiC vs版 (virtual switch版)の実験環境を用意します。

図1: GNS3上におけるZTPの試験構成

 また、ZTP JSONの例として以下を使用します。
{
    "ztp": {
        "01-firmware": {
            "install": {
                "url": "http://192.168.1.1/sonic-vs-ztp.bin"
            },
            "reboot-on-success": true
        },
        "02-provisioning-script": {
            "plugin": {
                "url":"http://192.168.1.1/apt_install.sh"
            }
        },
        "03-configdb-json": {
            "dynamic-url": {
                "source": {
                    "prefix": "http://192.168.1.1/",
                    "identifier": "hostname",
                    "suffix": "_config_db.json"
                }
            }
        },
        "04-connectivity-check": {
            "ping-hosts": [ "192.168.1.1" ]
        },
        "05-provisioning-script": {
            "plugin": {
                "url":"http://192.168.1.1/upload_log.sh"
            }
        }
    }
}
このZTP JSONファイルの場合、以下の制御を順番に行います。
01-firmware sonic-vs-ztp.binをインストールし、成功したらリブート
02-provisioning-script aptで必要なツール(tftp client)をインストール
03-configdb-json 装置毎のconfig_db.jsonをインストール
04-connectivity-check 管理ポートのGatewayへpingを実行
05-provisioning-script ZTPのログをProvisioningサーバにtftpにてアップロード
 補足になりますが、ZTP JSONでは01などの番号を付与することで、実行する順序を明示することが可能です。また、上記の"03-configdb-json"ではダウンロードするconfig_db.jsonは装置毎に変えたいため、dynamic-urlを使いHost nameによって別のファイルをダウンロードできるように制御しています。例えばHost NameがLeaf1の場合は、Leaf1_config_db.jsonをダウンロードします。これらのZTP JSONファイルの記述ルールは前述のZTPのHigh Level Designにて説明されています。
 上記のZTP JSONファイルを前提に、SONiC ZTPの試験環境を用意し、動作確認を行っていきます。

ZTPが有効なSONiCイメージの作成

 SONiCのZTPを使用するためにはZTPを有効にしたSONiCイメージを作成する必要があります。もしZTPが有効ではないSONiCイメージを起動した場合、SONiCイメージに埋め込まれているconfig_db.jsonを初期設定ファイルとして起動します。
 SONiCイメージのビルド方法の詳細はこちらを参照ください。今回はGNS3上で動作確認をしたいため、Platformはvsとなります。今回の試験では、Ubuntu 16.04 Server、docker-ce (version 19.03.9)を使ってビルドしました。また、試験実施時に使用したSONiC 201911ブランチのcommit numberは7b5fb95fc7505b592b173769bb2fd089b4723e0eです。 以下のように順番にコマンドを実行し、SONiCのイメージを作成しました。(サーバ環境によりますが、フルビルドに4時間程度かかります)
sudo modprobe overlay
git clone https://github.com/Azure/sonic-buildimage 
cd sonic-buildimage 
git checkout 201911 
git submodule update --init --recursive 
make init 
make configure PLATFORM=vs ENABLE_ZTP="y"
make all PLATFORM=vs ENABLE_ZTP="y"
 なお、今回のZTPの試験は、最初に起動するSONiCイメージと、その後にZTPにてダウンロードするSONiCイメージの二つが必要になります。ですので、上記にてSONiCイメージを二回ビルドして、一方のSONiCイメージをGNS3に登録(こちらの記事を参照ください)し、もう一方のSONiCイメージはGNS3内のZTPサーバに保存しました。

ZTPサーバの準備

 GNS3にて、図1の通り、ZTPを有効にしたSONiCを配置し、ZTPサーバを用意します。本試験ではGNS3上にUbuntu 18.04 serverを起動し、それをZTPサーバとして設定します。本試験では、一台のZTPサーバにDHCP、NAT、TFTP、Provisioningの4つの役割を持たせますが、複数のサーバに役割を分けることも可能です。
 まずはDHCPの設定を行います。DHCPは"apt-get install isc-dhcp-server"にてインストールしました。DHCPにて配布する情報を設定するために、GNS3にてLeaf1、Leaf2、SpineのMACアドレスを確認します。本試験では以下のMACアドレスの割り当てになっていました。

図2: GNS3における各SONiCスイッチのMACアドレス

 そこで、/etc/dhcp/dhcpd.conf に以下を追加します。この設定では、MACアドレス毎に異なるIPアドレスとHost nameを割り当てます。また、DHCP Option 67にてZTP JSONのURL(http://192.168.1.1/ztp.json)をホワイトボックススイッチにアナウンスしています。
subnet 192.168.1.0 netmask 255.255.255.0 {
  option domain-name "sonic-test.com";
  option routers 192.168.1.1;
}

host Leaf1 {
  hardware ethernet 0c:a6:61:d7:8b:00;
  fixed-address 192.168.1.11;
  option host-name "Leaf1";
  #option 67
  option bootfile-name "http://192.168.1.1/ztp.json";
}

host Leaf2 {
  hardware ethernet 0c:a6:61:4a:a4:00;
  fixed-address 192.168.1.12;
  option host-name "Leaf2";
  #option 67
  option bootfile-name "http://192.168.1.1/ztp.json";
}

host Spine {
  hardware ethernet 0c:a6:61:c0:be:00;
  fixed-address 192.168.1.13;
  option host-name "Spine";
  #option 67
  option bootfile-name "http://192.168.1.1/ztp.json";
}
 これで、SONiCが初期起動したときに、DHCPによって自身のHost nameを設定した上で、ZTP JSONも自動的にダウンロードします。そのためにはhttpサーバをZTPサーバにて起動しておき、ZTP JSONファイルを配置しておく必要があります。また、その後も、ZTP JSONに記載しているSONiCイメージやconfig_db.json、shellスクリプト類もhttpにてダウンロードできるようにしておく必要があります。httpサーバのインストールはsudo apt install apache2 にて行いました。その後に/var/www/htmlにダウンロード用の以下のファイルを配置します。
  • ztp.json
  • sonic-vs-ztp.bin (ZTPを有効にした再インストール用のSONiC イメージ)
  • Leaf1_config_db.json
  • Leaf2_config_db.json
  • Spine_config_db.json
  • apt_install.sh
  • upload_log.sh
 ztp.jsonの内容は前述を参照ください。3つのconfig_db.jsonは本記事の最後にサンプルを掲載します。二つのshellスクリプトは以下の内容にしております。 
・apt_install.sh
#! /bin/bash
sudo apt update
sudo apt install -y tftp-hpa
 このshellスクリプトにて、aptを使いtftpのクライアントツールをインストールしています。
・upload_log.sh
#! /bin/bash
sudo show ztp status --verbose > /tmp/ztp.log
tftp 192.168.1.1 -m binary -c put /tmp/ztp.log ztp_status_${HOSTNAME}.log
tftp 192.168.1.1 -m binary -c put /var/log/ztp.log ztp_log_${HOSTNAME}.log
 このshellスクリプトにて、後述するshow ztp statusの結果をztp.logに保存し、それをtftpサーバにアップロードします。また、SONiC ZTP機能の実行ログ(/var/log/ztp.log)も、tftpサーバにアップロードします。
 これらのshellスクリプトを実行するためには、aptとtftpを動作させる必要があります。SONiCからaptにてツールのインストールを可能にするためにZTPサーバにNATを設定します。/etc/sysctl.conf にnet.ipv4.ip_forward=1を追加し、sudo sysctl –pを実行することで、IPv4 forwardingを有効にします。そのうえで、iptablesにてNATを有効にするため、sudo iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE を実行します。次にTFTPサーバをsudo -E apt install tftpd-hpa にてインストールします。また、クライアントからファイルをTFTPにてアップロードできるように、/etc/default/tftpd-hpaに、TFTP_OPTIONS="--secure --create"を追記し、sudo /etc/init.d/tftpd-hpa restart にて設定を反映します。
 以上で、ZTPサーバの準備が完了しました。

SONiC ZTPの実行結果

 GNS3上にて、SONiCのZTPの動作試験をしていきます。今回の試験では、図1の試験構成の中でLeaf1, Spineは既にZTPによる初期設定が完了している状態で、Leaf2を新たに接続して、ZTPにて初期設定をしたときの結果を示します。Leaf2を起動するとコンソール画面にZTP関連のログが出力されました。ログの量が多いため、一部のログは"..."にて省略しております。
ZTP service started.
Downloading provisioning data from http://192.168.1.1/ztp.json to /var/run/ztp/ztp_data_opt67.json
Starting ZTP using JSON file /var/run/ztp/ztp_data_opt67.json at 2020-05-22 02:28:45 UTC.
Checking running configuration to load ZTP configuration profile.
…
Processing configuration section 01-firmware at 2020-05-22 02:29:18 UTC.
firmware: Downloading file 'http://192.168.1.1/sonic-vs-ztp.bin'.
firmware: Version SONiC-OS-201911.0-7b5fb95f is already installed and is operational.
Processed Configuration section 01-firmware with result SUCCESS, exit code (0) at 2020-05-22 02:29:18 UTC.
ZTP is rebooting the device as reboot-on-success flag is set.
…
ZTP service started.
Starting ZTP using JSON file /host/ztp/ztp_data.json at 2020-05-22 02:28:45 UTC.
Checking running configuration to load ZTP configuration profile.
…
Processing configuration section 02-provisioning-script at 2020-05-22 02:31:08 UTC.
…
Processed Configuration section 02-provisioning-script with result SUCCESS, exit code (0) at 2020-05-22 02:31:08 UTC.
Processing configuration section 03-configdb-json at 2020-05-22 02:31:18 UTC.
configdb-json: Downloading config_db.json file from 'http://192.168.1.1/Leaf2_config_db.json'.
configdb-json: Configuration change detected. Removing ZTP configuation from Config DB.
configdb-json: Stopping ZTP discovery on interfaces.
configdb-json: Reloading config_db.json to Config DB.
…
configdb-json: Copying downloaded config_db.json to startup configuration.
Processed Configuration section 03-configdb-json with result SUCCESS, exit code (0) at 2020-05-22 02:31:18 UTC.
Processing configuration section 04-connectivity-check at 2020-05-22 02:32:29 UTC.
connectivity-check: Attempting to connect to IPv4 hosts ['192.168.1.1'].
connectivity-check: Pinging host '192.168.1.1’.
…
Processed Configuration section 04-connectivity-check with result SUCCESS, exit code (0) at 2020-05-22 02:32:29 UTC.
Processing configuration section 05-provisioning-script at 2020-05-22 02:32:33 UTC.
Processed Configuration section 05-provisioning-script with result SUCCESS, exit code (0) at 2020-05-22 02:32:33 UTC.
Checking configuration section 01-firmware result: SUCCESS, ignore-result: False.
Checking configuration section 02-provisioning-script result: SUCCESS, ignore-result: False.
Checking configuration section 03-configdb-json result: SUCCESS, ignore-result: False.
Checking configuration section 04-connectivity-check result: SUCCESS, ignore-result: False.
Checking configuration section 05-provisioning-script result: SUCCESS, ignore-result: False.
ZTP successfully completed at 2020-05-22 02:32:34 UTC.
 ログをご覧いただくと最初にztp.jsonをダウンロードして、それ以降はzpt.jsonに記載された01から05の操作が順番に実行されていることが分かると思います。また、以下のようにSONiCのコマンドから、ZTPの状態を確認することが可能です。以下の例は、ZTPが完了した後の結果のため、全ての項目のStatusがSUCCESSになっていますが、ZTPが動作中に本コマンドを実行すると、中間状態を確認することも可能です。
$ sudo show ztp status --verbose
Command: ztp status --verbose
========================================
ZTP
========================================
ZTP Admin Mode : True
ZTP Service    : Inactive
ZTP Status     : SUCCESS
ZTP Source     : dhcp-opt67 (eth0)
Runtime        : 03m 51s
Timestamp      : 2020-05-22 02:32:34 UTC
ZTP JSON Version : 1.0

ZTP Service is not running

----------------------------------------
01-firmware
----------------------------------------
Status          : SUCCESS
Runtime         : 16s
Timestamp       : 2020-05-22 02:29:34 UTC
Exit Code       : 0
Ignore Result   : False
 
----------------------------------------
02-provisioning-script
----------------------------------------
Status          : SUCCESS
Runtime         : 10s
Timestamp       : 2020-05-22 02:31:18 UTC
Exit Code       : 0
Ignore Result   : False
 
----------------------------------------
03-configdb-json
----------------------------------------
Status          : SUCCESS
Runtime         : 01m 11s
Timestamp       : 2020-05-22 02:32:29 UTC
Exit Code       : 0
Ignore Result   : False
 
----------------------------------------
04-connectivity-check
----------------------------------------
Status          : SUCCESS
Runtime         : 04s
Timestamp       : 2020-05-22 02:32:33 UTC
Exit Code       : 0
Ignore Result   : False
 
----------------------------------------
05-provisioning-script
----------------------------------------
Status          : SUCCESS
Runtime         : 01s
Timestamp       : 2020-05-22 02:32:34 UTC
Exit Code       : 0
Ignore Result   : False
 また、今回のztp.jsonでは最後にupload_log.shを実行して、TFTPサーバにZTP関連のログをアップロードするようにしていましたが、以下の通り、ZTPサーバにZTP関連ログがアップロードされていることも確認できました。
$ ls -l /var/lib/tftpboot
-rw-rw-rw- 1 tftp tftp 13732 May 22 11:32 ztp_log_Leaf2.log
-rw-rw-rw- 1 tftp tftp  1605 May 22 11:32 ztp_status_Leaf2.log
 これでZTPによりLeaf2が初期設定された上で起動しました。以下のように、Leaf1、SpineからBGPにてルート情報を取得できており、またLeaf1配下のIPサブネットに対してLeaf2からのpingが成功することを確認しました。
Leaf2# show ip route 
Codes: K - kernel route, C - connected, S - static, R - RIP,
       O - OSPF, I - IS-IS, B - BGP, E - EIGRP, N - NHRP,
       T - Table, v - VNC, V - VNC-Direct, A - Babel, D - SHARP,
       F - PBR, f - OpenFabric,
       > - selected route, * - FIB route, q - queued route, r - rejected route

S>* 0.0.0.0/0 [200/0] via 192.168.1.1, eth0, 00:12:12
B>* 1.1.1.1/32 [20/0] via 10.1.2.1, Ethernet0, 00:00:41
C>* 2.2.2.2/32 is directly connected, Loopback0, 00:12:06
B>* 3.3.3.3/32 [20/0] via 10.1.2.1, Ethernet0, 00:00:41
C>* 10.1.2.0/24 is directly connected, Ethernet0, 00:12:05
B>* 10.11.0.0/24 [20/0] via 10.1.2.1, Ethernet0, 00:00:41
C>* 10.22.0.0/24 is directly connected, Vlan1001, 00:12:05
C>* 192.168.1.0/24 is directly connected, eth0, 00:12:14
Leaf2# 
Leaf2# ping 10.11.0.1
PING 10.11.0.1 (10.11.0.1) 56(84) bytes of data.
64 bytes from 10.11.0.1: icmp_seq=1 ttl=63 time=1.51 ms
64 bytes from 10.11.0.1: icmp_seq=2 ttl=63 time=1.35 ms
64 bytes from 10.11.0.1: icmp_seq=3 ttl=63 time=1.25 ms
^C
--- 10.11.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 1.254/1.373/1.514/0.115 ms
Leaf2#

最後に

 今回はSONiC ZTPの動作確認結果を共有させていただきました。今回はGNS3にてZTPの動作確認をしましたが、ハードウェアに依存する要素がないため、ハードウェアでも同様な手順でSONiCのZTPを動作させることができると考えます。SONiC 201911ブランチは他にも重要な機能が開発されておりますので、引き続き、動作確認した結果を共有していきたいと思います。

補足:使用したconfig_db.json (SONiC設定ファイル)

 本試験では、Leaf1, Leaf2, Spineには、以下のconfig_db.jsonを使用しました。
・Leaf1
{
    "BGP_NEIGHBOR": {
	"10.1.1.1": {
	    "asn": "65100",
	    "holdtime": "180",
	    "keepalive": "60",
	    "local_addr": "10.1.1.2",
	    "name": "Spine1",
	    "nhopself": "0",
	    "rrclient": "0"
	}
    },
    "DEVICE_METADATA": {
	"localhost": {
	    "bgp_asn": "65110",
	    "hostname": "Leaf1",
	    "hwsku": "Force10-S6000",
	    "mac": "52:54:00:12:34:01",
	    "platform": "x86_64-kvm_x86_64-r0",
	    "type": "LeafRouter"
	}
    },
    "MGMT_INTERFACE": {
        "eth0|192.168.1.11/24": {
            "gwaddr": "192.168.1.1"
        }
    },
    "INTERFACE": {
	"Ethernet0|10.1.1.2/24": {}
    },
    "VLAN_INTERFACE": {
	"Vlan1001|10.11.0.1/24": {}
    },
    "LOOPBACK_INTERFACE": {
	"Loopback0|1.1.1.1/32": {}
    },
    "PORT": {
	"Ethernet0": {
	    "admin_status": "up",
	    "alias": "fortyGigE0/0",
	    "index": "0",
	    "lanes": "25,26,27,28",
	    "mtu": "9100",
	    "speed": "40000"
	},
	"Ethernet4": {
	    "admin_status": "up",
	    "alias": "fortyGigE0/4",
	    "index": "1",
	    "lanes": "29,30,31,32",
	    "mtu": "9100",
	    "speed": "40000"
	}
    },
    "TELEMETRY": {
	"gnmi": {
	    "port": "8080"
	}
    },
    "VLAN": {
	"Vlan1001": {
	    "members": [
		"Ethernet4"
	    ],
	    "vlanid": "1001"
	}
    },
    "VLAN_MEMBER": {
	"Vlan1001|Ethernet4": {
	    "tagging_mode": "untagged"
	}
    }
}
・Leaf2
{
    "BGP_NEIGHBOR": {
	"10.1.2.1": {
	    "asn": "65100",
	    "holdtime": "180",
	    "keepalive": "60",
	    "local_addr": "10.1.2.2",
	    "name": "Spine1",
	    "nhopself": "0",
	    "rrclient": "0"
	}
    },
    "DEVICE_METADATA": {
	"localhost": {
	    "bgp_asn": "65120",
	    "hostname": "Leaf2",
	    "hwsku": "Force10-S6000",
	    "mac": "52:54:00:12:34:01",
	    "platform": "x86_64-kvm_x86_64-r0",
	    "type": "LeafRouter"
	}
    },
    "MGMT_INTERFACE": {
        "eth0|192.168.1.12/24": {
            "gwaddr": "192.168.1.1"
        }
    },
    "INTERFACE": {
	"Ethernet0|10.1.2.2/24": {}
    },
    "VLAN_INTERFACE": {
	"Vlan1001|10.22.0.1/24": {}
    },
    "LOOPBACK_INTERFACE": {
	"Loopback0|2.2.2.2/32": {}
    },
    "PORT": {
	"Ethernet0": {
	    "admin_status": "up",
	    "alias": "fortyGigE0/0",
	    "index": "0",
	    "lanes": "25,26,27,28",
	    "mtu": "9100",
	    "speed": "40000"
	},
	"Ethernet4": {
	    "admin_status": "up",
	    "alias": "fortyGigE0/4",
	    "index": "1",
	    "lanes": "29,30,31,32",
	    "mtu": "9100",
	    "speed": "40000"
	}
    },
    "TELEMETRY": {
	"gnmi": {
	    "port": "8080"
	}
    },
    "VLAN": {
	"Vlan1001": {
	    "members": [
		"Ethernet4"
	    ],
	    "vlanid": "1001"
	}
    },
    "VLAN_MEMBER": {
	"Vlan1001|Ethernet4": {
	    "tagging_mode": "untagged"
	}
    }
}
・Spine
{
    "BGP_NEIGHBOR": {
	"10.1.1.2": {
	    "asn": "65110",
	    "holdtime": "180",
	    "keepalive": "60",
	    "local_addr": "10.1.1.1",
	    "name": "Leaf1",
	    "nhopself": "0",
	    "rrclient": "0"
	},
	"10.1.2.2": {
	    "asn": "65120",
	    "holdtime": "180",
	    "keepalive": "60",
	    "local_addr": "10.1.2.1",
	    "name": "Leaf2",
	    "nhopself": "0",
	    "rrclient": "0"
	}
    },
    "DEVICE_METADATA": {
	"localhost": {
	    "bgp_asn": "65100",
	    "hostname": "Spine",
	    "hwsku": "Force10-S6000",
	    "mac": "52:54:00:12:34:01",
	    "platform": "x86_64-kvm_x86_64-r0",
	    "type": "LeafRouter"
	}
    },
    "MGMT_INTERFACE": {
        "eth0|192.168.1.13/24": {
            "gwaddr": "192.168.1.1"
        }
    },
    "INTERFACE": {
	"Ethernet0|10.1.1.1/24": {},
	"Ethernet4|10.1.2.1/24": {}
    },
    "LOOPBACK_INTERFACE": {
	"Loopback0|3.3.3.3/32": {}
    },
    "PORT": {
	"Ethernet0": {
	    "admin_status": "up",
	    "alias": "fortyGigE0/0",
	    "index": "0",
	    "lanes": "25,26,27,28",
	    "mtu": "9100",
	    "speed": "40000"
	},
	"Ethernet4": {
	    "admin_status": "up",
	    "alias": "fortyGigE0/4",
	    "index": "1",
	    "lanes": "29,30,31,32",
	    "mtu": "9100",
	    "speed": "40000"
	}
    },
    "TELEMETRY": {
	"gnmi": {
	    "port": "8080"
	}
    }
}