API 요청 생성 및 응답 처리

이 문서에서는 API 요청을 구성하고 Compute Engine API의 API 응답을 처리하는 방법을 설명합니다. 구체적으로는 다음을 수행하는 방법을 다룹니다.

  • 요청 본문을 구성합니다.
  • 요청에 필요한 리소스 URI를 확인합니다.
  • API 응답을 처리합니다.
  • API 요청이 성공적으로 이루어졌는지 확인합니다.

이 문서는 API에 승인하는 방법을 다루지 않습니다. API에 승인하는 방법을 알아보려면 요청 승인을 읽어보세요.

시작하기 전에

API 요청 구성

Compute Engine API는 API 요청이 JSON 형식일 것이라고 예상합니다. API 요청을 수행하려면 curl이나 httplib2 같은 도구를 이용하여 직접 HTTP 요청을 수행하거나, 사용 가능한 클라이언트 라이브러리 중 하나를 사용하면 됩니다.

POST, UPDATE, PATCH 요청과 같이 요청 본문이 필요한 API 요청을 수행할 경우, 해당 요청 본문에는 이 요청에 설정하려는 리소스 속성이 포함됩니다. 예를 들어, 아래의 curl 명령어는 인스턴스 리소스 URI에 POST 요청을 수행합니다. 이 요청은 요청 본문에 정의된 속성을 가진 새 인스턴스를 만듭니다.

curl -X POST -H "Authorization: Bearer [OAUTH_TOKEN]" -H "Content-Type: application/json"
https://www.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-8-jessie-v20160301"
      }
    }
  ],
  "machineType":"https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/zones/[ZONE]/machineTypes/n1-standard-1",
  "name":"[INSTANCE_NAME]",
  "networkInterfaces":[
    {
      "accessConfigs":[
        {
          "name":"external-nat",
          "type":"ONE_TO_ONE_NAT"
        }
      ],
      "network":"https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/global/networks/default"
    }
  ]
}'

이 경우, 요청 본문은 -d 플래그로 표시되며 아래와 같은 모습입니다(더 쉽게 볼 수 있는 형식으로 지정됨).

{
  "disks":[
    {
      "boot":"true",
      "initializeParams":{
        "sourceImage":"https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20160301"
      }
    }
  ],
  "machineType":"https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/zones/[ZONE]/machineTypes/n1-standard-1",
  "name":"[EXAMPLE_INSTANCE]",
  "networkInterfaces":[
    {
      "accessConfigs":[
        {
          "name":"external-nat",
          "type":"ONE_TO_ONE_NAT"
        }
      ],
      "network":"https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/global/networks/default"
    }
  ]
}

유의사항:

  • 다른 리소스를 참조할 때는 정규화된 리소스 URI가 사용됩니다. 예를 들어 network 속성은 default 네트워크에 정규화된 URI를 사용합니다.

  • 이미지 URI의 프로젝트 ID(debian-cloud)는 사용자의 프로젝트 ID와 다릅니다. 이미지는 그 유형에 따라 서로 다른 프로젝트에 속하기 때문입니다. 예를 들어 Compute Engine에서 제공하며 공개적으로 사용 가능한 모든 Debian 이미지는 debian-cloud 프로젝트에서 호스팅됩니다.

아래는 Python과 자바 클라이언트 라이브러리를 사용하는 API 요청의 다른 예입니다.

Python

def create_instance(compute, project, zone, name, bucket):
    # Get the latest Debian Jessie image.
    image_response = compute.images().getFromFamily(
        project='debian-cloud', family='debian-9').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'), 'r').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(
      "https://www.googleapis.com/compute/v1/projects/"
      + PROJECT_ID + "/zones/" + ZONE_NAME + "/machineTypes/n1-standard-1");

  // Add Network Interface to be used by VM Instance.
  NetworkInterface ifc = new NetworkInterface();
  ifc.setNetwork("https://www.googleapis.com/compute/v1/projects/" + PROJECT_ID + "/global/networks/default");
  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("https://www.googleapis.com/compute/v1/projects/" + PROJECT_ID + "/zones/"
                     + ZONE_NAME + "/diskTypes/pd-standard");
  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("gs://" + PROJECT_ID + "/vm-startup.sh");
  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 만들기

위의 예에서는 다른 Google Cloud Platform 리소스를 참조할 필요가 있을 때마다 다음과 같은 정규화된 URI를 제공했습니다.

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

API를 사용할 경우 이미지, 머신 유형, 네트워크, 기타 리소스를 지정할 때마다 리소스에 URI를 제공해야 합니다. gcloud와 Google Cloud Platform 콘솔 같은 클라이언트 도구를 사용하면 이러한 복잡성이 가려지고 이러한 리소스 URI가 자동으로 생성되지만 사용자가 API와 직접 상호작용할 때는 이러한 리소스 URI를 직접 만들어야 합니다.

리소스 유형마다 리소스 URI가 약간씩 다릅니다. 예를 들어, 영역 리소스는 다음과 같이 URI에 zone이 지정됩니다.

https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/zones/[ZONE]/machineTypes/n1-standard-1

지역 리소스에는 zone 대신 region이 지정됩니다.

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

마지막으로, 전역 리소스에는 global이 지정됩니다.

https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/global/images/[IMAGE_NAME]

Compute Engine API는 부분 URI도 허용하는데, 이는 이 서비스가 프로젝트 ID와 같은 정보를 유추할 수 있기 때문입니다. 따라서 상기 URI의 허용되는 부분 URI 버전은 다음과 같습니다.

zones/[ZONE]/machineTypes/n1-standard-1
regions/[REGION]/addresses/[ADDRESS_NAME]
project/[PROJECT_ID]/global/images/[IMAGE_NAME]

위의 부분 URI에서는 영역 URI와 지역 URI 모두에 프로젝트 ID가 생략되었으나 이미지 URI는 그렇지 않습니다. Compute Engine에서 제공하는 공개적으로 사용 가능한 이미지는 다른 프로젝트에서 호스팅되기 때문입니다(예: 모든 Debian 이미지는 debian-cloud, 모든 Ubuntu 이미지는 ubuntu-os-cloud). 이러한 이미지를 사용하기 위해서는 해당 프로젝트 ID를 제공해야 합니다. 이미지에 프로젝트 ID를 누락하면 Compute Engine이 프로젝트에서 이미지를 찾으려고 시도하지만 이미지가 존재하지 않기 때문에 요청이 실패하게 됩니다.

반면에 프로젝트(이 리소스를 만드는 프로젝트)에 속한 커스텀 이미지를 사용할 경우, 이미지 URI를 지정할 때 프로젝트 지정을 생략해도 됩니다.

필요한 속성 결정

v1베타 API에 제공되는 API 참조 문서는 특정 리소스에 설정할 수 있는 모든 속성을 설명합니다. 이 참조 문서는 변경 가능한 속성과 변경이 불가능한 속성을 구분하고 있으나(속성 설명에 [Output Only]로 표시) 리소스에 필요한 속성을 확인하려면 해당 작업과 관련된 문서를 검토해야 합니다.

예를 들어 새 인스턴스를 만들 경우에는 인스턴스 만들기 및 시작 문서를 참조하여 요청에 필요한 API 속성을 확인합니다. API에서 고정 외부 IP 주소를 만들려는 경우에는 고정 외부 IP 주소 문서를 참조합니다.

또는 API 탐색기에서 API 요청의 유효성을 검증하면 코드를 쉽고 빠르게 확인할 수 있습니다.

API 응답 처리

데이터 변경 요청을 실행할 경우, Compute Engine이 작업 객체를 반환하고 사용자가 이를 폴링하여 요청의 상태를 가져올 수 있습니다. 작업 리소스의 모습은 다음과 유사합니다.

{
 "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_INSTANCE]",
 "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 객체를 반환합니다. 작업의 name을 지정하여 특정 Operation 리소스에 대해 GET 요청을 실행하면 작업의 상태를 가져올 수 있습니다.

요청은 작업 리소스의 상태가 DONE으로 반환되어야 완료된 것입니다. 요청의 성격에 따라 어느 정도 시간이 걸릴 수 있습니다. 그리고 작업의 상태가 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://www.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://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/zones/[ZONE]/instances/[EXAMPLE_INSTANCE]
user: phunl@google.com
zone: https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/zones/[ZONE]

리소스에 GET 요청을 수행하여 작업이 존재하고 실행되고 있는지 확인해야 합니다. 예를 들면 다음과 같습니다.

GET /compute/v1/projects/[PROJECT_ID]/zones/[ZONE]/instances/[EXAMPLE_INSTANCE]

{
  "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_INSTANCE]",
  "status": "RUNNING",
  "tags": {
    "fingerprint": "42WmSpB8rSM="
  },
  "zone": "https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/zones/[ZONE]"
}

폴링 작업

마찬가지로, 작업이 성공적으로 반환되기를 바란다면 작업의 상태를 가져오는 요청을 개별적으로 수행하는 데 시간을 보내지 않는 것이 좋습니다. 대신, 작업을 정기적으로 폴링하고 작업 상태가 DONE일 때 반환하는 코드를 작성하는 것이 좋습니다. 다음은 Python과 자바에서 폴링을 사용하는 몇 가지 예입니다.

Python

def wait_for_operation(compute, project, zone, 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 = operation.getZone();  // null for global/regional operations
  if (zone != null) {
    String[] bits = zone.split("/");
    zone = bits[bits.length - 1];
  }
  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 {
      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();
}
이 페이지가 도움이 되었나요? 평가를 부탁드립니다.

다음에 대한 의견 보내기...

Compute Engine 문서