Ubuntu Weekly Recipe

第539回LXDのコンテナからSR-IOV対応デバイスを利用する

第521回でのLXD 3.0の基本的な使い方、第532回でのLXDコンテナからGPUを利用する方法、第535回でのLXDのネットワーク設定を取り上げました。それらに続き、今回はUbuntu 18.04.1 LTSのLXD 3.0.1にて、SR-IOV対応の10ギガビット・イーサーネットカードをコンテナから利用してみます。

SR-IOV

SR-IOV (Single Root I/O Virtualization) はPCI Expressの仕様の一つで、PCIeデバイスを複数の仮想的なデバイスとして見せる機能です。

ハードウェア側でデバイスを仮想的に分割するため、コンテナや仮想マシンが直接デバイスにアクセスでき、ホストOS上でソフトウェア的に一つのデバイスを共有する場合に比べて少ないオーバーヘッドでI/Oが可能になります。

とても魅力的な機能ですが、利用するにはPCIeデバイスがハードウェアレベルで対応している必要があります。10ギガビット・イーサーネット(10GbE)カードやInfiniBandといった広帯域のネットワークカードは対応しているものが多いようです。また、BIOS(UEFI)もSR-IOVをサポートしている必要があります。

基本的なSR-IOVデバイスの利用方法

まずはSR-IOVがどのようなものか簡単に試してみましょう。少し古いPCIeデバイスですが、筆者の手元にある10GbEカードのIntel X520-DA2がSR-IOVに対応していましたので、これをUbuntu 18.04.1 LTSのマシンに搭載してみます。

lspciコマンドでマシン上のPCI(PCIe)デバイスを一覧表示できます。X520-DA2のコントローラー名82599でgrepしてみましょう。

$ lspci | grep 82599
04:00.0 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)
04:00.1 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)

X520-DA2はSFP+ポートを2ポート持ちます。上記では各ポートがそれぞれ表示されています。続いてipコマンドでネットワークインタフェースを見てみましょう。

$ ip address
(略)
4: enp4s0f0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 00:1b:21:bc:04:a2 brd ff:ff:ff:ff:ff:ff
5: enp4s0f1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 00:1b:21:bc:04:a3 brd ff:ff:ff:ff:ff:ff

ネットワークインタフェース名は環境によって異なり、筆者の環境ではenp4s0f0enp4s0f1となっています。

SR-IOV対応のPCIeデバイスにおいて、これらの物理的なデバイスは「Physical Function」と呼ばれます。これに対し、SR-IOVで作成する仮想的なデバイスは「Virtual Function」と呼ばれます。では実際にこのPhysical Functionの下に複数のVirtual Functionを作成してみましょう。

IntelのSR-IOV対応10GbEカードを使用する場合は、親になるPhysical Functionを事前にインタフェースアップしておきます[1]⁠。この例ではenp4s0f0を親とします。

$ sudo ip link set dev enp4s0f0 up

Physical FunctionのstateがUPになっていることを確認します。

$ ip address show enp4s0f0
4: enp4s0f0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:1b:21:bc:04:a2 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::21b:21ff:febc:4a2/64 scope link 
       valid_lft forever preferred_lft forever

Virtual Functionを作成します。Ubuntuを含む、最近の主要なLinuxディストリビューションのkernelであれば、sysfsからVirtual Functionを作成可能です[2]⁠。SR-IOV対応のネットワークデバイスの場合、下記のパスにVirtual Function設定用のファイルが存在します。

/sys/class/net/ネットワークインタフェース名/device/sriov_numvfs

筆者の環境では、/sys/class/net/enp4s0f0/device/sriov_numvfsになります。

$ ls -l /sys/class/net/enp4s0f0/device/sriov_numvfs
-rw-rw-r-- 1 root root 4096 Sep 22 19:39 /sys/class/net/enp4s0f0/device/sriov_numvfs

$ cat /sys/class/net/enp4s0f0/device/sriov_numvfs
0

このsriov_numvfsファイルにroot権で数字を書き込むことで、その数だけVirtual Functionが作成されます。とりあえず2を書き込んでVirtual Functionを2個作ってみましょう。

$ sudo sh -c 'echo 2 > /sys/class/net/enp4s0f0/device/sriov_numvfs'

lspciコマンドでPCIeデバイス情報を見てみましょう。

$ lspci | grep 82599
04:00.0 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)
04:00.1 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)
04:10.0 Ethernet controller: Intel Corporation 82599 Ethernet Controller Virtual Function (rev 01)
04:10.2 Ethernet controller: Intel Corporation 82599 Ethernet Controller Virtual Function (rev 01)

「Intel Corporation 82599 Ethernet Controller Virtual Function」という名前でVirutal Functionが2つ作成されていることがわかります。ネットワークインタフェースも見てみましょう。

$ ip address
(略)
4: enp4s0f0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:1b:21:bc:04:a2 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::21b:21ff:febc:4a2/64 scope link
       valid_lft forever preferred_lft forever
5: enp4s0f1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 00:1b:21:bc:04:a3 brd ff:ff:ff:ff:ff:ff
6: enp4s16: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 62:b5:c8:8f:2b:ce brd ff:ff:ff:ff:ff:ff
7: enp4s16f2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 86:92:1a:8a:a6:f1 brd ff:ff:ff:ff:ff:ff

enp4s16enp4s16f2の2つのネットワークインタフェースが作成されています。この内のenp4s16にIPアドレス172.16.1.20/24を設定してみましょう。

$ sudo ip address add 172.16.1.20/24 dev enp4s16 
$ sudo ip link set dev enp4s16 up
$ ip address show enp4s16
6: enp4s16: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 62:b5:c8:8f:2b:ce brd ff:ff:ff:ff:ff:ff
    inet 172.16.1.20/24 scope global enp4s16
       valid_lft forever preferred_lft forever
    inet6 fe80::60b5:c8ff:fe8f:2bce/64 scope link 
       valid_lft forever preferred_lft forever

10GbEのネットワークの対向に位置するIPアドレス172.16.1.11のマシンと通信してみましょう[3]⁠。

$ ping -c 4 172.16.1.11
PING 172.16.1.11 (172.16.1.11) 56(84) bytes of data.
64 bytes from 172.16.1.11: icmp_seq=1 ttl=64 time=0.323 ms
64 bytes from 172.16.1.11: icmp_seq=2 ttl=64 time=0.197 ms
64 bytes from 172.16.1.11: icmp_seq=3 ttl=64 time=0.133 ms
64 bytes from 172.16.1.11: icmp_seq=4 ttl=64 time=0.198 ms

--- 172.16.1.11 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3049ms
rtt min/avg/max/mdev = 0.133/0.212/0.323/0.071 ms

通信できていますね。

ホストマシンからVirtual Functionを削除する場合は、sriov_numvfsファイルに0を書き込みます。

$ sudo sh -c 'echo 0 > /sys/class/net/enp4s0f0/device/sriov_numvfs'

コンテナからSR-IOVデバイスを利用する

今度はLXDからSR-IOV対応のネットワークデバイスを利用してみましょう。こちらもIntel 10GbEカードを使用する環境では事前に親Physical Functionをインタフェースアップしておきます[4]⁠。

$ sudo ip link set dev enp4s0f0 up

lxc config device addコマンドでコンテナにnicデバイスを追加します。この際、nictype=sriovを指定することで、Virtual Functionがコンテナにネットワークインタフェースとして追加されます。

$ lxc config device add コンテナ名 設定名 nic nictype=sriov parent=親Physical Function名

各項目の詳しい説明はUbuntu weekly recipeの535回およびLXDのドキュメントを参照してください。

nictype=sriovを指定する場合、parent=に親となるPhysical Functionを指定します。LXDがこのPhysical Functionを調べ、Virtual Functionが未作成であれば作成し、未使用のVirtual Functionをコンテナに割り当てます。LXDがVirtual Functionを作成・設定してくれるため、lxdグループに所属するユーザーアカウントであれば、コンテナを介してSR-IOV対応のネットワークデバイスを利用できます。

parent=にSR-IOV未対応のネットワークインタフェースを指定した場合や、ネットワークインタフェースの最大Virtual Function数[5]を超過した場合などは、lxcコマンドがエラーを返します。

実際に試してみましょう。この例ではubuntu:18.04イメージからnadeshikoという名前でコンテナを作成、起動しています。

$ lxc launch ubuntu:18.04 nadeshiko
Creating nadeshiko
Starting nadeshiko

$ lxc list -c ns4
+-----------+---------+----------------------+
|   NAME    |  STATE  |         IPV4         |
+-----------+---------+----------------------+
| nadeshiko | RUNNING | 10.150.118.16 (eth0) |
+-----------+---------+----------------------+

コンテナnadeshikoにVirtual Functionをネットワークインタフェースeth1として追加します※6※7⁠。

$ lxc config device add nadeshiko eth1 nic nictype=sriov parent=enp4s0f0
Device eth1 added to nadeshiko

コンテナnadeshiko内でipコマンドを実行して確認してみましょう[8]⁠。ここではホスト上からlxc execコマンド経由で実行しています。

$ lxc exec nadeshiko ip address show eth1
12: eth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 00:16:3e:b5:3e:cb brd ff:ff:ff:ff:ff:ff

コンテナnadeshikoeth1にIPアドレス172.16.1.12/24を設定し、インタフェースアップします。

$ lxc exec nadeshiko ip address add 172.16.1.12/24 dev eth1
$ lxc exec nadeshiko ip link set dev eth1 up
$ lxc exec nadeshiko ip address show eth1
12: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:16:3e:b5:3e:cb brd ff:ff:ff:ff:ff:ff
    inet 172.16.1.12/24 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::216:3eff:feb5:3ecb/64 scope link 
       valid_lft forever preferred_lft forever

lxc listコマンドでもnadeshikoeth1にIPアドレスが振られていることが確認できます。

$ lxc list -c ns4
+-----------+---------+----------------------+
|   NAME    |  STATE  |         IPV4         |
+-----------+---------+----------------------+
| nadeshiko | RUNNING | 172.16.1.12 (eth1)   |
|           |         | 10.150.118.16 (eth0) |
+-----------+---------+----------------------+

対向のIPアドレス172.16.1.11のマシンと通信してみましょう。

$ lxc exec nadeshiko -- ping -c 4 172.16.1.11
PING 172.16.1.11 (172.16.1.11) 56(84) bytes of data.
64 bytes from 172.16.1.11: icmp_seq=1 ttl=64 time=0.222 ms
64 bytes from 172.16.1.11: icmp_seq=2 ttl=64 time=0.126 ms
64 bytes from 172.16.1.11: icmp_seq=3 ttl=64 time=0.127 ms
64 bytes from 172.16.1.11: icmp_seq=4 ttl=64 time=0.180 ms

--- 172.16.1.11 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3053ms
rtt min/avg/max/mdev = 0.126/0.163/0.222/0.043 ms

コンテナnadeshikoと対向のマシンにネットワークスループット計測用ソフトウェアのiperf3をインストールし、転送スピードを計測してみます。

$ lxc exec nadeshiko -- iperf3 -c 172.16.1.11 -i 0
Connecting to host 172.16.1.11, port 5201
[  5] local 172.16.1.12 port 51452 connected to 172.16.1.11 port 5201
[ ID] Interval           Transfer     Bandwidth       Retr  Cwnd
[  5]   0.00-10.00  sec  11.0 GBytes  9.42 Gbits/sec   11    925 KBytes       
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bandwidth       Retr
[  5]   0.00-10.00  sec  11.0 GBytes  9.42 Gbits/sec   11             sender
[  5]   0.00-10.00  sec  11.0 GBytes  9.41 Gbits/sec                  receiver

iperf Done.

Bandwidthの値をみると9.42 Gbit/secの転送スピードが出ていることがわかります。

複数のコンテナでSR-IOVデバイスを利用する

もう一台コンテナを作成して、同じPhysical Function enp4s0f0を親に持つ別のVirtual Functionを割り当ててみましょう。コンテナ名はshimarinとします。

$ lxc launch ubuntu:18.04 shimarin
Creating shimarin
Starting shimarin

$ lxc config device add shimarin eth1 nic nictype=sriov parent=enp4s0f0
Device eth1 added to shimarin

コマンドはnadeshikoeth1を割り当てた時とコンテナ名が異なるだけです。LXDが未使用のVirtual Functionを選んでコンテナに割り当てるため、ユーザーはVirtual Functionの利用状況を意識せずに済みます。

コンテナshimarineth1にはIPアドレス172.16.1.13/24を設定します。

$ lxc exec shimarin ip address add 172.16.1.13/24 dev eth1
$ lxc exec shimarin ip link set dev eth1 up

$ lxc list -c ns4
+-----------+---------+-----------------------+
|   NAME    |  STATE  |         IPV4          |
+-----------+---------+-----------------------+
| nadeshiko | RUNNING | 172.16.1.12 (eth1)    |
|           |         | 10.150.118.16 (eth0)  |
+-----------+---------+-----------------------+
| shimarin  | RUNNING | 172.16.1.13 (eth1)    |
|           |         | 10.150.118.237 (eth0) |
+-----------+---------+-----------------------+

