Skip to main content

Deploying a Virtual Machine

This guide walks you through provisioning a managed virtual machine (or a stateful VM cluster) using Infrastream.

VMs are used for workloads that require persistent disks, stable network identities, or custom operating systems — for example, running a Kurrent DB event store, a Redpanda streaming cluster, or a GPU-based ML inference node. Infrastream abstracts the complexity of instance templates, managed instance groups, health checks, and service mesh integration.

The VM system uses a two-manifest pattern:

  1. VirtualMachineConfiguration — An organization-level blueprint that defines the vetted OS image, startup scripts, observability agents, and baseline secrets. This is typically managed by the platform team and represents a "golden image."
  2. VirtualMachine — A project-level instance that references a blueprint and adds workload-specific settings (machine type, ports, disks, scaling).

Prerequisites

  • A VirtualMachineConfiguration manifest must exist for the OS/runtime you need. Ask your platform team which configurations are available.
  • You need to know the identity of your project: organization, organizational-unit, environment, and project.

Understanding VirtualMachineConfiguration (The Blueprint)

A VirtualMachineConfiguration is an organization-level manifest that defines a vetted, reusable VM blueprint. It standardizes the operating system, startup scripts, observability agents, and baseline secrets across all VMs that reference it, ensuring fleet-wide consistency and compliance.

Note: Creating or modifying a VirtualMachineConfiguration is typically a platform team responsibility. Application developers reference existing configurations via configuration.source in their VirtualMachine manifest.

Here is an example of a VirtualMachineConfiguration:

apiVersion: lowops.manifests.v1
kind: VirtualMachineConfiguration
metadata:
name: ubuntu-22-base
organization: fincorp
spec:
operatingSystem: ubuntu-2204-lts # Boot disk source image
startup:
script: |
#!/bin/bash
apt-get update -y
apt-get install -y docker.io
systemctl enable docker
echo "VM_NAME={{.Name}}" >> /etc/environment
variables:
APP_ENV:
defaultValue: "production"
required: false
agent:
logFiles:
- /var/log/syslog
- /var/log/app/*.log
metrics:
- type: prometheus
prometheus:
scheme: http
endpoint: /metrics
port: 9090
secrets:
db-credentials:
type: FILE
target: /etc/secrets/db-password
volumes:
/data:
name: data-disk
type: pd-ssd
fileSystem: ext4
encrypted: true

Key fields:

  • operatingSystem — Determines the boot disk's source image (e.g., ubuntu-2204-lts, debian-12, rocky-linux-9).
  • startup — A bash script with templated variables that runs on every boot. Used for installing agents, configuring services, and injecting secrets.
  • agent — Configures the Google Cloud Ops Agent for log collection and Prometheus metric scraping.
  • secrets — Defines how baseline secrets are injected into the VM (as environment variables or files).
  • volumes — Defines the default attached disks, including filesystem type and encryption.

Step 1: Define Your VirtualMachine Manifest

Create a YAML file for your VM that references the blueprint:

apiVersion: lowops.manifests.v1
kind: VirtualMachine
metadata:
name: data-processor
project: analytics-platform
environment: production
organizational-unit: data-engineering
organization: fincorp
spec:
description: "Batch data processor for ETL pipeline"
machineType: e2-standard-4
meshStrategy: DISABLED # SIDECAR | PROXYLESS | DISABLED
ports:
8080: http
9090: grpc
configuration:
source: ubuntu-22-base # ← References the VirtualMachineConfiguration blueprint
variables:
BATCH_SIZE: "1000" # Passed to the startup script's template context
LOG_LEVEL: "info"
secrets:
DB_PASSWORD: db-credentials # Key = env var name, Value = Secret manifest name
volumeMounts:
/data:
source: "" # Empty for a new disk; or a snapshot self-link
diskConfig:
sizeGb: 500
type: pd-ssd
health:
startup:
protocol: HTTP
port: 8080
path: /healthz
liveness:
protocol: HTTP
port: 8080
path: /healthz
checkIntervalSec: 30
unhealthyThreshold: 3
accessControl:
additionalRoles:
- roles/bigquery.dataViewer
buckets:
- name: raw-data-lake
permission: READ_ONLY

Key fields:

  • machineType — The GCE machine type (e.g., e2-standard-4, n2-highmem-8).
  • meshStrategy — How the VM integrates with the service mesh. SIDECAR deploys a proxy, PROXYLESS uses gRPC-native discovery, DISABLED opts out.
  • configuration.sourceReferences a VirtualMachineConfiguration manifest by name. This is the vetted blueprint that defines the OS image, startup scripts, and agent configuration.
  • configuration.variables — Values passed to the startup script's template context (defined in the VMC's startup.variables).
  • volumeMounts — Mount persistent disks at specific paths. The disk configuration merges with any defaults from the blueprint.
  • health — Configures startup and liveness health checks.

Step 2: Deploying a Stateful Cluster

If your workload requires multiple VM instances with stable identities (e.g., a database cluster or a distributed processing system), enable the stateful block:

spec:
# ... other settings ...
stateful:
enabled: true
clusterSize: 3 # Number of instances
allowTcp:
- 6379 # Intra-cluster TCP ports
- 26379
health:
protocol: TCP
port: 6379

This provisions a stateful Managed Instance Group with persistent disks and stable network identities.

Step 3: Commit, Review, and Merge

Commit the manifest in a pull request.

After merge, the platform will:

  1. Resolve the VirtualMachineConfiguration blueprint to determine the OS image and startup script.
  2. Create the instance template with the specified machine type, boot image, and merged configuration.
  3. Provision persistent disks and mount them at the specified paths.
  4. Install and configure the Ops Agent for logging and metrics collection.
  5. Configure health checks and auto-healing policies.
  6. Set up IAM bindings for the VM's service account.
  7. If stateful, create the managed instance group with the specified cluster size.