PythonでVMware vSphere REST APIを使う: PowerCLIなしでVM管理とインベントリを自動化する

VMware tutorial - IT technology blog
VMware tutorial - IT technology blog

クイックスタート: 5分でvSphere APIに接続する

VMwareの管理コマンドを実行するためだけにWindows上でPowerCLIをインストールした経験がある方には、vSphereのREST APIがまさにうってつけです。Pythonとrequestsライブラリさえあれば、追加の依存関係なしにどのマシンからでもVMware vSphere 7.x/8.xのインフラ全体に接続できます。

必要なライブラリをインストール:

pip install requests urllib3

以下のコードはvCenterに接続し、セッショントークンを取得して全VMをリストします — すぐに実行できます:

import requests
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

VCENTER_HOST = "vcenter.lab.local"
USERNAME = "[email protected]"
PASSWORD = "YourPassword123!"
BASE_URL = f"https://{VCENTER_HOST}/api"

# セッションを作成
session = requests.Session()
session.verify = False  # ラボ環境 — 本番環境ではverifyを有効にすること

# 認証 — セッショントークンを取得
response = session.post(
    f"{BASE_URL}/session",
    auth=(USERNAME, PASSWORD)
)
token = response.json()
session.headers.update({"vmware-api-session-id": token})

# すべてのVMをリスト
vms = session.get(f"{BASE_URL}/vcenter/vm").json()
for vm in vms:
    print(f"{vm['name']} | Power: {vm['power_state']} | ID: {vm['vm']}")

以下のような出力が表示されればAPIは正常に動作しています:

ubuntu-22-web01 | Power: POWERED_ON | ID: vm-101
centos-db-prod  | Power: POWERED_OFF | ID: vm-145
win2022-dc01    | Power: POWERED_ON | ID: vm-203

詳細解説: vSphere REST APIはどのように動作するか

セッションベースの認証

vSphere REST APIはリクエストごとにBasic認証を使うのではなく、セッショントークンを使用します。一度クレデンシャルをPOSTしてstring型のトークンを受け取り、以降のリクエストのvmware-api-session-idヘッダーに付与します。トークンはアイドル状態が30分続くと自動的に失効します。BearerトークンやOAuthとは異なり、リフレッシュフローや複雑なミドルウェアは不要 — 5行のコードで実装完了です。

知っておくべき2つのベースパス

ベースパスは2種類あり、最も混乱しやすいポイントです:

  • /api — 新しいAPI、純粋なRESTful、JSONレスポンスがシンプル。vSphere 7.0以降の環境ではこちらを使用してください。
  • /rest — 旧API(8.0から非推奨)、まだ動作しますが、レスポンスに{"value": ...}という余分なラッパーが付いて扱いにくいです。

Swagger UIはhttps://vcenter-ip/apiから利用でき、コードを書かずにブラウザで直接エンドポイントをテストできます。どのエンドポイントが正しいか迷ったときは、ドキュメントを読むより手っ取り早く確認できます。

よく使うエンドポイント

# インベントリ
GET /api/vcenter/vm                    # VMをリスト
GET /api/vcenter/vm/{vm}               # VMの詳細
GET /api/vcenter/host                  # ESXiホストをリスト
GET /api/vcenter/datastore             # データストアをリスト
GET /api/vcenter/cluster               # クラスターをリスト

# 電源操作
POST /api/vcenter/vm/{vm}/power?action=start
POST /api/vcenter/vm/{vm}/power?action=stop
POST /api/vcenter/vm/{vm}/power?action=reset

# スナップショット
GET  /api/vcenter/vm/{vm}/snapshot
POST /api/vcenter/vm/{vm}/snapshot
DELETE /api/vcenter/vm/{vm}/snapshot/{snapshot}

応用編: 日常業務で使える実践スクリプト

インベントリをCSVにエクスポート

毎月末にインフラチームへのVM状況レポートとして使っているスクリプトです — 一度実行してCSVを生成し、グループチャットに送るだけで完了:

import requests
import urllib3
import csv
from datetime import datetime

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

class vSphereClient:
    def __init__(self, host, username, password):
        self.base_url = f"https://{host}/api"
        self.username = username
        self.password = password
        self.session = requests.Session()
        self.session.verify = False
        self._authenticate()

    def _authenticate(self):
        resp = self.session.post(
            f"{self.base_url}/session",
            auth=(self.username, self.password)
        )
        resp.raise_for_status()
        self.session.headers.update({
            "vmware-api-session-id": resp.json()
        })

    def get_vms(self, **filters):
        return self.session.get(
            f"{self.base_url}/vcenter/vm", params=filters
        ).json()

    def get_vm_details(self, vm_id):
        return self.session.get(
            f"{self.base_url}/vcenter/vm/{vm_id}"
        ).json()

    def logout(self):
        self.session.delete(f"{self.base_url}/session")


def export_inventory_csv(client, output_file):
    vms = client.get_vms()
    rows = []
    for vm in vms:
        details = client.get_vm_details(vm["vm"])
        memory_gb = details.get("memory", {}).get("size_MiB", 0) / 1024
        cpu_count = details.get("cpu", {}).get("count", 0)
        rows.append({
            "Name": vm["name"],
            "ID": vm["vm"],
            "Power State": vm["power_state"],
            "CPU": cpu_count,
            "Memory (GB)": round(memory_gb, 1),
            "Guest OS": details.get("guest_OS", "Unknown"),
        })

    with open(output_file, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=rows[0].keys())
        writer.writeheader()
        writer.writerows(rows)
    print(f"Exported {len(rows)} VMs to {output_file}")


