ゾーンまたはリージョン間での VM インスタンスの移動


このドキュメントでは、ゾーン間またはリージョン間で仮想マシン(VM)インスタンスを移動する方法について説明します。

始める前に

要件

このセクションでは、ゾーンとリージョンの間で VM を移動するための要件について説明します。

  • プロジェクトの割り当て。プロジェクトには、以下の操作に十分な割り当てが必要です。

    • 新しいスナップショットを作成する。
    • エフェメラル外部 IP アドレスを昇格させる。
    • 移行先のリージョンに新しい VM とディスクを作成する。

      たとえば、移動する VM にディスクが 3 つアタッチされている場合、永続ディスク 3 つ分の一時的なスナップショットと 3 つ分の新しいディスクの作成に十分な割り当てが必要です。新しいディスクを作成すると、一時スナップショットを削除できます。

    [割り当て] ページで、上記のリソースに十分な割り当てがあることを確認します。詳細については、割り当てについてをご覧ください。

  • 永続ディスク。移動する VM にアタッチされている永続ディスクは、他の VM にアタッチされません。

  • ローカル SSD。ローカル SSD は一時的なストレージとして使用されることを前提としています。VM を手動で終了すると、ローカル SSD 上のデータは保持されません。ローカル SSD のデータを維持する必要がある場合は、永続ディスクなど、耐久性のあるストレージ オプションを使用してください。

  • GPU。VM に GPU が含まれている場合は、使用する GPU が VM の宛先ゾーンで使用可能であることを確認します。GPU と使用可能なゾーンのリストについては、Compute Engine の GPU をご覧ください。

  • サブネットワークus-west1-aasia-south1-b の間など、リージョン間で VM を移動するときに、VM がサブネットワークに属している場合は、VM に新しいサブネットワークを選択する必要があります。サブネットの作成方法については、サブネットの追加をご覧ください。

制約事項

リージョン間で VM を移動する場合、VM のエフェメラル内部 IP アドレスまたは外部 IP アドレスを保持することはできません。VM を再作成する場合は、新しい IP アドレスを選択する必要があります。

リソース プロパティ

VM を移動するには、VM をシャットダウンし、移動先のゾーンまたはリージョンに移動してから再起動する必要があります。VM の移動後は、元のリソースへの参照をすべて更新します。たとえば、以前の VM を指すターゲット VM やターゲット プールなどでの参照を更新します。

移動の際、サーバーが生成した、VM とディスクのプロパティのいくつかが変更されます。

VM プロパティの変更

次の表は、VM 用に変更されるプロパティを示しています。

プロパティ名 変更
内部 IP アドレス 通常は新しい内部 IP アドレスが割り当てられますが、VM が元の内部 IP アドレスを保持する場合もあります。
外部 IP アドレス VM が同じリージョンのゾーンの間で移動する場合、外部 IP アドレスは変わりません。それ以外の場合は、VM インスタンスに別の外部 IP アドレスを選択します。
CPU プラットフォーム 移動先のゾーンで利用できる CPU のプラットフォームによっては、移動後の VM が別の CPU プラットフォームを使用する可能性があります。各ゾーンのすべての CPU プラットフォームの完全な一覧については、利用可能なリージョンとゾーンをご覧ください。
ネットワーク / サブネットワーク VM がサブネットワークに属している場合で、VM をリージョン間で移動する場合は、VM に新しいサブネットワークを選択する必要があります。同じリージョン内のゾーン間で VM を移動する場合は、同じサブネットワークが保持されます。

ディスク プロパティの変更

次の表は、ディスク用に変更されるプロパティを示しています。

プロパティ名 変更
ソース スナップショット 新しいディスクのソース スナップショットは、移動中に作成された一時スナップショットに設定されます。
ソース スナップショット ID ソース スナップショット ID は、一時スナップショットの ID に設定されます。
ソースイメージ ソースイメージ フィールドは空です。
イメージ ID イメージ ID は空です。
最後の接続解除のタイムスタンプ 最後の接続解除のタイムスタンプは空です。
最後の接続のタイムスタンプ 最後のアタッチのタイムスタンプは、新しいディスクが新しいインスタンスにアタッチされたときのタイムスタンプに変更されます。

VM とディスクの両方のプロパティの変更

次の表に、VM とディスクの両方に適用されるプロパティを示します。

プロパティ名 変更
ID 新しいリソース ID が生成されます。
作成のタイムスタンプ 新しい作成タイムスタンプが生成されます。
ゾーンのリソースの URL すべてのゾーンリソース URL が、移動先のゾーンを反映するよう変更されます。変更されるリソースの URL は次のとおりです。
  • VM のソースディスクの URL
  • VM のマシンタイプの URL
  • セルフリンクの URL
  • ゾーンの URL
  • ディスクタイプの URL
  • ディスクの users[] リストに記載された VM の URL(ある場合)

VM をゾーンまたはリージョン間で移動する

ゾーンまたはリージョン間で VM を移動するには、次の操作を行います。

  1. ソース VM のマシンイメージを作成します。
  2. 別のゾーンまたはリージョンにあるマシンイメージから VM を作成します。

次の例では、VM をゾーン間で移動する方法を示します。

gcloud

この例では、mybootdiskmydatadisk という名前の 2 つの永続ディスクを持つ myinstance という名前の VM を europe-west1-c から us-west1-b に移動します。

  1. 移動する VM に関連付けられているディスクを特定します。

    gcloud compute instances describe myinstance --format="list(name,status,disks)"
    

    この例では、myinstance VM に次の 2 つのディスクが関連付けられています。

    • mybootdisk というブートディスク
    • mydatadisk というデータディスク
  2. mybootdiskmydatadisk の自動削除の状態を false に設定して、VM の削除時に、これらのディスクが自動削除されないようにします。

    gcloud compute instances set-disk-auto-delete myinstance --zone europe-west1-c \
        --disk mybootdisk --no-auto-delete

    状態が更新された場合は、gcloud compute がレスポンス Updated [...] を返します。自動削除状態がすでに false に設定されている場合は、gcloud compute が次のレスポンスを返します。

    No change requested; skipping update for [myinstance].
  3. (省略可)VM メタデータを保存します。

    VM を削除すると、VM メタデータも削除されます。この情報を別のファイルに保存し、VM メタデータを新しい VM に再適用できます。

    VM のメタデータを次のように記述します。

    gcloud compute instances describe myinstance --zone europe-west1-c

    この内容は別のファイルに保存します。

  4. 永続ディスクのスナップショットを使用して、データのバックアップを作成します。

    予防措置として、VM に永続ディスクがアタッチしている間に、永続ディスクのスナップショットを使用してデータのバックアップを作成します。スナップショットを取得する前に、スナップショットのベスト プラクティスを遵守して、それが永続ディスクの状態と一致するようにします。

    ディスク バッファをクリアした後で、次のようにしてスナップショットを作成します。

    gcloud compute disks snapshot mybootdisk mydatadisk \
        --snapshot-names backup-mybootsnapshot,backup-mydatasnapshot \
        --zone europe-west1-c 

    スナップショットが作成されたことを確認するには、gcloud compute snapshots list を実行します。

  5. (省略可)同じリージョン内のゾーン間で VM を移動する場合で、内部または外部のエフェメラル IP アドレスを保持する場合は、内部または外部 IP アドレスを静的 IP アドレスに昇格させることで、後で再利用できます。

  6. VM を削除します。

    VM を削除すると、VM が完全にシャットダウンされ、永続ディスクが切断されます。

    gcloud compute instances delete myinstance --zone europe-west1-c

    gcloud から、削除を確定するよう求められます。

    
    The following VMs are deleted. Any attached disks configured to
    be auto-deleted are deleted unless they are attached to any other
    VMs or the `--keep-disks` flag is given and specifies them for keeping.
    Deleting a disk is irreversible and any data on the disk is lost.
    — [myinstance] in [europe-west1-c]
    

    Do you want to continue (Y/n)?

    すでにこのプロセスの以前の手順でディスクの自動削除状態をオフにしているため、「Y」と入力して続行し、警告を無視します。

  7. 次に、ブートディスクとデータディスクの両方について、別のスナップショットを作成します。

    gcloud compute disks snapshot mybootdisk mydatadisk \
        --snapshot-names mybootsnapshot,mydatasnapshot \
        --zone europe-west1-c 
    Created [.../mydatasnapshot].
    Created [.../mybootsnapshot].
  8. (省略可)永続ディスクを削除します。

    永続ディスクの名前を新しいディスクに再利用する場合は、その名前を解放するために既存のディスクを削除する必要があります。ディスクを削除すると、永続ディスクのストレージ費用も節約できます。

    同じディスク名を再使用する予定がない場合は、削除する必要はありません。

    gcloud compute disks delete mybootdisk mydatadisk --zone europe-west1-c
  9. 作成したスナップショットから us-west1-b に新しい永続ディスクを作成します。まずブートディスクを作成します。

    gcloud compute disks create mybootdiskb --source-snapshot mybootsnapshot \
        --zone us-west1-b
    Created [.../mybootdiskb].
    NAME        ZONE           SIZE_GB TYPE        STATUS
    mybootdiskb us-west1-b     100     pd-standard READY

    次に、データディスクを作成します。

    gcloud compute disks create mydatadiskb --source-snapshot mydatasnapshot \
        --zone us-west1-b
    Created [.../mydatadiskb].
    NAME        ZONE           SIZE_GB TYPE        STATUS
    mydatadiskb us-west1-b 4000    pd-standard READY
  10. us-west1-b に VM を再作成します。

    • VM のメタデータをファイル(たとえば、myinstance.describe)に保存することを選択した場合、そのファイルを使用して VM に同じメタデータを設定できます。

    • VM に静的外部 IP アドレスがある場合、そのアドレスを新しい VM に再度割り当てるには --address [ADDRESS] オプションを指定します。リージョン間で VM を移動する場合は、新しい VM インスタンスに別の外部 IP アドレスを選択する必要があります。

    • VM に静的内部 IP アドレスがある場合、そのアドレスを新しい VM に再度割り当てるには --private-network-ip ADDRESS オプションを指定します。リージョン間で VM を移動する場合は、新しい VM インスタンスに別の内部 IP アドレスを選択する必要があります。

    • VM に GPU が含まれている場合は、--accelerator オプションを使用して VM に GPU を追加します。

    • VM で特定のサブネットを使用する場合は、--subnet [SUBNET_NAME] フラグを追加します。

    その他のフラグの完全なリストについては、gcloud compute instances create コマンドをご覧ください。

    gcloud compute instances create myinstanceb --machine-type n1-standard-4 \
        --zone us-west1-b \
        --disk name=mybootdiskb,boot=yes,mode=rw \
        --disk name=mydatadiskb,mode=rw 
    Created [.../myinstanceb].
    NAME        ZONE           MACHINE_TYPE  INTERNAL_IP    EXTERNAL_IP     STATUS
    myinstanceb us-west1-b     n1-standard-4 10.240.173.229 146.148.112.106 RUNNING
  11. (省略可)永続ディスクのスナップショットを削除します。

    仮想マシンが移動されたことを確認した後は、作成した一時スナップショットを削除するとストレージ費用を節約できます。

    gcloud compute snapshots delete mybootsnapshot mydatasnapshot

    バックアップ スナップショットが不要になった場合は、そのスナップショットも削除します。

    gcloud compute snapshots delete backup-mybootsnapshot backup-mydatasnapshot

Go

  1. VM の詳細を取得し、VM にアタッチされているディスクを特定します。

    import (
    	"context"
    	"fmt"
    	"io"
    
    	compute "cloud.google.com/go/compute/apiv1"
    	computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
    )
    
    // getInstance prints a name of a VM instance in the given zone in the specified project.
    func getInstance(w io.Writer, projectID, zone, instanceName string) error {
    	// projectID := "your_project_id"
    	// zone := "europe-central2-b"
    	// instanceName := "your_instance_name"
    
    	ctx := context.Background()
    	instancesClient, err := compute.NewInstancesRESTClient(ctx)
    	if err != nil {
    		return fmt.Errorf("NewInstancesRESTClient: %w", err)
    	}
    	defer instancesClient.Close()
    
    	reqInstance := &computepb.GetInstanceRequest{
    		Project:  projectID,
    		Zone:     zone,
    		Instance: instanceName,
    	}
    
    	instance, err := instancesClient.Get(ctx, reqInstance)
    	if err != nil {
    		return fmt.Errorf("unable to get instance: %w", err)
    	}
    
    	fmt.Fprintf(w, "Instance: %s\n", instance.GetName())
    
    	return nil
    }
    
  2. VM が削除されたときにディスクが自動的に削除されないように、ブートディスクとデータディスクの自動削除状態を false に設定します。

    import (
    	"context"
    	"fmt"
    	"io"
    
    	compute "cloud.google.com/go/compute/apiv1"
    	computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
    )
    
    // setDiskAutodelete sets the autodelete flag of a disk to given value.
    func setDiskAutoDelete(
    	w io.Writer,
    	projectID, zone, instanceName, diskName string, autoDelete bool,
    ) error {
    	// projectID := "your_project_id"
    	// zone := "us-west3-b"
    	// instanceName := "your_instance_name"
    	// diskName := "your_disk_name"
    	// autoDelete := true
    
    	ctx := context.Background()
    	instancesClient, err := compute.NewInstancesRESTClient(ctx)
    	if err != nil {
    		return fmt.Errorf("NewInstancesRESTClient: %w", err)
    	}
    	defer instancesClient.Close()
    
    	getInstanceReq := &computepb.GetInstanceRequest{
    		Project:  projectID,
    		Zone:     zone,
    		Instance: instanceName,
    	}
    
    	instance, err := instancesClient.Get(ctx, getInstanceReq)
    	if err != nil {
    		return fmt.Errorf("unable to get instance: %w", err)
    	}
    
    	diskExists := false
    
    	for _, disk := range instance.GetDisks() {
    		if disk.GetDeviceName() == diskName {
    			diskExists = true
    			break
    		}
    	}
    
    	if !diskExists {
    		return fmt.Errorf(
    			"instance %s doesn't have a disk named %s attached",
    			instanceName,
    			diskName,
    		)
    	}
    
    	req := &computepb.SetDiskAutoDeleteInstanceRequest{
    		Project:    projectID,
    		Zone:       zone,
    		Instance:   instanceName,
    		DeviceName: diskName,
    		AutoDelete: autoDelete,
    	}
    
    	op, err := instancesClient.SetDiskAutoDelete(ctx, req)
    	if err != nil {
    		return fmt.Errorf("unable to set disk autodelete field: %w", err)
    	}
    
    	if err = op.Wait(ctx); err != nil {
    		return fmt.Errorf("unable to wait for the operation: %w", err)
    	}
    
    	fmt.Fprintf(w, "disk autoDelete field updated.\n")
    
    	return nil
    }
    
  3. 永続ディスクのスナップショットを使用して、データのバックアップを作成します。

    予防措置として、VM に永続ディスクがアタッチしている間に、永続ディスクのスナップショットを使用してデータのバックアップを作成します。スナップショットを取得する前に、スナップショットのベスト プラクティスを遵守して、それが永続ディスクの状態と一致するようにします。

    ディスク バッファをクリアした後で、次のようにしてスナップショットを作成します。

    import (
    	"context"
    	"fmt"
    	"io"
    
    	compute "cloud.google.com/go/compute/apiv1"
    	computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
    	"google.golang.org/protobuf/proto"
    )
    
    // createSnapshot creates a snapshot of a disk.
    func createSnapshot(
    	w io.Writer,
    	projectID, diskName, snapshotName, zone, region, location, diskProjectID string,
    ) error {
    	// projectID := "your_project_id"
    	// diskName := "your_disk_name"
    	// snapshotName := "your_snapshot_name"
    	// zone := "europe-central2-b"
    	// region := "eupore-central2"
    	// location = "eupore-central2"
    	// diskProjectID = "YOUR_DISK_PROJECT_ID"
    
    	ctx := context.Background()
    
    	snapshotsClient, err := compute.NewSnapshotsRESTClient(ctx)
    	if err != nil {
    		return fmt.Errorf("NewSnapshotsRESTClient: %w", err)
    	}
    	defer snapshotsClient.Close()
    
    	if zone == "" && region == "" {
    		return fmt.Errorf("you need to specify `zone` or `region` for this function to work")
    	}
    
    	if zone != "" && region != "" {
    		return fmt.Errorf("you can't set both `zone` and `region` parameters")
    	}
    
    	if diskProjectID == "" {
    		diskProjectID = projectID
    	}
    
    	disk := &computepb.Disk{}
    	locations := []string{}
    	if location != "" {
    		locations = append(locations, location)
    	}
    
    	if zone != "" {
    		disksClient, err := compute.NewDisksRESTClient(ctx)
    		if err != nil {
    			return fmt.Errorf("NewDisksRESTClient: %w", err)
    		}
    		defer disksClient.Close()
    
    		getDiskReq := &computepb.GetDiskRequest{
    			Project: projectID,
    			Zone:    zone,
    			Disk:    diskName,
    		}
    
    		disk, err = disksClient.Get(ctx, getDiskReq)
    		if err != nil {
    			return fmt.Errorf("unable to get disk: %w", err)
    		}
    	} else {
    		regionDisksClient, err := compute.NewRegionDisksRESTClient(ctx)
    		if err != nil {
    			return fmt.Errorf("NewRegionDisksRESTClient: %w", err)
    		}
    		defer regionDisksClient.Close()
    
    		getDiskReq := &computepb.GetRegionDiskRequest{
    			Project: projectID,
    			Region:  region,
    			Disk:    diskName,
    		}
    
    		disk, err = regionDisksClient.Get(ctx, getDiskReq)
    		if err != nil {
    			return fmt.Errorf("unable to get disk: %w", err)
    		}
    	}
    
    	req := &computepb.InsertSnapshotRequest{
    		Project: projectID,
    		SnapshotResource: &computepb.Snapshot{
    			Name:             proto.String(snapshotName),
    			SourceDisk:       proto.String(disk.GetSelfLink()),
    			StorageLocations: locations,
    		},
    	}
    
    	op, err := snapshotsClient.Insert(ctx, req)
    	if err != nil {
    		return fmt.Errorf("unable to create snapshot: %w", err)
    	}
    
    	if err = op.Wait(ctx); err != nil {
    		return fmt.Errorf("unable to wait for the operation: %w", err)
    	}
    
    	fmt.Fprintf(w, "Snapshot created\n")
    
    	return nil
    }
    
  4. 移動元のゾーンから VM を削除します。

    import (
    	"context"
    	"fmt"
    	"io"
    
    	compute "cloud.google.com/go/compute/apiv1"
    	computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
    )
    
    // deleteInstance sends a delete request to the Compute Engine API and waits for it to complete.
    func deleteInstance(w io.Writer, projectID, zone, instanceName string) error {
    	// projectID := "your_project_id"
    	// zone := "europe-central2-b"
    	// instanceName := "your_instance_name"
    	ctx := context.Background()
    	instancesClient, err := compute.NewInstancesRESTClient(ctx)
    	if err != nil {
    		return fmt.Errorf("NewInstancesRESTClient: %w", err)
    	}
    	defer instancesClient.Close()
    
    	req := &computepb.DeleteInstanceRequest{
    		Project:  projectID,
    		Zone:     zone,
    		Instance: instanceName,
    	}
    
    	op, err := instancesClient.Delete(ctx, req)
    	if err != nil {
    		return fmt.Errorf("unable to delete instance: %w", err)
    	}
    
    	if err = op.Wait(ctx); err != nil {
    		return fmt.Errorf("unable to wait for the operation: %w", err)
    	}
    
    	fmt.Fprintf(w, "Instance deleted\n")
    
    	return nil
    }
    
  5. 次に、ブートディスクとデータディスクの両方について、別のスナップショットを作成します。

    import (
    	"context"
    	"fmt"
    	"io"
    
    	compute "cloud.google.com/go/compute/apiv1"
    	computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
    	"google.golang.org/protobuf/proto"
    )
    
    // createSnapshot creates a snapshot of a disk.
    func createSnapshot(
    	w io.Writer,
    	projectID, diskName, snapshotName, zone, region, location, diskProjectID string,
    ) error {
    	// projectID := "your_project_id"
    	// diskName := "your_disk_name"
    	// snapshotName := "your_snapshot_name"
    	// zone := "europe-central2-b"
    	// region := "eupore-central2"
    	// location = "eupore-central2"
    	// diskProjectID = "YOUR_DISK_PROJECT_ID"
    
    	ctx := context.Background()
    
    	snapshotsClient, err := compute.NewSnapshotsRESTClient(ctx)
    	if err != nil {
    		return fmt.Errorf("NewSnapshotsRESTClient: %w", err)
    	}
    	defer snapshotsClient.Close()
    
    	if zone == "" && region == "" {
    		return fmt.Errorf("you need to specify `zone` or `region` for this function to work")
    	}
    
    	if zone != "" && region != "" {
    		return fmt.Errorf("you can't set both `zone` and `region` parameters")
    	}
    
    	if diskProjectID == "" {
    		diskProjectID = projectID
    	}
    
    	disk := &computepb.Disk{}
    	locations := []string{}
    	if location != "" {
    		locations = append(locations, location)
    	}
    
    	if zone != "" {
    		disksClient, err := compute.NewDisksRESTClient(ctx)
    		if err != nil {
    			return fmt.Errorf("NewDisksRESTClient: %w", err)
    		}
    		defer disksClient.Close()
    
    		getDiskReq := &computepb.GetDiskRequest{
    			Project: projectID,
    			Zone:    zone,
    			Disk:    diskName,
    		}
    
    		disk, err = disksClient.Get(ctx, getDiskReq)
    		if err != nil {
    			return fmt.Errorf("unable to get disk: %w", err)
    		}
    	} else {
    		regionDisksClient, err := compute.NewRegionDisksRESTClient(ctx)
    		if err != nil {
    			return fmt.Errorf("NewRegionDisksRESTClient: %w", err)
    		}
    		defer regionDisksClient.Close()
    
    		getDiskReq := &computepb.GetRegionDiskRequest{
    			Project: projectID,
    			Region:  region,
    			Disk:    diskName,
    		}
    
    		disk, err = regionDisksClient.Get(ctx, getDiskReq)
    		if err != nil {
    			return fmt.Errorf("unable to get disk: %w", err)
    		}
    	}
    
    	req := &computepb.InsertSnapshotRequest{
    		Project: projectID,
    		SnapshotResource: &computepb.Snapshot{
    			Name:             proto.String(snapshotName),
    			SourceDisk:       proto.String(disk.GetSelfLink()),
    			StorageLocations: locations,
    		},
    	}
    
    	op, err := snapshotsClient.Insert(ctx, req)
    	if err != nil {
    		return fmt.Errorf("unable to create snapshot: %w", err)
    	}
    
    	if err = op.Wait(ctx); err != nil {
    		return fmt.Errorf("unable to wait for the operation: %w", err)
    	}
    
    	fmt.Fprintf(w, "Snapshot created\n")
    
    	return nil
    }
    
  6. (省略可)永続ディスクを削除します。

    永続ディスクの名前を新しいディスクに再利用する場合は、その名前を解放するために既存のディスクを削除する必要があります。ディスクを削除すると、永続ディスクのストレージ費用も節約できます。

    同じディスク名を再使用する予定がない場合は、削除する必要はありません。

    import (
    	"context"
    	"fmt"
    	"io"
    
    	compute "cloud.google.com/go/compute/apiv1"
    	computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
    )
    
    // deleteDisk deletes a disk from a project.
    func deleteDisk(w io.Writer, projectID, zone, diskName string) error {
    	// projectID := "your_project_id"
    	// zone := "us-west3-b"
    	// diskName := "your_disk_name"
    
    	ctx := context.Background()
    	disksClient, err := compute.NewDisksRESTClient(ctx)
    	if err != nil {
    		return fmt.Errorf("NewDisksRESTClient: %w", err)
    	}
    	defer disksClient.Close()
    
    	req := &computepb.DeleteDiskRequest{
    		Project: projectID,
    		Zone:    zone,
    		Disk:    diskName,
    	}
    
    	op, err := disksClient.Delete(ctx, req)
    	if err != nil {
    		return fmt.Errorf("unable to delete disk: %w", err)
    	}
    
    	if err = op.Wait(ctx); err != nil {
    		return fmt.Errorf("unable to wait for the operation: %w", err)
    	}
    
    	fmt.Fprintf(w, "Disk deleted\n")
    
    	return nil
    }
    
  7. 作成したスナップショットから、移動先のゾーンに新しい永続ディスクを作成します。まずブートディスクを作成し、次にデータディスクを作成します。

    import (
    	"context"
    	"fmt"
    	"io"
    
    	compute "cloud.google.com/go/compute/apiv1"
    	computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
    	"google.golang.org/protobuf/proto"
    )
    
    // createDiskFromSnapshot creates a new disk in a project in given zone.
    func createDiskFromSnapshot(
    	w io.Writer,
    	projectID, zone, diskName, diskType, snapshotLink string,
    	diskSizeGb int64,
    ) error {
    	// projectID := "your_project_id"
    	// zone := "us-west3-b" // should match diskType below
    	// diskName := "your_disk_name"
    	// diskType := "zones/us-west3-b/diskTypes/pd-ssd"
    	// snapshotLink := "projects/your_project_id/global/snapshots/snapshot_name"
    	// diskSizeGb := 120
    
    	ctx := context.Background()
    	disksClient, err := compute.NewDisksRESTClient(ctx)
    	if err != nil {
    		return fmt.Errorf("NewDisksRESTClient: %w", err)
    	}
    	defer disksClient.Close()
    
    	req := &computepb.InsertDiskRequest{
    		Project: projectID,
    		Zone:    zone,
    		DiskResource: &computepb.Disk{
    			Name:           proto.String(diskName),
    			Zone:           proto.String(zone),
    			Type:           proto.String(diskType),
    			SourceSnapshot: proto.String(snapshotLink),
    			SizeGb:         proto.Int64(diskSizeGb),
    		},
    	}
    
    	op, err := disksClient.Insert(ctx, req)
    	if err != nil {
    		return fmt.Errorf("unable to create disk: %w", err)
    	}
    
    	if err = op.Wait(ctx); err != nil {
    		return fmt.Errorf("unable to wait for the operation: %w", err)
    	}
    
    	fmt.Fprintf(w, "Disk created\n")
    
    	return nil
    }
    
  8. 移行先のゾーンの新しいディスクで VM を再作成します。

    import (
    	"context"
    	"fmt"
    	"io"
    
    	compute "cloud.google.com/go/compute/apiv1"
    	computepb "google.golang.org/genproto/googleapis/cloud/compute/v1"
    	"google.golang.org/protobuf/proto"
    )
    
    // createWithExistingDisks create a new VM instance using selected disks.
    // The first disk in diskNames will be used as boot disk.
    func createWithExistingDisks(
    	w io.Writer,
    	projectID, zone, instanceName string,
    	diskNames []string,
    ) error {
    	// projectID := "your_project_id"
    	// zone := "europe-central2-b"
    	// instanceName := "your_instance_name"
    	// diskNames := []string{"boot_disk", "disk1", "disk2"}
    
    	ctx := context.Background()
    	instancesClient, err := compute.NewInstancesRESTClient(ctx)
    	if err != nil {
    		return fmt.Errorf("NewInstancesRESTClient: %w", err)
    	}
    	defer instancesClient.Close()
    
    	disksClient, err := compute.NewDisksRESTClient(ctx)
    	if err != nil {
    		return fmt.Errorf("NewDisksRESTClient: %w", err)
    	}
    	defer disksClient.Close()
    
    	disks := [](*computepb.Disk){}
    
    	for _, diskName := range diskNames {
    		reqDisk := &computepb.GetDiskRequest{
    			Project: projectID,
    			Zone:    zone,
    			Disk:    diskName,
    		}
    
    		disk, err := disksClient.Get(ctx, reqDisk)
    		if err != nil {
    			return fmt.Errorf("unable to get disk: %w", err)
    		}
    
    		disks = append(disks, disk)
    	}
    
    	attachedDisks := [](*computepb.AttachedDisk){}
    
    	for _, disk := range disks {
    		attachedDisk := &computepb.AttachedDisk{
    			Source: proto.String(disk.GetSelfLink()),
    		}
    		attachedDisks = append(attachedDisks, attachedDisk)
    	}
    
    	attachedDisks[0].Boot = proto.Bool(true)
    
    	instanceResource := &computepb.Instance{
    		Name:        proto.String(instanceName),
    		Disks:       attachedDisks,
    		MachineType: proto.String(fmt.Sprintf("zones/%s/machineTypes/n1-standard-1", zone)),
    		NetworkInterfaces: []*computepb.NetworkInterface{
    			{
    				Name: proto.String("global/networks/default"),
    			},
    		},
    	}
    
    	req := &computepb.InsertInstanceRequest{
    		Project:          projectID,
    		Zone:             zone,
    		InstanceResource: instanceResource,
    	}
    
    	op, err := instancesClient.Insert(ctx, req)
    	if err != nil {
    		return fmt.Errorf("unable to create instance: %w", err)
    	}
    
    	if err = op.Wait(ctx); err != nil {
    		return fmt.Errorf("unable to wait for the operation: %w", err)
    	}
    
    	fmt.Fprintf(w, "Instance created\n")
    
    	return nil
    }
    
  9. (省略可)一時的なディスク スナップショットを削除します。仮想マシンが移動されたことを確認した後は、作成した一時スナップショットを削除するとストレージ費用を節約できます。