Capítulo 13: Casos de Uso Reales
Capítulo 13: Casos de Uso Reales
Caso 1: Serverless local (FaaS)
Implementar un sistema básico de funciones-como-servicio usando Firecracker, similar a AWS Lambda pero en tu máquina local.
Arquitectura
graph LR
A[HTTP Request] --> B[Gateway]
B --> C{Pool de VMs}
C -->|VM disponible| D[microVM con handler]
C -->|Pool vacío| E[Crear VM desde snapshot]
D -->|Resultado| B
B -->|Response| A
Preparar snapshot base para funciones Node.js
# 1. Arrancar VM base con Node.js instalado
API_SOCKET="/tmp/fc-base.sock"
firecracker --api-sock "${API_SOCKET}" &
# Configurar y arrancar VM...
# (dentro del guest: instalar Node.js, preparar handler)
# 2. Crear snapshot cuando Node.js está listo
curl -s -X PATCH \
--unix-socket "${API_SOCKET}" \
--data '{"state": "Paused"}' \
"http://localhost/vm"
curl -s -X PUT \
--unix-socket "${API_SOCKET}" \
--data '{
"snapshot_type": "Full",
"snapshot_path": "./snapshots/nodejs-ready/snapshot",
"mem_file_path": "./snapshots/nodejs-ready/memory"
}' \
"http://localhost/snapshot/create"
Handler de funciones (dentro del guest)
// /opt/handler/server.js — servidor HTTP dentro del guest
const http = require('http');
const { execSync } = require('child_process');
// Leer la función a ejecutar desde MMDS
async function getFunctionCode() {
const token = execSync(
'curl -s -X PUT -H "X-metadata-token-ttl-seconds: 60" http://169.254.169.254/latest/api/token'
).toString().trim();
const code = execSync(
`curl -s -H "X-metadata-token: ${token}" http://169.254.169.254/function/code`
).toString();
return code;
}
const server = http.createServer(async (req, res) => {
const code = await getFunctionCode();
const fn = new Function('event', code);
const result = await fn(JSON.parse(req.url.slice(1)));
res.end(JSON.stringify(result));
});
server.listen(3000);
Invocar una función
# Inyectar código de función via MMDS
curl -s -X PUT \
--unix-socket "${API_SOCKET}" \
--data '{
"function": {
"code": "return { result: event.x * event.y };"
}
}' \
"http://localhost/mmds"
# Llamar la función (desde el host, via red)
curl "http://172.16.0.2:3000/$(echo '{"x":6,"y":7}' | python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.stdin.read()))')"
Caso 2: Aislamiento para CI/CD
Cada job de CI corre en una microVM efímera — el pipeline no puede afectar al host ni a otros jobs.
#!/bin/bash
# run-ci-job.sh — ejecutar un job de CI en microVM aislada
JOB_ID="$1"
REPO_URL="$2"
COMMAND="$3"
# Crear rootfs con copy-on-write (cambios del job no afectan la base)
cp --reflink=auto /opt/ci-base.ext4 "/tmp/ci-${JOB_ID}.ext4"
# Montar e inyectar el código del repositorio
MOUNT_DIR="/tmp/ci-mount-${JOB_ID}"
mkdir -p "${MOUNT_DIR}"
sudo mount "/tmp/ci-${JOB_ID}.ext4" "${MOUNT_DIR}"
git clone --depth=1 "${REPO_URL}" "${MOUNT_DIR}/workspace"
sudo umount "${MOUNT_DIR}"
# Crear configuración de la VM
cat > "/tmp/ci-${JOB_ID}-config.json" << EOF
{
"boot-source": {
"kernel_image_path": "/opt/ci-kernel/vmlinux",
"boot_args": "console=ttyS0 reboot=k panic=1 pci=off init=/opt/ci-runner/init.sh"
},
"drives": [{
"drive_id": "rootfs",
"path_on_host": "/tmp/ci-${JOB_ID}.ext4",
"is_root_device": true,
"is_read_only": false
}],
"machine-config": {
"vcpu_count": 2,
"mem_size_mib": 2048
}
}
EOF
# Arrancar VM (bloqueante hasta que el job termine)
SOCKET="/tmp/ci-${JOB_ID}.sock"
firecracker --api-sock "${SOCKET}" \
--config-file "/tmp/ci-${JOB_ID}-config.json"
# Recolectar artefactos y limpiar
sudo mount "/tmp/ci-${JOB_ID}.ext4" "${MOUNT_DIR}"
cp -r "${MOUNT_DIR}/artifacts" "/opt/ci-results/${JOB_ID}/"
sudo umount "${MOUNT_DIR}"
rm -f "/tmp/ci-${JOB_ID}.ext4" "/tmp/ci-${JOB_ID}-config.json"
Caso 3: Sandbox para ejecución de código no confiable
Ejecutar código arbitrario de usuarios de forma segura:
# sandbox.sh — ejecutar código del usuario en VM aislada
USER_CODE="$1" # código a ejecutar
TIMEOUT=30 # máximo 30 segundos
# Inyectar código via MMDS después del arranque
run_in_sandbox() {
local SOCKET="/tmp/sandbox-$$.sock"
# Arrancar desde snapshot pre-calentado
firecracker --api-sock "${SOCKET}" &
FC_PID=$!
sleep 0.1
# Restaurar snapshot con Python listo
curl -sf -X PUT \
--unix-socket "${SOCKET}" \
--data '{
"snapshot_path": "/opt/snapshots/python-ready/snapshot",
"mem_backend": {
"backend_path": "/opt/snapshots/python-ready/memory",
"backend_type": "File"
},
"resume_vm": true
}' \
"http://localhost/snapshot/load"
# Inyectar código via MMDS
curl -sf -X PUT \
--unix-socket "${SOCKET}" \
--data "{\"user_code\": \"${USER_CODE}\"}" \
"http://localhost/mmds"
# Esperar resultado con timeout
timeout "${TIMEOUT}" bash -c "
while true; do
result=\$(curl -sf --unix-socket ${SOCKET} \
'http://localhost/mmds' 2>/dev/null | jq -r '.result // empty')
[ -n \"\${result}\" ] && echo \"\${result}\" && break
sleep 0.1
done
"
# Matar la VM
kill "${FC_PID}" 2>/dev/null
rm -f "${SOCKET}"
}
run_in_sandbox "${USER_CODE}"
Caso 4: Ambientes de desarrollo reproducibles
Reemplazar Docker Desktop con microVMs para desarrollo en Linux:
# devenv.sh — ambiente de desarrollo en microVM
start_devenv() {
local PROJECT="$1"
local SOCKET="/tmp/devenv-${PROJECT}.sock"
# Montar el directorio del proyecto como drive adicional
# (usando virtio-fs o 9p si el kernel lo soporta)
firecracker --api-sock "${SOCKET}" \
--config-file "${HOME}/.devenvs/${PROJECT}/config.json" &
echo "Ambiente listo. SSH:"
echo " ssh -i ~/.devenvs/${PROJECT}/key [email protected].$(( $(cat /tmp/vm-counter) + 2 ))"
}