client = vSphereClient("vcenter.lab.local", "[email protected]", "Password123!")
timestamp = datetime.now().strftime("%Y%m%d_%H%M")
export_inventory_csv(client, f"vm_inventory_{timestamp}.csv")
client.logout()

一括電源管理

パターンに一致する名前の全VMをシャットダウン — ホストのメンテナンスやテスト環境のクリーンアップ時に使用:

def bulk_power_off(client, name_pattern):
    vms = client.get_vms()
    targets = [
        vm for vm in vms
        if name_pattern.lower() in vm["name"].lower()
        and vm["power_state"] == "POWERED_ON"
    ]
    print(f"Found {len(targets)} VMs matching '{name_pattern}'")
    for vm in targets:
        resp = client.session.post(
            f"{client.base_url}/vcenter/vm/{vm['vm']}/power",
            params={"action": "stop"}
        )
        status = "✓" if resp.status_code == 204 else "✗"
        print(f"  {status} {vm['name']}")

bulk_power_off(client, "test")

アップデート前のスナップショット作成

パッチ適用前の習慣: スナップショットを素早く取得し、アップデートし、問題があればリバート。以下のスクリプトはRAMダンプをスキップ(memory: False)— 処理が軽く、ほとんどのケースで十分です:

def create_snapshot(client, vm_id, name, description=""):
    payload = {
        "description": description,
        "memory": False,    # RAMをスナップショットしない — 処理が大幅に速くなる
        "name": name,
        "quiesce": True     # VMware Toolsが実行中の場合はファイルシステムを静止化する
    }
    resp = client.session.post(
        f"{client.base_url}/vcenter/vm/{vm_id}/snapshot",
        json=payload
    )
    if resp.status_code == 204:
        print(f"Snapshot '{name}' created successfully")

create_snapshot(client, "vm-101", "pre-patch-july", "Before July patch Tuesday")

日常使用の経験から得た実践的なヒント

Tip 1: セッションを再利用し、リクエストごとに認証しない

最もよく見かけるミスは、ループ内で認証を行うコードです — VMごとに1回ログインしています。vSphereはデフォルトで100並行セッションに制限されており、大規模なバッチ処理では503 Service Unavailableエラーがすぐに発生します。vSphereClientのインスタンスを1つ作成し、スクリプト全体で使い回しましょう。

Tip 2: フィルターパラメーターでクエリを高速化

500台以上のVMがある環境で全件取得してPythonでフィルタリングすると、レスポンスタイムは8〜10秒程度かかります。APIのフィルターを使えば1秒以下に短縮できます:

# 特定のクラスター内で起動中のVMのみ取得
params = {
    "filter.power_states": "POWERED_ON",
    "filter.clusters": "domain-c10"  # Cluster IDはGET /api/vcenter/clusterから取得
}
vms = client.session.get(f"{client.base_url}/vcenter/vm", params=params).json()

帯域幅も同様に削減されます — VPN経由でラップトップからスクリプトを実行する場合に特に重要です。

Tip 3: 長時間実行スクリプトのセッションタイムアウト対処

トークンはアイドル状態が30分続くと失効します。500台以上のVMの詳細情報をエクスポートするスクリプトは、VMごとに個別のAPIコールを行うため、30分を超えやすいです。シンプルな再認証を実装しましょう:

def safe_get(client, endpoint):
    resp = client.session.get(f"{client.base_url}{endpoint}")
    if resp.status_code == 401:
        # セッションが期限切れ — 再ログイン
        client._authenticate()
        resp = client.session.get(f"{client.base_url}{endpoint}")
    resp.raise_for_status()
    return resp.json()

Tip 4: 本番環境でのSSL証明書

ラボスクリプトでは便宜上SSL verifyをオフにしていますが、本番環境ではすべきではありません。正しい方法はvCenterのCA証明書を使用することです:

# vCenterから証明書バンドルをダウンロード(zipファイルが返される)
curl -o vcenter-certs.zip "https://vcenter.corp.local/certs/download"
unzip vcenter-certs.zip -d vcenter-certs
# CA証明書はvcenter-certs/certs/lin/(Linux)またはcerts/win/(Windows)に格納されている
# 拡張子を.0から.pemに変更し、Pythonで使用する:
session.verify = "/path/to/vcenter-certs/certs/lin/root-ca.pem"

Tip 5: PowerCLIからREST APIへのマッピング

VMwareのドキュメントにはPowerCLIの例しか載っていないことがあります。マッピングのパターンはかなり一貫しています: Get-VMGET /api/vcenter/vmStart-VMPOST /api/vcenter/vm/{id}/power?action=startNew-SnapshotPOST /api/vcenter/vm/{id}/snapshot。このパターンに慣れると変換は非常に速くなります。

ラボをProxmoxに移行したときに気づいた興味深い点があります: ProxmoxのAPIも同様のセッショントークンを使用しており(エンドポイント: /api2/json/access/ticket)、vSphere向けに書いたPythonコードの大部分をそのまま流用できます — エンドポイントとフィールド名を変えるだけでOKです。このパターンはVMwareに依存しないという利点があります。

vSphereのREST APIはバージョン間で安定しており — 7.0向けに書いたスクリプトは修正なしで8.0でも動作することがほとんどです。自動化にPowerCLIではなくREST APIを選ぶ理由はここにあります: 依存関係が少なく、あらゆるOSで動作し、Ansible、Prometheus、SlackへのNotificationをPythonで直接統合できます — 追加のミドルウェアやラッパーは不要です。

Share: