建立 API 請求和處理回應


本文件說明如何透過 Compute Engine API 建立 API 要求及處理 API 回應,所涵蓋的內容如下:

  • 如何建構要求內文。
  • 如何決定要求所需的資源 URI。
  • 如何處理 API 回應。
  • 判斷 API 要求是否成功。

本文不會說明如何驗證 API。如要瞭解如何驗證 API,請參閱「向 Compute Engine 進行驗證」。

事前準備

建立 API 要求

Compute Engine API 預期 API 要求採用 JSON 格式。 如要發出 API 要求,您可以透過 curlhttplib2 等工具直接發出 HTTP 要求,也可以使用可用的用戶端程式庫

當您提出需要要求主體的 API 要求 (例如 POSTUPDATEPATCH 要求) 時,要求主體會包含您想在這項要求中設定的資源屬性。舉例來說,下列 curl 指令會向 Instances 資源 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 和控制台等用戶端工具會隱藏這項複雜性,並為您建立這些資源 URI,但直接與 API 互動時,您必須自行建立這些資源 URI。 Google Cloud

不同類型的資源有稍微不同的資源 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 也接受部分 URI,因為服務可以推斷專案 ID 等資訊。因此,下列先前 URI 的部分版本也適用:

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

在部分 URI 中,可用區和地區 URI 都省略了專案 ID,但映像檔 URI 並未省略。這是因為 Compute Engine 提供給大眾使用的映像檔,是託管在其他專案中,例如所有 Debian 映像檔的 debian-cloud,以及所有 Ubuntu 映像檔的 ubuntu-os-cloud。如要使用這些圖片,請先提供適當的專案 ID。如果省略映像檔的專案 ID,Compute Engine 會嘗試在您的專案中尋找映像檔,但由於映像檔不存在,要求會失敗。

不過,如果您使用屬於專案的自訂映像檔 (與您建立此資源的專案相同),提供映像檔 URI 時可以省略專案規格。

判斷必要屬性

Compute Engine API 參考資料文件 (適用於 v1Beta 版 API) 說明您可以為特定資源設定的所有屬性。參考說明文件會區分可變動和不可變動的屬性 (以屬性說明中的 [Output Only] 標示),但如要判斷資源的必要屬性,您需要查看特定工作的說明文件。

舉例來說,如果您要建立執行個體,請參閱「從映像檔建立執行個體」說明文件,瞭解要求所需的 API 屬性。如果您想在 API 中建立靜態外部 IP 位址,請參閱靜態外部 IP 位址說明文件。

驗證 API 要求

如要驗證 API 要求,請按照下列步驟操作:

  1. Compute Engine API 參考資料中,找出程式碼呼叫的方法。例如:v1/compute.instances.insert
  2. 在內容選單中,按一下「立即試用」系統會開啟「試用這個 API」視窗。

    內容選單中的「立即試用!」按鈕。

  3. 在「要求參數」下方,您不需要提供 projectzone,因為驗證不需要提交要求。

  4. 在「要求主體」下方,貼上要求。

    「Try this API」視窗,顯示「Request body」欄位,說明要將驗證要求貼到何處。

要求中格式不正確的元素會以藍色底線標示。按一下各個加上底線的部分,即可進一步瞭解要解決的問題。

處理 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 物件。同樣地,區域和全域資源會分別傳回 regionOperationsglobalOperations 物件。如要取得作業的狀態,您可以對特定 Operation 資源執行使用 getwait 方法的要求,並提供作業的 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"
}

輪詢作業

您可以編寫一些程式碼,定期使用 getwait 要求輪詢作業,當作業狀態為 DONE 時,要求就會傳回。

無論作業狀態為何,get 要求都會立即傳回作業。您需要定期輪詢作業,才能瞭解作業何時完成。

如果您提出 wait 要求,當作業為 DONE 或要求即將達到 2 分鐘的期限時,要求就會傳回。您可以選擇使用 waitget 輪詢作業,但 wait 方法比 get 方法有以下優點:

  • 您可以設定用戶端,降低輪詢作業狀態的頻率,減少 Compute Engine API 的 QPS 使用量。
  • 作業完成後,伺服器會立即通知用戶端,因此作業完成到用戶端收到通知之間的平均延遲時間大幅縮短。
  • 這個方法提供有界等待。這個方法最多會等待預設 HTTP 超時時間 (2 分鐘),然後傳回作業的目前狀態,可能是 DONE 或仍在進行中。

wait 方法是盡可能提供最佳結果的 API。如果伺服器超載,要求可能會在達到預設期限前傳回,或等待零秒後傳回。此外,方法也不保證只會在作業為 DONE 時傳回。舉例來說,如果要求即將超過 2 分鐘的期限,即使作業尚未完成,方法也會傳回。如要檢查作業,建議您使用 waitget 方法,在重試迴圈中加入間隔時間,定期輪詢作業狀態。重試間隔上限不得超過作業保留時間下限

輪詢範例

下列範例使用 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();
}