Nico
Nico
Creator of this small website
Jun 1, 2020 3 min read

Using Containers Directly on GCE

thumbnail for this post

Summary

GCP, and in particular its components GCE has a nice feature to run containers directly on a VM, without the need to spin up a GKE cluster or to provision a machine by yourself. It leverages the use of Container Optimized OS maintained by google.

I’m a big fan of Terraform (and most Hashicorp products) but sadly it does not support the aforementioned feature out-of-the-box. Let’s see how to make this happen easily in a simple manner

How does this work ?

A container-only instance is a regular instance, and the specifics lay down in the instance metadata. Let’s take a look of what it looks like.

First, let’s launch a instance :

gcloud compute instances create-with-container test-vm --container-image gcr.io/cloud-marketplace/google/nginx1:latest --zone=zzz --network=yyy --subnet=xxx --container-env="foo=bar,spam=baz"

Then let’s look into its metadata :

gcloud compute instances describe test-vm --format=json | jq

And in the output

  "metadata": {
    "items": [
      {
        "key": "google-logging-enabled",
        "value": "true"
      },
      {
        "key": "gce-container-declaration",
        "value": "spec:\n  containers:\n    - name: test-vm\n      image: 'gcr.io/cloud-marketplace/google/nginx1:latest'\n      env:\n        - name: foo\n          value: bar\n        - name: spam\n          value: baz\n      stdin: false\n      tty: false\n  restartPolicy: Always\n\n# This container declaration format is not public API and may change without notice. Please\n# use gcloud command-line tool or Google Cloud Console to run Containers on Google Compute Engine."
      }
    ],
    "kind": "compute#metadata"
  },

Note the warning embedded, but also that the metadata is simply YAML

The terraform version

After seeing this, all we need is to reproduce the work done by the gcloud command.

# this is maintained by GCP -> Container Optimized OS
data "google_compute_image" "cos" {
  family = "cos-stable"
  project = "cos-cloud"
}

data "google_compute_default_service_account" "default" {}

resource "google_compute_instance" "container_vm" {
  name = "container_vm"
  machine_type = "f1-micro"
  allow_stopping_for_update = true

  network_interface {
# snip
  }

  boot_disk {
    initialize_params {
      image = data.google_compute_image.cos.self_link
    }
  }

  service_account {
    email = data.google_compute_default_service_account.default.email
    scopes = [
      "https://www.googleapis.com/auth/devstorage.read_only",
      "https://www.googleapis.com/auth/logging.write",
      "https://www.googleapis.com/auth/monitoring.write",
      "https://www.googleapis.com/auth/pubsub",
      "https://www.googleapis.com/auth/service.management.readonly",
      "https://www.googleapis.com/auth/servicecontrol",
      "https://www.googleapis.com/auth/trace.append"
    ]
  }

  metadata = {
    # This container declaration format is not a public API and may change without notice
    # Use gcloud command-line tool or Google Cloud Console to create a new one and dump metadata if it breaks
    gce-container-declaration =<<EOT
spec:
  containers:
    - image: your_image_here:latest
      name: containervm
      securityContext:
        privileged: false
      env:
        - name: foo
          value: bar
        - name: meh
          value: spam
      stdin: false
      tty: false
      volumeMounts: []
      restartPolicy: Always
      volumes: []
EOT
    google-logging-enabled = "true"
  }
}

And that’s it ! You now have a hassle free dedicated image running.

If you get your metadata wrong then the instance will be created but won’t launch the container; and the gce-container-declaration field will appear in the metadata.