3.2. Cloud-init
In this section we will use cloud-init to initialize a Fedora Cloud1 VM. Cloud-init is the de-facto standard for providing startup scripts to VMs.
Cloud-init is widely adopted. Some of the known users of cloud-init are:
- Ubuntu
- Arch Linux
- CentOS
- Red Hat
- FreeBSD
- Fedora
- Gentoo Linux
- openSUSE
Supported data sources
OpenShift Virtualization supports the cloudInitNoCloud and cloudInitConfigDrive data source methods.
Note
As it is the simplest data source you should stick tocloudInitNoCloud as the go-to data source. Only if cloudInitNoCloud is not supported
by the cloud-init implementation you should switch to cloudInitConfigDrive. For example the implementation of coreos-cloudinit was known to
require the cloudInitConfigDrive data source. However, as CoreOS has built Ignition this implementation is superseded but there
may be more implementations.cloudInitNoCloud data source
cloudInitNoCloud is a flexible data source to configure an instance locally. It can work without network access but can also
fetch configuration from a remote server. The relevant configuration of a cloudInitNoCloud data source in a VM looks like this:
volumes:
- name: cloudinitdisk
cloudInitNoCloud:
userData: "#cloud-config"
[...]
This volume must be referenced after the VM disk in the spec.template.spec.domain.devices.disks section:
- name: cloudinitdisk
disk:
bus: virtio
Using the cloudInitNoCloud attribute gives us the following possibilities to provide our configuration:
userData: inlinecloudInitNoCloudconfiguration in the user data formatuserDataBase64:cloudInitNoCloudconfiguration in the user data format as a base64-encoded stringsecretRef: reference to a K8s secret containingcloudInitNoClouduserdatanetworkData: inlinecloudInitNoCloudnetwork datanetworkDataBase64:cloudInitNoCloudnetwork data as a base64-encoded stringnetworkDataSecretRef: reference to a K8s secret containingcloudInitNoCloudnetwork data
The most convenient for the lab is to use the cloudInitNoCloud user data method.
The user data format recognizes the following headers. Depending on the header, the content is interpreted and executed
differently. For example, if you use the #!/bin/sh header the content is treated as an executable shell script.
| User data format | Content header | Expected content-type |
|---|---|---|
| Cloud config data | #cloud-config | text/cloud-config |
| User data script | #! | text/x-shellscript |
| Cloud boothook | #cloud-boothook | text/cloud-boothook |
| MIME multi-part | Content-Type: multipart/mixed | multipart/mixed |
| Cloud config archive | #cloud-config-archive | text/cloud-config-archive |
| Jinja template | ## template: jinja | text/jinja |
| Include file | #include | text/x-include-url |
| Part handler | #part-handler | text/part-handler |
If you want to combine multiple items, you can do that using #cloud-config-archive.
Here is an example how to configure multiple items:
volumes:
- name: cloudinitdisk
cloudInitNoCloud:
userData: |
#cloud-config-archive
- type: "text/cloud-config"
content: |
timezone: Europe/Zurich
- type: "text/x-shellscript"
content: |
#!/bin/sh
yum install -y nginx
Check cloud-init’s network configuration sources
for more information about the network data format.
Be aware that there is a different format used whenever you use cloudInitNoCloud or cloudInitConfigDrive.
Important
Make sure you usesecretRef or networkDataSecretRef whenever you provide sensitive data like credentials, certificates and so on.cloudInitConfigDrive data source
The cloudInitConfigDrive data source works identically to the cloudInitNoCloud data source by defining:
volumes:
- name: cloudinitdisk
cloudInitConfigDrive:
userData: "#cloud-config"
[...]
The volume must be referenced after the VM disk in the spec.template.spec.domain.devices.disks section:
- name: cloudinitdisk
disk:
bus: virtio
When using cloudInitConfigDrive, the network data has to be in the OpenStack Metadata Service Network
format.
Task 3.2.1: Create a cloud-init config secret
We are now going to create a Fedora Cloud VM and provide a cloud-init userdata configuration to initialize our VM.
First, we are going to create a secret containing our configuration: On the top left of OpenShift’s web console, make sure you change into OpenShift’s Administrator view, then click on Workloads and Secrets in the menu on the left. Make sure you’re in your own namespace, then click on Create on the right side of the page and choose Key/value secret.

Fill in the following information:
Note
Simply paste the values into the provided text boxes.
Secret name: lab04-cloudinit
Key: userdata
Value:
#cloud-config
password: kubevirt
chpasswd: { expire: False }
Click on Create to finally create the secret.
Switch back into the Virtualization view.
Task 3.2.2: Create a VirtualMachine using cloud-init
Back in the Virtualization view, create a new VM:
- Click on VirtualMachines in the left menu, then on the Create button and choose With YAML
- Replace the already filled-in content with the following:
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
name: lab04-cloudinit
spec:
runStrategy: Halted
template:
metadata:
labels:
kubevirt.io/domain: lab04-cloudinit
spec:
domain:
devices:
disks:
- name: containerdisk
disk:
bus: virtio
interfaces:
- name: default
masquerade: {}
resources:
requests:
memory: 2Gi
networks:
- name: default
pod: {}
volumes:
- name: containerdisk
containerDisk:
image: quay.io/containerdisks/fedora:43
Extend the VM configuration to include our secret lab04-cloudinit we created earlier.
Solution
Your VirtualMachine configuration should look like this:
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
name: lab04-cloudinit
spec:
runStrategy: Halted
template:
metadata:
labels:
kubevirt.io/domain: lab04-cloudinit
spec:
domain:
devices:
disks:
- name: containerdisk
disk:
bus: virtio
- name: cloudinitdisk
disk:
bus: virtio
interfaces:
- name: default
masquerade: {}
resources:
requests:
memory: 2Gi
networks:
- name: default
pod: {}
volumes:
- name: containerdisk
containerDisk:
image: quay.io/containerdisks/fedora:43
- name: cloudinitdisk
cloudInitNoCloud:
secretRef:
name: lab04-cloudinit
Finally, click Create to create the VM.
Task 3.2.3: Log in to the VirtualMachine
Start the VM and verify whether logging in with the defined user and password works as expected.
Solution
Connect to the console and log in as soon as the prompt shows up. Either use OpenShift’s web console to do so or execute:
virtctl console lab04-cloudinit --namespace lab-<username>
You might also see the cloud-init execution messages in the console log during startup:
[...]
[ OK ] Started systemd-logind.service - User Login Management.
[ 147.604999] cloud-init[796]: Cloud-init v. 23.4.4 running 'init-local' at Fri, 06 Sep 2024 11:42:25 +0000. Up 147.17 seconds.
Starting systemd-hostnamed.service - Hostname Service...
[...]
[ 210.442576] cloud-init[973]: Cloud-init v. 23.4.4 finished at Fri, 06 Sep 2024 11:43:29 +0000. Datasource DataSourceNoCloud [seed=/dev/vdb][dsmode=net]. Up 210.34 seconds
[...]
Note
Hit the Enter key if the login prompt doesn’t show up automatically.
Log in using user fedora and the password you defined in the secret you created earlier in this chapter, usually kubevirt.
Task 3.2.4: Enhance your startup script
In the previous section we have created a VM using a cloud-init script. Enhance the startup script with the following functionality:
- Set the timezone to
Europe/Zurich - Install the nginx package
- Write a custom nginx.conf to
/etc/nginx/nginx.conf - Start the nginx service
For the custom nginx configuration, you can use the following content:
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 4096;
include /etc/nginx/mime.types;
default_type text/plain;
server {
listen 8080;
server_name _;
root /usr/share/nginx/html;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location /health {
return 200 'ok';
}
location / {
set $response 'Hello from ${hostname}\n';
set $response '${response}GMT time: $date_gmt\n';
set $response '${response}Local time: $date_local\n';
return 200 '${response}';
}
}
}
Solution
Your cloud-init configuration will look like this:
#cloud-config
password: kubevirt
chpasswd: { expire: False }
packages:
- nginx
timezone: Europe/Zurich
write_files:
- content: |
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 4096;
include /etc/nginx/mime.types;
default_type text/plain;
server {
listen 8080;
server_name _;
root /usr/share/nginx/html;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location /health {
return 200 'ok';
}
location / {
set $response 'Hello from ${hostname}\n';
set $response '${response}GMT time: $date_gmt\n';
set $response '${response}Local time: $date_local\n';
return 200 '${response}';
}
}
}
path: /etc/nginx/nginx.conf
runcmd:
- systemctl enable nginx
- systemctl start nginx
You need to edit your secret using above content:
- Switch into the Administrator view and choose Workloads then Secrets from the menu on the left
- Make sure you’re in the correct project and click on secret
lab04-cloudinit - Edit it by clicking on Actions on the top right and choose Edit Secret
- Replace the existing Value’s content with the one above
- Click Save
Switch back into the Virtualization view, choose VirtualMachines in the menu on the left and restart your VM.
Note
It may take some minutes until your server is fully provisioned.
While booting you may watch out for the message Reached target cloud-init.target in your VM’s console using OpenShift’s web console or by executing:
virtctl console lab04-cloudinit --namespace lab-<username>
Task 3.2.5: Test your webserver on your virtual machine
We have spawned a virtual machine that uses cloud-init and installs a simple nginx webserver. Let us test the webserver:
In the menu on the left, open Networking and choose Services. Click on the Create Service button on the top right.
Replace the already filled-in content with the following:
apiVersion: v1
kind: Service
metadata:
name: lab04-cloudinit
spec:
ports:
- name: http
port: 8080
protocol: TCP
targetPort: 8080
selector:
kubevirt.io/domain: lab04-cloudinit
type: ClusterIP
Click Create.
Test your working webserver from your web terminal :
curl -s lab04-cloudinit.lab-<username>.svc.cluster.local:8080
You should see output similar to this:
Hello from lab04-cloudinit
GMT time: Wednesday, 29-Oct-2025 10:53:14 GMT
Local time: Wednesday, 29-Oct-2025 11:53:14 CET
Task 3.2.6: (Optional) Expose the webserver
The nginx webserver is now only accessible within our Kubernetes cluster. In this optional lab we are going to expose it to the internet.
For that, we need to create an Ingress resource:
- In the menu on the left, open Networking and choose Ingresses
- Click on the button Create Ingress
- Replace the already filled-in content with the following:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
route.openshift.io/termination: edge
name: lab04-cloudinit
spec:
ingressClassName: openshift-default
rules:
- host: lab04-cloudinit-lab-<username>.<appdomain>
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: lab04-cloudinit
port:
name: http
tls:
- {}
Create the Ingress by clicking the Create button.
After that open a new browser tab and enter the URL:
https://lab04-cloudinit-lab-<username>.<appdomain>
Congratulations, you’ve successfully exposed an nginx webserver to the internet that is running in a Fedora VM on Kubernetes!
End of lab
Cleanup resources
You have reached the end of this lab. Please stop your running virtual machines to save resources on the cluster.
Stop your running VM with:
virtctl stop lab04-cloudinit --namespace lab-<username>
References
You can find additional information about cloud-init here: