Skip to content

Commit 3c81e14

Browse files
authored
Merge pull request #8712 from ovh/feat-opcp-image-creation-with-dib
feat(opcp): add OS image creation with diskimage-builder
2 parents bd8b62f + 5190eec commit 3c81e14

File tree

4 files changed

+634
-0
lines changed

4 files changed

+634
-0
lines changed
Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
---
2+
title: "Building a custom OpenStack Image on OPCP"
3+
excerpt: "Find out how to create your own operating system image on On-Prem Cloud Platform"
4+
updated: 2025-11-21
5+
---
6+
7+
## Objective
8+
9+
This guide focuses on building customized disk images for both OpenStack Ironic (bare metal) and Nova (compute) using [diskimage-builder](https://github.com/openstack/diskimage-builder).
10+
11+
It's a good starting point to understand how [diskimage-builder](https://github.com/openstack/diskimage-builder) (DiB) works with a practical example. We will build a custom Debian 13 image from the upstream image and customize it with Ansible.
12+
13+
At the end, we will generate a `debian13.qcow2` whole disk image (with the kernel and initramfs), ready to be imported into OpenStack Glance.
14+
15+
## OpenStack Ironic/Nova expectations
16+
17+
### Image Format
18+
19+
- **qcow2** (recommended): Compressed and efficient for Glance storage
20+
- **raw**: Uncompressed disk image
21+
22+
The image should be a **whole disk image** that includes:
23+
24+
- Boot sector/EFI system partition
25+
- Operating system partition(s)
26+
- Kernel and initramfs embedded in the disk
27+
28+
### Partitioning and Boot Requirements
29+
30+
#### For BIOS Boot:
31+
32+
- GPT or MBR partition table
33+
- BIOS boot partition (1-2MB, type `ef02` for GPT)
34+
- Root partition with bootloader installed (GRUB2)
35+
- Bootloader must be installed to MBR/boot sector
36+
37+
#### For EFI Boot:
38+
39+
- GPT partition table required
40+
- EFI System Partition (ESP): 512MB, FAT32, mounted at `/boot/efi`
41+
- Root partition with GRUB2 EFI bootloader
42+
- EFI boot entries properly configured
43+
44+
#### For Ironic Bare Metal (Recommended Configuration):
45+
46+
**Standard Partition Layout (Simple):**
47+
48+
```yaml
49+
# GPT partition table
50+
- EFI System Partition (ESP): 512MiB, type EF00, FAT32, mounted at /boot/efi
51+
- BIOS Boot Partition (BSP): 8MiB, type EF02 (for hybrid BIOS/EFI compatibility)
52+
- Root partition: remaining space, type 8300, ext4, mounted at /
53+
```
54+
55+
## Requirements
56+
57+
**System Requirements:**
58+
59+
- Root permissions
60+
- At least 10GB available
61+
62+
- Some packages:
63+
64+
```bash
65+
# Debian/Ubuntu
66+
apt update -y && apt install -y \
67+
dosfstools \
68+
python3 \
69+
python3-venv \
70+
python3-pip \
71+
virtualenv \
72+
kpartx \
73+
debootstrap \
74+
lvm2 \
75+
squashfs-tools \
76+
qemu-utils
77+
```
78+
79+
Install diskimage-builder (DiB) in a virtualenv:
80+
81+
```bash
82+
mkdir -p diskimage-builder
83+
python3 -m venv venv
84+
. ./venv/bin/activate
85+
pip install diskimage-builder
86+
```
87+
88+
### Understanding diskimage-builder Elements
89+
90+
Elements are modular components that customize your image.
91+
92+
| Directory | Purpose |
93+
|-----------|---------|
94+
| `environment.d/` | Environment variables |
95+
| `extra-data.d/` | Files added before installation |
96+
| `pre-install.d/` | Pre-installation scripts |
97+
| `post-install.d/` | Post-installation scripts |
98+
| `finalise.d/` | Final customization |
99+
| `cleanup.d/` | Cleanup tasks |
100+
| `package-installs.yaml` | Package declarations |
101+
| `block-device-default.yaml` | Disk partitioning |
102+
103+
Scripts in these directories execute in numerical/alphabetical order.
104+
105+
### Creating a Customization Element with Ansible
106+
107+
Create an element that uses Ansible for customization:
108+
109+
**Directory Structure:**
110+
111+
```bash
112+
mkdir -p elements/os-custom/extra-data.d/ansible/{roles/customize/tasks,inventory/host_vars/sys_image}
113+
mkdir -p elements/os-custom/extra-data.d/ansible/roles/customize/files
114+
mkdir -p elements/os-custom/post-install.d
115+
```
116+
117+
**Post-install Scripts:**
118+
119+
`elements/os-custom/post-install.d/00-install-ansible`:
120+
121+
```bash
122+
#!/bin/bash
123+
set -eux
124+
apt install --yes ansible
125+
```
126+
127+
`elements/os-custom/post-install.d/01-apply-ansible`:
128+
129+
```bash
130+
#!/bin/bash
131+
set -eux
132+
export LANG=C.UTF-8
133+
export LC_ALL=C.UTF-8
134+
cd /tmp/in_target.d/extra-data.d/ansible
135+
ANSIBLE_STDOUT_CALLBACK=debug ansible-playbook -i inventory main.yml
136+
```
137+
138+
`elements/os-custom/post-install.d/02-remove-ansible`:
139+
140+
```bash
141+
#!/bin/bash
142+
set -eux
143+
apt remove --yes ansible
144+
apt autoremove --yes
145+
```
146+
147+
**Make scripts executable:**
148+
149+
```bash
150+
chmod +x elements/os-custom/post-install.d/*
151+
```
152+
153+
**Ansible Configuration:**
154+
155+
`elements/os-custom/extra-data.d/ansible/main.yml`:
156+
157+
```yaml
158+
---
159+
- hosts: all
160+
gather_facts: true
161+
roles:
162+
- name: customize
163+
```
164+
165+
`elements/os-custom/extra-data.d/ansible/inventory/hosts`:
166+
167+
```ini
168+
[all]
169+
sys_image sys_image ansible_connection=local ansible_become=no
170+
```
171+
172+
In this example, we are using `cloud-init` with the `netplan` renderer to configure `systemd-networkd`. This is a working example of customizing network configuration; feel free to adapt it to your needs.
173+
174+
**Note:** When Ironic configures bare metal on first boot, it will propagate a `network_metadata` manifest (configdrive) that can be interpreted by `cloud-init` to automatically configure the network (such as static IP, LACP, etc.).
175+
176+
`elements/os-custom/extra-data.d/ansible/roles/customize/tasks/main.yml`:
177+
178+
```yaml
179+
---
180+
- name: Install additional packages
181+
apt:
182+
name:
183+
- cloud-init
184+
state: latest
185+
update_cache: yes
186+
187+
- name: Remove unwanted packages
188+
apt:
189+
name:
190+
- ifupdown
191+
- ifenslave
192+
- vlan
193+
state: absent
194+
purge: yes
195+
196+
# Allow Baremetal LACP auto-conf from Neutron
197+
- name: Configure cloud-init for netplan
198+
copy:
199+
src: 50-netplan.cfg
200+
dest: /etc/cloud/cloud.cfg.d/50-netplan.cfg
201+
owner: root
202+
group: root
203+
mode: 0644
204+
```
205+
206+
`elements/os-custom/extra-data.d/ansible/roles/customize/files/50-netplan.cfg`:
207+
```yaml
208+
system_info:
209+
network:
210+
renderers: ['netplan']
211+
```
212+
213+
**Additional Packages:**
214+
215+
`elements/os-custom/package-installs.yaml`:
216+
```yaml
217+
man:
218+
nano:
219+
tcpdump:
220+
iputils-ping:
221+
```
222+
223+
**Element Dependencies:**
224+
225+
`elements/os-custom/element-deps`:
226+
227+
```
228+
debian
229+
```
230+
231+
## Building the Image
232+
233+
Create an environment file `debian13.env`:
234+
235+
```bash
236+
export DIB_RELEASE=trixie
237+
export DIB_CLOUD_INIT_DATASOURCES="ConfigDrive, OpenStack"
238+
export DIB_GRUB_TIMEOUT=10
239+
```
240+
241+
Build the image:
242+
243+
```bash
244+
mkdir -p tmp-build-dir
245+
export TMPDIR="$(pwd)/tmp-build-dir"
246+
export ELEMENTS_PATH="$(pwd)/elements"
247+
248+
source debian13.env
249+
disk-image-create -t qcow2 --image-size 16GB -a amd64 vm block-device-efi os-custom debian -o debian13
250+
```
251+
252+
You should get a file `debian13.qcow2`.
253+
254+
If you want environment and packages SBOM files:
255+
256+
```bash
257+
cp debian13.d/dib-manifests/dib_environment debian13.env.sbom
258+
cp debian13.d/dib-manifests/dib-manifest-dpkg-debian13 debian13.pkg.sbom
259+
```
260+
261+
### Testing the Image
262+
263+
A quick way to test the generated image is using qemu to spawn a virtual machine using the qcow2 image and a VNC client to connect to the monitor. We can test both EFI and BIOS boot.
264+
265+
#### BIOS Boot
266+
267+
```bash
268+
qemu-system-x86_64 -enable-kvm -vnc 0.0.0.0:0,password=on -monitor stdio -m 2048 -drive file=debian13.qcow2,if=virtio,format=qcow2
269+
```
270+
271+
```bash
272+
# Set up a custom VNC password
273+
QEMU 10.0.3 monitor - type 'help' for more information
274+
(qemu) change vnc password
275+
Password: ********
276+
```
277+
278+
Use any VNC client to connect :5200
279+
280+
#### EFI
281+
282+
```bash
283+
# Copy OVMF vars to avoid modifying the original
284+
cp /usr/share/OVMF/OVMF_VARS_4M.fd /tmp/debian13-OVMF_VARS_4M.fd
285+
286+
qemu-system-x86_64 -enable-kvm \
287+
-machine q35,smm=on,accel=kvm \
288+
-drive if=pflash,format=raw,unit=0,file=/usr/share/OVMF/OVMF_CODE_4M.fd,readonly=on \
289+
-drive if=pflash,format=raw,unit=1,file=/tmp/debian13-OVMF_VARS_4M.fd \
290+
-vnc 0.0.0.0:0,password=on \
291+
-monitor stdio \
292+
-m 2048 \
293+
-drive file=debian13.qcow2,if=virtio,format=qcow2
294+
```
295+
296+
```bash
297+
# Set up a custom VNC password
298+
QEMU 10.0.3 monitor - type 'help' for more information
299+
(qemu) change vnc password
300+
Password: ********
301+
```
302+
303+
Use any VNC client to connect :5200
304+
305+
### Upload Image to OpenStack:
306+
307+
```bash
308+
openstack image create \
309+
--disk-format qcow2 \
310+
--container-format bare \
311+
--file debian13.qcow2 \
312+
debian13
313+
```
314+
315+
Done! You can now create a baremetal instance or compute instance with the new created image.

0 commit comments

Comments
 (0)