4.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
KubeVirt 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 4.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 define our configuration. Create a file called cloudinit-userdata.yaml in the folder labs/lab04 with the following content:
#cloud-config
password: kubevirt
chpasswd: { expire: False }
This will set the password of the default user (fedora for Fedora Core) to kubevirt and configure the password
to never expire.
We need to create the secret from this configuration. You can use the following command to create it:
kubectl create secret generic lab04-cloudinit --from-file=userdata=labs/lab04/cloudinit-userdata.yaml --namespace lab-<username>
The output should be:
secret/lab04-cloudinit created
Inspect the secret with:
kubectl get secret lab04-cloudinit -o yaml --namespace lab-<username>
apiVersion: v1
data:
userdata: I2Nsb3VkLWNvbmZpZw[...]
type: Opaque
kind: Secret
metadata:
name: lab04-cloudinit
[...]
Task 4.2.2: Create a VirtualMachine using cloud-init
Create a file vm_lab04-cloudinit.yaml
in the folder labs/lab04
and start with the following VM configuration:
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
Make sure you create your VM with:
kubectl apply -f labs/lab04/vm_lab04-cloudinit.yaml --namespace lab-<username>
Task 4.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
Start the newly-created VM. This might take a couple of minutes:
virtctl start lab04-cloudinit --namespace lab-<username>
Connect to the console and log in as soon as the prompt shows up:
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 4.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 (cloudinit-userdata.yaml in the folder labs/lab04) 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 recreate your secret:
kubectl delete secret lab04-cloudinit --namespace lab-<username>
kubectl create secret generic lab04-cloudinit --from-file=userdata=labs/lab04/cloudinit-userdata.yaml --namespace lab-<username>
Next, we need to restart our VM to pick up the changes in the cloud-init configuration:
virtctl restart lab04-cloudinit --namespace lab-<username>
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 by executing:
virtctl console lab04-cloudinit --namespace lab-<username>
Task 4.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:
Create the following Kubernetes Service (file: service-cloudinit.yaml folder: labs/lab04):
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
And create it with:
kubectl apply -f labs/lab04/service-cloudinit.yaml --namespace lab-<username>
Test your working webserver from your webshell:
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 4.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:
Create a file called ingress-cloudinit.yaml in the folder labs/lab04 with the following content:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: lab04-cloudinit
spec:
rules:
- host: lab04-cloudinit-lab-<username>.<appdomain>
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: lab04-cloudinit
port:
name: http
tls:
- hosts:
- lab04-cloudinit-lab-<username>.<appdomain>
Create the Ingress by executing:
kubectl apply -f labs/lab04/ingress-cloudinit.yaml --namespace lab-<username>
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: