Las dos lecciones anteriores han trabajado siempre sobre una máquina: meteo-01, con su RAID 1, su /dev/md0, su meteora.conf en modo 600 y su sistema de ficheros ext4. Hemos virtualizado dentro de ella, hemos contenido procesos dentro de ella, pero seguía habiendo una máquina física en un armario, que alguien instaló, que tiene un número de serie y que si se avería deja el servicio caído hasta que alguien vaya a arreglarla.

Esta lección quita ese supuesto. En la nube, la máquina deja de ser un objeto y pasa a ser una llamada a una API: pides una instancia y en cuarenta segundos la tienes; la borras y desaparece; se muere sola a las tres de la madrugada porque el anfitrión físico falló y nadie te avisa. Ese cambio no es solo administrativo: cambia lo que significa administrar un sistema operativo, cambia dónde deben vivir los datos, cambia qué se puede depurar y cambia cómo se diseña un servicio para que sobreviva.

Vas a ver qué cambia exactamente y por qué, dónde queda la frontera de responsabilidad del sistema operativo en cada modelo de servicio, cómo arranca de verdad una instancia y cómo se aprovisiona con cloud-init, qué son la infraestructura inmutable y los sistemas operativos mínimos, cómo se comporta el almacenamiento en red frente al SSD local con números concretos, y cómo un orquestador de contenedores es literalmente un sistema operativo del clúster donde reaparecen los cgroups de la lección anterior y el planificador de 02-02. Terminaremos migrando Meteora, decisión a decisión, incluyendo lo que no conviene migrar. Los sistemas móviles y de tiempo real cierran el módulo en 06-04, y las herramientas de monitorización son ya el módulo 7.

Contenido

  1. Qué cambia cuando la máquina deja de ser física
  2. Ganado y no mascotas
  3. Modelos de servicio y la frontera del sistema operativo
  4. El arranque de una instancia, paso a paso
  5. cloud-init: aprovisionar meteo-01 en la nube
  6. Metadatos de instancia y su riesgo de seguridad
  7. Infraestructura inmutable frente a mutable
  8. Sistemas operativos mínimos e inmutables
  9. Almacenamiento en la nube: bloque, efímero y objetos
  10. Redes definidas por software
  11. Orquestación: el sistema operativo del clúster
  12. Funciones sin servidor, microVM y sandboxes
  13. Escalado automático y el arranque en frío
  14. Observabilidad cuando la máquina desaparece
  15. Coste y densidad como criterio de diseño
  16. Caso final: migrar Meteora

Qué cambia cuando la máquina deja de ser física

Enumeremos los cambios sin adornos, porque cada uno tiene consecuencias técnicas concretas:

Propiedad Servidor físico (meteo-01) Instancia en la nube
Aprovisionamiento Semanas: compra, montaje, instalación Segundos, por API
Vida esperada 3-5 años Horas o días; puede desaparecer sin aviso
Identidad Nombre propio, número de serie, inventario Un identificador generado, desechable
Fallo del hardware Incidente: alguien va y lo arregla Rutina: se sustituye la instancia
Disco Físico, dentro de la caja Un volumen de red, atado y desatado por API
Red Cables, VLAN, switch físico Definida por software, creada por API
Escala Fija Elástica, con coste por hora o por segundo
Coste Inversión inicial (CAPEX) Gasto corriente (OPEX), proporcional al uso

Lo verdaderamente disruptivo es la fila de la vida esperada. Un servidor físico se diseña para durar; una instancia de nube se diseña asumiendo que va a morir. Los proveedores lo dicen abiertamente: el compromiso de disponibilidad de una instancia individual suele ser del 99,5 % mensual —unas 3,6 horas de caída al mes— y solo se elevan las garantías cuando repartes la carga entre varias zonas.

De ahí sale la primera regla de diseño de esta lección, que contradice todo lo que uno aprende administrando un servidor: no intentes que la máquina no falle; diseña para que su fallo no importe.

Ganado y no mascotas

La metáfora es de 2012 y sigue siendo la mejor explicación del cambio cultural:

  • Una mascota tiene nombre propio (meteo-01), se instala a mano, se le hace un seguimiento cariñoso, se le aplican parches con cuidado y, cuando enferma, se la cura. Es única e irremplazable.
  • El ganado se numera (meteo-api-7f3c), se aprovisiona automáticamente y es intercambiable. Cuando uno enferma, no se cura: se sustituye.

Las consecuencias prácticas son incómodas al principio y liberadoras después:

  • Nada de cambios manuales. Un apt install a mano en una instancia crea un estado que ninguna otra tiene y que se perderá al sustituirla. Si el cambio importa, va en la imagen o en la automatización.
  • Ningún dato importante vive en el disco de la instancia. Todo lo que debe sobrevivir sale de ahí: a un volumen persistente, a una base de datos gestionada, a almacenamiento de objetos.
  • El acceso por SSH deja de ser la herramienta principal. No porque esté prohibido, sino porque si lo necesitas para operar, algo falta en la automatización.
  • La reinstalación es la respuesta por defecto. Lo que en 05-04 era el desenlace de un incidente grave —reconstruir desde cero— aquí es la operación de martes por la mañana.

Un matiz honesto, porque el dogma se exagera: no todo puede ser ganado. Una base de datos con estado, un controlador de dominio o un sistema con licencias atadas al hardware siguen siendo mascotas, y pretender lo contrario provoca desastres. La regla útil es que el estado se concentre en las menos piezas posibles, y que todo lo demás sea intercambiable.

Modelos de servicio y la frontera del sistema operativo

La pregunta relevante para este curso no es "qué es la nube", sino quién administra el sistema operativo en cada modelo:

Modelo Tú gestionas El proveedor gestiona ¿Ves el SO? Ejemplo
On-premise Todo Nada Sí, entero meteo-01 en el armario
IaaS SO, parches, tiempo de ejecución, aplicación Hardware, hipervisor, red física Sí, entero Una instancia de cómputo
CaaS Imagen del contenedor y su configuración Además, el SO de los nodos Parcialmente (la imagen) Un servicio de contenedores gestionado
PaaS Solo el código y la configuración Además, el tiempo de ejecución No Una plataforma de aplicaciones
FaaS Solo la función Todo lo demás, incluido el escalado No Funciones sin servidor

Lo importante es entender qué se gana y qué se pierde al subir por esa escalera. Se gana operación: menos parches que aplicar, menos configuración que mantener, menos guardias. Se pierde control y, muy en concreto, se pierden las herramientas de este curso: en un PaaS no puedes mirar /proc, no puedes ajustar swappiness, no puedes poner una regla de nftables, no puedes ejecutar strace. Cuando algo va mal y el proveedor no te da la métrica que necesitas, estás ciego.

Y una advertencia que no suele decirse: en IaaS sigues siendo responsable de todo el sistema operativo. Es el modelo de responsabilidad compartida. El proveedor garantiza que el hardware y el hipervisor funcionan; los parches del núcleo, el endurecimiento de 05-03, el cortafuegos, los usuarios y los registros siguen siendo tuyos. Una instancia recién creada con la imagen oficial de una distribución es exactamente igual de insegura que un servidor recién instalado.

El arranque de una instancia, paso a paso

Conviene ver la secuencia completa, porque explica dónde encaja cada pieza:

sequenceDiagram
    participant U as Tú (API/terraform)
    participant P as Plano de control
    participant H as Anfitrión físico
    participant I as Instancia
    U->>P: crear_instancia(imagen, tipo, red, user-data)
    P->>P: elegir anfitrión con hueco (planificación)
    P->>H: crear VM, adjuntar volumen desde la imagen
    H->>I: arrancar (firmware → núcleo → init)
    I->>I: cloud-init: consulta al servicio de metadatos
    I->>I: aplica user-data: usuarios, claves, paquetes, ficheros
    I->>P: instancia lista (~30-60 s)

Las piezas, una a una:

  1. La imagen de máquina. Una plantilla de disco con un sistema ya instalado —la misma idea de las plantillas de 06-01—, identificada por un ID. Puede ser oficial del proveedor, de la distribución, o propia, construida por ti con todo lo que necesitas ya dentro. Esta última opción es la base de la infraestructura inmutable.
  2. El tipo de instancia. Determina vCPU, memoria, red y a veces disco local. Aquí recuperas todo 06-01: esas vCPU son hilos de un proceso en un anfitrión compartido, y su steal time es real.
  3. El volumen raíz. Se crea copiando (o enlazando) la imagen. Al terminar, normalmente se borra con la instancia, salvo que se marque lo contrario.
  4. La red: una interfaz virtual en la red definida por software, con IP privada, y un grupo de seguridad asociado.
  5. El arranque: firmware, gestor de arranque, núcleo, init. Exactamente lo que ocurre en cualquier máquina; lo verás en detalle en Servicios, Arranque y systemd.
  6. cloud-init: la pieza que convierte una imagen genérica en tu servidor.

cloud-init: aprovisionar meteo-01 en la nube

cloud-init es un conjunto de servicios que se ejecutan en el primer arranque, consultan el servicio de metadatos y aplican la configuración que le has pasado, llamada user-data. Es un estándar de facto: funciona igual en casi todos los proveedores y también en KVM local, lo que lo hace la herramienta natural para la réplica de pruebas que creamos en 06-01.

#cloud-config
hostname: meteo-01
fqdn: meteo-01.meteora.internal
timezone: Europe/Madrid

# --- Usuarios: cuenta de operación con clave pública, sin contraseña ---
users:
  - name: operador
    groups: [sudo]
    shell: /bin/bash
    sudo: ["ALL=(ALL) NOPASSWD:/usr/bin/systemctl restart meteo-api"]
    ssh_authorized_keys:
      - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... operador@meteora
  # Cuenta de servicio: MISMO UID que en el servidor físico
  - name: meteora
    uid: 990
    gid: 990
    system: true
    shell: /usr/sbin/nologin
    lock_passwd: true

# --- Paquetes ---
package_update: true
package_upgrade: true
packages: [nftables, chrony, python3-venv, unattended-upgrades]

# --- Disco de datos: formatear y montar con las opciones del curso ---
disk_setup:
  /dev/nvme1n1: {table_type: gpt, layout: true, overwrite: false}
fs_setup:
  - device: /dev/nvme1n1
    partition: 1
    filesystem: ext4
    label: meteora-datos
mounts:
  - [LABEL=meteora-datos, /var/lib/meteora, ext4,
     "defaults,noatime,nosuid,nodev,data=ordered", "0", "2"]

# --- Ficheros: configuración SIN secretos ---
write_files:
  - path: /etc/meteora/meteora.conf
    owner: meteora:meteora
    permissions: '0600'
    content: |
      [general]
      datos = /var/lib/meteora/lecturas
      log   = /var/log/meteora/meteo-api.log
      # Los secretos NO van aquí: se leen del gestor de secretos al arrancar
  - path: /etc/nftables.conf
    permissions: '0644'
    content: |
      table inet filtro {
        chain entrada {
          type filter hook input priority 0; policy drop;
          ct state established,related accept
          iif lo accept
          tcp dport 443 accept
        }
      }

# --- Órdenes finales, en orden ---
runcmd:
  - [install, -d, -o, meteora, -g, meteora, -m, '0750', /var/lib/meteora/lecturas]
  - [install, -d, -o, meteora, -g, meteora, -m, '0750', /var/log/meteora]
  - [systemctl, enable, --now, nftables]
  - [systemctl, enable, --now, meteo-api]

final_message: "meteo-01 listo tras $UPTIME segundos"

Qué hace y por qué cada decisión:

  • #cloud-config en la primera línea es obligatorio: es la firma que le dice a cloud-init que interprete el YAML. Sin ella el fichero se ignora en silencio, y es el error número uno.
  • Usuarios con clave pública y sin contraseña. lock_passwd: true para la cuenta de servicio y ssh_authorized_keys para la de operación aplican directamente 05-02. El sudo está acotado a un comando concreto, no a ALL.
  • uid: 990 explícito, por la misma razón que en el Dockerfile de 06-02: los volúmenes se comparten por número, no por nombre. Si cloud-init asigna el UID 999 y el volumen tiene ficheros del 990, obtienes permisos rotos.
  • mounts con noatime,nosuid,nodev: las opciones de endurecimiento de 04-03 y 05-03, declaradas en el aprovisionamiento en lugar de editadas a mano en /etc/fstab. Aquí está el cambio cultural completo: la configuración se declara, no se aplica.
  • Configuración sin secretos. Este es el punto crítico: el user-data es legible desde dentro de la instancia por cualquier proceso, como veremos ahora mismo. Meter ahí una contraseña equivale a publicarla. Los secretos se leen en el arranque de un gestor de secretos, autenticándose con la identidad de la instancia.
  • runcmd al final, en orden, para lo que no cubren los módulos declarativos.

Metadatos de instancia y su riesgo de seguridad

Toda instancia puede consultar información sobre sí misma en una dirección local especial, típicamente 169.254.169.254:

curl http://169.254.169.254/latest/meta-data/instance-id
curl http://169.254.169.254/latest/meta-data/local-ipv4
curl http://169.254.169.254/latest/user-data          # ¡tu cloud-config entero!
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/rol-meteo
# {"AccessKeyId":"...","SecretAccessKey":"...","Token":"...","Expiration":"..."}

Qué acabas de ver, y por qué es el mayor riesgo de seguridad específico de la nube. Ese endpoint devuelve, sin ninguna autenticación, las credenciales temporales del rol asignado a la instancia. Cualquier proceso de la máquina puede pedirlas y actuar en la nube con esos permisos. Y ahí está el problema: si tu aplicación tiene una vulnerabilidad de falsificación de peticiones del lado del servidor (SSRF) —algo tan tonto como un parámetro ?url= que la aplicación descarga—, un atacante puede pedirle a tu propia aplicación que consulte 169.254.169.254 y le devuelva las credenciales. No necesita ejecutar código: le basta con que tu servidor haga una petición HTTP por él.

Es exactamente el patrón de la brecha de Capital One de 2019, que expuso datos de más de cien millones de personas. Las mitigaciones son concretas y todas necesarias:

  • Exigir la versión con token del servicio de metadatos (IMDSv2 y equivalentes), que obliga a un PUT previo para obtener un token de sesión con TTL bajo: un SSRF simple, que solo sabe hacer GET, deja de funcionar.
  • Bloquear el acceso al endpoint desde los procesos que no lo necesitan, con nftables o el propio contenedor.
  • Rol con permisos mínimos: si meteo-api solo debe escribir en un bucket concreto, que el rol no permita nada más. Es el mínimo privilegio de 05-01 aplicado al plano de la nube.
  • Nunca poner secretos en el user-data, porque es legible desde ahí.

Infraestructura inmutable frente a mutable

Dos filosofías opuestas de gestionar el ciclo de vida de un servidor:

Mutable Inmutable
Actualizar Se modifica el servidor existente (apt upgrade) Se construye una imagen nueva y se reemplazan las instancias
Herramientas Gestión de configuración continua Construcción de imágenes + despliegue
Reversión Difícil: hay que deshacer cambios Trivial: redesplegar la imagen anterior
Deriva de configuración Inevitable con el tiempo Imposible por construcción
Reproducibilidad Depende del historial de cada máquina Total: la imagen es la misma para todos
Depuración Entras y miras Complicado: la instancia mala ya no está
Tiempo de despliegue Minutos Más largo (construir + arrancar)

La deriva de configuración es el argumento decisivo. En un parque de veinte servidores mutables gestionados durante dos años, ninguna máquina es idéntica a otra: un apt install de urgencia aquí, un fichero editado a mano allá, un paquete que falló al actualizar en dos de ellas. El resultado es que un fallo aparece solo en tres servidores y nadie sabe por qué, y que la frase "en el mío funciona" se convierte en la respuesta habitual.

Con infraestructura inmutable, todas las instancias de la versión 1.4.0 son bit a bit idénticas, así que un fallo o está en todas o no está en ninguna. Y la reversión es redesplegar la 1.3.0, lo que convierte un despliegue arriesgado en una operación aburrida.

Lo que se pierde, y es real: depurar se complica. Si una instancia se comporta mal y la política es sustituirla, el problema desaparece con ella y no aprendes nada. La práctica correcta es sacar a la instancia enferma del balanceador pero no destruirla, dejándola en cuarentena para investigarla —lo que enlaza directamente con la preservación de evidencias de 05-04—, y sustituirla por otra para restablecer el servicio. Contener y analizar no son incompatibles: son fases distintas.

Sistemas operativos mínimos e inmutables

Si la instancia es ganado y solo ejecuta contenedores, ¿para qué quiere un sistema operativo completo con compilador, gestor de paquetes, servidor de correo y cincuenta utilidades?

Sistema Idea Actualización Gestor de paquetes
Flatcar / Container Linux SO mínimo solo para contenedores Dos particiones raíz A/B, atómica No hay
Bottlerocket SO de contenedores, raíz de solo lectura, verificada Imagen A/B con reversión No hay
Talos Linux Solo Kubernetes: sin shell ni SSH, API gRPC Declarativa por API No hay
Fedora CoreOS Sistema base atómico con rpm-ostree Transaccional, reversible Limitado
"Distroless" (imágenes) Solo el tiempo de ejecución y la aplicación Reconstruir la imagen No hay

Lo que se gana es medible y sustancial:

  • Superficie de ataque diminuta. Sin shell no hay reverse shell; sin curl ni wget no se descarga la segunda fase de un ataque; sin compilador no se compila un exploit local. Es el principio de superficie mínima de 05-01 llevado al extremo, y elimina clases enteras de ataque, no las dificulta.
  • Menos parches. Un sistema con 80 paquetes tiene menos CVE al mes que uno con 500. Menos ruido, menos guardias.
  • Actualizaciones atómicas y reversibles. El esquema A/B escribe la imagen nueva en la partición inactiva y solo cambia el arranque al final: o se actualiza entera o no se actualiza, y si no arranca bien, vuelve sola a la anterior. Es lo contrario del apt upgrade que se queda a medias.
  • Arranque rápido, porque hay poquísimos servicios que iniciar.

Lo que se pierde es igual de real: depurar es incómodo. Sin strace, sin tcpdump, sin ss y a veces sin shell, las técnicas del módulo 7 no se pueden aplicar directamente. La respuesta del ecosistema son los contenedores de depuración efímeros: se lanza un contenedor con las herramientas dentro de los namespaces del proceso problemático —el nsenter de 06-02, industrializado—, se investiga y se descarta. Funciona bien, pero hay que saber hacerlo antes de que ocurra el incidente, no durante.

Almacenamiento en la nube: bloque, efímero y objetos

Aquí es donde el módulo 2 se vuelve muy visible, y donde más errores de diseño se cometen.

Tipo Qué es Latencia típica Persistencia Semántica
Efímero de instancia NVMe físico del anfitrión 20-100 µs Se pierde al parar la instancia Sistema de ficheros normal
Volumen de bloque en red Disco virtual accedido por red 0,3-1 ms Sobrevive a la instancia Sistema de ficheros normal
Almacenamiento de objetos Almacén clave-valor por HTTP 20-100 ms Muy alta (11 nueves) No es un sistema de ficheros

Volúmenes de bloque en red

Se comportan como un disco: se particionan, se formatean con ext4 y se montan. Pero están al otro lado de una red, y eso se nota exactamente donde predice 02-05:

Métrica SSD NVMe local Volumen de bloque en red
Latencia de lectura 4 KB 20-100 µs 300-1.000 µs (5-10× peor)
IOPS 500.000+ 3.000-64.000 (según lo que pagues)
Throughput 3-7 GB/s 125-1.000 MB/s
Persistencia No
Instantáneas No Sí, integradas

La diferencia clave no es solo el número: es que las IOPS se aprovisionan y se pagan. Un volumen básico da a menudo 3 IOPS por GB, de modo que un volumen de 100 GB rinde 300 IOPS —menos que un disco duro giratorio de 2005— por mucho que el hardware subyacente sea flash. Es la fuente de sorpresa más habitual al migrar: la misma aplicación, en la misma versión, va cuatro veces más lenta, y el motivo no es la CPU sino un techo de IOPS contratado.

Para Meteora, el ingestor escribe 8.000 lecturas por segundo. Si las escribe de una en una, son 8.000 IOPS y el volumen básico se ahoga; si las agrupa en lotes de 512 lecturas (12.288 bytes), bajan a unas 16 escrituras por segundo. La agrupación, que en el servidor físico era una optimización menor, aquí es la diferencia entre funcionar y no funcionar.

Almacenamiento efímero

Es un NVMe físico del anfitrión: rapidísimo y, a menudo, incluido en el precio de la instancia. Pero se pierde al parar la instancia o si el anfitrión falla. Su uso correcto es todo lo que se puede reconstruir: cachés, ficheros temporales, índices derivados, datos intermedios de un proceso por lotes. Nunca la fuente de la verdad.

Almacenamiento de objetos

Es la pieza más incomprendida, porque no es un sistema de ficheros aunque su interfaz lo parezca:

Aspecto Sistema de ficheros Almacenamiento de objetos
Unidad Fichero con inodo (04-01) Objeto completo, con una clave
Modificación parcial Sí, pwrite en un desplazamiento No: se sustituye el objeto entero
Jerarquía Directorios reales Plana; el / de la clave es decorativo
Renombrar Cambio de entrada de directorio, instantáneo Copiar y borrar: proporcional al tamaño
Listar readdir sobre un directorio Consulta paginada sobre un prefijo, lenta
Bloqueo, fsync, permisos POSIX No
Latencia Microsegundos Decenas de milisegundos

Por eso montarlo como si fuera un disco es casi siempre mala idea: las herramientas que traducen POSIX a objetos tienen que emular lo que no existe, y un mv de un fichero grande se convierte en copiar todo el objeto y borrarlo, un readdir se convierte en varias peticiones HTTP paginadas, y no hay bloqueo ni escritura parcial. Funciona en demostraciones y se rompe en producción con datos reales.

Aplicado a Meteora, la decisión es limpia:

Dato Dónde debe vivir Por qué
Fichero del día en curso (2026-08-31.dat) Volumen de bloque Se escribe continuamente, con append y fsync
Ficheros de días cerrados Objetos (comprimidos) Solo se leen enteros, y salen mucho más baratos
Índices y agregados horarios Volumen de bloque, o base de datos gestionada Acceso aleatorio y consultas
Caché /dev/shm/meteora-cache Memoria de la instancia Reconstruible
Copias de seguridad Objetos, con versionado e inmutabilidad Es la copia 3-2-1 de 05-03, ahora con retención forzada por el proveedor

Un detalle que redondea la decisión: el almacenamiento de objetos ofrece clases de acceso con precios muy distintos. Un fichero diario de 17,3 MB comprimido queda en unos 4 MB; a lo largo de cinco años son unos 7,3 GB, con un coste de almacenamiento frío ridículo. Mantener esos mismos datos en volúmenes de bloque cuesta un orden de magnitud más y no aporta nada, porque nadie consulta el 14 de marzo de 2023 con latencia de milisegundos.

Redes definidas por software

La red de la nube es una simulación construida sobre la red física del proveedor, y sus piezas se corresponden una a una con lo que ya conoces:

  • Red virtual privada: un espacio de direcciones propio (por ejemplo 10.0.0.0/16) aislado del de otros clientes, subdividido en subredes por zona de disponibilidad. Conceptualmente es el puente de 06-01, a escala de centro de datos.
  • Subredes públicas y privadas: la diferencia real es si su tabla de rutas tiene salida a una pasarela de internet. Las bases de datos y los servicios internos van en privadas; solo el balanceador vive en la pública.
  • Grupos de seguridad: un cortafuegos con estado, aplicado por interfaz de red, no por máquina. Es la diferencia con nftables: la regla viaja con la instancia, se evalúa antes de que el paquete llegue al sistema operativo, y por defecto deniega todo lo entrante.
  • Direcciones: la IP privada es estable durante la vida de la instancia; la pública, si no se reserva, cambia en cada arranque, motivo por el cual jamás se debe configurar nada por IP pública.

Dos consejos que evitan la mayoría de los incidentes de red en la nube:

  • El grupo de seguridad no sustituye al cortafuegos del sistema. Es defensa en profundidad, como en 05-03: si alguien se equivoca en la regla del grupo, nftables con policy drop sigue ahí.
  • Referencia grupos, no rangos de IP. La regla correcta para la base de datos no es "permitir 10.0.1.0/24", sino "permitir el tráfico que venga del grupo de seguridad de meteo-api". Así la regla sigue siendo correcta cuando el autoescalado crea instancias con IP nuevas.

Orquestación: el sistema operativo del clúster

Aquí está la idea que hace que esta lección pertenezca de pleno derecho a un curso de sistemas operativos. Un orquestador de contenedores hace con un clúster exactamente lo que un sistema operativo hace con una máquina:

Función Sistema operativo (módulo 2) Orquestador
Unidad de ejecución Proceso Pod (uno o varios contenedores)
Recurso a repartir CPU, RAM de la máquina CPU, RAM de todos los nodos
Planificador CFS-EEVDF elige qué proceso ejecuta Elige en qué nodo se coloca el pod
Aislamiento Namespaces y cgroups Los mismos, más políticas de red
Nombres /proc, sockets, ficheros Servicios y DNS interno
Reinicio ante fallo Restart= de systemd Controlador de réplicas
Almacenamiento VFS y montajes (04-03) Volúmenes persistentes

El componente que cierra el círculo con la lección anterior es el kubelet, el agente que corre en cada nodo. Su trabajo es sencillo de describir: pregunta al plano de control qué pods le tocan, habla con el runtime de contenedores (containerd o CRI-O, vía la interfaz CRI) para arrancarlos, monta sus volúmenes, ejecuta sus sondas de salud y reporta el estado. Es, literalmente, el init del nodo para las cargas del clúster.

Peticiones y límites son cgroups, literalmente

Cuando escribes esto en un manifiesto:

resources:
  requests:            # lo que el planificador RESERVA para colocarte
    cpu: "500m"        # 0,5 núcleos
    memory: "256Mi"
  limits:              # el TECHO que no puedes pasar
    cpu: "1500m"       # 1,5 núcleos
    memory: "512Mi"

El kubelet lo traduce exactamente a los ficheros que escribimos a mano en 06-02:

Campo del manifiesto Fichero de cgroup v2 Valor
requests.cpu: 500m cpu.weight ~51 (peso proporcional)
limits.cpu: 1500m cpu.max 150000 100000
requests.memory: 256Mi (solo planificación) No se escribe: se usa para elegir nodo
limits.memory: 512Mi memory.max 536870912

Y de ahí se deducen, sin magia alguna, los dos comportamientos que más desconciertan a quien empieza:

  • Superar el límite de CPU no mata: estrangula. Es el throttling de cpu.max que diagnosticamos en 06-02 con nr_throttled. La aplicación no falla, solo va a tirones y con un p99 horrible.
  • Superar el límite de memoria mata al instante. Es el OOM killer del cgroup: el contenedor muere con código 137 y el pod aparece como OOMKilled. Exactamente el caso del agregador del ejercicio anterior.

El planificador del clúster es el equivalente del planificador de CPU de 02-02, un nivel más arriba: filtra los nodos que no sirven (sin recursos libres suficientes según las requests, sin las etiquetas pedidas, con restricciones activas) y puntúa los que quedan para repartir la carga. Y aquí una regla operativa que se aprende cara: el planificador coloca según las peticiones, no según el uso real. Si pides 2 CPU y usas 0,1, el clúster te reserva 2 y no coloca a nadie más en ese hueco. Peticiones infladas son la causa número uno de clústeres carísimos con nodos al 15 % de uso.

Funciones sin servidor, microVM y sandboxes

Las funciones sin servidor llevan la escalera al extremo: subes una función, el proveedor la ejecuta cuando llega un evento y te cobra por milisegundo. No hay servidor que administrar, no hay sistema operativo que parchear.

Pero por debajo sí hay algo, y ese algo es interesante para este curso, porque plantea un problema difícil: hay que ejecutar código de miles de clientes distintos en el mismo hardware, con aislamiento de máquina virtual pero arranque de contenedor. Las máquinas virtuales de 06-01 aíslan bien pero tardan decenas de segundos; los contenedores de 06-02 arrancan en milisegundos pero comparten núcleo. Dos tecnologías ocupan ese espacio intermedio:

  • Firecracker es un VMM minimalista escrito en Rust sobre KVM. Renuncia a casi todo lo que QEMU emula: sin BIOS heredada, sin USB, sin VGA, sin PCI completo; solo virtio para disco, red y consola. El resultado es una microVM que arranca en ~125 ms, consume unos 5 MB de sobrecarga y expone al hipervisor una superficie diminuta. Es el hipervisor real detrás de las plataformas de funciones sin servidor, y es la demostración práctica de la lección de VENOM en 06-01: quitar dispositivos emulados mejora a la vez la seguridad y el rendimiento.
  • gVisor ataca el problema desde el otro lado: es un núcleo en espacio de usuario que intercepta las llamadas al sistema del contenedor y las implementa él mismo, en Go, en lugar de dejarlas pasar al núcleo anfitrión. Reduce drásticamente la superficie —el contenedor ya no habla con las 350 syscalls de Linux, sino con gVisor—, a costa de una penalización de rendimiento notable en cargas con mucha E/S.
Tecnología Aislamiento Arranque en frío Sobrecarga de memoria Compatibilidad
Contenedor (runc) Núcleo compartido 20-200 ms 1-10 MB Total
gVisor Núcleo interceptado en espacio de usuario 100-300 ms 15-50 MB Alta, con huecos
Firecracker (microVM) Núcleo propio, VM real ~125 ms ~5 MB + el invitado Total
VM completa (QEMU) Núcleo propio, VM real 10-60 s 200-500 MB Total

La conclusión que conviene retener: la frontera entre VM y contenedor ha dejado de ser binaria. Firecracker consigue aislamiento de VM con arranque casi de contenedor recortando todo lo que no hace falta, y es la razón técnica de que las funciones sin servidor puedan ser a la vez multiinquilino y baratas.

Escalado automático y el arranque en frío

El escalado automático ajusta el número de instancias o de pods según una métrica: uso de CPU, longitud de una cola, peticiones por segundo. Suena trivial y tiene dos problemas reales.

El primero es el arranque en frío. Escalar no es instantáneo, y la latencia se acumula por capas:

Capa Tiempo típico hasta servir tráfico
Pod nuevo, imagen ya en el nodo 1-5 s
Pod nuevo, imagen por descargar 10-60 s
Instancia nueva (IaaS) + arranque + cloud-init 40-120 s
Función sin servidor (microVM) en frío 100-500 ms
Función en caliente 1-10 ms

Si tu carga se duplica en 30 segundos y una instancia nueva tarda 90 en estar lista, el escalado automático llega tarde y los usuarios ven errores. Las respuestas son escalar por una métrica adelantada (la longitud de la cola del ingestor crece antes de que la CPU se sature), mantener un colchón de capacidad ociosa, precargar imágenes en los nodos, y escalar de forma agresiva al subir y conservadora al bajar para no oscilar.

El segundo problema es la oscilación: si la métrica sube y baja alrededor del umbral, el sistema crea y destruye instancias sin parar, con un coste alto y ninguna estabilidad. Se resuelve con histéresis —umbrales distintos para subir y bajar— y con periodos de enfriamiento.

Y un matiz importante para Meteora: el ingestor no escala igual que meteo-api. Las consultas HTTP son sin estado y se reparten a voluntad; la ingesta de estaciones concretas tiene afinidad y orden. Escalar horizontalmente algo con estado sin pensarlo lleva a lecturas duplicadas o perdidas.

Observabilidad cuando la máquina desaparece

Aquí se ve con crudeza por qué 05-04 insistía tanto en sacar los registros de la máquina. En la nube, esa recomendación pasa de buena práctica a requisito absoluto, por una razón nueva: la instancia que generó el registro puede no existir cuando vayas a leerlo.

  • Un pod que muere por OOM se reinicia y sus registros anteriores desaparecen salvo que se hayan enviado fuera.
  • Una instancia sustituida por el autoescalado se lleva su journalctl con ella.
  • El disco efímero, con lo que hubiera en /var/log, se borra al parar la instancia.

De ahí las reglas prácticas:

  • La aplicación escribe a la salida estándar, no a un fichero. Un agente en el nodo recoge, etiqueta y envía. Es lo contrario de /var/log/meteora/meteo-api.log, y en un contenedor tiene todo el sentido: quien tiene que preocuparse de dónde acaba el registro no es la aplicación.
  • Registro estructurado con contexto de la nube: al request_id de 05-04 se le añaden ahora identificador de instancia, zona, versión de la imagen y nombre del pod. Sin eso, un error que ocurre en una sola instancia de veinte es indistinguible del ruido.
  • Trazas distribuidas, porque una petición atraviesa balanceador, varios servicios y una base de datos: un registro por servicio ya no reconstruye la historia.
  • Métricas por agregado, no por máquina. "La CPU de meteo-01" deja de significar nada cuando hay quince instancias efímeras; lo que importa es el percentil y la distribución.

¿Qué se pierde al depurar? Bastante, y conviene decirlo: no puedes entrar a mirar /proc en una instancia que ya no existe; no puedes lanzar strace sobre un proceso que murió hace diez minutos; no puedes reproducir el estado exacto de un disco efímero borrado. La compensación es que puedes conservar la instancia enferma en cuarentena en lugar de matarla, que es la técnica que hay que tener escrita en el procedimiento antes de necesitarla.

Coste y densidad como criterio de diseño

En un servidor físico, el coste es una inversión hecha hace dos años y no influye en el diseño diario. En la nube, cada decisión técnica tiene una factura mensual, y eso convierte al coste en un criterio de arquitectura de pleno derecho.

Algunas relaciones que conviene interiorizar:

  • Sobredimensionar sale caro y es invisible. Peticiones infladas en un manifiesto no producen ningún error: producen nodos al 15 % de uso y una factura triple. Medir el uso real y ajustar es una tarea de ingeniería, no de contabilidad.
  • La transferencia de datos se paga, y sobre todo la de salida. Mover los ficheros diarios entre zonas o hacia internet puede costar más que almacenarlos. Colocar el cómputo junto a los datos deja de ser una cuestión de latencia y pasa a ser de dinero.
  • Las clases de almacenamiento importan mucho. Ya lo vimos: los días cerrados en almacenamiento frío cuestan una fracción de lo que cuestan en volúmenes de bloque.
  • Las instancias interrumpibles (spot) cuestan entre un 60 % y un 90 % menos a cambio de que te las puedan quitar con un aviso de dos minutos. Para el agregador, que es un trabajo por lotes reanudable, son ideales. Para meteo-api, no.
  • La densidad es ahorro. Cada contenedor que cabe de más en un nodo es un nodo menos que pagar; aquí es donde el coste base de 1-10 MB de un contenedor frente a 200-500 MB de una VM se convierte en euros.

Caso final: migrar Meteora

Decisión a decisión, con la justificación explícita:

Pieza Decisión Por qué
meteo-api Contenedor en un servicio gestionado, 3 réplicas en 2 zonas, autoescalado por peticiones/s, tras un balanceador con TLS Sin estado, es el caso ideal de "ganado"; con 3 réplicas se sobrevive a la pérdida de una zona
ingestor Contenedor con estado, réplicas fijas con afinidad por estación, cola gestionada delante Tiene orden y afinidad: escalarlo a ciegas duplicaría o perdería lecturas. La cola absorbe los picos y desacopla
agregador Trabajo programado en instancias interrumpibles, con reintento Por lotes, reanudable y tolerante a interrupción: ahorro del 70-90 % sin riesgo
Fichero del día Volumen de bloque con IOPS aprovisionadas, escritura en lotes de 512 lecturas Reduce de 8.000 a ~16 escrituras/s: la diferencia entre funcionar y ahogarse
Días cerrados Objetos comprimidos, con transición automática a clase fría a los 30 días Solo se leen enteros; el coste baja un orden de magnitud
Copias de seguridad Objetos en otra región, con versionado e inmutabilidad Es la 3-2-1 de 05-03; la retención forzada por el proveedor es la única defensa real contra el ransomware
meteora.conf Gestor de secretos, leído al arrancar con la identidad de la instancia Jamás en la imagen ni en el user-data, por lo visto en el apartado 6
SO de los nodos Distribución mínima e inmutable, actualización A/B Superficie mínima y actualizaciones reversibles; nadie entra por SSH a un nodo
Red Subred privada para todo; solo el balanceador en la pública; grupos de seguridad que se referencian entre sí Mínima exposición, y reglas que sobreviven al autoescalado
Registros y métricas Salida estándar → agente → colector externo, con contexto de nube La instancia puede no existir cuando vayas a leer
Base de datos de agregados Servicio gestionado con réplica en otra zona Es la mascota del sistema: mejor que la cuide quien tiene guardias 24×7

Y lo que no conviene migrar, que es igual de importante:

  • El almacenamiento histórico completo, tal cual está. Mover cinco años de ficheros diarios a volúmenes de bloque sería carísimo y absurdo: van a objetos, comprimidos y en clase fría. Migrar "igual que estaba" es el error clásico.
  • La caché en /dev/shm/meteora-cache como pieza compartida. En una máquina tenía sentido; con réplicas en varias zonas, una caché local por instancia es incoherente. O se acepta que cada réplica tenga la suya (si es reconstruible y tolerante a estar desfasada), o se usa una caché en red.
  • El cron y los scripts de mantenimiento hechos a mano. No sobreviven a instancias efímeras: pasan a trabajos programados del orquestador.
  • La FIFO /run/meteora/lecturas.fifo entre servicios. Es IPC local (03-03) y solo funciona dentro de una máquina; entre instancias hace falta una cola de verdad, con persistencia y reintentos.
  • Los datos regulados fuera de su jurisdicción. Si hay obligaciones de residencia de datos, la región no es una decisión técnica sino legal, con las implicaciones de 05-04.
  • Y quizá nada, si el caso no lo justifica. Un servicio estable, con carga previsible y sin necesidad de elasticidad puede salir más caro y más frágil en la nube. La nube se paga con dinero y con complejidad; ambos deben justificarse.

Errores Comunes y Consejos

Tratar las instancias como mascotas. Instalar a mano, configurar por SSH y confiar en que la máquina siga ahí. La primera sustitución automática se lleva por delante todos esos cambios y nadie sabe reconstruirlos.

Meter secretos en el user-data. Es legible desde dentro de la instancia por cualquier proceso a través del servicio de metadatos. Un secreto ahí es un secreto publicado.

No proteger el servicio de metadatos. Un SSRF en la aplicación se convierte en robo de las credenciales del rol de la instancia; es el patrón exacto de una de las mayores brechas conocidas. Exige la versión con token, bloquea el acceso desde donde no haga falta y reduce los permisos del rol.

Suponer que el volumen de bloque rinde como un SSD local. Es 5-10 veces peor en latencia y tiene un techo de IOPS que has contratado. Diseña la aplicación para agrupar escrituras antes de migrar, no después.

Montar almacenamiento de objetos como si fuera un disco. No hay escritura parcial, ni bloqueo, ni fsync, ni renombrado barato, y la latencia es mil veces mayor. Úsalo con su API.

Confundir peticiones con límites. Las peticiones deciden dónde te colocan y qué se te reserva; los límites deciden cuándo te estrangulan o te matan. Peticiones infladas dan clústeres carísimos al 15 % de uso; límites de memoria demasiado bajos dan reinicios con código 137 sin ninguna traza.

Olvidar que el escalado automático tarda. Si tu pico llega en 30 segundos y la instancia tarda 90 en arrancar, has escalado tarde. Escala por una métrica adelantada y mantén colchón.

Depender solo de los registros locales. En un entorno efímero, el registro que no ha salido de la máquina es un registro que se ha perdido.

Consejo: empieza por el estado. Antes de migrar nada, haz el inventario de dónde vive cada dato y de cuál es su fuente de la verdad. Todo lo que no tiene estado migra fácil; todo lo demás es donde están las decisiones difíciles.

Consejo: prueba a matar una instancia a propósito. En un entorno de pruebas, destruye una réplica de meteo-api en horario laboral y observa qué pasa. Si el servicio no se recupera solo en menos de un minuto, tu diseño todavía trata a esa instancia como mascota. Es la única forma de saberlo antes de que ocurra de verdad.

Consejo: pon una alarma de coste el primer día. Un bucle de escalado mal configurado, un volumen olvidado o una transferencia entre regiones pueden multiplicar la factura sin generar un solo error.

Ejercicios

Ejercicio 1: escribir el cloud-config de una réplica del agregador

Escribe un fichero #cloud-config completo que aprovisione una instancia para ejecutar el agregador, cumpliendo: (a) usuario de servicio meteora con UID 990, sin shell y con la contraseña bloqueada; (b) una cuenta de operación con clave pública y sudo limitado a reiniciar el servicio; (c) un disco adicional formateado en ext4 y montado en /var/lib/meteora con las opciones del curso; (d) cortafuegos con política de denegación y solo SSH desde la subred privada; (e) el fichero /etc/meteora/meteora.conf en modo 600 sin secretos, explicando de dónde salen; (f) actualizaciones de seguridad automáticas. Justifica cada bloque y señala tres cosas que deliberadamente no pones ahí y por qué.

Ejercicio 2: dimensionar recursos y diagnosticar un pod

meteo-api se despliega con este manifiesto en nodos de 4 vCPU y 8 GiB:

resources:
  requests: {cpu: "2000m", memory: "4Gi"}
  limits:   {cpu: "2000m", memory: "4Gi"}

Medido en producción, cada réplica usa de forma sostenida 0,2 vCPU y 300 MiB, con picos de 0,8 vCPU y 450 MiB.

(a) ¿Cuántas réplicas caben por nodo con este manifiesto, y cuántas cabrían con peticiones ajustadas? Calcula el desperdicio y su impacto en el número de nodos necesarios para 30 réplicas. (b) Propón valores de requests y limits justificados. (c) Tras el cambio, algunos pods empiezan a reiniciarse con código 137 y otros muestran latencia p99 muy alta sin reiniciarse: explica qué le pasa a cada grupo, qué fichero de cgroup lo confirma y cómo lo corriges. (d) Explica por qué poner requests igual a limits en memoria es buena idea pero en CPU suele no serlo.

Ejercicio 3: decidir el almacenamiento de Meteora en la nube

Meteora genera un fichero diario de 17.280.000 bytes (720.000 lecturas × 24 B). Las consultas son: el 92 % sobre las últimas 48 horas, el 7 % sobre el último mes y el 1 % sobre el histórico de cinco años. El ingestor recibe 8.000 lecturas por segundo. El volumen de bloque básico ofrece 3 IOPS por GB.

(a) Calcula el volumen anual de datos crudos y comprimidos (factor 4:1), y diseña la ubicación de cada conjunto de datos con su clase de almacenamiento. (b) Calcula las IOPS que necesita el ingestor escribiendo lectura a lectura y agrupando en lotes de 512, y di qué tamaño de volumen básico haría falta en cada caso solo para alcanzar esas IOPS. (c) Justifica por qué el fichero del día en curso no puede estar en almacenamiento de objetos, con al menos tres razones técnicas concretas. (d) Diseña la política de copias de seguridad cumpliendo la regla 3-2-1 de 05-03 y explica qué añade la inmutabilidad frente a un ransomware.

Soluciones

Solución 1

#cloud-config
hostname: agregador-01
timezone: Europe/Madrid

users:
  - name: meteora                      # (a) cuenta de servicio
    uid: 990
    gid: 990
    system: true
    shell: /usr/sbin/nologin
    lock_passwd: true
  - name: operador                     # (b) cuenta de operación
    groups: [sudo]
    shell: /bin/bash
    lock_passwd: true
    sudo: ["ALL=(ALL) NOPASSWD:/usr/bin/systemctl restart agregador"]
    ssh_authorized_keys:
      - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... operador@meteora

package_update: true
packages: [nftables, chrony, unattended-upgrades]

disk_setup:                            # (c) disco de datos
  /dev/nvme1n1: {table_type: gpt, layout: true, overwrite: false}
fs_setup:
  - {device: /dev/nvme1n1, partition: 1, filesystem: ext4, label: meteora-datos}
mounts:
  - [LABEL=meteora-datos, /var/lib/meteora, ext4,
     "defaults,noatime,nosuid,nodev,data=ordered", "0", "2"]

write_files:
  - path: /etc/nftables.conf           # (d) cortafuegos
    permissions: '0644'
    content: |
      table inet filtro {
        chain entrada {
          type filter hook input priority 0; policy drop;
          ct state established,related accept
          iif lo accept
          ip saddr 10.0.0.0/16 tcp dport 22 accept
        }
      }
  - path: /etc/meteora/meteora.conf    # (e) configuración SIN secretos
    owner: meteora:meteora
    permissions: '0600'
    content: |
      [general]
      datos = /var/lib/meteora/lecturas
      # secretos: se leen del gestor al arrancar, con la identidad de la instancia
  - path: /etc/apt/apt.conf.d/20auto-upgrades   # (f)
    content: |
      APT::Periodic::Update-Package-Lists "1";
      APT::Periodic::Unattended-Upgrade "1";

runcmd:
  - [install, -d, -o, meteora, -g, meteora, -m, '0750', /var/lib/meteora/lecturas]
  - [systemctl, enable, --now, nftables]
  - [systemctl, enable, --now, unattended-upgrades]

Justificación. El UID 990 explícito es imprescindible porque el volumen se comparte por número, no por nombre (mismo motivo que en el Dockerfile de 06-02). lock_passwd: true en ambas cuentas implanta el "solo clave pública" de 05-02, y el sudo acotado a un comando concreto aplica el mínimo privilegio de 05-01 —recordando su límite: acota lo que se ejecuta, no lo que se puede conseguir—. Las opciones de montaje noatime,nosuid,nodev son el endurecimiento de 04-03 declarado, no editado a mano. El cortafuegos con policy drop y SSH restringido a la subred privada es defensa en profundidad además del grupo de seguridad. Y las actualizaciones automáticas cierran la primera medida de 05-03.

Tres cosas que deliberadamente no van ahí:

  1. Ningún secreto, porque el user-data es legible por cualquier proceso de la instancia vía 169.254.169.254/latest/user-data: ponerlo ahí es publicarlo. Se lee del gestor de secretos al arrancar, autenticándose con el rol de la instancia.
  2. Ninguna clave privada SSH ni certificado TLS, por lo mismo. Solo la clave pública del operador.
  3. Ninguna instalación pesada de aplicación (compilar, descargar artefactos grandes, configurar a mano). Eso va en la imagen, construida y versionada, no en el arranque: si va aquí, cada instancia tarda minutos en estar lista, depende de que los repositorios respondan y deja de ser reproducible —es infraestructura mutable disfrazada—.

Solución 2

(a) Cálculo de densidad. Con requests: 2000m / 4Gi en nodos de 4 vCPU y 8 GiB: por CPU caben 2 pods, por memoria caben 2 (dejando además margen para el sistema del nodo, en realidad 1). Tomemos 2 pods por nodo en el mejor caso: para 30 réplicas hacen falta 15 nodos.

Con peticiones ajustadas al uso real (por ejemplo 300m / 512Mi): por CPU caben 13 pods, por memoria 16; el límite es la CPU, unos 12 pods por nodo dejando margen para el sistema. Para 30 réplicas bastan 3 nodos.

El desperdicio es de 5 veces: se reservan 2000m para usar 200m, es decir se aprovecha el 10 % de lo reservado. Y como el planificador coloca según las peticiones y no según el uso, el clúster aparecerá con nodos al 5-10 % de CPU real y aun así "lleno", incapaz de aceptar más pods. Es la causa número uno de facturas desproporcionadas.

(b) Valores propuestos:

resources:
  requests: {cpu: "300m", memory: "512Mi"}
  limits:   {cpu: "1000m", memory: "512Mi"}

Justificación: la petición de CPU se sitúa algo por encima del uso sostenido (0,2 vCPU) para que el planificador reserve lo necesario sin inflar; el límite de CPU se pone holgado (1 vCPU) para absorber los picos de 0,8 sin estrangular. En memoria, petición y límite iguales a 512 MiB, con margen sobre el pico de 450 MiB, porque la memoria no es comprimible.

(c) Los dos síntomas y su causa:

  • Los pods que reinician con código 137 están siendo matados por el OOM killer del cgroup: su consumo real supera memory.max. El 137 es 128 + 9 (SIGKILL) y el estado del pod será OOMKilled. Se confirma con memory.events (campo oom_kill distinto de cero) o con el evento del pod. Corrección: subir el límite de memoria por encima del pico real medido —quizá el pico de 450 MiB no cubría todos los casos— y añadir un memory.high equivalente para tener presión antes que muerte.
  • Los pods con p99 alta que no reinician están estrangulados por CPU: han agotado su cuota cpu.max dentro del periodo y quedan detenidos hasta el siguiente. Se confirma con nr_throttled frente a nr_periods en cpu.stat, exactamente como en 06-02. Corrección: subir el límite de CPU o ajustar el número de hilos de la aplicación a la cuota; si lo único que se buscaba era prioridad, quitar el límite y confiar en el peso derivado de la petición.

(d) Por qué requests == limits es buena idea en memoria y mala en CPU. La memoria no es comprimible: no se puede "ir más despacio" en memoria, o la tienes o te matan. Igualar petición y límite garantiza que lo que el planificador te reservó es exactamente lo que puedes usar, evita el OOM por sobrecompromiso del nodo y da la clase de servicio más estable. La CPU, en cambio, sí es comprimible: se puede repartir en el tiempo. Igualar petición y límite en CPU desperdicia la capacidad ociosa del nodo —el pod no puede aprovechar picos aunque haya núcleos libres— y provoca estrangulamiento evitable. Lo habitual es petición ajustada al uso sostenido y límite generoso o incluso ausente en cargas de confianza.

Solución 3

(a) Volumen y ubicación. Diario: 17.280.000 B ≈ 16,5 MiB. Anual: 17.280.000 × 365 ≈ 6,3 GB crudos, y con compresión 4:1 unos 1,6 GB/año. En cinco años, 7,9 GB comprimidos: una cantidad ridícula, lo que refuerza que el criterio no debe ser el espacio sino la latencia y el patrón de acceso.

Conjunto Ubicación Clase Por qué
Día en curso + 48 h (92 % de consultas) Volumen de bloque con IOPS aprovisionadas Estándar Escritura continua y lectura de baja latencia
Último mes (7 %) Objetos, sin comprimir o con compresión ligera Estándar/acceso infrecuente Consultas ocasionales, tolera decenas de ms
Histórico de 5 años (1 %) Objetos comprimidos Frío / archivo Se consulta muy poco; coste mínimo
Agregados horarios Base de datos gestionada Acceso aleatorio y consultas analíticas

(b) IOPS.

  • Lectura a lectura: 8.000 escrituras/s → 8.000 IOPS. Con 3 IOPS/GB haría falta un volumen de 2.667 GB (unos 2,7 TB) para almacenar 16,5 MiB al día. Es absurdo: se estaría pagando un volumen enorme solo para comprar IOPS.
  • En lotes de 512 lecturas (512 × 24 = 12.288 B por escritura): 8.000 / 512 ≈ 15,6 IOPS. Con 3 IOPS/GB bastan ~6 GB, y cualquier volumen razonable (por ejemplo 100 GB, con 300 IOPS) sobra con un margen de 20 veces.

La conclusión es la de la lección: agrupar escrituras no es una micro-optimización, es un requisito de diseño en la nube. El coste de la agrupación es una ventana de pérdida —las lecturas del lote en curso si la instancia muere—, que se acota con un fsync periódico por tiempo además de por tamaño, o poniendo una cola persistente delante.

