pyVmomi から API 操作で Instant Clone をやってみる

Share on:

Table of contents

はじめに

本記事では pyVmomi を使って vSphere 単体で Instant Clone を行う方法について書いていきます。

事の発端は以下のVMware社の中の人が PowerCLI から Nested ESXi をインスタントクローンする記事です。

Leveraging Instant Clone in vSphere 6.7 for extremely fast Nested ESXi provisioning

vSphere Web Services API と pyVmomi

vCenter Server のタスクをコマンドで実行する場合、PowerCLI を使うことが多いと思います。もしくは、開発やトラブルシューティングなどの一環で MOB (Managed Object Browser) を使用したことがある方もいらっしゃるかもしれません。

これらのツールは「vSphere Web Services API」という vSphere のローレベルな SOAP API を操作しています1

今回の記事で紹介する「pyVmomi」という Python SDK も PowerCLI と同様に vSphere Web Services API を操作することが出来ます。

pyVmomi のインストール手順やコミュニティのサンプルコードは GitHub の以下のリポジトリで公開されています。

インスタントクローンと vSphere Web Services API

インスタントクローンといえば Horizon 7 で利用できる非常に高速な仮想マシンのクローンです。パワーオン状態の仮想マシンが2秒程度で複製でき、親仮想マシンとメモリを共有するためリソースを節約できるという極めて強力な機能で、vSphere Integrated Containers でもコンテナを Photon OS 仮想マシンで隔離するために利用されています。

vSphere 6.5 以前では Horizon など vSphere 外部の製品のみ使用できましたが、vSphere 6.7 から vSphere Web Services API で仮想マシンオブジェクトに InstantClone_Task メソッドが追加されており vSphere 単体でも API 経由なら実行できるようになっています。

vSphere Web Services SDK 6.7 Release Notes

New InstantClone method added.

The vSphere 6.7 release includes the InstantClone_Task() method for low-latency deployment of cloned virtual machines from a running or frozen source virtual machine.

なお、インスタントクローン自体は上述の通り Horizon 7 でだいぶ前から使われている機能で、具体的な仕組みは既に色々な方がブログなどで解説を書いてくれていますので Google などで検索してみていただければと思います。本記事では VMware Docs だけ置いておきます。

インスタント クローンを使用した仮想マシンのクローン作成

pyVmomi でインスタントクローン

冒頭に記載した中の人の記事やリンクされている GitHub リポジトリのコードを読むと分かるのですが、PowerCLI から vSphere Web Services API の InstantClone_Task メソッドを呼び出す実装になっています。このため「もしかしたら pyVmomi でもインスタントクローンが出来るのでは?」という発想になりました。

1年半ほど前に試しに作ってみた際にかなり便利だったので検証用の Nested ESXi などの払い出しに使っていましたが、せっかくブログを立ち上げましたのでサンプルコードと合わせて解説をしていきたいと思います。

Python 実行環境と pyVmomi のインストール手順

今回使用した私の実行環境は以下の通りです。

  • WSL2 の Ubuntu 18.04 LTS
  • Python 3.6.9 および pyVmomi 7.0
  • vCenter Server 7.0 / ESXi 6.7 Update 3

pyVmomi は PyPI で提供されていますので pip によるインストールが可能です。pip をインストールしてから pyvmomi パッケージをインストールします。

1$ sudo apt-get install python3-pip
2$ pip3 install --upgrade pyvmomi

サンプルコードとその解説

サンプルコードはこちらの GitHub リポジトリに置いてあります。基本的に vmware/pyvmomi-community-samples のサンプルコード clone_vm.py をベースにしていますが、少し長いので一番主要な部分にコメントを入れたものを以下に抜粋します。

 1# インスタントクローンを実行する関数。
 2def instant_clone_vm(content, parent_vm, vm_name, datacenter_name, vm_folder, resource_pool):
 3
 4    # 指定の仮想マシンフォルダを取得します(無ければルート仮想マシンフォルダ)。
 5    datacenter = get_obj(content, [vim.Datacenter], datacenter_name)
 6    if vm_folder:
 7        dst_folder = get_obj(content, [vim.Folder], vm_folder)
 8    else:
 9        dst_folder = datacenter.vmFolder
10
11    # 特定のリソースプールの明示的な指定が必要です(当記事の補足参照)。
12    resource_pool = get_obj(content, [vim.ResourcePool], resource_pool)
13
14    # RelocateSpec オブジェクトで仮想マシンの配置情報を指定します。
15    vm_relocate_spec = vim.vm.RelocateSpec()
16    vm_relocate_spec.folder = dst_folder
17    vm_relocate_spec.pool = resource_pool
18
19    # InstantCloneSpec オブジェクトでインスタントクローンの
20    # 各種パラメータを指定します。
21    instant_clone_spec = vim.vm.InstantCloneSpec()
22    instant_clone_spec.name = vm_name
23    instant_clone_spec.location = vm_relocate_spec
24
25    # InstantClone_Task メソッドに作成した InstantCloneSpec オブジェクト
26    # を渡すことでインスタントクローンを実行します。メソッドの戻り値は Task
27    # オブジェクトのため、WaitForTask 関数に渡してタスクの完了を待ちます。
28    WaitForTask(parent_vm.InstantClone_Task(spec=instant_clone_spec))
29
30def main():
31    args = get_args()
32
33    # vCenter Server のマシン SSL 証明書が信頼されていない場合も
34    # 多いので、サーバ証明書の検証は無視して進むようにしています。
35    context = None
36    if hasattr(ssl, "_create_unverified_context"):
37        context = ssl._create_unverified_context()
38
39    # vCenter Server に接続して、インベントリ全体のルートに相当する
40    # ServiceInstance / ServiceContent オブジェクトを取得します。
41    si = SmartConnect(
42                    host=args.host,
43                    user=args.user,
44                    pwd=args.password,
45                    port=args.port,
46                    sslContext=context)
47    atexit.register(Disconnect, si)
48    content = si.content
49
50    # 親仮想マシンを VirtualMachine オブジェクトとして取得します。
51    parent_vm = get_obj(content, [vim.VirtualMachine], args.parent_vm)
52
53    # 上述の instant_clone_vm 関数を呼び出してインスタントクローンを実行します。
54    if parent_vm:
55        instant_clone_vm(content, parent_vm, args.vm_name,
56            args.datacenter_name, args.vm_folder, args.resource_pool)
57    else:
58        print("parent_vm not found")
59        quit()
60
61if __name__ == '__main__':
62    main()

上述のコードを見てもらえると、VirtualMachine オブジェクトのメソッドとして InstantClone_Task が呼び出されていることが分かると思います。

InstantClone_Task(instantClone) | Managed Object - VirtualMachine(vim.VirtualMachine)

コード全体としても実質的に行っていることは単純で、要約すると以下の流れになっています。

  1. vCenter Server にログインする。
  2. vCenter Server のインベントリから親仮想マシンを取得する。
  3. クローン仮想マシンの配置先を指定する。
  4. 仮想マシン名と3.の配置先をインスタントクローンのパラメータに指定する。
  5. 2.の親仮想マシンに対して4.の内容に基づきインスタントクローンを実行する。

この Python スクリプトを実行する場合は以下のようなフォーマットになります。

1$ git clone https://github.com/Jangari-nTK/pyvmomi-instant-clone-sample.git
2$ cd pyvmomi-instant-clone-sample
3$ python3 instant_clone.py --host vcsa01.corp.local --user administrator@vsphere.local \
4    --password VMware1! --vm-name "New_VM" --parent-vm "Parent_VM" --resource-pool "Destination-Pool"

実行すると以下のようにインスタントクローンが実行されます。タスクの実行時間(赤枠)を見ると、開始~完了までが3秒と極めて高速です。通常のクローンのようにゲストOSの起動も必要がないため複製後に即座に使用できるようになります。

インスタントクローンの実行後

おわりに

今回の記事では pyVmomi でインスタントクローンを行う方法について見てきました。

これだけでも良いのですが、以下の中の人のブログにも記載がある通り、親仮想マシンが動いている状態でインスタントクローンを行うとリソース周りが最適化されません2。加えて、現状のスクリプトではゲストにカスタマイズを何も加えないため、クローン前後の仮想マシンでネットワーク情報の競合が発生してしまいます。

Instant Clone in vSphere 6.7 rocks!

これに対しては、この中の人の記事カスタマイズスクリプトのように、vmware-rpcinfo を使った仮想マシンの freeze、NIC ドライバの unbind/bind で認識中の MAC アドレスの更新からの IP アドレス再設定、VMware Tools の guestinfo 変数でゲスト上のスクリプトに値を渡すなどのテクニックがあります。
※ゲスト上スクリプトで guestinfo 変数を使う場合はインスタントクローンのスクリプト側も修正が必要になるはずです。

私は Nested ESXi の払い出しには (いちいちネットワーク情報の指定が面倒なので) DHCP でネットワーク情報を払い出していましたが、この辺りは気力が続けば別途続きとして記事を作成・公開しようかなと思います。

補足

GitHub の vmware/pyvmomi-community-samples リポジトリにあるサンプルコード clone_vm.py とは異なり、今回のコードではリソースプールの指定を必須にしています。

これは、リソースプールを明示せずに(ルートリソースプールを使用して) InstantClone_Task メソッドを呼び出したところ、 RelocateSpec の pool プロパティが不正な値とみなされてタスクが失敗したためです。

 1pyVmomi.VmomiSupport.InvalidArgument: (vmodl.fault.InvalidArgument) {
 2   dynamicType = <unset>,
 3   dynamicProperty = (vmodl.DynamicProperty) [],
 4   msg = 'A specified parameter was not correct: spec.pool',
 5   faultCause = <unset>,
 6   faultMessage = (vmodl.LocalizableMessage) [
 7      (vmodl.LocalizableMessage) {
 8         dynamicType = <unset>,
 9         dynamicProperty = (vmodl.DynamicProperty) [],
10         key = 'com.vmware.vim.vpxd.vmcheck.hostPoolMismatch',
11         arg = (vmodl.KeyAnyValue) [],
12         message = 'Resource pool and host do not belong to the same compute resource.'
13      }
14   ],
15   invalidProperty = 'spec.pool'
16}

エラーの詳細メッセージをそのまま読むと「ホストとリソースプールが同じ計算リソースに所属してはいけない」となりますが、この状況は (クラスタやスタンドアロンホストの) ルートリソースプールをクローン先として使用する場合に限られるかと考えています。

リソース プールの管理

各スタンドアロン ホストと各 DRS クラスタには、(非表示の) ルート リソース プールがあり、そのホストまたはクラスタのリソースがグループ分けされています。ホスト (またはクラスタ) のリソースとルート リソース プールのリソースは常に同じであるため、ルート リソース プールは表示されません。


  1. PowerCLI と vSphere Web Services API の関係は「Documentation Walkthrough - VMware PowerCLI Blog」の記事が参考になります。 ↩︎

  2. 親仮想マシンにスナップショットの差分ディスク(*-00000n.vmdk)が毎回作成されてしまいます。なお、そのままでは削除や統合が出来なかったため、ダミーのスナップショットを作成してから [すべてのスナップショットの削除] で削除しました。この削除方法は何らかの要因で変に差分ディスクが残った時に便利です。 ↩︎