Create custom images

This document describes how to create a custom image for use with Google Distributed Cloud (GDC) air-gapped virtual machine (VM) instances.

You can create custom images from existing source disks and use them to create and start virtual machines (VMs). Custom images are ideal for when you create and modify a persistent boot disk to a certain state and need to save that state for creating VMs. Creating and saving a custom image to use as a new VM image in future VM creation prevents duplicating your setup steps later.

This document is for developers in platform administrator or application operator groups that create VMs and manage VM images in a Google Distributed Cloud (GDC) air-gapped environment. For more information, see Audiences for GDC air-gapped documentation.

Before you begin

To use gdcloud command-line interface (CLI) commands, ensure that you have downloaded, installed, and configured the gdcloud CLI. All commands for Distributed Cloud use the gdcloud or kubectl CLI, and require an operating system (OS) environment.

Get the kubeconfig file path

To run commands against the Management API server, ensure you have the following resources:

  1. Sign in and generate the kubeconfig file for the Management API server if you don't have one.

  2. Use the path to the kubeconfig file of the Management API server to replace MANAGEMENT_API_SERVER in these instructions.

Request IAM roles

Contact your Project IAM Admin to request the following roles on your project:

  • Virtual Machine Project Admin (project-vm-admin): create, modify, list, and delete VMs in the project namespace.

  • Project Viewer(project-viewer): view all resources within the project namespaces.

  • Virtual Machine Image Project Admin (project-vm-image-admin): create, list, and delete custom VM images in the project namespace.

All VM roles must bind to the namespace of the project where the VM resides. Follow the steps to verify your access.

Create a custom image

This section describes how to create a custom image on a VM.

Prepare your VM for an image

You can create an image from a disk while it is attached to a running VM. However, your image is more reliable if you put the VM into a state for the image to capture.

Stop writing data to the persistent disk

Stop the VM so that it can shut down and stop writing any data to the persistent disk.

Create the image

Follow these steps to create disk images from a persistent disk, even while that disk is attached to a VM:

Console

  1. Select a project.

  2. In the navigation menu, click Virtual Machines > Images.

  3. Click Create Image.

  4. Enter a unique name for the image. The name must be no longer than 35 characters.

  5. Enter a version to add to the image name.

  6. In the Source Disk field, select a disk.

  7. In the Minimum Disk Size field, enter a disk size.

  8. Enter a description of the image.

  9. Click Create.

The image appears in the list of images.

API

  1. List all VirtualMachineDisk objects:

    kubectl --kubeconfig MANAGEMENT_API_SERVER \
      -n PROJECT \
      get virtualmachinedisks.virtualmachine.gdc.goog
    
  2. Select a VirtualMachineDisk object to use as source disk for the new image.

  3. Check whether the VM disk is attached to a VM:

      kubectl --kubeconfig MANAGEMENT_API_SERVER \
        -n PROJECT \
        get virtualmachinedisks.virtualmachine.gdc.goog DISK_NAME \
        -o jsonpath='{.status.virtualMachineAttachments}'
    

    The following example output shows a disk attached to a VM:

      [{"autoDelete":true,"nameRef":{"name":"vm1"},"uid":"...."}]
    
  4. Check the VM's running status. If the status is not Stopped, Stop the VM and proceed with creating the VirtualMachineImage.

  5. Get the size of VirtualMachineDisk to create the image:

    kubectl --kubeconfig MANAGEMENT_API_SERVER \
      -n PROJECT \
      get virtualmachinedisks.virtualmachine.gdc.goog DISK_NAME \
      -o jsonpath='{.spec.size}'
    
  6. Create a VirtualMachineImageImport object and deploy it to the Management API server:

    kubectl --kubeconfig MANAGEMENT_API_SERVER \
      -n PROJECT \
      apply -f - <<EOF
    apiVersion: virtualmachine.gdc.goog/v1
    kind: VirtualMachineImageImport
    metadata:
      name: VM_IMAGE_IMPORT_NAME
    spec:
      source:
        diskRef:
          name: DISK_NAME
      imageMetadata:
        name: IMAGE_NAME
        operatingSystem: OS_NAME
        minimumDiskSize: MINIMUM_DISK_SIZE
    EOF
    
  7. Verify that the image import has finished and the status is Ready:

    kubectl --kubeconfig MANAGEMENT_API_SERVER \
      -n PROJECT \
      get virtualmachineimageimports.virtualmachine.gdc.goog VM_IMAGE_IMPORT_NAME \
      -o jsonpath='{.status}'
    

    When the import is complete, the status output looks similar to the following:

    {
      "conditions": [
        {
          "lastTransitionTime": "",
          "message": "",
          "observedGeneration": 1,
          "reason": "ImportJobComplete",
          "status": "True",
          "type": "Ready"
        }
      ],
      "imageName": IMAGE_NAME
    }
    
  8. Verify the image has been created:

    kubectl --kubeconfig MANAGEMENT_API_SERVER \
      -n PROJECT \
      get virtualmachineimages.virtualmachine.gdc.goog  \
      CREATED_IMAGE_NAME
    

    Replace the variables using the following definitions.

    VariableDefinition
    MANAGEMENT_API_SERVER The Management API server kubeconfig file.
    PROJECT The GDC project in which to create the image.
    DISK_NAME The name of the source disk, such as vm1-boot-disk.
    VM_IMAGE_IMPORT_NAME The name of the VM image import. The name must be no longer than 35 characters.
    IMAGE_NAME The name of the created image, such as custom-image.
    OS_NAME The name of the image OS, must be one of these four:
    ubuntu-2004, windows-2019, windows-2022, or rhel-8.
    MINIMUM_DISK_SIZE The minimum disk size in the VM image import, such as 20G:
    minimumDiskSize must always be greater than or equal to the source boot disk size.
    CREATED_IMAGE_NAME The name of the created image. The created image name must be unique; you cannot use an image name that already exists in the project.

Terraform

  1. List all VirtualMachineDisk objects:

    kubectl --kubeconfig MANAGEMENT_API_SERVER \
      -n PROJECT \
      get virtualmachinedisks.virtualmachine.gdc.goog
    
  2. Select a VirtualMachineDisk object to use as source disk for the new image.

  3. Check whether the VM disk is attached to a VM:

      kubectl --kubeconfig MANAGEMENT_API_SERVER \
        -n PROJECT \
        get virtualmachinedisks.virtualmachine.gdc.goog DISK_NAME \
        -o jsonpath='{.status.virtualMachineAttachments}'
    

    Example output showing a disk is attached to a VM:

      [{"autoDelete":true,"nameRef":{"name":"vm1"},"uid":"...."}]
    
  4. Check the VM's running status. If the status is not Stopped, Stop the VM and proceed with creating the VirtualMachineImage.

  5. Get the size of VirtualMachineDisk to create the image:

    kubectl --kubeconfig MANAGEMENT_API_SERVER \
      -n PROJECT \
      get virtualmachinedisks.virtualmachine.gdc.goog DISK_NAME \
      -o jsonpath='{.spec.size}'
    
  6. Create a file named main.tf with the following content:

    provider "kubernetes" {
      config_path = "MANAGEMENT_API_SERVER"
    }
    
    resource "kubernetes_manifest" "image_import" {
      manifest = {
        "apiVersion" = "virtualmachine.gdc.goog/v1"
        "kind" = "VirtualMachineImageImport"
        "metadata" = {
          "name" = "VM_IMAGE_IMPORT_NAME"
          "namespace" = "PROJECT"
        }
        "spec" = {
          "source" = {
            "diskRef" = {
              "name" = "DISK_NAME"
            }
          }
          "imageMetadata" = {
            "name" = "IMAGE_NAME"
            "operatingSystem" = "OS_NAME"
            "minimumDiskSize" = "MINIMUM_DISK_SIZE"
          }
        }
      }
    }
    
  7. Apply the VirtualMachineImageImport object using Terraform:

    terraform apply
    
  8. Verify that the image import has finished and the status is Ready:

    kubectl --kubeconfig MANAGEMENT_API_SERVER \
      -n PROJECT \
      get virtualmachineimageimports.virtualmachine.gdc.goog VM_IMAGE_IMPORT_NAME \
      -o jsonpath='{.status}'
    

    The status should look like this when the import is complete:

    {
      "conditions": [
        {
          "lastTransitionTime": "",
          "message": "",
          "observedGeneration": 1,
          "reason": "ImportJobComplete",
          "status": "True",
          "type": "Ready"
        }
      ],
      "imageName": IMAGE_NAME
    }
    
  9. Verify the image has been created:

    kubectl --kubeconfig MANAGEMENT_API_SERVER \
      -n PROJECT \
      get virtualmachineimages.virtualmachine.gdc.goog  \
      CREATED_IMAGE_NAME
    

    Replace the variables, using the following definitions.

    VariableDefinition
    MANAGEMENT_API_SERVER The Management API server kubeconfig file.
    PROJECT The GDC project in which to create the image.
    DISK_NAME The name of the source disk, such as vm1-boot-disk.
    VM_IMAGE_IMPORT_NAME The name of the VM image import. The name must be no longer than 35 characters.
    IMAGE_NAME The name of the created image, such as custom-image.
    OS_NAME The name of the image OS, must be one of these four:
    ubuntu-2004, windows-2019, windows-2022, or rhel-8.
    MINIMUM_DISK_SIZE The minimum disk size in the VM image import, such as 20G:
    minimumDiskSize must always be greater than or equal to the source boot disk size.
    CREATED_IMAGE_NAME The name of the created image. The created image name must be unique; you cannot use an image name that already exists in the project.

What's next