Instalación de CentOS Stream 10: configuración “Next RHEL” para laboratorios y CI

¿Te fue útil?

No hay nada que grite “cultura de ingeniería productiva” como un host de laboratorio que arranca en una shell de emergencia cinco minutos antes de cortar una release. O un runner de CI que empieza a perder builds porque se acabaron los inodos, no el disco. Esos no son fallos interesantes. Son fallos evitables—por lo general causados por una instalación descuidada y un modelo mental aún más descuidado.

CentOS Stream 10 encaja muy bien para laboratorios y CI porque está lo bastante cerca de RHEL para enseñarte las mismas lecciones operativas, pero también es donde los cambios aparecen antes—exactamente lo que quieres cuando validas pipelines, controladores, comportamiento del kernel y tus propias suposiciones.

Qué es realmente CentOS Stream 10 (y por qué lo quieres)

CentOS Stream no es “RHEL gratis.” Es una distribución entregada de forma continua que se sitúa entre Fedora y RHEL en el flujo de desarrollo. En términos prácticos: es donde aterrizan los cambios antes de congelarse en la próxima release menor de RHEL. Para laboratorios y CI, eso es útil porque puedes detectar cambios ABI, movimientos en las herramientas y rarezas de empaquetado antes de que afecten a tus flotas de pago—o a las de tus clientes.

Aquí hay una trampa: la gente instala Stream y lo trata como un SO empresarial de larga vida y dormido. No lo hagas. Trátalo como un canario controlado, parecido a producción. Si quieres algo que puedas ignorar durante tres años, usa otra cosa y sé honesto al respecto.

Cuando CentOS Stream 10 es la decisión correcta

  • Compilas RPMs o módulos de kernel y quieres advertencias tempranas sobre cambios en el buildroot.
  • Tu CI necesita un comportamiento tipo RHEL: systemd, SELinux, firewalld, NetworkManager y la misma herramienta general.
  • Quieres validar roles de Ansible y bases de hardening contra el “próximo RHEL.”
  • Ejecutas KVM/libvirt o hosts de contenedores y necesitas una base relativamente estable, pero toleras que las actualizaciones se muevan más rápido que en el clásico entorno empresarial.

Cuando no es la decisión correcta

  • Tu laboratorio es en realidad un entorno de producción sombra sin control de cambios.
  • No puedes parchear con frecuencia o no tienes puertas de prueba (test gates).
  • Tu “runner de CI” es una VM mascota a la que la gente accede por SSH y modifica a mano.

Hechos e historia que importan operativamente

Aquí hay datos concretos de contexto que realmente cambian decisiones, no trivia para hablar en conferencias:

  1. CentOS Stream se presentó como una vista previa continua de RHEL, cambiando el viejo modelo donde CentOS Linux se reconstruía tras las releases de RHEL. Eso modifica tu perfil de riesgo: estás más cerca del frente de cambios.
  2. Las réplicas de RHEL solían ser la opción por defecto “Linux empresarial gratuito”, lo que entrenó a toda una generación a tratar las reconstrucciones como idénticas. Stream rompe esa suposición por diseño.
  3. systemd ha sido el sistema init para la familia RHEL durante años, y en Stream verás algunos valores por defecto de servicios cambiar primero (timeouts, dependencias, opciones de hardening).
  4. NetworkManager ya no es opcional en la mayoría de despliegues reales, porque las herramientas, el comportamiento de cloud-init y el nombrado moderno de NIC lo asumen. Oponerse a ello es perder tiempo.
  5. SELinux en modo “Enforcing” es el valor por defecto en entornos sensatos, y el ecosistema RHEL pasó una década haciéndolo viable. Apagarlo sigue siendo popular—sobre todo entre quienes no hacen guardias on-call.
  6. Anaconda históricamente ha sido potente y fácil de clicar mal, especialmente en particionado personalizado y colocación del cargador de arranque. “Creo que hice clic en el disco correcto” no es una estrategia de almacenamiento.
  7. DNF reemplazó a YUM como el gestor de paquetes visible al usuario hace años, y el comportamiento de metadata de repos (y cachés) importa cuando CI tira cientos de paquetes al día.
  8. cgroups v2 se ha convertido en el estándar en sistemas modernos tipo RHEL, lo que cambia el comportamiento de contenedores y límites de recursos respecto a flotas antiguas con memoria muscular v1.
  9. Podman (rootless) es una herramienta de contenedores de primera clase en la familia RHEL, y suele ser una mejor opción por defecto para muchos entornos CI que “simplemente ejecutar Docker como root y rezar.”

Objetivos de diseño para laboratorios y CI (elige un bando)

Antes de arrancar un ISO del instalador, decide qué estás optimizando. Laboratorios y CI no son lo mismo, pero comparten un requisito: fallos previsibles.

Objetivo 1: Reproducibilidad por encima de ingenio

Para runners de CI, quiero una imagen que pueda reconstruirse desde cero y converger con automatización en menos de una hora. Si no puedes reconstruirla rápido, la conservarás “por si acaso”, y así acabas depurando un host donde el último cambio humano ocurrió en otro año fiscal.