(c) Por qué el día en curso no puede ir en objetos, con tres razones técnicas:

  1. No hay escritura parcial ni append. Añadir una lectura al final exigiría reescribir el objeto entero (16,5 MiB) cada vez: 8.000 veces por segundo es imposible y costaría una fortuna en peticiones.
  2. La latencia es mil veces peor. 20-100 ms por operación frente a los microsegundos de un write local; con 8.000 escrituras/s, ni de lejos.
  3. No hay fsync, ni bloqueo, ni semántica POSIX. No se puede garantizar la durabilidad de un registro concreto como en 04-05, ni coordinar escritores concurrentes, ni usar las herramientas del sistema de ficheros.

Y una cuarta razón de coste: el almacenamiento de objetos se factura por petición, así que 8.000 peticiones por segundo serían más caras que todo lo demás junto.

(d) Política 3-2-1 con inmutabilidad.

  • 3 copias: la del volumen de bloque (producción), la de objetos en la región principal y la de objetos en otra región.
  • 2 medios distintos: volumen de bloque y almacenamiento de objetos, que son sistemas independientes con modos de fallo distintos.
  • 1 fuera del emplazamiento: la copia en otra región cubre la pérdida de una región entera.
  • Verificación: suma de verificación al escribir y comprobación periódica; y sobre todo, prueba de restauración trimestral con el tiempo medido, porque una copia no probada no es una copia (05-03).

Qué añade la inmutabilidad. Un ransomware con las credenciales de la instancia puede cifrar el volumen y también borrar o sobrescribir los objetos, porque tiene los mismos permisos que la aplicación. Con versionado más bloqueo de objetos en modo de cumplimiento, el proveedor rechaza cualquier borrado o modificación hasta que expire la retención, incluso si la petición llega con credenciales válidas y de administrador. Eso saca la copia del alcance del atacante, que es exactamente el mismo razonamiento que en 05-04 llevaba a enviar los registros a un colector externo con credenciales de solo envío. La medida se completa con credenciales separadas para las copias, distintas de las de la aplicación, y con alertas ante cualquier intento de borrado.

Conclusión

Cuando la máquina deja de ser un objeto físico y pasa a ser una llamada a una API, lo que cambia no es la administración sino el modelo mental: la instancia se aprovisiona en segundos, vive horas o días, y puede desaparecer sin aviso, con un compromiso de disponibilidad individual que ronda el 99,5 %. De ahí la regla que gobierna toda la lección —no intentes que la máquina no falle; diseña para que su fallo no importe— y la metáfora del ganado frente a las mascotas, con sus consecuencias incómodas: nada de cambios manuales, ningún dato importante en el disco de la instancia, SSH como excepción y no como herramienta, y la reinstalación como respuesta normal. Con el matiz honesto de que no todo puede ser ganado: lo que tiene estado sigue siendo una mascota, y el objetivo es concentrarlo en las menos piezas posibles.

Los modelos de servicio dibujan dónde queda la frontera del sistema operativo: en IaaS es tuyo entero —y con él los parches, el endurecimiento de 05-03, el cortafuegos y los registros: una instancia recién creada es tan insegura como un servidor recién instalado—, y según se sube a CaaS, PaaS y FaaS se gana operación y se pierden, muy literalmente, las herramientas de este curso. El arranque de una instancia encadena imagen, tipo, volumen raíz, red y cloud-init, y este último es la pieza que convierte una plantilla genérica en tu servidor: usuarios con clave pública, UID 990 explícito porque los volúmenes se comparten por número, montajes con noatime,nosuid,nodev declarados en vez de editados, y jamás un secreto, porque el user-data es legible desde dentro a través del servicio de metadatos —el mismo endpoint que entrega las credenciales del rol y que convierte un SSRF cualquiera en un robo de credenciales, como en la brecha de Capital One—.

De ahí sale la infraestructura inmutable: en lugar de modificar servidores se construyen imágenes nuevas, lo que elimina por construcción la deriva de configuración y convierte la reversión en algo aburrido, a cambio de complicar la depuración —y de ahí la técnica de sacar del balanceador sin destruir, que enlaza con la preservación de evidencias de 05-04—. Los sistemas operativos mínimos e inmutables llevan la idea al extremo: sin shell no hay reverse shell, sin curl no hay segunda fase, sin compilador no hay exploit local; se eliminan clases enteras de ataque a cambio de tener que depurar con contenedores efímeros que entran en los namespaces del proceso, el nsenter de 06-02 industrializado.

En almacenamiento, los números mandan: el volumen de bloque en red es 5-10 veces peor en latencia que un NVMe local y tiene un techo de IOPS que se contrata, lo que convierte la agrupación de escrituras en un requisito de diseño —de 8.000 IOPS a 16 con lotes de 512 lecturas—; el efímero es rapidísimo y desaparece, así que solo vale para lo reconstruible; y el almacenamiento de objetos no es un sistema de ficheros: sin escritura parcial, sin fsync, sin bloqueo, con renombrado que copia y latencias de decenas de milisegundos, por lo que montarlo como disco es casi siempre un error. Aplicado a Meteora: día en curso en bloque, días cerrados en objetos comprimidos con transición a clase fría, y copias en otra región con versionado e inmutabilidad, que es la única defensa real contra un ransomware que tiene tus credenciales.

Y la pieza que cierra el círculo del curso: la orquestación es un sistema operativo del clúster. El pod es el proceso, el planificador del clúster es el planificador de 02-02 un nivel más arriba, el kubelet es el init del nodo, y las requests y limits de un manifiesto se traducen literalmente a cpu.weight, cpu.max y memory.max en /sys/fs/cgroup —de ahí que pasarse de CPU estrangule y pasarse de memoria mate con un 137—. El planificador coloca según las peticiones y no según el uso, lo que hace de las peticiones infladas la causa número uno de clústeres carísimos al 15 %. Alrededor, Firecracker y gVisor demuestran que la frontera entre VM y contenedor ha dejado de ser binaria (una microVM que arranca en 125 ms recortando todo lo que QEMU emulaba de más), el escalado automático tropieza siempre con el arranque en frío y la oscilación, la observabilidad exige sacar los registros de la instancia porque la instancia puede no existir cuando vayas a leerlos, y el coste se convierte en criterio de arquitectura de pleno derecho.

Nos queda una familia entera de sistemas operativos que va justo en la dirección contraria a todo esto. Aquí hemos hablado de máquinas que sobran, que se crean por API y que se tiran cuando estorban; de escalar hacia fuera cuando falta capacidad; de latencias medidas en milisegundos y de fallos que se toleran reintentando. Pero las estaciones meteorológicas de Meteora —esos dispositivos empotrados que decidimos en 01-03 que llevarían un RTOS— no pueden hacer nada de eso: tienen 64 KB de RAM, funcionan con una batería que debe durar meses, no pueden pedir otra unidad cuando les falta memoria, y si el muestreo del sensor llega 10 milisegundos tarde, el dato está mal, no "llega despacio". Y en el otro extremo del mismo problema, el teléfono desde el que un usuario consulta la API de Meteora ejecuta un Linux que ha tenido que reinventar su IPC, su gestión de memoria y su modelo de permisos porque la batería y la ausencia de swap lo cambian todo.

Dos familias con la misma raíz que hemos estudiado y con restricciones opuestas a las del servidor. Es Sistemas Operativos Móviles y de Tiempo Real, y cierra el módulo.

Fundamentos de Sistemas Operativos

Módulo 1: Introducción a los Sistemas Operativos

Módulo 2: Gestión de Recursos

Módulo 3: Concurrencia

Módulo 4: Estructuras de Archivos

Módulo 5: Protección y Seguridad del Sistema

Módulo 6: Virtualización y Contenedores

Módulo 7: Administración y Diagnóstico en la Práctica

© Copyright 2026. Todos los derechos reservados