Blog ·
Reinstalar un contenedor es reemplazarlo
Un contenedor no tiene disco de plantilla ni cloud-init, así que reinstalarlo es destruirlo y reconstruirlo. Eso cambia lo que se le debe antes al cliente.
Por Provibr
Reinstalar una máquina virtual en Proxmox es casi aburrido. Se detiene, se borra, se clona la plantilla sobre el mismo identificador y cloud-init hace el resto. La plantilla lleva un disco y el disco lleva un sistema operativo, así que «reinstalar» es en realidad «hacer una copia nueva de algo que se sabe bueno».
Un contenedor Linux no tiene ni lo uno ni lo otro. Se desempaqueta de un tarball, y todo lo que es a partir de ahí, los núcleos, la memoria, el swap, el tamaño del sistema de archivos raíz, la interfaz de red, el resolutor, el nombre de host, vive en un fichero de configuración del hipervisor y en ningún otro sitio. No hay imagen que clonar encima. Así que reinstalar un contenedor es destruir y después crear, y en cuanto eso se dice en voz alta aparece un conjunto de obligaciones que una reinstalación de máquina virtual nunca tiene.
Así se construyó, y la parte que más tiempo llevó no fue la de destruir.
Dos fases, porque la plataforma no sabe qué hay montado
El flujo está partido en dos. La primera fase solo lee, y no cambia nada: el agente obtiene la configuración del contenedor y la devuelve. La segunda fase detiene, destruye, crea y arranca, usando lo que leyó la primera como condición previa.
La alternativa obvia es una sola fase con una vista previa construida a partir de lo que la plataforma ya sabe. Aquí eso no está disponible. La inspección de hardware para contenedores no está implementada, porque el analizador que usaría lee claves de configuración de QEMU, así que no hay en nuestra base de datos ninguna instantánea guardada de los puntos de montaje de un contenedor. Una vista previa que etiquete todos los puntos de montaje como unknown no es una vista previa, es un descargo de responsabilidad.
Cinco palabras para lo que les pasa a los datos
Cada punto de montaje de la configuración recibe exactamente una etiqueta, y la etiqueta habla de los datos, no del montaje.
| Etiqueta | Significado |
|---|---|
erased | el volumen se destruye junto con el contenedor; el sistema de archivos raíz, siempre |
excluded | el volumen pertenece a otro invitado, así que Proxmox lo salta y no se toca nada |
externally_managed | un bind mount, una ruta del host; el directorio sobrevive, el montaje no vuelve |
unknown | la línea no se pudo leer, y esta es la única etiqueta que bloquea toda la operación |
preserved | los datos se quedan y el montaje vuelve |
La última fila está en la lista y no se produce nunca. Conservar un volumen a través de una destrucción significa desconectarlo, borrar el contenedor sin recoger los discos sin referencia y volver a conectarlo después: tres pasos cuyo paso central, al fallar, deja un volumen del que ya no es dueño nadie. Eso es un trabajo aparte con su propio bucle de limpieza. El valor existe en el vocabulario para que una versión posterior no tenga que archivarlo sin ruido bajo otro nombre, una función declara que todavía no se ofrece, y una prueba afirma que el clasificador nunca lo devuelve.
La distinción importante es la de la primera frase. Un punto de montaje puede conservar sus datos y aun así no volver, porque la llamada de creación que enviamos solo fija el sistema de archivos raíz. Todos los demás montajes desaparecen del contenedor nuevo, pase lo que pase con sus bytes. Invertir eso produce la frase más peligrosa de toda esta funcionalidad: preserved junto a un montaje que después no va a existir.
La propiedad está en el identificador de volumen
Proxmox escribe un volumen con una de dos formas, y las dos dicen qué invitado es su dueño. local-lvm:vm-5001-disk-0 y local:5001/vm-5001-disk-0.raw pertenecen los dos al contenedor 5001. Un bind mount no es ninguna de las dos: es una ruta absoluta, y la barra inicial es lo que comprueba el propio Proxmox.
Ese análisis decide entre excluded y erased. Un punto de montaje de la configuración de este contenedor que apunta al volumen de otro invitado queda fuera de la destrucción, porque Proxmox comprueba la propiedad, de modo que esos datos no se borran. Sin la comparación, la vista previa diría erased sobre datos que se quedan. Alguien restauraría entonces una copia de seguridad que no necesitaba, que es un desastre menor que el contrario pero un desastre que causamos igual.
La vista previa es una condición previa, no una foto
Lo que vio el cliente viaja de vuelta con el comando de aplicación como una lista de confirmaciones, y el agente lee la configuración otra vez justo antes de destruir. Si difiere, un punto de montaje añadido, un volumen distinto bajo la misma clave, se niega.
Entre la vista previa y el clic hay un ser humano, y en ese tiempo un administrador puede conectar un disco desde la interfaz de Proxmox. «Los bind mounts y los puntos de montaje gestionados por el host no forman parte automáticamente de una reinstalación» solo es cierto si algo lo hace cumplir.
Cada confirmación lleva una clave y un volumen, y deliberadamente no la etiqueta. La etiqueta es el juicio de la plataforma y no tiene nada que decidir en el otro extremo; si se le entregase al agente como parte de aquello con lo que compara, habría una rama que eligiese en función de ella, y entonces las reglas vivirían en dos sitios. La etiqueta sí queda escrita, junto a las confirmaciones en el registro del comando y en la fila de intención, porque ese es el lado de auditoría, y ahí tiene que decir exactamente lo que se le mostró al cliente.
Solo crecer, comparado en bytes, mostrado en gibibytes
Una reinstalación no puede encoger el sistema de archivos raíz. La comparación se hace sobre bytes y la pantalla redondea hacia arriba a gibibytes enteros.
Redondear hacia arriba es la dirección conservadora, y el contraejemplo dice por qué. Si 8,5 GiB se redondean hacia abajo a «8 GiB» en la pantalla y contra esa cifra se acepta una petición de 8 GiB, se pierde medio gibibyte que nadie pidió perder. Medido contra un Proxmox reconstruido, un contenedor que informaba de 16 GiB donde el plan pedía 8 fue rechazado.
Hay una trampa una capa más abajo. Proxmox escribe los tamaños como 8G, 512M o como un número pelado, y un número pelado son kibibytes. Leído como bytes, 8388608 son 8 MiB; leído correctamente son 8 GiB. Un analizador que se equivoque aquí no lanza nada, simplemente aprueba todos los encogimientos que le pongan delante.
Nueve razones para negarse
Antes de que ocurra nada irreversible hay nueve bloqueos con nombre: el contenedor ha desaparecido, está puesto protection=1, Proxmox mantiene un bloqueo sobre el invitado, se está ejecutando uno de nuestros propios trabajos de copia o de restauración, existe un punto de restauración protegido, el sistema de archivos raíz encogería, su tamaño actual no se pudo leer, un punto de montaje no se pudo clasificar, o ya hay una reinstalación en marcha.
Cada uno es un valor tipado con su propia frase traducida a nueve idiomas, así que una razón que no esté en la lista no puede tener una. Se comprueban dos veces en nuestro lado, una al dibujar la vista previa y otra al pulsar el botón de confirmación, y una tercera vez las comprueba el agente contra la configuración que lee él mismo. Tres cierres, porque este es el paso irreversible del capítulo.
La contraseña de root se pide en la segunda fase, no en la primera, y viaja como dato transitorio en lugar de almacenarse. Un secreto que se teclea justo antes del paso irreversible no pasa media hora en una pestaña del navegador.
Necesita reconciliación es el tercer desenlace, y mantiene el bloqueo
Si algo falla entre la destrucción y la creación, la ejecución no pasa a ser «fallida». Pasa a ser «necesita reconciliación», y lleva consigo el identificador del contenedor, el paso en el que murió y si el contenedor seguía existiendo cuando el agente se dio por vencido. Ese último campo puede ser null, y null no es false: «no se pudo determinar» no debe leerse como «el identificador está libre».
Que un fallo sea destructivo o no se deriva de la posición del paso dentro de la lista ordenada de etapas, de la destrucción en adelante. Por eso las etapas son una lista y no un conjunto de cadenas sueltas: la pregunta «¿había ocurrido ya algo irreversible?» se responde en exactamente un sitio.
Una ejecución en ese estado mantiene cerrado el bloqueo sobre el servidor. Empezar un segundo reemplazo sobre un identificador en el que nadie sabe si hay un contenedor sentado es precisamente lo que este diseño prohíbe, y tiene que cerrarlo una persona.
El perdedor de un compare and swap tiene que releer
No hay ningún trabajador que actualice estas filas. El estado avanza cuando alguien lo lee: la lectura mira el comando al que está enganchada la intención y hace avanzar la fila, con un compare and swap sobre un contador de generación, de modo que dos lectores simultáneos realizan juntos cada transición exactamente una vez.
Hasta ahí estaba diseñado. Lo que no estaba diseñado, y lo que atrapó la prueba de navegador, es qué debe devolver el perdedor de ese intercambio.
El panel consulta una vez por segundo después de pedir una vista previa, y esa consulta realiza la misma transición. Si el cliente carga la página en ese mismo segundo, la consulta gana el intercambio, la página devuelve su propia fila, ya obsoleta, y la pantalla sigue diciendo «esperando al agente» cuando la vista previa ya está en la base de datos. Una navegación, una petición y una pantalla que muestra un estado que ya no existía. Solo lo arreglaba una segunda recarga.
La corrección cabe en una frase y merece la pena interiorizarla: perder un compare and swap significa, por construcción, que otro acaba de hacer exactamente la transición que se quería. La respuesta correcta es por tanto su fila, y cuesta un select. Devolver la propia es lo intuitivo y siempre está mal.
Lo que dejaríamos por escrito
- Si aquello que se reemplaza no tiene imagen, conviene llamarlo reemplazo. La palabra cambia lo que se le debe al cliente antes de empezar.
- Etiquetar los datos y el montaje por separado. Son dos preguntas, y combinar una respuesta cierta a una con una respuesta falsa a la otra es la peor salida que puede producir el sistema.
- Hacer que la vista previa viaje de vuelta como condición, releerla en el otro extremo y negarse ante cualquier diferencia. En medio ha hecho clic una persona.
- Redondear de forma conservadora cuando un error de redondeo solo puede destruir.
- Dar a una operación destructiva a medio hacer un estado propio, mantener cerrado el bloqueo y no dejar nunca que caiga ni en éxito ni en fallo.
- Releer después de perder un compare and swap, siempre.
La compatibilidad con contenedores en Proxmox se está desplegando por etapas, y la reinstalación queda tarde en ese orden por las razones de arriba. Para quien esté antes en esa secuencia, las contrapartidas entre los dos tipos de invitado están en ¿máquina virtual o contenedor en Proxmox?, y la razón por la que una llamada de API correcta no es un trabajo terminado está en por qué un 200 de Proxmox no es un resultado.
Del trabajo de Proxmox en Provibr.