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-userwith 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:
-
Delete the VM:
evroc compute vm delete myvm -
Delete the boot disk:
evroc compute disk delete mybootdisk -
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
- Learn about Cloud-init concepts including datasources and userdata formats
- See how to create a VM with different configurations
- Read the official cloud-init documentation for advanced use cases and modules