Capítulo 6: Storage y Block Devices
Capítulo 6: Storage y Block Devices
Firecracker expone almacenamiento al guest mediante VirtIO-Block devices, respaldados por archivos en el sistema de archivos del host (generalmente formato ext4).
Tipos de almacenamiento
graph TD
A[microVM] --> B[Drive raíz<br/>is_root_device: true]
A --> C[Drive adicional<br/>is_root_device: false]
A --> D[initrd<br/>en boot-source]
B --> E[rootfs.ext4]
C --> F[data.ext4]
D --> G[initrd.cpio]
Configurar el drive raíz
Ya lo vimos en el capítulo 3. Aquí con todas las opciones disponibles:
API_SOCKET="/tmp/firecracker.socket"
curl -s -X PUT \
--unix-socket "${API_SOCKET}" \
--header "Content-Type: application/json" \
--data '{
"drive_id": "rootfs",
"path_on_host": "./rootfs.ext4",
"is_root_device": true,
"is_read_only": false,
"rate_limiter": {
"bandwidth": {
"size": 104857600,
"refill_time": 1000
},
"ops": {
"size": 1000,
"refill_time": 1000
}
}
}' \
"http://localhost/drives/rootfs"
Los campos del rate limiter usan token buckets:
size: cantidad de tokens (bytes para bandwidth, operaciones para ops)refill_time: milisegundos para reponersizetokens
En el ejemplo: máximo 100 MiB/s y 1000 IOPS.
Agregar un segundo drive (datos)
# Crear una imagen ext4 vacía de 2 GB
dd if=/dev/zero of=data.ext4 bs=1M count=2048
mkfs.ext4 -F data.ext4
# Configurarlo en la API
curl -s -X PUT \
--unix-socket "${API_SOCKET}" \
--header "Content-Type: application/json" \
--data '{
"drive_id": "data",
"path_on_host": "./data.ext4",
"is_root_device": false,
"is_read_only": false
}' \
"http://localhost/drives/data"
Dentro del guest, el segundo drive aparecerá como /dev/vdb:
# Dentro del guest
mount /dev/vdb /mnt/data
df -h /mnt/data
Los drives se nombran
/dev/vda,/dev/vdb, etc. en el guest según el orden de configuración.
Drive de solo lectura
Útil para compartir datos de solo lectura entre múltiples VMs (como imágenes de contenedores):
curl -s -X PUT \
--unix-socket "${API_SOCKET}" \
--header "Content-Type: application/json" \
--data '{
"drive_id": "shared-data",
"path_on_host": "./shared.ext4",
"is_root_device": false,
"is_read_only": true
}' \
"http://localhost/drives/shared-data"
Actualizar drive en caliente
Puedes cambiar el archivo de un drive mientras la VM corre (útil para intercambiar discos):
# Solo se puede actualizar path_on_host y rate_limiter en PATCH
curl -s -X PATCH \
--unix-socket "${API_SOCKET}" \
--header "Content-Type: application/json" \
--data '{
"drive_id": "data",
"path_on_host": "./data-v2.ext4"
}' \
"http://localhost/drives/data"
Crear un rootfs personalizado
Para producción deberás construir tu propio rootfs. El método más simple usa Docker:
# Extraer un sistema de archivos de una imagen Docker
docker pull alpine:3.19
CONTAINER_ID=$(docker create alpine:3.19)
IMAGE_FILE="alpine-rootfs.ext4"
IMAGE_SIZE_MB=300
# Crear imagen ext4
dd if=/dev/zero of="${IMAGE_FILE}" bs=1M count="${IMAGE_SIZE_MB}"
mkfs.ext4 -F "${IMAGE_FILE}"
# Montar y poblar
mkdir -p /tmp/rootfs-mount
sudo mount "${IMAGE_FILE}" /tmp/rootfs-mount
sudo docker export "${CONTAINER_ID}" | sudo tar -xC /tmp/rootfs-mount
sudo umount /tmp/rootfs-mount
docker rm "${CONTAINER_ID}"
Usar initrd en lugar de rootfs
Para microVMs stateless o de arranque ultrarrápido, puedes usar un initrd (archivo cpio) en lugar de una imagen de disco completa:
# Configurar boot con initrd
curl -s -X PUT \
--unix-socket "${API_SOCKET}" \
--header "Content-Type: application/json" \
--data '{
"kernel_image_path": "./vmlinux",
"boot_args": "console=ttyS0 reboot=k panic=1 pci=off",
"initrd_path": "./initrd.cpio"
}' \
"http://localhost/boot-source"
Con initrd no configures is_root_device: true en ningún drive. El initrd es el rootfs.
Ventajas del initrd:
- Arranque más rápido (no hay acceso a disco en el boot)
- Perfecto para funciones efímeras
- Fácil de crear y distribuir
Desventajas:
- Todo el sistema de archivos en RAM (límite de
mem_size_mib) - Los cambios no persisten entre reinicios
Crear un initrd mínimo
# Directorio temporal para el initrd
INITRD_DIR=$(mktemp -d)
cd "${INITRD_DIR}"
# Estructura mínima
mkdir -p bin sbin etc proc sys dev
# Copiar busybox estático como /init
cp /usr/bin/busybox bin/busybox
chmod +x bin/busybox
# Crear /init
cat > init << 'EOF'
#!/bin/busybox sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
exec /bin/busybox sh
EOF
chmod +x init
# Empaquetar como cpio
find . | cpio -H newc -o > ~/firecracker-lab/initrd.cpio
cd ~
rm -rf "${INITRD_DIR}"