shimarinにもiperf3をインストールし、nadeshikoshimarinの2台で対向のIPアドレス 172.16.1.11のマシン間のスループットを計測してみましょう。対向のマシンではサーバーモードのiperf3を2プロセス、それぞれポート5201、5202で待ち受けさせています。nadeshikoからポート5201宛に、shimarinからポート5202宛に、iperf3のクライアントモードで同時に接続します。実行タイミングがずれると正確な転送スピードが測れないため、ここではsystemd-runコマンドを使用してホスト上のsystemd(systemd.timer)から同時刻にiperf3を実行してみます。

$ systemd-run --user --timer-property=AccuracySec=1s --on-calendar='2018-09-25 22:55:00 JST' \
  lxc exec nadeshiko -- /bin/sh -c 'iperf3 -c 172.16.1.11 -i 0 -p 5201 > nadeshiko.iperf3.txt'

$ systemd-run --user --timer-property=AccuracySec=1s --on-calendar='2018-09-25 22:55:00 JST' \
  lxc exec shimarin  -- /bin/sh -c 'iperf3 -c 172.16.1.11 -i 0 -p 5202 > shimarin.iperf3.txt'

--user --timer-property=AccuracySec=1s --on-calendar='2018-09-25 22:55:00 JST'までがsytemd.timerの設定です。--userでユーザー権限での登録、--timer-property=AccuracySec=1sでタイマー精度を1秒に指定(デフォルトは1分⁠⁠、--on-calendar='2018-09-25 22:55:00 JST'でコマンドの実行開始時刻を指定しています。lxc exec以降が各コンテナ内でのiperf3実行部分です。コンテナ内でiperf3の出力をファイルにリダイレクトする都合上、/bin/shコマンドの-cオプションでシェルプロセスとして実行しています。

指定した実行開始時刻になると、ホスト上のsystemd.timerから各lxc execコマンドが実行されます。実行終了まで待って結果を見てみましょう。

コンテナnadeshikoの実行結果です。

$ lxc exec nadeshiko -- ls -l nadeshiko.iperf3.txt
-rw-r--r-- 1 root root 534 Sep 25 22:55 nadeshiko.iperf3.txt

$ lxc exec nadeshiko -- cat nadeshiko.iperf3.txt
Connecting to host 172.16.1.11, port 5201
[  5] local 172.16.1.12 port 48584 connected to 172.16.1.11 port 5201
[ ID] Interval           Transfer     Bandwidth       Retr  Cwnd
[  5]   0.00-10.00  sec  5.48 GBytes  4.71 Gbits/sec   17    714 KBytes
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bandwidth       Retr
[  5]   0.00-10.00  sec  5.48 GBytes  4.71 Gbits/sec   17             sender
[  5]   0.00-10.00  sec  5.48 GBytes  4.71 Gbits/sec                  receiver

iperf Done.

コンテナshimarinの実行結果です。

$ lxc exec shimarin -- ls -l shimarin.iperf3.txt
-rw-r--r-- 1 root root 534 Sep 25 22:55 shimarin.iperf3.txt

$ lxc exec shimarin -- cat shimarin.iperf3.txt
Connecting to host 172.16.1.11, port 5202
[  5] local 172.16.1.13 port 34928 connected to 172.16.1.11 port 5202
[ ID] Interval           Transfer     Bandwidth       Retr  Cwnd
[  5]   0.00-10.00  sec  5.49 GBytes  4.71 Gbits/sec    2    691 KBytes
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bandwidth       Retr
[  5]   0.00-10.00  sec  5.49 GBytes  4.71 Gbits/sec    2             sender
[  5]   0.00-10.00  sec  5.48 GBytes  4.71 Gbits/sec                  receiver

iperf Done.

nadeshikoshimarinのどちらも4.71 Gbits/sec程度、合計9.42 Gbits/secの転送スピードが出ています。10GbEのほぼ全帯域を2コンテナで半分ずつ利用できていることがわかります。

コンテナからSR-IOVデバイスを削除する

コンテナからSR-IOVデバイスを削除する方法は、他の種類のデバイスを削除する場合と変わりません。

$ lxc config device remove shimarin eth1
Device eth1 removed from shimarin

nictype=sriovのデバイスを削除すると、そのコンテナに割り当てられていたVirtual FunctionはLXDによりリリースされ、他のコンテナから利用可能になります。同様に、コンテナ自体を削除した場合も、LXDが自動的にVirtual Functionを回収してくれます。

おすすめ記事

記事・ニュース一覧