仮想化通信

日本仮想化技術株式会社の公式エンジニアブログ

GPUパススルーを利用したKVM仮想マシンが不安定なときのチェックポイント

前回は、KVM環境でGPUパススルーをする方法について構築手順を紹介しました。

しかしながら、環境によってはいくつかの要因が重なり手順どおりにはいかないことがあります。

ここでは、KVM仮想マシンでGPUパススルーが動作しない または 不安定なときの確認ポイントを紹介いたします。

BIOSのバージョン確認

BIOSの不具合等により最新のGPUカードが正しく認識しないことがあります。メーカーのリリースノート等を確認し、該当しそうな不具合が見つかったら最新版のBIOSへアップデートします。

BIOSの設定確認

複数のGPUを利用する場合、4GB以上のMemory mapped I/O addressがサポートされていないとホストシステム上でGPUカードが正しく認識されないことがあります。

BIOSの設定にabove 4G decodingという項目がある場合は設定を有効にします。

KVM仮想マシンのチップセット設定

仮想マシンのOSがLinuxでGPUパススルーを利用する場合はQ35が安定すると言われています。仮想マシンを作成するとき、特別な理由がない限りチップセットはQ35を選択することを推奨します。

(参考:https://wiki.lime-technology.com/UnRAID_Manual_6#Creating_Your_Own_Virtual_Machines

KVM仮想マシンのCPUモード設定

仮想マシンのCPUモードが適切なものに設定されていないと仮想マシン上でGPUが正しくに動作しない場合があります。ライブマイグレーション機能を利用しない場合は "host-model" にすることを推奨します。

$ virsh edit [仮想マシン名]

### CPU Mode 設定例 ###
 <cpu mode='host-model'>
   <model fallback='allow'/>
 </cpu>

KVMホスト側の設定

PCI Expressでのデータ破損を防止するACS(Access Control Service)が有効になっていると、KVM環境では逆に不具合の要因となる場合があります。動作が不安定なときは /etc/libvirt/qemu.conf (Ubuntu 16.04の場合)を開き、以下の設定を加えてACSを無効化してみます。

relaxed_acs_check = 1

認識されているPCI Expressの世代の確認

GPUが想定していたほど性能を出していないときは、ホスト上でPCI Expressの世代が低く認識されていたり、何らかの理由でPCI Expressの転送速度が落ちている場合があります。まずは PCI Express の世代が正しく認識されているか確認します。

$ nvidia-smi -q

--- コマンド結果から抜粋 --- 
        GPU Link Info
            PCIe Generation
                Max                 : 3
                Current             : 3
            Link Width
                Max                 : 16x
                Current             : 16x
        Bridge Chip
            Type                    : N/A
            Firmware                : N/A
        Replays since reset         : 0
        Tx Throughput               : 0 KB/s
        Rx Throughput               : 0 KB/s

ここで仮想マシン上の環境では結果がGen1となっていた場合でも、物理マシン上では本来の世代で認識されていて、転送スピードを測定するとGen1の規定値以上になることがあります。次項を参考にバススピードも測ってみます。

GPUバススピードの確認方法

バススピードの実測値を測定して適切な値となるか確認します。 前回も軽く触れましたが、バススピード測定ツールはCUDAに添付されているサンプルプログラムに含まれているので、それを実行してみます。

### サンプルプログラムをまだコンパイルしていない場合は make を実行
$ cd ~/NVIDIA_CUDA-8.0_Sample
$ make

### バススピードの測定
$ cd ~/NVIDIA_CUDA-8.0_Samples/bin/x86_64/linux/release/
$ ./bandwidthTest

<出力例>
[CUDA Bandwidth Test] - Starting...
Running on...

 Device 0: Tesla P40
 Quick Mode

 Host to Device Bandwidth, 1 Device(s)
 PINNED Memory Transfers
   Transfer Size (Bytes)    Bandwidth(MB/s)
   33554432         11537.8

 Device to Host Bandwidth, 1 Device(s)
 PINNED Memory Transfers
   Transfer Size (Bytes)    Bandwidth(MB/s)
   33554432         12961.7

 Device to Device Bandwidth, 1 Device(s)
 PINNED Memory Transfers
   Transfer Size (Bytes)    Bandwidth(MB/s)
   33554432         240433.7

Result = PASS

NOTE: The CUDA Samples are not meant for performance measurements. Results may vary when GPU Boost is enabled.

実測値が1世代前の制限値より低い場合は、ハードウェアやソフトウェアに何らかの問題が起きている可能性があります(ハードウェアの相性問題であることもあります)。

各世代の制限値(一方向)

  • Gen1: 4.0 GB/sec
  • Gen2: 8.0 GB/sec
  • Gen3: 16.0 GB/sec
  • Gen4: 32.0 GB/sec

(参考:https://ja.wikipedia.org/wiki/PCI_Express

ベンチマーク

バススピードに問題がない場合は、GPUへ直接負荷をかけてみます。ベンチマークを長時間稼働させたとき、処理数が一定量で増えない場合はなんらかの問題が発生していると考えられます。ベンチマークツールの一つとして、GPU Burnを紹介します。

なお、実行する際にはGPUの温度の変化に注意して下さい。

<実行例>

$ ./gpu_burn 60

GPU 0: Tesla P40 (UUID: GPU-1f4d9f28-51e7-065d-2db9-af4397809c49)
Initialized device 0 with 22912 MB of memory (22552 MB available, using 20297 MB of it), using FLOATS
13.3%  proc'd: 3798 (9453 Gflop/s)   errors: 0   temps: 51 C
    Summary at:   Tue Dec 19 11:47:57 JST 2017

25.0%  proc'd: 7596 (9443 Gflop/s)   errors: 0   temps: 54 C
    Summary at:   Tue Dec 19 11:48:04 JST 2017

40.0%  proc'd: 12660 (9438 Gflop/s)   errors: 0   temps: 58 C
    Summary at:   Tue Dec 19 11:48:13 JST 2017

51.7%  proc'd: 16458 (9444 Gflop/s)   errors: 0   temps: 60 C
    Summary at:   Tue Dec 19 11:48:20 JST 2017

63.3%  proc'd: 20256 (9451 Gflop/s)   errors: 0   temps: 60 C
    Summary at:   Tue Dec 19 11:48:27 JST 2017

75.0%  proc'd: 24054 (9441 Gflop/s)   errors: 0   temps: 61 C
    Summary at:   Tue Dec 19 11:48:34 JST 2017

90.0%  proc'd: 29118 (9446 Gflop/s)   errors: 0   temps: 63 C
    Summary at:   Tue Dec 19 11:48:43 JST 2017

100.0%  proc'd: 32916 (9440 Gflop/s)   errors: 0   temps: 64 C
Killing processes.. done

Tested 1 GPUs:
    GPU 0: OK

Kernelログの調査

NVIDIAドライバでなんらかしらの問題が発生したとき、syslogへXidメッセージが出力されることがあります。XidメッセージにはIDが振られており、そのIDを元に公式ドキュメントを調べることでメッセージの詳細を得ることができます。

<出力例>
NVRM: Xid (PCI:0000:00:09): 32, Channel ID 0000000e intr 00008000

上記の場合、Xidは32になります。

モジュールパラメーターの設定・確認

環境によってはNVIDIAドライバのモジュールパラメータを指定することで改善される場合があります。パラメーターの項目はNVIDIAドライバのヘッダーファイル内にドキュメントがあります。

現在のパラメータの設定は以下のコマンドで確認することができます

$ cat /proc/driver/nvidia/params

パラメータを任意の値に設定するにはUbuntuの場合、以下の手順で設定します。

1. パラメーターの指定

nvidiaモジュールの設定ファイルを新たに作成します。

  • ここでは、パラメータ NVreg_CheckPCIConfigSpace=0 と NVreg_EnableMSI=1 を設定しています。
  • XXXにはNVIDIAドライバーのバージョンが入ります。
$ vi /etc/modprobe.d/nvidia-params.conf  # ←ファイルは新規に作成します

--- ファイル内容 ---
options nvidia_XXX NVreg_CheckPCIConfigSpace=0 NVreg_EnableMSI=1
--- ここまで ---

2. Initramfsの更新

$ update-initramfs -u

3. VMの再起動

$ sudo shutdown -r now

KVMでGPUパススルーを使った仮想マシンを構築

最近のGPU性能は著しく向上しており、GPUのグラフィック処理以外での利用(GPGPU)が増えてきています。とくにディープラーニングやデータマイニングなど、人工知能分野での利用が大きな注目を集めております。

KVM仮想マシン上でGPUを利用する方法の一つとしてPCIパススルーを利用する方法があります。ここではUbuntu 16.04でKVMの構築方法と仮想マシンでUbuntu 16.04の環境を作成方法までを紹介いたします。

前提条件

KVMホストとなるサーバーへUbuntu 16.04 Serverと仮想マシンマネージャーをインストールしておいて下さい。仮想マシンマネージャのインストールは別のマシンでも構いません。仮想マシンマネージャの詳細については以下のサイトを参照して下さい。

環境構築手順

1. 事前準備

KVMホストとなるマシンへKVMとLinuxブリッジをインストールします。

$ sudo apt install libvirt0 qemu-kvm libvirt-bin virt-manager bridge-utils

Ubuntu server 16.04 のISOイメージをダウンロードします。

$ wget http://releases.ubuntu.com/16.04/ubuntu-16.04.3-server-amd64.img
$ sudo mv ./ubuntu-16.04.3-server-amd64.img /var/lib/libvirt/images

2. 仮想マシンの作成

仮想マシンを作成する前に、GPUに割り当てられているPCIアドレスと接続状況の確認します。

$ lspci | grep -i nvidia
03:00.0 3D controller: NVIDIA Corporation Device 1b38 (rev a1)

$ virsh nodedev-list --tree

<出力結果の一部>
  +- pci_0000_00_02_0
  |   |
  |   +- pci_0000_03_00_0
  |

virt-installコマンドを使って仮想マシンを作成します。

$ virt-install --accelerate --hvm \
    --connect qemu:///system \     # 接続先(ローカルの場合)
    --name gpuvm01 \               # 仮想マシン名
    --vcpus 1 \                    # 仮想CPU
    --ram 2048 \                   # メモリサイズ
    --file-size 24 \               # 仮想ディスク
    --network type=direct,source=eno1,model=virtio \ # ネットワーク
    --file "/var/lib/libvirt/images/gpuvm01.img" \
    --os-variant ubuntu16.04 \     # OSの種類
    --boot hd,cdrom,menu=on \
    --cdrom "/var/lib/libvirt/images/ubuntu-16.04.3-server-amd64.img" \
    --machine q35 \                # チップセットを Q35 に設定
    --host-device=pci_0000_03_00_0 # GPUを指定

3. 仮想マシンへUbuntu Server 16.04をインストール

仮想マシンマネージャを使って作成した仮想マシンへUbuntu Server 16.04をインストールします(仮想マシンマネージャの使用方法についてはここでは省略します)。

  • 仮想マシンマネージャを起動します。
  • [ファイル] - [新しい仮想マシン] を開き、KVMホストへ接続します。
  • 作成した仮想マシンを起動します。
  • 仮想マシンのコンソールを開きインストーラーを進めます。

4. 仮想マシンへNVIDIAドライバーをインストール

GPUが仮想マシン上で認識されていることを確認します

$ lspci | grep -i nvidia

aptへリポジトリを追加し、NVIDIAドライバーをインストールします。

$ sudo add-apt-repository ppa:graphics-drivers/ppa
$ sudo apt update
$ apt-cache search '^nvidia-[0-9]+'
$ sudo apt install nvidia-XXX
$ sudo reboot

再起動後、nvidia-smiコマンドでGPUのステータスが出力されることを確認します。

$ nvidia-smi -q

5. CUDAのインストール

NVIDIAのサイトへアクセスし、CUDAをダウンロードします https://developer.nvidia.com/cuda-downloads

CUDAをインストールします

$ dpkg -i cuda-repo-ubuntu1604-8-0-local-ga2_8.0.61-1_amd64.deb
$ sudo apt update
$ sudo apt install cuda

CUDAのインストールが正常に完了したら、サンプルプログラムをコンパイルします。

$ export PATH=/usr/local/cuda/bin${PATH:+:${PATH}}
$ export LD_LIBRARY_PATH=/usr/local/cuda/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
$ ./cuda-install-samples-8.0.sh ~
$ cd ~/NVIDIA_CUDA-8.0_Samples
$ make

Tips

サンプルプログラムの一つcudaDecodeGLのコンパイルは、インストールされているnvidiaドライバのバージョンと UBUNTU_PKG_NAME の値が合っていないとmakeに失敗します。 失敗した場合は正しい値に修正し、もう一度実行します。

$ vi 3_Imaging/cudaDecodeGL/findgllib.mk

<編集>
UBUNTU_PKG_NAME = "nvidia-384"

動作確認

サンプルプログラムの一つである bandwidthTest を実施してみます。プログラムが正しくコンパイルできたことと、バススピードが妥当な値であることを確認します。

$ ~/NVIDIA_CUDA-8.0_Samples/bin/x86_64/linux/release/bandwidthTest

<出力結果例>
[CUDA Bandwidth Test] - Starting...
Running on...

 Device 0: GeForce GTX TITAN
 Quick Mode

 Host to Device Bandwidth, 1 Device(s)
 PINNED Memory Transfers
   Transfer Size (Bytes)    Bandwidth(MB/s)
   33554432         11437.2

 Device to Host Bandwidth, 1 Device(s)
 PINNED Memory Transfers
   Transfer Size (Bytes)    Bandwidth(MB/s)
   33554432         13225.2

 Device to Device Bandwidth, 1 Device(s)
 PINNED Memory Transfers
   Transfer Size (Bytes)    Bandwidth(MB/s)
   33554432         227939.5

Result = PASS

トラブルシューティング

うまく動作しなかった場合の対処法について、別エントリで紹介いたします。

tech.virtualtech.jp

HoloLens雑感

先日開催されたマイクロソフトのイベント「Connect();」の協賛連動企画で、Twitterでリツイートのキャンペーンに参戦し、景品のHoloLensをいただきました。

Microsoft Connect ();2017 - Microsoft Events & Seminars

バタバタしていたのでなかなか触れなかったのですが、やっと開封できたので、雑感を書き残しておこうかと思います。

f:id:tmiyahar:20171130125528j:plain 本体とクリッカー

f:id:tmiyahar:20171130125559j:plain キャリングケース

f:id:tmiyahar:20171130125620j:plain 開封

f:id:tmiyahar:20171130125750j:plain 装着してみた

この手のゴーグルで一番経験があるのはPS VRですが、ケーブルがない分取り回しがいいけど、重さはかなりあります。かなりのフロントヘビー。かつ、ゴーグル部分に傷をつけないようにと注意深く扱わないといけないのがやや難点かな。仕方ないですけどね。

最初はメガネをかけずに使っていたのですが、文字が読めないので仕方なくメガネの上から。でも、フロントヘビーのため、段々とずり下がってきてしまいます。視界からすると、ゴーグル部分の上端が少し黒っぽく見えるので、どうしても視界が遮られる感じに。逆に上に持ち上げていくと、液晶画面部分を少し見下ろすような感じになってしまって、どうも収まりが悪い。ここは上下に縁があるようなゴーグルにして、もう少し視野の安定感を出して欲しいなあと感じたところ。

指のジェスチャーによる操作ですが、なかなか難しい。慣れるまで時間がかかりました。また、スクロールさせる時、速度がかなりセンシティブなので、感度の調整ができるといいですね(多分どこかで設定できるはず)。

基本的な機能を試した後、おすすめされた「Robo Raid」をインストールして遊んでみました。

RoboRaid - 拡張現実 FPS ゲームの誕生 | HoloLens

壁が必要なので、会議室に一人こもって遊んでみましたが、何コレ楽しい。 前面だけでなく、側方にも視点移動ができるし、奥行き立体感もあるのが良いですね。PS VRにも同じようなゲームがありましたが、それに比べるとVR酔いも少ない(乗り物酔いしない私も、さすがに長時間プレイの後は調子が悪くなった)ので、ゲームデバイスとしてはアリな気がしました。

一方で、職業訓練用のシミュレータとしての利用などが考えられているようですが、現時点では視野が狭すぎることや、結局目の前にある程度の実物が必要なことを考えると、今すぐにこのデバイスで、というのは難しそう。むしろ、PC部分は別付けにして、必要最低限の表示情報だけをワイアレスに送信、表示してくれた方が使いやすいような気がしました。これはあくまでSDKで、今後は多分、そういう方向になっていくのでしょうけど。

あと、操作がジェスチャー+クリッカーだけはさすがに厳しい。ゲームパッドのような操作デバイスを有効活用できた方がいいですね。音声認識もあるんですが、「Select」を認識してくれない!?LとRの発音を聞き分けているのでしょうね。ネイティブ日本人には無理です・・

Juju/MAASでデプロイするOpenStackでLXDを使う方法

Juju/MAASを使ってOpenStackをデプロイ...と言いますか、Nova Computeチャームを使ってデフォルト設定のままデプロイすると、virt-type KVMとしてデプロイされます。インスタンスを起動するとVMが作成され、その中で指定したOSが起動します。

これをNova ComputeでLXDを使うには次のような内容のyamlファイルを書き、juju deployコマンドを実行します。Nova-LXDを動かすにはデプロイノードに2つのストレージボリュームが必要です。そのうち1つには起動フラグを設定しておく必要があります。

yamlファイル例

nova-compute:
    openstack-origin: "cloud:xenial-pike"
    enable-live-migration: yes
    enable-resize: yes

nova-compute-lxd:
    openstack-origin: "cloud:xenial-pike"
    enable-live-migration: yes
    enable-resize: yes
    virt-type: lxd

juju deployコマンド例

% juju add-machine --constraints tags=lxd1
(lxd1タグを持つJuju Machineを追加)

% juju machines
(Juju Machine番号を確認)

% juju deploy --config lxd-compute.yaml nova-compute nova-compute-lxd --to 10
% juju deploy lxd && juju config lxd block-devices=/dev/sdb storage-type=lvm
(Juju Machine番号が10の場合の実行例)

f:id:virtualtech:20171018141927p:plain:w480

KVMと比べると利用できるOSは制限されるものの、インスタンスの作成や削除、起動が速くてちょっと感動してしまいました。Juju/MAASを使ってOpenStackを構築する際にはぜひ、Nova-LXDをお試しください。

ちなみに参考サイトの一番下にリンクを貼りましたが、Nova-KVMとNova-LXDの共存をする方法をまとめています。必要であれば参考情報としてどうぞご覧ください。

参考サイト

Ubuntu JujuでデプロイしたマシンをAnsibleで管理するには

はじめに

最近、JujuとMAASを使ったOpenStack環境の構築を検証しています。 こちらでその辺りの情報を公開しています。

Jujuは様々なインフラにアプリケーションのデプロイと構成管理を行うツールで、MAASはMetal As a Serviceを提供するツールです。 これらはオープンソースで開発されており、ユーザーは公開されたドキュメントとパッケージを使って、自由にインストールして使うことができます。 サポートが必要であれば、Ubuntu Advantageを結ぶことで、 Canonicalによる技術支援を受けることができます。

JujuはAWSやAzureといったクラウド環境のほか、MAASで管理するサーバーを使うこともできるため、JujuとMAASを組み合わせることで既存のサーバーを使ってOpenStackやHadoop、最近流行りのKubernetes環境なども構築可能です。

Jujuはアプリケーションの展開を物理層にデプロイできるだけではなく、コンテナーにも展開できます。例えばApache Webサーバーを任意のノードに展開する場合は次のようにコマンドを実行することで簡単にデプロイすることができます。

ytooyama@maas:~⟫  juju deploy apache2 --to 1

これで、Jujuのマシン番号1のサーバーに対して物理環境にOSをインストールして、apache2をセットアップします。一方、コンテナーにデプロイする場合は次のようにコマンドを実行します。

ytooyama@maas:~⟫  juju deploy apache2 --to lxd:1

これで、Jujuのマシン番号1のサーバーに対してコンテナーを動かすために物理環境にOSのインストールとLXD/LXC環境を構築してコンテナーを起動、コンテナー上にapache2をセットアップします。

実際はapache2のインストール、サービスの起動、ポートの開放などの処理が行われます。コンテナーにデプロイした場合は、ブリッジ接続のコンフィグレーションも行われます。必要な場合は特定の形式(yaml形式)で記述した設定を元にカスタマイズの上デプロイすることもできます。

Jujuについては次のドキュメントをご覧ください。

jujucharms.com

Jujuには視覚的でわかりやすい、Juju GUIというものが用意されており、このDashboardを使ってデプロイしたアプリケーションの管理(アップデート、設定変更など)ができますし、スケールの拡大、縮小も可能です。同じ操作をJuju CLIで行うこともできます。

ただし、対象の台数が多くなってくると、Juju GUIやCLIツールを使って状況の確認をしたり、場合によってssh接続して様々なログを確認したりする事になります。数台規模の環境であればそれでも十分ですが、50台から100台規模になってくると結構しんどいですよね。

そこでようやく本題になるのですが、JujuでデプロイしたアプリケーションやOSを管理するためにAnsibleを使おうという話です。

Juju関連のこと

Jujuをセットアップすると、Jujuがアプリケーションを展開する前にOSをその環境にセットアップします。現状はOSとしてUbuntuが使われます。Jujuが構成管理をする際、インストールしたときに自動生成したキーペアを使って公開鍵認証によってリモートアクセスします。生成されるキーペアは、~/.local/share/juju/sshのパスの配下に展開されます。

AnsibleはSSHプロトコルとPythonを利用してノードを構成管理するツールです。構成管理を適用するノードにアクセスする際に公開鍵認証かパスワード認証を用いますが、Jujuがデプロイする環境ではパスワードが設定されません。従って、自動生成された鍵を使って公開鍵認証をする形になります。

Jujuがデプロイしたアプリケーションノードの一覧はJuju machenesコマンドで一覧表示できます。実際にコマンドを実行すると次のような結果が表示されます。

ytooyama@maas:~⟫ juju machines 
Machine  State    DNS            Inst id              Series  AZ       Message
0        started  172.17.29.223  cqd8ba               xenial  default  Deployed
0/lxd/0  started  172.17.29.225  juju-575e9e-0-lxd-0  xenial  default  Container started
0/lxd/1  started  172.17.29.227  juju-575e9e-0-lxd-1  xenial  default  Container started
0/lxd/2  started  172.17.29.193  juju-575e9e-0-lxd-2  xenial  default  Container started
0/lxd/3  started  172.17.29.194  juju-575e9e-0-lxd-3  xenial  default  Container started
0/lxd/4  started  172.17.29.195  juju-575e9e-0-lxd-4  xenial  default  Container started
0/lxd/6  started  172.17.29.201  juju-575e9e-0-lxd-6  xenial  default  Container started
1        started  172.17.29.224  4pp3th               xenial  default  Deployed
1/lxd/0  started  172.17.29.226  juju-575e9e-1-lxd-0  xenial  default  Container started
1/lxd/1  started  172.17.29.228  juju-575e9e-1-lxd-1  xenial  default  Container started
1/lxd/2  started  172.17.29.196  juju-575e9e-1-lxd-2  xenial  default  Container started
1/lxd/4  started  172.17.29.197  juju-575e9e-1-lxd-4  xenial  default  Container started
1/lxd/5  started  172.17.29.198  juju-575e9e-1-lxd-5  xenial  default  Container started
1/lxd/6  started  172.17.29.199  juju-575e9e-1-lxd-6  xenial  default  Container started
1/lxd/7  started  172.17.29.200  juju-575e9e-1-lxd-7  xenial  default  Container started
2        started  172.17.29.222  kcnfd8               xenial  default  Deployed
2/lxd/0  started  172.17.29.229  juju-575e9e-2-lxd-0  xenial  default  Container started

Machineはそれぞれの環境につけられたJujuが管理するマシン番号で、番号しか表示されていないものは物理サーバー、lxdの記述のあるものはコンテナーを示しています。例えば0/lxd/1ならば、マシン番号0のサーバーのコンテナーの1番ということがわかります。マシン番号0番には6個のコンテナーが存在することがわかります。

この出力結果の中でAnsibleでノードを管理するときに必要な情報はIPアドレスです。awkコマンドを使って抜き出してみましょう。

ytooyama@maas:~⟫ juju machines|awk '{print $3}'
DNS
172.17.29.223
172.17.29.225
172.17.29.227
172.17.29.193
172.17.29.194
172.17.29.195
172.17.29.201
172.17.29.224
172.17.29.226
172.17.29.228
172.17.29.196
172.17.29.197
172.17.29.198
172.17.29.199
172.17.29.200
172.17.29.222
172.17.29.229

うまくいった...とおもいきや、1行目のDNSがちょっと邪魔です。インベントリーファイルに書き込んでから一行目を削除する方法でも良いですが、つぎのようにフィルタリングできます。

ytooyama@maas:~⟫ juju machines|awk 'NR>1 {print $3}'
172.17.29.223
172.17.29.225
172.17.29.227
172.17.29.193
172.17.29.194
172.17.29.195
172.17.29.201
172.17.29.224
172.17.29.226
172.17.29.228
172.17.29.196
172.17.29.197
172.17.29.198
172.17.29.199
172.17.29.200
172.17.29.222
172.17.29.229

いい感じです。次のように実行して、Ansibleのインベントリーファイルを作っておきます。 hostsという名前で作成したインベントリーファイルにはIPアドレスの一覧のみ追記されます。

ytooyama@maas:~⟫ juju machines|uniq|awk 'NR>1 {print $3}' > hosts

Ansibleコマンドによる管理

Ansibleがシステムにインストールされており、インベントリーファイルも作成済みであれば、Ansibleとモジュールを使ってノードの管理が可能になります。AnsibleではインベントリーファイルにAnsibleで操作する対象のノードをIPアドレスかFQDNで記述しておく必要があります。Ansibleで操作する想定でないノードを誤操作しないようにする予防策というわけですね。

ansibleコマンドでノードを操作するにはインベントリーファイルを-iオプションで指定します。そのほか、利用するモジュールやユーザー認証に関わる情報(ユーザー、パスワードやキーペアなど)も必要です。これらはインベントリーファイルに記述することができますが、とりあえず次のようにコマンドにオプション指定することで実行可能です。

ytooyama@maas:~⟫ ansible -i hosts 172.17.29.223 -m ping -u ubuntu --private-key=~/.local/share/juju/ssh/juju_id_rsa

実はこのままだとエラーで失敗します。Ansibleは対象のノードにPythonのバージョン2がインストールされている必要があるためです。 ただ、Python 2は現行は利用可能ですが、サポート期限が迫っているという現状があります。また、Linux ディストリビューションによってはPython 2はデフォルトでインストールされず、必要な場合は別途インストールする必要が出てきます。例えばUbuntu 16.04とか。

現行のAnsibleをインストールする場合はPython2が依存パッケージとして同時にインストールされますが、「Ansible 2.2でPython3サポート」が行われたため、オプションを追加することでPyton 3.5以降がインストール済みであることを条件として、その条件にマッチしたノードをサポートするようになりました。

Ansible 2.2以降でのPython3サポートについては先ほどのリンク先の情報に書かれているように大体のコードは正常に動くが動かない場合もあると書かれています。うまく動かない場合はノードにPython2をインストールして対応しましょう。

ytooyama@maas:~⟫ ansible -i hosts 172.17.29.223 -m ping -u ubuntu --private-key=~/.local/share/juju/ssh/juju_id_rsa --extra-vars=ansible_python_interpreter=/usr/bin/python3
172.17.29.223 | SUCCESS => {
    "changed": false, 
    "failed": false, 
    "ping": "pong"
}

これで当初の目的は達成できるわけですが、コマンドとオプションが長すぎて入力が大変です。そこで、インベントリーファイルに必要な情報を書き込んでみます。

[remote]のようにグループを作成して、その下にIP、FQDNを記述するとコマンドを実行するときにそのグループを指定して複数のターゲットに対してコマンドを実行できます。また、[remote:vars]でそのグループの設定を記述します。公式のマニュアルに記述例が掲載されています。

[remote]
172.17.29.223
172.17.29.225
172.17.29.227
172.17.29.193
172.17.29.194
172.17.29.195
172.17.29.201
172.17.29.224
172.17.29.226
172.17.29.228
172.17.29.196
172.17.29.197
172.17.29.198
172.17.29.199
172.17.29.200
172.17.29.222
172.17.29.229

[remote:vars]
ansible_python_interpreter=/usr/bin/python3
ansible_user=ubuntu
ansible_ssh_private_key_file=/home/ytooyama/.local/share/juju/ssh/juju_id_rsa

このインベントリーファイルを使ってansibleコマンドでpingを実行してみます。全てのサーバーにpingを実行できます。

ytooyama@maas:~⟫ ansible -i hosts remote -m ping
172.17.29.193 | SUCCESS => {
    "changed": false, 
    "failed": false, 
    "ping": "pong"
}
...
172.17.29.229 | SUCCESS => {
    "changed": false, 
    "failed": false, 
    "ping": "pong"
}

Ansible Playbookコマンドによる管理

ansibleコマンドは一つの処理を行うには十分ですが、複数の処理を一括で実行する場合は不向きです。そんなときにはAnsible Playbookを使います。Ansible PlaybookはYAML形式で処理を記述します。例えばpingを実行したい場合は次のようにPlaybookを作成します。 Playbookの先頭にはハイフンを3つ入れてください。そのあとに処理や実行先の情報、ユーザー情報を指定したり、変数などを定義します。

---
- hosts: remote
  tasks:
   - name: try ping
     ping:

早速実行してみましょう。

ytooyama@maas:~⟫ ansible-playbook ping.yaml -i hosts

PLAY [remote] ********************************************************************************************

TASK [Gathering Facts] ***********************************************************************************
ok: [172.17.29.194]
ok: [172.17.29.227]
ok: [172.17.29.225]
ok: [172.17.29.193]
ok: [172.17.29.223]
ok: [172.17.29.195]
ok: [172.17.29.201]
ok: [172.17.29.228]
ok: [172.17.29.224]
ok: [172.17.29.226]
ok: [172.17.29.196]
ok: [172.17.29.198]
ok: [172.17.29.199]
ok: [172.17.29.200]
ok: [172.17.29.197]
ok: [172.17.29.222]
ok: [172.17.29.229]

TASK [try ping] ******************************************************************************************
ok: [172.17.29.223]
ok: [172.17.29.194]
ok: [172.17.29.227]
ok: [172.17.29.193]
ok: [172.17.29.225]
ok: [172.17.29.195]
ok: [172.17.29.201]
ok: [172.17.29.224]
ok: [172.17.29.226]
ok: [172.17.29.228]
ok: [172.17.29.196]
ok: [172.17.29.197]
ok: [172.17.29.198]
ok: [172.17.29.200]
ok: [172.17.29.199]
ok: [172.17.29.222]
ok: [172.17.29.229]

PLAY RECAP ***********************************************************************************************
172.17.29.193              : ok=2    changed=0    unreachable=0    failed=0   
172.17.29.194              : ok=2    changed=0    unreachable=0    failed=0   
172.17.29.195              : ok=2    changed=0    unreachable=0    failed=0   
172.17.29.196              : ok=2    changed=0    unreachable=0    failed=0   
172.17.29.197              : ok=2    changed=0    unreachable=0    failed=0   
172.17.29.198              : ok=2    changed=0    unreachable=0    failed=0   
172.17.29.199              : ok=2    changed=0    unreachable=0    failed=0   
172.17.29.200              : ok=2    changed=0    unreachable=0    failed=0   
172.17.29.201              : ok=2    changed=0    unreachable=0    failed=0   
172.17.29.222              : ok=2    changed=0    unreachable=0    failed=0   
172.17.29.223              : ok=2    changed=0    unreachable=0    failed=0   
172.17.29.224              : ok=2    changed=0    unreachable=0    failed=0   
172.17.29.225              : ok=2    changed=0    unreachable=0    failed=0   
172.17.29.226              : ok=2    changed=0    unreachable=0    failed=0   
172.17.29.227              : ok=2    changed=0    unreachable=0    failed=0   
172.17.29.228              : ok=2    changed=0    unreachable=0    failed=0   
172.17.29.229              : ok=2    changed=0    unreachable=0    failed=0   

ここまでできれば、あとはPlaybookの書き方次第で様々なことを実現可能です。 AnsibleのPlaybookのサンプルがドキュメントやGithub上に公開されているので、色々試してみたら楽しいかと思います。

ちなみにUbuntuで新しいバージョンのAnsibleを使うにはAnsible PPAを追加すると便利です。ページに書かれているコマンドを実行すると、Ubuntuのサポートされたバージョンで最新のAnsibleが利用できます。このPPAはRed Hat社のAnsibleチームが管理しているので安心です。Ubuntu 14.04 LTSや16.04 LTSなどで利用することを推奨します。

Ubuntu Xenial (16.04) でOpen vSwitch+DPDKな環境を作る(vHost User Clientモード編)

以前、UbuntuでOpen vSwitch+DPDKを導入してKVMで利用する手順をここで書かせていただきました。

tech.virtualtech.jp

tech.virtualtech.jp

tech.virtualtech.jp

以前取り上げた方法は全てvHost User Serverモードによって動かす手順です。vHost User ServerモードはOVS-DPDKがリセットされるとソケットが破棄されるため、OVSとQEMUを再起動するまでVMとの接続が再確立されませんでした。

これでは不便だよねということでそのあと開発されたのがvHost User Clientモードです。vHost User ClientモードはOVS-DPDKはvHostクライアントとして動作し、QEMUがサーバーとして動作します。この構成では、Open vSwitchがクラッシュしたとしても、ソケットはQEMUによって管理されるため、vHost User Serverモードよりも接続の復旧が容易になります。

詳細はこちらをご覧ください。

software.intel.com

OpenStackでDPDKを使う場合も、Ocata以降ではvHost User Clientモードをサポートしているので、デフォルトではこちらのモードで動作します。vHost User Clientモードを利用するにはOVS 2.6、QEMU 2.7以降が必要です。ただし、RHEL7の場合はqemu-kvm-rhev-2.6.0-23.el7以降のバージョンにバックポートされています。CentOS 7ではバージョン7.3以上で同バージョンのqemu-kvm-evを利用可能です。

vHost User Clientモードに切り替える

以降、これまで紹介した手順でDPDK、OVS、QEMUがインストールされていることを前提に、vHost User Clientモードに切り替える方法をご紹介します。

sockディレクトリー作成

sockファイルを展開するディレクトリーを作成します。

# mkdir -p /usr/local/openvswitch/
sockファイル作成

ファイルを作っておきます。作成したファイルはOpen vSwitchが起動するとsockファイルになるようです。

# touch /usr/local/openvswitch/dpdkvhostclient0
# touch /usr/local/openvswitch/dpdkvhostclient1    
(作成するポートの数だけ作成) 
ブリッジ作成

dpdkvhostuserclientポートをぶら下げるためのOVSブリッジポートをnetdevタイプで作成します。

# ovs-vsctl add-br ovsbr1 -- set bridge ovsbr1 datapath_type=netdev
ポート作成

ポート作成方法はvHost User Serverモードとほとんど一緒ですが、type=dpdkvhostuserclientという設定とoptionsとしてvhost-server-pathを指定します。

# ovs-vsctl add-port ovsbr1 dpdkvhostclient0 \
    -- set Interface dpdkvhostclient0 type=dpdkvhostuserclient \
       options:vhost-server-path=/usr/local/openvswitch/dpdkvhostclient0

# ovs-vsctl add-port ovsbr1 dpdkvhostclient1 \
    -- set Interface dpdkvhostclient1 type=dpdkvhostuserclient \
       options:vhost-server-path=/usr/local/openvswitch/dpdkvhostclient1
DPDKポートのマウント

次に、DPDKデバイスポートを追加します。これはvHost User Serverモードと方法は一緒です。

# ovs-vsctl add-port ovsbr1 dpdk0 -- set Interface dpdk0 type=dpdk
vhostuserclient インターフェイスの追加

virsh editコマンドでvHost User Interfaceを設定します。mode='server'と設定するのがvHost User Serverモードで動かした時と異なる点です。この設定にあるmodeはQEMU側の動作モードを指定します。vHost User ClientモードはQEMUをサーバーモードとして動かすのでmode='server'と設定します。

        <interface type='vhostuser'>
                <source type='unix'
                        path='/usr/local/openvswitch/dpdkvhostclient0'
                        mode='server'/>
                <model type='virtio'/>
        </interface>
サービスの再起動

Open vSwitchdサービスを再起動します。

# service openvswitch-switch restart

以上で準備完了です。あとは仮想マシンを再起動して、ゲストOSでIPアドレスを設定することでDPDKポートがアクセス可能になります。通信できない場合、またはエラーが出た場合はovs-vswitchd.logを確認してください。

今回取り上げた手順で、CentOS 7 + OVS/DPDK/KVMの構成で稼働していた環境もvHost User Clientモードに切り替えることができたことを最後に付け加えておきます。

作業後のovs-vsctl showコマンドの実行結果

実行結果から、これまでとは異なるモードである「dpdkvhostuserclient」で動いていることがわかります。

# ovs-vsctl show
51bf3a85-d776-47a7-b9cb-2da68f465b3b
    Bridge "ovsbr1"
        Port "dpdk0"
            Interface "dpdk0"
                type: dpdk
        Port "dpdkvhostclient1"
            Interface "dpdkvhostclient1"
                type: dpdkvhostuserclient
                options: {vhost-server-path="/usr/local/openvswitch/dpdkvhostclient1"}
        Port "dpdkvhostclient0"
            Interface "dpdkvhostclient0"
                type: dpdkvhostuserclient
                options: {vhost-server-path="/usr/local/openvswitch/dpdkvhostclient0"}
        Port "dpdkvhostclient2"
            Interface "dpdkvhostclient2"
                type: dpdkvhostuserclient
                options: {vhost-server-path="/usr/local/openvswitch/dpdkvhostclient2"}
        Port "dpdkvhostclient3"
            Interface "dpdkvhostclient3"
                type: dpdkvhostuserclient
                options: {vhost-server-path="/usr/local/openvswitch/dpdkvhostclient3"}
        Port "ovsbr1"
            Interface "ovsbr1"
                type: internal
    ovs_version: "2.6.1"

Ubuntu Xenial (16.04) でOpen vSwitch+DPDKな環境を作る(QEMUアップデート編)

以前、UbuntuでOpen vSwitch+DPDKを導入してKVMで利用する手順をここで書かせていただきました。

tech.virtualtech.jp

tech.virtualtech.jp

現在Ubuntu 16.04ではOcataリポジトリーを有効にすると、標準パッケージよりも新しいQEMU-KVMパッケージがインストールできるようになります。

~$ apt list -a qemu-kvm
Listing... Done
qemu-kvm/xenial-updates 1:2.8+dfsg-3ubuntu2.3~cloud0 amd64
qemu-kvm/xenial-updates,xenial-security 1:2.5+dfsg-5ubuntu10.14 amd64
qemu-kvm/xenial 1:2.5+dfsg-5ubuntu10 amd64

当然新しいものを選択すると思うのですが、実際に従来のバージョンからアップグレードする場合および新規インストールする際にそれぞれ注意することがあったので、ここにまとめようと思います。

アップグレードする場合

DPDKとOpenvSwitch、KVM環境をNewton版パッケージを使って構築して、Ocata版のパッケージに更新したいと思った場合、通常はこのように実行すると思います。

# add-apt-repository cloud-archive:ocata
# apt update
# apt upgrade && reboot
...
# add-apt-repository -r cloud-archive:newton  #Newtonリポジトリーを無効化

ああ簡単、よかったよかった…などと思っていると、次のようなエラーになりOVS+DPDKがうまく動いていないことがわかります。

~# ovs-vsctl show
2910fa6f-1c8b-4e8d-b321-bf36a65509ba
    Bridge "ovsbr0"
        Port "vhost-user1"
            Interface "vhost-user1"
                type: dpdkvhostuser
                error: "could not open network device vhost-user1 (Address family not supported by protocol)"
        Port "ovsbr0"
            Interface "ovsbr0"
                type: internal
        Port "vhost-user2"
            Interface "vhost-user2"
                type: dpdkvhostuser
                error: "could not open network device vhost-user2 (Address family not supported by protocol)"
        Port "dpdk0"
            Interface "dpdk0"
                type: dpdk
                error: "could not open network device dpdk0 (Address family not supported by protocol)"
    ovs_version: "2.6.1"

とりあえずUbuntuでDPDKを使っている環境でアップグレードするときは、apt upgradeした後、update-alternatives --set ovs-vswitchd /usr/lib/openvswitch-switch-dpdk/ovs-vswitchd-dpdkコマンドを実行してDPDK enableモードでOVSを実行する設定を改めて行なったあと、openvswitch-switchサービスを再起動しなさいってことみたいです。

DPDKが正常に動くようになったから早速VMを起動しようとすると、次のようなログが出力されてVMが起動できない問題に出くわします。

仮想マシンの開始中にエラーが発生しました: unsupported configuration: Shared memory mapping is supported only with hugepages
Traceback (most recent call last):
  File "/usr/share/virt-manager/virtManager/asyncjob.py", line 90, in cb_wrapper
    callback(asyncjob, *args, **kwargs)
  File "/usr/share/virt-manager/virtManager/asyncjob.py", line 126, in tmpcb
    callback(*args, **kwargs)
  File "/usr/share/virt-manager/virtManager/libvirtobject.py", line 83, in newfn
    ret = fn(self, *args, **kwargs)
  File "/usr/share/virt-manager/virtManager/domain.py", line 1402, in startup
    self._backend.create()
  File "/usr/lib/python2.7/dist-packages/libvirt.py", line 1035, in create
    if ret == -1: raise libvirtError ('virDomainCreate() failed', dom=self)
libvirtError: unsupported configuration: Shared memory mapping is supported only with hugepages

これはVirt-Managerを使って起動しようとした場合のログですが、virshコマンドでVMを起動してもだいたい同じようなエラーが出て起動しません。hugepagesの書き方がQEMUのバージョンの更新(2.5から2.8)によって変更されたようです。記述の仕方はこちらにまとまっています。

libvirt.org

この「MemoryBacking」というところが今回重要です。HugePagesとして確保したメモリーをどこのノードからVMに割り当てるのか厳密に書く必要があるようです。実際の例で説明すると、従来は次のように記述するだけでした。

<memory unit='KiB'>1048576</memory>
  <currentMemory unit='KiB'>1048576</currentMemory>
  <memoryBacking>
    <hugepages/>
  </memoryBacking>

これで良きに計らってくれていたのですが、QEMU 2.8では次のように記述する必要がありました。

  <memory unit='KiB'>1048576</memory>
  <currentMemory unit='KiB'>1048576</currentMemory>
  <memoryBacking>
    <hugepages>
      <page size='2048' unit='KiB' nodeset='0'/>
    </hugepages>
    <nosharepages/>
  </memoryBacking>

システムで1GのHugePagesを確保している場合は2Mではなく、1Gを割り当てることもできます。以上の設定変更でVMが正常起動し、ネットワークはvhostuserで提供できるようになります。

nodeはNuma Nodeを指定します。numactl -Hコマンドで確認できますので、その出力結果をもとにどのノードからメモリーを確保するか指定します。

Ubuntu XenialでOpen vSwitch+DPDKを新規セットアップする場合

基本的には前編後編の流れでセットアップすれば構いません。新しいQEMUを使いたい場合はOcata以降のリポジトリーを有効にしてDPDK、OVS、KVM環境を構築します。

# add-apt-repository cloud-archive:ocata
# apt update

あとはVMの設定を前で説明したようにHugePagesの設定でpage size、nodeをきちんと指定します。それ以外は前編、後編の流れでOpen vSwitch+DPDK+Linux KVM環境を導入できます。