Use custom cloud-init userdata

This guide shows you how to use custom cloud-init userdata to configure virtual machines automatically during their first boot.

For background on cloud-init and how it works, see Cloud-init.

Prerequisites

  • Access to an evroc organization and resource group
  • evroc CLI installed and configured, or kubectl configured to access the evroc Kubernetes API
  • A cloud-init userdata file prepared in a supported format (cloud-config YAML, shell script, or Jinja2 template)

Create a basic cloud-config file

Create a cloud-config YAML file to define your VM configuration. Cloud-config files must start with #cloud-config.

Install packages and create a user

Create a file named cloud-config.yaml:

#cloud-config
packages:
  - nginx
  - git
users:
  - name: evroc-user
    gecos: evroc VM user
    lock_passwd: true
    sudo: ALL=(ALL) NOPASSWD:ALL
    groups:
      - sudo
    shell: /bin/bash
    ssh_authorized_keys:
      - ssh-rsa AAAA...
runcmd:
  - systemctl start nginx

This configuration:

  • Installs nginx and git packages
  • Creates a user named evroc-user with sudo permissions
  • Disables password login for the user
  • Adds an SSH public key for authentication
  • Starts the nginx service after installation

Create a VM with cloud-init userdata

Using the CLI

Load the cloud-config from a file using command substitution:

evroc compute vm create myvm \
  --running=true \
  --vm-virtual-resources-ref=a1a.s \
  --disk=mybootdisk \
  --boot-from=true \
  --cloud-init-user-data="$(cat cloud-config.yaml)"

Alternatively, use base64 encoding to avoid formatting issues:

evroc compute vm create myvm \
  --running=true \
  --vm-virtual-resources-ref=a1a.s \
  --disk=mybootdisk \
  --boot-from=true \
  --cloud-init-user-data="$(base64 cloud-config.yaml)"

Base64 encoding is recommended for complex configurations to ensure special characters and line breaks are preserved correctly.

Using the API

Include the cloud-init userdata in the VM specification:

apiVersion: compute.evroclabs.net/v1alpha1
kind: VirtualMachine
metadata:
  name: myvm
spec:
  running: true
  vmVirtualResourcesRef:
    vmVirtualResourcesRefName: a1a.s
  diskRefs:
  - bootFrom: true
    name: mybootdisk
  osSettings:
    cloudInitUserData: |
      #cloud-config
      packages:
        - nginx
        - git
      users:
        - name: evroc-user
          sudo: ALL=(ALL) NOPASSWD:ALL
          ssh_authorized_keys:
            - ssh-rsa AAAA...

Apply the configuration:

kubectl apply -f vm.yaml

Include SSH keys using Jinja2 templates

When you provide custom cloud-init userdata, SSH keys specified via --ssh-authorized-key or the API are not automatically included. Use a Jinja2 template to explicitly insert them.

Create a Jinja2-templated cloud-config file:

## template: jinja
#cloud-config
ssh_pwauth: false
users:
  - name: evroc-user
    gecos: evroc VM user
    lock_passwd: true
    sudo: ALL=(ALL) NOPASSWD:ALL
    groups:
      - sudo
    shell: /bin/bash
{% if public_ssh_keys %}
    ssh_authorized_keys:
{% for pubkey in public_ssh_keys %}
      - {{ pubkey }}
{% endfor %}
{% endif %}

The template inserts SSH keys from --ssh-authorized-key (CLI) or OSSettings.SSH.AuthorizedKeys (API) into the public_ssh_keys variable.

Using the CLI with templated SSH keys

evroc compute vm create myvm \
  --running=true \
  --vm-virtual-resources-ref=a1a.s \
  --disk=mybootdisk \
  --boot-from=true \
  --ssh-authorized-key="ssh-ed25519 AAAAC3Nz..." \
  --cloud-init-user-data="$(cat cloud-config-template.yaml)"

The SSH key specified with --ssh-authorized-key is available to the Jinja2 template as public_ssh_keys.

Use a shell script instead of cloud-config

For simple tasks, you can use a shell script as cloud-init userdata. The script must start with a shebang line.

Create a file named init-script.sh:

#!/bin/bash
apt-get update
apt-get install -y nginx
systemctl start nginx
echo "Setup complete" > /var/log/custom-init.log

Use the script when creating a VM:

evroc compute vm create myvm \
  --running=true \
  --vm-virtual-resources-ref=a1a.s \
  --disk=mybootdisk \
  --boot-from=true \
  --ssh-authorized-key="ssh-ed25519 AAAAC3Nz..." \
  --cloud-init-user-data="$(cat init-script.sh)"

Verify cloud-init completed successfully

After the VM boots, SSH into it:

ssh evroc-user@<vm-ip-address>

From the VM, check the cloud-init status:

cloud-init status

If the status shows done, cloud-init completed successfully. If it shows running, cloud-init is still executing. If it shows error, check the logs.

Troubleshoot cloud-init issues

If cloud-init fails or doesn't apply your configuration, check the log files on the VM:

ssh evroc-user@<vm-ip-address>

Then:

cat /var/log/cloud-init.log
cat /var/log/cloud-init-output.log

Common issues:

  • YAML syntax errors - Invalid indentation, tabs instead of spaces, or incorrect YAML formatting will cause cloud-init to skip the configuration
  • Missing SSH keys - If you specified SSH keys via the API but didn't template them in your userdata, they won't be applied
  • Package installation failures - Some distributions (SLES, SL Micro) require repository configuration before packages can be installed

Fix cloud-init configuration errors

You cannot update the customCloudInitUserData field after creating a VM. If you need to fix a cloud-init configuration error:

  1. Delete the VM:

    evroc compute vm delete myvm
    
  2. Delete the boot disk:

    evroc compute disk delete mybootdisk
    
  3. Create a new boot disk and VM with the corrected cloud-init userdata

Do not keep the boot disk when recreating the VM, as this may cause cloud-init to skip initialization on the next boot.

Next steps