Objetivo 2: El almacenamiento debe fallar de forma aburrida

Las cargas de CI son brutales con el almacenamiento: muchos archivos pequeños, alta rotación, cachés que crecen hasta un límite y logs que no paran. Tu diseño debe hacer que “disco lleno” sea ruidoso y localizado, no silencioso y global.

Objetivo 3: Mantener los valores de seguridad activados

No desactives SELinux porque un build falló una vez. Arregla el etiquetado. No vacíes el firewall porque tu runner de pruebas no llega a un puerto. Abre el puerto. Si tu laboratorio está en una red plana, la mayor amenaza no son hackers de Hollywood—es el “servicio temporal” de otro equipo escuchando en 0.0.0.0.

Una realidad seca: un host de laboratorio sin salvaguardas es simplemente producción, excepto que nadie lo admite. Así es como surgen outages “sorpresa” que en realidad son “rendición de cuentas sorpresa”.

Ruta de instalación: del ISO al primer arranque sin drama

Puedes instalar Stream 10 de forma interactiva o vía Kickstart. Para laboratorios y CI, Kickstart gana porque convierte el conocimiento tribal en un archivo. Las instalaciones interactivas están bien para una VM de prueba puntual, pero también son la forma en que “construcción estándar” se convierte en cinco construcciones distintas.

Elige el perfil de instalación correcto

Para runners de CI y servidores de laboratorio sin GUI: instala un entorno mínimo más los paquetes que necesitas. Las instalaciones con GUI son convenientes hasta que parcheas 50 hosts y te das cuenta de que has estado arrastrando una pila de escritorio como un lastre.

UEFI vs BIOS: elige UEFI a menos que tengas una razón

Servidores modernos y VMs deberían ser UEFI. Es aburrido, consistente y las herramientas han madurado. BIOS/legacy es para compatibilidad con hipervisores antiguos o chatarra embebida que no puedes reemplazar.

Postura Kickstart

Un Kickstart para laboratorios/CI debería hacer estas cosas:

  • Fijar explícitamente el disco de instalación (no confiar en el “primer disco”).
  • Definir particiones y volúmenes LVM con tamaños intencionales y reglas de crecimiento.
  • Crear un usuario admin con claves SSH (y bloquear SSH por contraseña).
  • Habilitar SELinux en modo enforcing y firewalld.
  • Configurar zona horaria, NTP y un esquema de nombres de host estable.
  • Opcionalmente registrar repos/mirror internos si el tráfico de CI es alto.

Broma #1: Trata “Next, Next, Finish” como un arma cargada. Solo hace falta un clic para enseñarle a tu cargador de arranque discos nuevos y emocionantes.

Diseño de almacenamiento: particiones, LVM y dominios de fallo

El almacenamiento es donde la mayoría de las instalaciones de laboratorio/CI se vuelven perezosas. Y luego el almacenamiento se convierte en el cuello de botella, y todo el mundo culpa “a la red” porque es reconfortante culpar cosas que no puedes ver.

Cómo es un diseño bueno

Para una VM de disco único o un nodo bare-metal pequeño, un valor sensato por defecto es:

  • Partición del sistema UEFI (ESP): pequeña, fija.
  • /boot: tamaño fijo, ext4.
  • PV LVM para todo lo demás.
  • LV separados para / (root), /var, y opcionalmente /var/lib/containers o /var/lib/libvirt.

¿Por qué separar /var? Porque CI escribe en /var como si le pagaran por byte. Logs, cachés de paquetes, capas de contenedores, artefactos de build y archivos temporales adoran /var. Si /var se llena y está en el mismo sistema de archivos que /, no obtienes un problema de “disco lleno”—obtienes un problema de “el sistema no puede escribir estado”. Esa es otra clase de dolor.

Ext4 vs XFS

XFS es común en ecosistemas tipo RHEL y se comporta bien con archivos grandes y IO paralelo. Ext4 sigue siendo una opción perfectamente respetable para /boot y a veces para volúmenes más pequeños. La regla real: no te pongas creativo. Usa lo que tus herramientas esperan y lo que tu equipo puede recuperar a las 3 a.m.

LVM thin: ten cuidado

La provisión thin de LVM puede parecer espacio libre. No es espacio libre. Es una promesa a tu futuro yo de que vas a monitorizar el uso del pool y reaccionar antes de que llegue al 100%. Los thin pools son geniales en setups virtualizados donde entiendes la sobreasignación. Son catastróficos cuando nadie los vigila.

Swap: elige una política, no una vibra

Para runners de CI con picos de memoria, algo de swap puede evitar que el kernel mate tu job de build. Para hosts sensibles a latencia, demasiado swap puede ocultar presión de memoria hasta que el rendimiento se vuelve “misteriosamente lento”. Si no estás seguro: mantén un swap modesto y confía en el monitoreo para ajustar.

Línea base de red: IP previsibles, DNS previsible

Runners de CI que fallan porque DNS falló no es una historia heroica. Es un fallo de lo básico.

Direcciones estáticas vs DHCP

