API リクエストの作成とレスポンスの処理


このドキュメントでは、API リクエストを作成して、Compute Engine API からの API レスポンスを処理する方法について説明します。このドキュメントで説明する内容は以下のとおりです。

  • リクエスト本文を作成する。
  • リクエストに必要なリソース URI を確認する。
  • API レスポンスを処理する。
  • API リクエストが成功したかどうかを確認する。

このドキュメントでは、API の認証方法については説明しません。API の認証方法については、Compute Engine への認証をご覧ください。

始める前に

  • REST API について理解を深めます。
  • Compute Engine API に対する認証の方法を学習します。

API リクエストの作成

Compute Engine API は、API リクエストが JSON 形式で記述されているものとして処理を進めます。API リクエストを作成するには、curl または httplib2 のようなツールを使用して直接 HTTP リクエストを行うか、利用可能なクライアント ライブラリの一つを使用します。

リクエスト本文を必要とする API リクエスト(POSTUPDATEPATCH リクエストなど)を行う場合、リクエストの本文には、このリクエストで設定するリソース プロパティを含めます。たとえば、次の curl コマンドは、インスタンス リソース URI に対して POST リクエストを行います。このリクエストでは、リクエストの本文に定義されたプロパティを持つインスタンスが作成されます。リクエストの本文は、-d フラグで指定します。

curl -X POST -H "Authorization: Bearer [OAUTH_TOKEN]" -H "Content-Type: application/json"
https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/instances -d
'{
  "disks":[
    {
      "boot":"true",
      "initializeParams":{
        "sourceImage":"https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-10-buster-v20210122"
      }
    }
  ],
  "machineType":"https://www.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/machineTypes/e2-standard-2",
  "name":"VM_NAME",
  "networkInterfaces":[
    {
      "accessConfigs":[
        {
          "name":"external-nat",
          "type":"ONE_TO_ONE_NAT"
        }
      ],
      "network":"https://www.googleapis.com/compute/v1/projects/PROJECT_ID/global/networks/default"
    }
  ]
}'

イメージ URI のプロジェクト ID(debian-cloud)が、ユーザーのプロジェクト ID と異なっています。これはイメージがそのタイプに応じて、異なるプロジェクトに属するためです。たとえば、Compute Engine によって提供されているすべての一般公開 Debian イメージは、debian-cloud プロジェクトでホストされています。

別のリソースを参照する場合は、完全修飾リソース URI を使用します。たとえば、network プロパティでは、default ネットワークへの完全修飾 URI を使用します。

API リクエストの例

Python

def create_instance(
    compute: object,
    project: str,
    zone: str,
    name: str,
    bucket: str,
) -> str:
    """Creates an instance in the specified zone.

    Args:
      compute: an initialized compute service object.
      project: the Google Cloud project ID.
      zone: the name of the zone in which the instances should be created.
      name: the name of the instance.
      bucket: the name of the bucket in which the image should be written.

    Returns:
      The instance object.
    """
    # Get the latest Debian Jessie image.
    image_response = (
        compute.images()
        .getFromFamily(project="debian-cloud", family="debian-11")
        .execute()
    )
    source_disk_image = image_response["selfLink"]

    # Configure the machine
    machine_type = "zones/%s/machineTypes/n1-standard-1" % zone
    startup_script = open(
        os.path.join(os.path.dirname(__file__), "startup-script.sh")
    ).read()
    image_url = "http://storage.googleapis.com/gce-demo-input/photo.jpg"
    image_caption = "Ready for dessert?"

    config = {
        "name": name,
        "machineType": machine_type,
        # Specify the boot disk and the image to use as a source.
        "disks": [
            {
                "boot": True,
                "autoDelete": True,
                "initializeParams": {
                    "sourceImage": source_disk_image,
                },
            }
        ],
        # Specify a network interface with NAT to access the public
        # internet.
        "networkInterfaces": [
            {
                "network": "global/networks/default",
                "accessConfigs": [{"type": "ONE_TO_ONE_NAT", "name": "External NAT"}],
            }
        ],
        # Allow the instance to access cloud storage and logging.
        "serviceAccounts": [
            {
                "email": "default",
                "scopes": [
                    "https://www.googleapis.com/auth/devstorage.read_write",
                    "https://www.googleapis.com/auth/logging.write",
                ],
            }
        ],
        # Metadata is readable from the instance and allows you to
        # pass configuration from deployment scripts to instances.
        "metadata": {
            "items": [
                {
                    # Startup script is automatically executed by the
                    # instance upon startup.
                    "key": "startup-script",
                    "value": startup_script,
                },
                {"key": "url", "value": image_url},
                {"key": "text", "value": image_caption},
                {"key": "bucket", "value": bucket},
            ]
        },
    }

    return compute.instances().insert(project=project, zone=zone, body=config).execute()

Java

public static Operation startInstance(Compute compute, String instanceName) throws IOException {
  System.out.println("================== Starting New Instance ==================");

  // Create VM Instance object with the required properties.
  Instance instance = new Instance();
  instance.setName(instanceName);
  instance.setMachineType(
      String.format(
          "https://www.googleapis.com/compute/v1/projects/%s/zones/%s/machineTypes/e2-standard-1",
          PROJECT_ID, ZONE_NAME));
  // Add Network Interface to be used by VM Instance.
  NetworkInterface ifc = new NetworkInterface();
  ifc.setNetwork(
      String.format(
          "https://www.googleapis.com/compute/v1/projects/%s/global/networks/default",
          PROJECT_ID));
  List<AccessConfig> configs = new ArrayList<>();
  AccessConfig config = new AccessConfig();
  config.setType(NETWORK_INTERFACE_CONFIG);
  config.setName(NETWORK_ACCESS_CONFIG);
  configs.add(config);
  ifc.setAccessConfigs(configs);
  instance.setNetworkInterfaces(Collections.singletonList(ifc));

  // Add attached Persistent Disk to be used by VM Instance.
  AttachedDisk disk = new AttachedDisk();
  disk.setBoot(true);
  disk.setAutoDelete(true);
  disk.setType("PERSISTENT");
  AttachedDiskInitializeParams params = new AttachedDiskInitializeParams();
  // Assign the Persistent Disk the same name as the VM Instance.
  params.setDiskName(instanceName);
  // Specify the source operating system machine image to be used by the VM Instance.
  params.setSourceImage(SOURCE_IMAGE_PREFIX + SOURCE_IMAGE_PATH);
  // Specify the disk type as Standard Persistent Disk
  params.setDiskType(
      String.format(
          "https://www.googleapis.com/compute/v1/projects/%s/zones/%s/diskTypes/pd-standard",
          PROJECT_ID, ZONE_NAME));
  disk.setInitializeParams(params);
  instance.setDisks(Collections.singletonList(disk));

  // Initialize the service account to be used by the VM Instance and set the API access scopes.
  ServiceAccount account = new ServiceAccount();
  account.setEmail("default");
  List<String> scopes = new ArrayList<>();
  scopes.add("https://www.googleapis.com/auth/devstorage.full_control");
  scopes.add("https://www.googleapis.com/auth/compute");
  account.setScopes(scopes);
  instance.setServiceAccounts(Collections.singletonList(account));

  // Optional - Add a startup script to be used by the VM Instance.
  Metadata meta = new Metadata();
  Metadata.Items item = new Metadata.Items();
  item.setKey("startup-script-url");
  // If you put a script called "vm-startup.sh" in this Google Cloud Storage
  // bucket, it will execute on VM startup.  This assumes you've created a
  // bucket named the same as your PROJECT_ID.
  // For info on creating buckets see:
  // https://cloud.google.com/storage/docs/cloud-console#_creatingbuckets
  item.setValue(String.format("gs://%s/vm-startup.sh", PROJECT_ID));
  meta.setItems(Collections.singletonList(item));
  instance.setMetadata(meta);

  System.out.println(instance.toPrettyString());
  Compute.Instances.Insert insert = compute.instances().insert(PROJECT_ID, ZONE_NAME, instance);
  return insert.execute();
}

URI リソースを作成する

Compute Engine API では、別の Google Cloud リソースへの参照を完全修飾 URI として指定します。

https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/RESOURCE_TYPE/SPECIFIC_RESOURCE

API を使用するときに、イメージ、マシンタイプ、ネットワーク、その他の任意のリソースを指定する際にはいつでも、そのリソースに対する URI を指定する必要があります。Google Cloud CLI や Google Cloud コンソールのようなクライアント ツールを使用すれば、こうしたリソース URI の作成という面倒な作業をすべて肩代わりしてくれますが、API を直接オペレーションする場合は、そうしたリソース URI を自分で作成する必要があります。

リソース URI は、リソースのタイプに応じて若干異なります。たとえば、ゾーンリソースには、URI に zone 仕様があります。

https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/machineTypes/e2-standard-2

リージョン リソースでは、zone 仕様が region 仕様に置き換わります。

https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/regions/REGION/addresses/ADDRESS_NAME

同様に、グローバル リソースでは、global 仕様になります。

https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/global/images/VM_NAME

Compute Engine API では、プロジェクト ID などの情報を推測できるため、部分 URI も使用できます。そのため、次のように、上記 URI の部分 URI バージョンも使用できます。

zones/ZONE/machineTypes/e2-standard-2
regions/REGION/addresses/ADDRESS_NAME
project/PROJECT_ID/global/images/VM_NAME

部分 URI では、ゾーン URI とリージョン URI の両方でプロジェクト ID が省略されていますが、イメージ URI では省略されていません。これは、Compute Engine によって一般公開されているイメージが他のプロジェクト(すべての Debian イメージは debian-cloud、すべての Ubuntu イメージは ubuntu-os-cloud など)でホストされているためです。こうしたイメージを使用する前に、適切なプロジェクト ID を指定する必要があります。イメージのプロジェクト ID を省略すると、Compute Engine は現在のプロジェクト内のイメージを見つけようとしますが、イメージが存在しないため、リクエストは失敗します。

ただし、プロジェクト(このリソースを作成しているプロジェクト)に属するカスタム イメージを使用する場合は、イメージ URI を指定する際にプロジェクトの指定を省略できます。

必要なプロパティを確認する

v1 API とベータ版 API で利用可能な Compute Engine API リファレンス ドキュメントでは、特定のリソースに設定可能なすべてのプロパティについて説明しています。このリファレンス ドキュメントでは、変更可能なプロパティと変更不可のプロパティ(プロパティの説明で [Output Only] とマークされている)を区別していますが、リソースの必須プロパティを確認するには、そのタスクに固有のドキュメントを参照する必要があります。

たとえば、インスタンスを作成する場合は、イメージからのインスタンスの作成のドキュメントで、リクエストに必要な API プロパティを確認してください。API で静的外部 IP アドレスを作成する場合は、静的外部 IP アドレスのドキュメントをご覧ください。

API リクエストの検証

API リクエストを検証するには:

  1. Compute Engine API リファレンスで、コードが呼び出すメソッドを確認します(例: v1/compute.instances.insert)。
  2. コンテンツ メニューから [試してみる] をクリックします。[この API を試す] ウィンドウが開きます。

    コンテンツ メニューにある [試してみる] ボタン

  3. 検証ではリクエストを送信する必要がないため、[リクエスト パラメータ] でプロジェクトゾーンを指定する必要はありません。

  4. [リクエスト本文] に、リクエストを貼り付けます。

    検証用のリクエストの貼り付け先を示す [Request body] フィールドを表示している [この API を試す] ウィンドウ。

リクエストの形式が正しくない要素に、下線が引かれます。対処すべき問題の詳細については、下線付きのセクションをクリックしてください。

API レスポンスを処理する

データを変化させる(変更する)リクエストを作成すると、Compute Engine は Operation オブジェクトを返します。リクエストのオペレーションのステータスは、このオブジェクトをポーリングすることで取得できます。Operation リソースは、次のような形になります。

{
 "kind": "compute#operation",
 "id": "7127550864823803662",
 "name": "operation-1458856416788-52ed27a803e22-1c3bd86a-9e95017b",
 "zone": "https://www.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE",
 "operationType": "insert",
 "targetLink": "https://www.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/instances/EXAMPLE_VM",
 "targetId": "4132355614508179214",
 "status": "PENDING",
 "user": "user@example.com",
 "progress": 0,
 "insertTime": "2016-03-24T14:53:37.788-07:00",
 "selfLink": "https://www.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/operations/operation-1458856416788-52ed27a803e22-1c3bd86a-9e95017b"
}

元のリクエストがゾーンリソースを変更する場合(ディスクのスナップショットやインスタンスの停止など)、Compute Engine は zoneOperations オブジェクトを返します。同様に、リージョン リソースとグローバル リソースは、それぞれ regionOperations オブジェクトまたは globalOperations オブジェクトを返します。オペレーションのステータスを取得するには、特定の Operation リソースに対して get メソッドまたは wait メソッドを使用するリクエストを行い、オペレーションの name を指定します。

Operation リソースのステータスが DONE と返されるまでリクエストは完了していません。リクエストの性質によっては、完了までに時間を要することもあります。Operation リソースのステータスが DONE として返されたら、オペレーションが成功したかどうか、エラーが発生していないかどうかを確認できます。

たとえば、次のレスポンスは、上記のオペレーションが完了したこと表しており、DONE ステータスにより示されています。

endTime: '2016-03-24T14:54:07.119-07:00'
id: '7127550864823803662'
insertTime: '2016-03-24T14:53:37.788-07:00'
kind: compute#operation
name: operation-1458856416788-52ed27a803e22-1c3bd86a-9e95017b
operationType: insert
progress: 100
selfLink: https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/operations/operation-1458856416788-52ed27a803e22-1c3bd86a-9e95017b
startTime: '2016-03-24T14:53:38.397-07:00'
status: DONE
targetId: '4132355614508179214'
targetLink: https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/instances/EXAMPLE_VM
user: user@example.com
zone: https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE

確認するには、リソースに対して get リクエストを行い、リソースが存在し、実行中であることを確かめます。例:

GET /compute/v1/projects/PROJECT_ID/zones/ZONE/instances/EXAMPLE_VM

{
  "cpuPlatform": "Intel Haswell",
  "creationTimestamp": "2016-03-24T14:53:37.170-07:00",
  "disks": [
    ..[snip]..
  "selfLink": "https://www.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/instances/EXAMPLE_VM",
  "status": "RUNNING",
  "tags": {
    "fingerprint": "42WmSpB8rSM="
  },
  "zone": "https://www.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE"
}

オペレーションをポーリングする

オペレーションを定期的にポーリングするコードは、get リクエストか、オペレーションのステータスが DONE になったときに戻ってくる wait リクエストを使用して作成できます。

get リクエストを使用すると、オペレーションのステータスに関係なく、オペレーションがすぐに返されます。オペレーションが完了したかどうかを知るためには、オペレーションを定期的にポーリングする必要があります。

wait リクエストを使用すると、オペレーションが DONE になるか、リクエストが 2 分間の期限に近づくとリクエストが戻ります。オペレーションをポーリングするには、waitget を使用しますが、wait メソッドの方が get メソッドより明らかなメリットがあります。

  • クライアントでオペレーション ステータスをポーリングする頻度を下げることで、Compute Engine API の QPS 使用量を削減できます。
  • オペレーションが完了してからクライアントにオペレーションの完了が通知されるまでの平均レイテンシは、オペレーションが完了したらすぐにサーバーが応答するため、大幅に削減されます。
  • このメソッドは期限付きで待機します。このメソッドは、デフォルトの HTTP タイムアウト(2 分)まで待機すると、オペレーションの現在の状態(DONE または進行中)を返します。

wait メソッドは、ベスト エフォートの API です。サーバーが過負荷状態になると、デフォルトの期限に達する前や、0 秒間の待機でリクエストが戻ることがあります。また、オペレーションが DONE の場合にのみメソッドが戻ることは保証しません。たとえば、リクエストが 2 分間の期限に近づくと、オペレーションが完了していなくてもメソッドが戻ります。オペレーションを確認するには、wait メソッドまたは get メソッドを(スリープを間に挟んだ再試行ループで)使用し、オペレーションのステータスを定期的にポーリングすることをおすすめします。最大再試行間隔は、最小オペレーション保持期間を超えないようにしてください。

ポーリングの例

次の例では、get メソッドを使用します。get メソッドは wait メソッドに置き換えることができます。

Python

def wait_for_operation(
    compute: object,
    project: str,
    zone: str,
    operation: str,
) -> dict:
    """Waits for the given operation to complete.

    Args:
      compute: an initialized compute service object.
      project: the Google Cloud project ID.
      zone: the name of the zone in which the operation should be executed.
      operation: the operation ID.

    Returns:
      The result of the operation.
    """
    print("Waiting for operation to finish...")
    while True:
        result = (
            compute.zoneOperations()
            .get(project=project, zone=zone, operation=operation)
            .execute()
        )

        if result["status"] == "DONE":
            print("done.")
            if "error" in result:
                raise Exception(result["error"])
            return result

        time.sleep(1)

Java

/**
 * Wait until {@code operation} is completed.
 *
 * @param compute the {@code Compute} object
 * @param operation the operation returned by the original request
 * @param timeout the timeout, in millis
 * @return the error, if any, else {@code null} if there was no error
 * @throws InterruptedException if we timed out waiting for the operation to complete
 * @throws IOException if we had trouble connecting
 */
public static Operation.Error blockUntilComplete(
    Compute compute, Operation operation, long timeout) throws Exception {
  long start = System.currentTimeMillis();
  final long pollInterval = 5 * 1000;
  String zone = getLastWordFromUrl(operation.getZone()); // null for global/regional operations
  String region = getLastWordFromUrl(operation.getRegion());
  String status = operation.getStatus();
  String opId = operation.getName();
  while (operation != null && !status.equals("DONE")) {
    Thread.sleep(pollInterval);
    long elapsed = System.currentTimeMillis() - start;
    if (elapsed >= timeout) {
      throw new InterruptedException("Timed out waiting for operation to complete");
    }
    System.out.println("waiting...");
    if (zone != null) {
      Compute.ZoneOperations.Get get = compute.zoneOperations().get(PROJECT_ID, zone, opId);
      operation = get.execute();
    } else if (region != null) {
      Compute.RegionOperations.Get get = compute.regionOperations().get(PROJECT_ID, region, opId);
      operation = get.execute();
    } else {
      Compute.GlobalOperations.Get get = compute.globalOperations().get(PROJECT_ID, opId);
      operation = get.execute();
    }
    if (operation != null) {
      status = operation.getStatus();
    }
  }
  return operation == null ? null : operation.getError();
}