Para runners efímeros de CI creados y destruidos automáticamente, DHCP está bien si tu DHCP y DNS son fiables e integrados. Para hosts de laboratorio de larga vida y bare metal, las IP estáticas reducen sorpresas. Si haces IPs estáticas, hazlas vía perfiles de NetworkManager, no editando archivos al azar y rezando para recordar lo hecho.

Nombres de host y dominios de búsqueda

Elige un esquema de nombres que sobreviva al reimaging. Por ejemplo: rol + sitio + índice. No codifiques secretos ni dramas de propiedad en los hostnames. Además, mantén los dominios de búsqueda al mínimo. Dominios de búsqueda demasiado amplios causan retrasos y comportamiento extraño de resolución cuando el DNS interno está mal.

Línea base de seguridad: SELinux, firewalld y SSH

Los controles de seguridad no solo tratan de seguridad. Tratan de previsibilidad operativa. SELinux y firewalld te obligan a ser explícito sobre lo que hacen tus servicios. Eso hace que tus sistemas sean más fáciles de razonar.

SELinux: mantenlo en enforcing

El modo enforcing atrapa mal etiquetado, valores por defecto erróneos y contenedores “funcionó en mi portátil”. Si algo se rompe, tu primer instinto debe ser: lee las denegaciones AVC y corrige etiquetas o política. Tu último instinto debe ser: setenforce 0.

firewalld: define zonas y abre solo lo necesario

Los hosts de CI típicamente necesitan SSH entrante y quizá scraping de métricas entrante. No necesitan que el mundo alcance puertos efímeros. Si ejecutas un mirror de registry o un cache de artefactos, eso es diferente—abre esos puertos intencionalmente y documéntalo.

SSH: claves, no contraseñas

Desactiva la autenticación por contraseña si puedes. Si no puedes, al menos restringe al tráfico de redes internas y fuerza contraseñas fuertes. Los runners de CI no deben ser un lugar donde la fuerza bruta por contraseña tome pie.

Una frase para mantener cerca del terminal: La esperanza no es una estrategia. — Gene Kranz

Elecciones de runtime CI: Podman, contenedores y virtualización

Para laboratorios y CI, normalmente eliges uno de tres patrones:

  • Builds en el host: instala toolchains en el host. Rápido, pero se degrada y se convierte en territorio snowflake.
  • Builds en contenedores: aísla entornos de build con Podman. Reproducible, limpieza más fácil, buen valor por defecto.
  • Builds en VM: OS completo por job vía libvirt/KVM. Más pesado, pero más cercano al comportamiento real de despliegue.

Mi sesgo: builds en contenedores para la mayoría de pipelines, builds en VM para trabajo de kernel/controlador y “necesitamos probar arranque y servicios del sistema”, y builds en host solo cuando realmente es necesario (como toolchains ligados a hardware).

Tareas prácticas con comandos (y qué decidir a partir de la salida)

Estos no son comandos de juguete. Son cosas que ejecutas el primer día, y otra vez cuando algo se siente mal. Cada tarea incluye: comando, salida de ejemplo, qué significa y qué decides después.

Tarea 1: Confirma que instalaste lo que crees que instalaste

cr0x@server:~$ cat /etc/os-release
NAME="CentOS Stream"
VERSION="10"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="10"
PLATFORM_ID="platform:el10"
PRETTY_NAME="CentOS Stream 10"

Significado: Estás en Stream 10 y el identificador de plataforma es tipo EL10. Si esto dice otra cosa, tu pipeline de imágenes está mintiendo.

Decisión: Si no es exactamente lo que esperas, detente y arregla la fuente de build. No “continúes de todos modos”.

Tarea 2: Revisa kernel y modo de arranque (UEFI vs legacy)

cr0x@server:~$ uname -r
6.12.0-0.el10.x86_64
cr0x@server:~$ test -d /sys/firmware/efi && echo UEFI || echo BIOS
UEFI

Significado: La versión del kernel te dice qué estás depurando. El modo de arranque importa para grub, comportamiento de secure boot y cómo se particionan los discos.

Decisión: Estandariza en UEFI para nuevas builds salvo que una limitación de plataforma te obligue a BIOS.

Tarea 3: Identifica discos y asegúrate de particionar el correcto

cr0x@server:~$ lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS,MODEL
NAME        SIZE TYPE FSTYPE MOUNTPOINTS MODEL
sda         200G disk                 QEMU HARDDISK
├─sda1      600M part vfat   /boot/efi
├─sda2        1G part ext4   /boot
└─sda3    198.4G part LVM2_member
  ├─cs-root   40G lvm  xfs    /
  ├─cs-var    80G lvm  xfs    /var
  └─cs-home   20G lvm  xfs    /home

Significado: Puedes ver el mapa de particiones y qué está montado. Si ves tu disco de datos sosteniendo /boot, ya has tenido un mal día.

Decisión: Si se usó el disco equivocado, reinstala. Intentar “arreglarlo luego” suele costar más que una reconstrucción limpia.

Tarea 4: Verifica capacidad de sistema de archivos y holgura de inodos

cr0x@server:~$ df -hT
Filesystem          Type  Size  Used Avail Use% Mounted on
/dev/mapper/cs-root xfs    40G  3.2G   37G   8% /
/dev/mapper/cs-var  xfs    80G   12G   68G  15% /var
/dev/sda2           ext4  1020M  238M  713M  26% /boot
cr0x@server:~$ df -i
Filesystem           Inodes IUsed   IFree IUse% Mounted on
/dev/mapper/cs-var  41943040 92321 41850719    1% /var

Significado: Espacio y disponibilidad de inodos. CI puede quedarse sin inodos mucho antes que sin espacio, especialmente con ecosistemas de lenguajes que aman archivos pequeñísimos.

Decisión: Si /var es pequeño o el uso de inodos sube rápido, asigna más a /var ahora. Tu yo futuro no lo hará con calma.

Tarea 5: Revisa salud de LVM y extents libres para crecimiento

cr0x@server:~$ vgs
  VG #PV #LV #SN Attr   VSize   VFree
  cs   1   3   0 wz--n- 198.38g 58.38g
cr0x@server:~$ lvs -a -o lv_name,vg_name,lv_size,lv_attr,data_percent,metadata_percent
  LV   VG  LSize  Attr       Data%  Meta%
  root cs  40.00g -wi-ao----      
  var  cs  80.00g -wi-ao----      
  home cs  20.00g -wi-ao----      

Significado: Tienes espacio libre en el VG para crecer /var cuando CI se ponga exigente. Si ves VFree = 0, lo dimensionaste al límite.

Decisión: Deja holgura en el VG. “Usamos toda la capacidad del disco” no es un logro; es un incidente futuro.

Tarea 6: Confirma cadencia de actualizaciones y salud de repos

cr0x@server:~$ dnf repolist
repo id                       repo name
baseos                        CentOS Stream 10 - BaseOS
appstream                     CentOS Stream 10 - AppStream
cr0x@server:~$ dnf check-update
Last metadata expiration check: 0:12:17 ago on Tue 06 Feb 2026 09:10:44 AM UTC.
kernel.x86_64                  6.12.2-0.el10           baseos

Significado: Los repos son alcanzables, la metadata está actual y hay actualizaciones. En Stream, las actualizaciones son parte del trato.

Decisión: Si la expiración de metadata es enorme o repolist está vacío, arregla DNS/proxy/mirrors antes de confiar el host para CI.

Tarea 7: Valida sincronización horaria (a CI le disgusta el skew)

cr0x@server:~$ timedatectl
               Local time: Tue 2026-02-06 09:23:18 UTC
           Universal time: Tue 2026-02-06 09:23:18 UTC
                 RTC time: Tue 2026-02-06 09:23:18
                Time zone: UTC (UTC, +0000)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no
cr0x@server:~$ chronyc tracking
Reference ID    : 0A0B0C0D (ntp1.example)
Stratum         : 3
Last offset     : -0.000021 seconds
RMS offset      : 0.000112 seconds

Significado: El reloj está sincronizado. Firmado de tokens, TLS, timestamps de artefactos y builds distribuidos fallan de maneras estúpidas con skew de tiempo.

Decisión: Si NTP no está activo, arréglalo antes de depurar fallos “aleatorios” de TLS.

Tarea 8: Inspecciona la configuración de red a la manera de NetworkManager

cr0x@server:~$ nmcli -t -f NAME,DEVICE,TYPE,STATE con show --active
Wired connection 1:ens192:802-3-ethernet:activated
cr0x@server:~$ nmcli dev show ens192 | egrep 'IP4.ADDRESS|IP4.GATEWAY|IP4.DNS'
IP4.ADDRESS[1]:                         10.20.30.40/24
IP4.GATEWAY:                            10.20.30.1
IP4.DNS[1]:                             10.20.30.10

Significado: Tienes un perfil de conexión activo y una IP/DNS sensata. Si CI no puede resolver repos, aquí es donde empiezas.

Decisión: Si el DNS apunta a algo raro (como un router de consumo), arréglalo. No “arregles” con entradas en /etc/hosts.

Tarea 9: Revisa modo SELinux y denegaciones recientes

cr0x@server:~$ getenforce
Enforcing
cr0x@server:~$ sudo ausearch -m avc -ts recent | tail -n 5
type=AVC msg=audit(1738833941.112:842): avc:  denied  { name_connect } for  pid=2213 comm="podman" dest=53 scontext=system_u:system_r:container_t:s0 tcontext=system_u:object_r:dns_port_t:s0 tclass=tcp_socket permissive=0

Significado: SELinux está en enforcing y tienes una denegación AVC que involucra a un contenedor intentando alcanzar DNS. Esto no es “SELinux molestando”; es una señal sobre etiquetado/política y reglas de red de contenedores.

Decisión: Investiga el contexto y la política necesaria; no desactives SELinux globalmente. Arregla la causa raíz (política de red de contenedores, etiquetado del puerto DNS o configuración del runtime de contenedores).

Tarea 10: Verifica estado de firewalld y puertos abiertos

cr0x@server:~$ sudo systemctl status firewalld --no-pager
● firewalld.service - firewalld - dynamic firewall daemon
     Loaded: loaded (/usr/lib/systemd/system/firewalld.service; enabled; preset: enabled)
     Active: active (running) since Tue 2026-02-06 09:02:11 UTC; 22min ago
cr0x@server:~$ sudo firewall-cmd --get-active-zones
public
  interfaces: ens192
cr0x@server:~$ sudo firewall-cmd --list-services
ssh

Significado: El firewall está activo, la interfaz está en la zona public, solo SSH está abierto. Buena línea base.

Decisión: Si necesitas node_exporter, añade el puerto/servicio explícitamente. Si ves “services: dhcpv6-client samba cockpit whatever,” límpialo.

Tarea 11: Revisa presión de recursos del sistema (CPU, memoria, IO) de forma rápida

cr0x@server:~$ uptime
 09:27:51 up  1:12,  1 user,  load average: 0.26, 0.18, 0.09
cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:           15Gi       1.8Gi       11Gi       170Mi       2.4Gi        13Gi
Swap:          4.0Gi         0B       4.0Gi

Significado: La carga es baja, la memoria está saludable, swap sin uso. Si los jobs de CI están lentos, probablemente no sea saturación cruda de CPU ahora mismo.

Decisión: Si la carga es alta y la memoria disponible baja, decide si añadir RAM, reducir concurrencia o aislar jobs ruidosos.

Tarea 12: Detecta cuellos de botella de IO y picos de latencia

cr0x@server:~$ iostat -xz 1 3
Linux 6.12.0-0.el10.x86_64 (runner01)  02/06/2026  _x86_64_ (4 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.10    0.00    3.20    8.60    0.00   76.10

Device            r/s     rkB/s   rrqm/s  %rrqm r_await rareq-sz     w/s     wkB/s   wrqm/s  %wrqm w_await wareq-sz aqu-sz  %util
sda              8.00    320.0     0.00   0.00    6.20    40.00   45.00   2048.0     2.00   4.26   18.40    45.51   0.92  68.00

Significado: iowait no es trivial, write await es algo alto y la utilización del disco es alta. Perfil clásico de runner CI: escrituras pesadas, churn de metadata y cachés.

Decisión: Considera almacenamiento más rápido, separar discos de build/workspace o mover cachés a tmpfs/ramdisk selectivamente (con límites). También reducir concurrencia por nodo.

Tarea 13: Confirma que journald/retención de logs no consumirá /var

cr0x@server:~$ sudo journalctl --disk-usage
Archived and active journals take up 1.2G in the file system.
cr0x@server:~$ sudo grep -E 'SystemMaxUse|RuntimeMaxUse' /etc/systemd/journald.conf
#SystemMaxUse=
#RuntimeMaxUse=

Significado: Los journals ya usan espacio y no hay un tope explícito. En nodos CI chatty eso crece hasta que golpea el límite del sistema de archivos.

Decisión: Fija SystemMaxUse (y quizá SystemMaxFileSize) a un tope sensato según tu dimensionamiento de /var.

Tarea 14: Valida ubicación de almacenamiento de contenedores y su crecimiento

cr0x@server:~$ sudo podman info --format '{{.Store.GraphRoot}}'
/var/lib/containers/storage
cr0x@server:~$ sudo du -sh /var/lib/containers/storage
6.4G	/var/lib/containers/storage

Significado: Las capas de contenedores viven bajo /var. Por eso importa dimensionar /var.

Decisión: Si ejecutas builds pesados de contenedores, considera colocar el almacenamiento de contenedores en su propio LV (o disco dedicado) para evitar asfixiar el estado del sistema.

Tarea 15: Revisa cgroups v2 y compatibilidad de contenedores

cr0x@server:~$ stat -fc %T /sys/fs/cgroup/
cgroup2fs

Significado: Estás en cgroups v2. Algunas herramientas de contenedores antiguas y agentes de monitoreo aún asumen semántica v1.

Decisión: Asegúrate de que tu tooling CI soporta cgroups v2. Si no, actualiza tooling en lugar de degradar el comportamiento del SO salvo que no te quede opción.

Tarea 16: Revisa preparación para virtualización (si ejecutas runners KVM/libvirt)

cr0x@server:~$ lscpu | egrep 'Virtualization|Vendor ID|Model name'
Vendor ID:                       GenuineIntel
Model name:                      Intel(R) Xeon(R) CPU
Virtualization:                  VT-x
cr0x@server:~$ lsmod | egrep 'kvm|kvm_intel'
kvm_intel             503808  0
kvm                  1490944  1 kvm_intel

Significado: La CPU soporta virtualización y los módulos KVM están cargados.

Decisión: Si la virtualización no está disponible, no pierdas tiempo depurando libvirt. Arregla opciones de BIOS o elige otro host/perfil de hipervisor.

Playbook de diagnóstico rápido

Este es el playbook que quiero en la pared junto al cluster CI. Cuando los builds se vuelven lentos o las instalaciones fallan, no “hurgas”. Sigues el camino más corto al cuello de botella.

Primero: demuestra que no es DNS/acceso a repos

  • Revisa resolución DNS y accesibilidad a repos/mirrors. Las fallas de CI a menudo se presentan como “install package failed” pero la raíz es resolución de nombres o configuración de proxy.
  • Confirma sincronización horaria. Fallos TLS y errores de metadata de repo pueden ser skew de tiempo.
cr0x@server:~$ getent hosts mirror.internal
10.20.30.50   mirror.internal
cr0x@server:~$ chronyc tracking | head
Reference ID    : 0A0B0C0D (ntp1.example)

Decisión: Si DNS es lento o inestable, arréglalo antes de tocar otra cosa. No puedes optimizar un sistema que no encuentra sus dependencias.

Segundo: revisa presión de almacenamiento y latencia de IO

  • ¿Disco lleno? ¿Inodos agotados? ¿Thin pool lleno? Estas se presentan como “fallos aleatorios”.
  • ¿Latencia IO (await) alta? Por eso los builds van lentos.
cr0x@server:~$ df -hT | sed -n '1,6p'
Filesystem          Type  Size  Used Avail Use% Mounted on
/dev/mapper/cs-root xfs    40G  3.2G   37G   8% /
/dev/mapper/cs-var  xfs    80G   78G  2.0G  98% /var
cr0x@server:~$ iostat -xz 1 2 | tail -n 5
sda              9.00    360.0     0.00   0.00    8.10    40.00   60.00   2600.0     1.00   1.64   25.90    43.33   1.40  85.00

Decisión: Si /var está al 98%, para. Limpia cachés/logs o agranda el LV. Si await de disco es alto, reduce concurrencia o mejora el almacenamiento.

Tercero: revisa CPU, memoria y contención del scheduler

  • Carga alta con iowait bajo? Probable saturación de CPU.
  • Poca memoria disponible con actividad de swap? Presión de memoria o jobs descontrolados.
  • Steal time alto? Contención del hipervisor; tu host VM está sobreasignado.
cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 1123456  81234 2234560  0    0     5    80  150  300 12  3 77  8  0
 3  1      0  123456  40000  900000  0    0     0  2000  500 1200 40 10 10 40  0

Decisión: Si st (steal) es no cero y persistente, el cuello de botella real está upstream. Escala con el equipo de virtualización o mueve runners.

Errores comunes: síntomas → causa raíz → solución

1) Síntoma: Jobs de CI fallan aleatoriamente con “No space left on device” pero df muestra espacio libre

Causa raíz: Inodos agotados (demasiados archivos pequeños) o un sistema de archivos distinto está lleno (a menudo /var o /tmp).

Solución: Revisa df -i y uso por punto de montaje. Aumenta capacidad de inodos redimensionando/reescribiendo el sistema de archivos (a largo plazo) y reduce churn de archivos (limpieza, retención de artefactos). Separa /var temprano.

2) Síntoma: Builds lentos solo en algunos runners

Causa raíz: Diferencias en latencia de almacenamiento (distintos tiers de disco), o steal time en hipervisores sobreasignados.

Solución: Compara iostat -xz y vmstat entre runners. Estandariza backend de disco. Deja de mezclar nodos “rápidos” y “lentos” en la misma pool a menos que el scheduler conozca la topología.

3) Síntoma: Tras una actualización, un servicio no arranca; los logs mencionan permiso denegado

Causa raíz: Denegación SELinux tras un cambio de ruta, un nuevo puerto o hardening de una unidad.

Solución: Usa ausearch -m avc para encontrar denegaciones, luego corrige el etiquetado con restorecon o ajusta la política. No desactives SELinux globalmente.

4) Síntoma: Host arranca en la shell de emergencia de dracut tras reiniciar

Causa raíz: UUID equivocado en fstab, drivers faltantes en initramfs o cambios en el orden de discos en una plantilla VM.

Solución: Arranca en rescue, verifica blkid y /etc/fstab, rebuild de initramfs si hace falta. Prefiere montar por UUID y nombres de dispositivo estables.

5) Síntoma: DNF es dolorosamente lento, “metadata download” se cuelga

Causa raíz: Problemas de DNS, MTU de proxy raro o selección de mirror con problemas.

Solución: Valida DNS (getent hosts), comprueba MTU y prefiere mirrors internos para entornos CI intensivos.

6) Síntoma: Builds de contenedores fallan tras una actualización del SO, pero los builds en host siguen funcionando

Causa raíz: Expectativas de cgroups v2, restricciones de red rootless, o cambios de política SELinux que afectan almacenamiento de contenedores.

Solución: Confirma cgroups (stat -fc %T /sys/fs/cgroup), revisa podman info, estudia denegaciones AVC y actualiza tooling/imágenes de contenedores.

7) Síntoma: SSH funciona desde algunas subredes pero no desde otras

Causa raíz: Asignación de zona en firewalld o mismatch de ACLs en la red upstream.

Solución: Revisa zonas activas e interfaces, luego aplica reglas explícitas. No desactives firewalld porque la red es difícil.

Tres mini-historias corporativas (de las que no te jactas)

Mini-historia 1: El incidente causado por una suposición errónea

La compañía tenía un cluster CI ordenado: una docena de VMs, un par de caches de build y una ventana de parches semanal que todo el mundo ignoraba hasta que algo se rompía. Decidieron pasarse de una réplica de RHEL a CentOS Stream para “compatibilidad temprana”. Suena responsable.

La suposición errónea fue sutil: asumieron que Stream se comportaba como su vieja reconstrucción en una cosa específica—estabilidad de repos. Su pipeline empezó a tirar actualizaciones durante la jornada laboral porque alguien dejó un timer de dnf -y update programado en los runners. Una tarde, llegó una actualización de toolchain, cambió la versión menor del compilador y un subconjunto de builds empezó a producir artefactos ligeramente diferentes. Nada falló obviamente al principio. Las comparaciones de checksum sí, y entonces el proceso de release se detuvo como si hubiera chocado contra un muro.

Los equipos de ingeniería discutieron sobre “builds no deterministas” y “tal vez la capa de caching está corrupta”. No era ninguno. Eran los runners actualizándose a sí mismos a mitad de camino. Lo doloroso no fue el arreglo; lo doloroso fue darse cuenta de que no tenían política para cuándo y cómo pasan las actualizaciones en la infraestructura CI.

Se recuperaron congelando actualizaciones en horario laboral, construyendo imágenes golden semanalmente y desplegando esas imágenes primero por una pool de staging. Stream no fue el villano. El villano fue la creencia de que las actualizaciones son algo que haces cuando te acuerdas.

Mini-historia 2: La optimización que salió mal

Otra organización tuvo un plan de almacenamiento ingenioso: usaron LVM thin provisioning para workspaces de CI porque les permitía “asignar” volúmenes enormes sin comprar más disco. En papel era elegante. En realidad era un thin pool compartido por demasiados proyectos entusiastas.

También “optimizaron” subiendo la concurrencia de jobs. Los builds se hicieron más rápidos—hasta que no. Luego una mañana múltiples runners empezaron a fallar con errores de sistema de archivos. El thin pool llegó al 100% de uso de datos. La provisión thin no falla de forma gradual; falla como una trampa. Las escrituras se estancan, los sistemas de archivos entran en pánico y todos aprenden de golpe qué significa “metadata percent”.

La solución inmediata fue fea: parar el mundo, borrar caches y extender el almacenamiento subyacente. La solución a largo plazo fue aburrida: poner monitorización en uso del thin pool, imponer cuotas por proyecto y dejar de sobreasignar almacenamiento que no tiene guardrails operativos.

Mantuvieron la provisión thin, pero solo donde tenían alertas y propiedad clara. La optimización no era mala. Era prematura, no monitorizada y vendida internamente como “capacidad gratis”, que es la mentira más cara en almacenamiento.

Mini-historia 3: La práctica aburrida pero correcta que salvó el día

Un tercer equipo tenía la reputación de ser “lento” porque insistían en builds basados en Kickstart y una pipeline estricta de imágenes base. Los desarrolladores querían libertad para tocar runners directamente. Los SRE dijeron que no y recibieron quejas en reuniones.

Luego una actualización de kernel en el laboratorio descubrió una regresión que afectaba a un controlador de NIC bajo carga intensa. Algunos runners empezaron a perder conexiones de red durante uploads de artefactos. Parecía una flaky aleatoria—exactamente el tipo que hace perder semanas.

Porque el equipo tenía una pipeline de imágenes golden, pudieron hacer rollback del kernel conocido-bueno en la flota de forma controlada. Más importante: pudieron reproducir el problema levantando runners de prueba desde ambas imágenes y comparando comportamiento bajo cargas idénticas. No hubo arqueología. No hubo “quién cambió qué”. Solo experimentación controlada.

El postmortem fue casi decepcionantemente calmado. Su práctica aburrida—imágenes casi inmutables, actualizaciones controladas y una pool de staging—convirtió un posible festival de culpas en un evento operativo pequeño y contenido. Enviaron a tiempo. Nadie escribió un hilo dramático en Slack. Eso es una victoria.

Broma #2: El mejor runner de CI es como una buena plomería—nadie lo nota hasta que alguien intenta “optimizarlo”.

Listas de verificación / plan paso a paso

Plan de instalación paso a paso (interactivo o guiado por Kickstart)

  1. Decide tu rol: runner de CI, hypervisor de laboratorio o host de pruebas de propósito general. Esto define almacenamiento y selección de paquetes.
  2. Elige arranque UEFI (salvo restricciones) y confirma la configuración de firmware de la VM antes de instalar.
  3. Selecciona instalación mínima más paquetes requeridos; evita la GUI salvo que la necesites explícitamente.
  4. Diseño de almacenamiento:
    • Crea ESP y particiones /boot fijas.
    • Crea PV y VG LVM con espacio sobrante.
    • Crea LV separado para /var dimensionado para logs + contenedores + cachés.
  5. Red: configura vía NetworkManager, asegúrate de que DNS apunte a un resolvedor fiable.
  6. Tiempo: configura zona horaria UTC para flotas de servidores; habilita NTP.
  7. Usuarios: crea usuario admin; restringe SSH a claves si es posible.
  8. Seguridad: mantiene SELinux en enforcing; mantiene firewalld habilitado.
  9. Política de actualizaciones: decide cadencia de parches y si los runners se autoactualizan. Mi consejo: no autoactualizar runners sin gates.
  10. Snapshot/imagen golden: captura una imagen base solo después de que pasen los comandos de validación.

Checklist de validación post-instalación (ejecuta antes de añadir al pool CI)

  • Identidad del SO correcta: /etc/os-release
  • Modo de arranque correcto: chequeo UEFI
  • Layout de disco correcto: lsblk, df -hT, df -i
  • Holgura LVM disponible: vgs
  • Repos saludables: dnf repolist, dnf check-update
  • Sincronización horaria: timedatectl, chronyc tracking
  • Red correcta: salida de nmcli coincide con IP/DNS previstos
  • SELinux en enforcing: getenforce; sin AVC inesperado spam
  • firewalld habilitado y mínimo: firewall-cmd
  • Sanidad de rendimiento base: iostat, vmstat bajo un build de muestra

Checklist operativo (semanal)

  • Aplicar actualizaciones en ventana controlada; desplegar primero en la pool de staging.
  • Revisar tendencias de crecimiento de /var; limitar journald; podar capas de contenedores.
  • Verificar NTP; vigilar deriva de tiempo en VMs.
  • Revisar denegaciones AVC; abordar las recurrentes correctamente.
  • Confirmar que la concurrencia de CI corresponde a la capacidad de disco, no a deseos.

Preguntas frecuentes

1) ¿Es CentOS Stream 10 lo bastante estable para CI?

Sí, si tu CI está diseñado para absorber cambios: despliegues escalonados, imágenes reproducibles y una política de parches. Si tus runners son mascotas mantenidas a mano, Stream expondrá eso rápido.

2) ¿Debería usar Stream 10 en producción?

A veces, pero no lo trates como la opción por defecto. Para producción la pregunta no es tanto “puede funcionar” como “¿tienes disciplina operativa para gestionar actualizaciones que se mueven más rápido?” Muchas organizaciones no la tienen.

3) ¿Instalación mínima o servidor completo?

Mínima. Añade lo que necesites. Cada paquete extra es superficie de actualización y potencial conflicto en entornos CI.

4) ¿Realmente necesito un /var separado?

Si ejecutas cargas de CI, sí. /var es donde el estado del sistema y los desechos de CI de alta rotación colisionan. Separarlo es un seguro barato.

5) ¿XFS o ext4 para runners CI?

XFS es un buen valor por defecto para / y /var en sistemas tipo RHEL. Mantén ext4 para /boot. No mezcles sistemas de archivos exóticos a menos que tu equipo tenga playbook de recuperación y experiencia real.

6) ¿Debería desactivar SELinux para facilitar builds de contenedores?

No. Usa logs AVC para arreglar etiquetado/política. Desactivar SELinux cambia un problema de configuración solucionable por un riesgo continuo y comportamiento inconsistente entre entornos.

7) ¿Cómo evito que los runners deriven con el tiempo?

Imágenes golden + gestión de configuración. Reconstruye runners regularmente. Si un runner es “especial”, también es poco fiable.

8) Mis instalaciones DNF van lentas en CI. ¿Cuál es la mejor solución?

Usa un mirror interno o proxy de cache, y arregla DNS. Luego ajusta el comportamiento de caché de DNF. CI magnifica ineficiencias del gestor de paquetes en tiempo y dinero reales.

9) ¿Contenedores o VMs para jobs de CI?

Contenedores para la mayoría de builds y tests. VMs cuando necesites probar comportamiento de arranque, interacciones con el kernel o servicios del sistema en un entorno realista.

10) ¿Cuál es el cuello de botella más común en hosts CI basados en Stream?

Almacenamiento. Específicamente: /var llenándose, bloat de capas de contenedores y latencia IO bajo concurrencia. La CPU suele ser el segundo.

Conclusión: siguientes pasos que realmente reducen alarmas

CentOS Stream 10 es el tipo correcto de incomodidad para laboratorios y CI: te empuja hacia instalaciones disciplinadas, políticas de actualización claras e infraestructura que tolera el cambio. Si lo instalas como un SO de hobby, se comportará como tal. Si lo instalas como gestionas producción, se convierte en un sistema de avisos tempranos para el “próximo RHEL”.

Siguientes pasos prácticos:

  • Escribe (o arregla) tu Kickstart para que la selección de disco, el dimensionado de /var y los valores por defecto de seguridad sean explícitos.
  • Levanta una pool de CI de staging que parchee primero; solo promueve a la pool principal tras un día de ejecuciones limpias.
  • Limita journald, poda almacenamiento de contenedores y monitoriza uso de /var y consumo de inodos.
  • Adopta el playbook de diagnóstico rápido y hazlo la respuesta por defecto a “CI está lento”.
  • Decide tu política de actualizaciones por escrito. Luego aplícala con automatización, no con buenas intenciones.
← Anterior
Mitos sobre el pinning de CPU en Proxmox — La opción que empeora la latencia
Siguiente →
Cuenta de Microsoft vs Cuenta local: compensaciones de seguridad

Deja un comentario