Conservative SmartOS global-zone memory pressure guard with dry-run-first policy and SMF integration.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 

19 KiB

o6h-memory-guard 1.0.2

Primera implementación para SmartOS global zone: daemon C de 64 bits, libkstat, /proc, identidad UUID + generación de zona + PID + instante de creación, configuración separada y servicio SMF. Entrega de código fuente; no contiene un binario nativo validado en SmartOS. Véase VALIDATION.md.

Arranca en dry-run. El manifiesto añade --force-dry-run, por lo que cambiar sólo ACTION_MODE no arma el servicio. No se entrega ninguna zona autorizada. No se ha desplegado ni se han enviado señales reales durante la preparación.

Documentación de la entrega

  • README.md: instalación, semántica, política, operación y rollback.
  • VALIDATION.md: validación local realizada al construir esta entrega y comprobaciones nativas que en ese momento quedaban pendientes.
  • CHECKPOINT.md: checkpoint operativo posterior, incorporado sin cambios, con la evidencia acumulada de compilación, ejecución y logging de v1.0.2 en SmartOS real, además del estado exacto y los requisitos antes de armed.
  • CHANGELOG.md: evolución de versiones y revisión documental del paquete.

El checkpoint es cronológicamente posterior a VALIDATION.md; por ello amplía sus resultados de campo sin cambiar el código ni autorizar el modo armed.

Instalación inicial y activación en dry-run

Los comandos siguientes se ejecutan por el operador, como root en la global zone. Requieren gcc de 64 bits, cabeceras nativas y GNU make (gmake) en PATH. Si no hay herramientas de desarrollo, compilar en una zona nativa SmartOS de la misma plataforma (no LX), ejecutar allí gmake test, y copiar el binario junto a este árbol al host. No instalar un compilador en un nodo bajo presión.

Copiar el archivo distribuido a /var/tmp/o6h-memory-guard-1.0.2.tar.gz:

mkdir -p /opt/custom/src
cd /opt/custom/src
gzip -dc /var/tmp/o6h-memory-guard-1.0.2.tar.gz | tar -xf -
cd /opt/custom/src/o6h-memory-guard
gmake CC=gcc
gmake test CC=gcc
file ./o6h-memory-guard
ldd ./o6h-memory-guard
sh ./install.sh

file debe identificar un ELF nativo de 64 bits. ldd no debe mostrar dependencias ausentes. El instalador sólo copia archivos y valida el manifiesto; no importa ni inicia servicios, y rechaza sobrescribir una instalación existente. Si ya se copió un binario compilado en una zona nativa, omitir gmake CC=gcc en la global zone; conservar los resultados de compilación y pruebas.

Inventariar UUID y alias fuera del daemon:

vmadm list -o uuid,alias
vi /opt/custom/etc/o6h-memory-guard.conf

Añadir una línea por zona, con UUID real y alias sin espacios:

zone <UUID-real-de-web5> web5 allow
zone <UUID-real-de-base-de-datos> database protect
zone <UUID-real-de-monitorizacion> monitoring protect

Esos marcadores no son valores válidos: sustituirlos, incluidos los signos < >. allow hace elegible una zona para las simulaciones; no habilita señales mientras el servicio esté en dry-run. protect, observe o la ausencia de una regla excluyen la zona de toda acción. Sin reglas allow el monitor funciona y registra métricas/top de procesos, pero informa no_authorized_candidate.

/opt/custom/bin/o6h-memory-guard --check-config --force-dry-run
/opt/custom/bin/o6h-memory-guard --probe --force-dry-run
svccfg validate /opt/custom/smf/o6h-memory-guard.xml
svccfg import /opt/custom/smf/o6h-memory-guard.xml
svcadm enable -s svc:/site/o6h-memory-guard:default
svcs -xv svc:/site/o6h-memory-guard:default
svcprop -p start/exec svc:/site/o6h-memory-guard:default
tail -f /opt/custom/var/log/o6h-memory-guard/guard.log

Verificar mode=dry-run en event=start, --force-dry-run en start/exec, arc_ok=1, swap_committed_ok=1, swap_physical_used_ok=1, anon_pageout_ok=1 y proc_scan complete=1. No generar presión artificial en producción. Para comprobar selección y UUID, revisar candidate, zone y top_process durante carga natural; WOULD_ACTION sólo aparece si se alcanzan los umbrales y hay candidato autorizado. --probe lee una muestra y sale sin locks, logs ni acciones; --check-config sólo valida configuración y permisos.

Para que se active automáticamente tras un reinicio de SmartOS, después de validar este arranque editar únicamente create_default_instance a enabled="true" en el manifiesto persistente y volver a importarlo:

vi /opt/custom/smf/o6h-memory-guard.xml
svccfg validate /opt/custom/smf/o6h-memory-guard.xml
svccfg import /opt/custom/smf/o6h-memory-guard.xml

Mantener --force-dry-run. La ubicación /opt/custom/smf se utiliza para servicios personalizados persistentes de SmartOS; no depender exclusivamente del estado habilitado de la instancia en el repositorio del arranque actual.

Métricas y ventanas

Cada segundo se leen unix:0:system_pages:{freemem,lotsfree,desfree,minfree,nscan, pp_kernel,pageslocked}, cpu:*:vm:anonpgout y zfs:0:arcstats:size mediante libkstat. swapctl(SC_AINFO) proporciona asignación, reserva y disponibilidad de swap virtual, equivalente a swap -s. La métrica inequívoca es swap_committed_pages = allocated + reserved, e incluye compromiso anónimo que puede seguir residente en RAM; no representa pageout ni bytes escritos en el zvol. La reserva aún no asignada es ani_resv - (ani_max - ani_free).

Por separado, swapctl(SC_LIST) obtiene las mismas entradas que swap -l. swap_physical_used_bytes suma (blocks - free) * 512 de todos los dispositivos; los campos nativos de la API están en páginas y se convierten primero a los bloques de 512 bytes que muestra la utilidad. Esta cifra sí representa ocupación física del swap configurado. Con la validación observada: swap -s indicó unos 28,5 GiB comprometidos, mientras swap -l indicó sólo 8.093.064 bloques usados, es decir 4.143.648.768 bytes (unos 3,86 GiB). No deben compararse como si fueran la misma magnitud.

El log conserva unidades: páginas para VM/compromiso virtual/pageout, bytes para ARC y swap físico, KiB para RSS. page_bytes se obtiene de sysconf, nunca se presupone 4096. nscan es una medida del scanner, no un contador acumulativo; su delta se registra como cambio de la medida y no se interpreta como tasa de pageout.

Historias circulares con timestamps monotónicos, deltas 5/15/60 s para libre, kernel, locked, ARC, compromiso/disponibilidad virtual, ocupación física de swap, pageout anónimo y nscan. Los deltas incluyen la edad real (-100@5.02s). Se exige una muestra a ±1,25 s de la ventana; si falta, sale NA, sin inventar ceros. /proc se recorre cada 5 s en NORMAL y cada 1 s bajo lotsfree; RSS y crecimiento de procesos/zonas usan las mismas ventanas.

El contrato de cada event=sample expone los tres pares inequívocos swap_committed_ok/swap_committed_pages, swap_physical_used_ok/swap_physical_used_bytes y anon_pageout_ok/anon_pageout_pages. Cada muestra va seguida por líneas event=delta con esos tres nombres exactos y d5, d15 y d60. swap_used_pages no es un nombre válido. Se conservan como diagnóstico auxiliar swap_alloc_pages, swap_reserved_pages y swap_available_pages: son los componentes de compromiso virtual de SC_AINFO, todos en páginas, y ninguno representa bytes escritos en el dispositivo swap.

En los datos originales, 425950 > 130875 páginas y nscan=0 corresponden a NORMAL. Los valores de 130875/65437/49077 páginas no están codificados como umbrales: se vuelven a leer del kernel. ARC y pp_kernel son diagnóstico, nunca una prueba de que un proceso concreto causó la presión.

Estados y acción

Estado/condición de entrada Comportamiento predeterminado
NORMAL Sin intervención; muestreo VM de 1 s.
PRE-PRESSURE free < lotsfree continuo durante 3 s; atribución de 1 s.
PRESSURE Bajo lotsfree durante 10 s y scanner activo o libre descendiendo.
CRITICAL Bajo desfree con presión corroborada continua durante 2 s; TERM.
EMERGENCY free < minfree; entrada en la misma muestra, sin confirmación. TERM a nuevo candidato; KILL tras 1 s al pendiente.
Floor free < minfree/2; KILL directo o escalada inmediata al pendiente.
RECOVERY Al recuperar lotsfree se cancela la escalada. NORMAL requiere 15 s con free >= lotsfree*1.10 y nscan=0.

La presión se corrobora con nscan > 0, caída respecto a la muestra anterior o delta libre de 5 s negativo. Además, bajo lotsfree, el crecimiento simultáneo en 5 s de swap_physical_used_bytes y anon_pageout_pages corrobora presión. La ocupación física jamás es un trigger único: sin pageout creciente y memoria bajo lotsfree no cambia el estado. El compromiso virtual tampoco se interpreta como pageout. Los tiempos son segundos monotónicos transcurridos, no número de filas leídas. Un hueco de más de 2,5 s reinicia las confirmaciones. La gravedad se retiene hasta recuperación; action_pressure indica si la condición actual permite actuar. Por eso un estado retenido CRITICAL no autoriza una señal por sí solo.

TERM tiene 5 s de gracia en CRITICAL. Sólo se escala si la condición crítica sigue confirmada y la lectura fresca sigue bajo desfree. Por encima de desfree se suspenden señales; alcanzar lotsfree cancela el pendiente. Tras KILL se puede elegir otro candidato si sigue la presión. EMERGENCY evita la espera normal entre víctimas de 10 s. Se mantiene como máximo una decisión de señal por tick; un pendiente desaparecido puede sustituirse en ese mismo tick.

Hay un máximo predeterminado de 4 víctimas por episodio y 4 por ventana móvil de 60 s, tanto en simulación como en armed. TERM+KILL del mismo proceso cuentan como una víctima. El presupuesto del episodio sólo se reinicia al volver a NORMAL; el de 60 s sobrevive a ese cambio. En armed, un arranque o reinicio tiene 60 s de inhibición obligatoria de señales (también en EMERGENCY), para evitar eludir el límite por minuto mediante reinicios repetidos. Los contadores son en memoria; el presupuesto por episodio sí se reinicia al reiniciar el daemon. Revisar esta elección antes de usarlo como mecanismo de rescate. No hay garantía de evitar un panic.

Atribución y protecciones

Se suma RSS por UUID/generación de zona y se puntúa como RSS + 2*max(crecimiento,0), usando preferentemente 60 s, después 15 s y 5 s. Se elige la zona elegible con mayor puntuación y dentro de ella el proceso elegible con mayor puntuación. Sin historia se usa RSS. Empates conservan el primer candidato encontrado. La suma de RSS puede contar memoria compartida varias veces: es una aproximación de atribución, no memoria física exclusiva ni memoria garantizada que se recuperará al terminar un proceso.

El UUID es el nombre de zona devuelto por el kernel, validado como UUID canónico en minúscula (convención SmartOS). Se conserva además ZONE_ATTR_UNIQID; se consulta ZONE_ATTR_INITPID. Los alias son etiquetas del catálogo del operador, no se obtienen mediante comandos ni se usan para autorizar señales. Un alias renombrado debe actualizarse manualmente. En illumos con nombres de zona que no sean UUID, el muestreo funciona pero esas zonas quedan excluidas de acciones.

Protecciones obligatorias: global zone completa; PID 0/1 y proceso propio; padre 0; procesos sin LWPs; init de cada zona; identidades no resueltas o sin generación; zonas sin regla allow; RSS menor de 64 MiB por defecto. También se excluyen los nombres init, systemd, svc.startd, svc.configd, sshd, zoneadmd, vminfod, vmadmd, qemu, qemu-system-x86, bhyve y el guard. Las protecciones se aplican incluso al floor y prevalecen sobre toda autorización.

protect_process añade nombres exactos pr_fname de hasta 15 caracteres, sin comodines ni argumentos; protect_pid añade PID global, no PID visto dentro de LX. La config entregada añade PostgreSQL, MySQL/MariaDB, Redis y Zabbix. Las zonas de base de datos/infraestructura deben figurar como protect. Los nombres pueden cambiar/truncarse y no constituyen una frontera contra un proceso malicioso: la protección por UUID completo es la más robusta.

Antes de actuar se comprueban UUID, generación, PID, inicio, nombre, UID/EUID, init, RSS y reglas. Se abren psinfo/ctl respecto al mismo descriptor de directorio /proc y se envía PCKILL al descriptor, no a un número PID mediante kill(2). El inicio de un PID reutilizado invalida el candidato. Cambios de nombre/UID ya visibles también lo invalidan. Queda la carrera inherente de un exec ordinario entre la última lectura de psinfo y la escritura: no se detiene el proceso para eliminarla. No se tocan flags de tracing, no se usa kill-on-close ni señales a grupos. La integración de esta ruta todavía necesita validación nativa de laboratorio.

En dry-run nunca se abre ctl: se revalida con lecturas, se escribe WOULD_ACTION=SIGTERM/SIGKILL y se actualiza sólo el estado simulado. Los presupuestos evitan repetir infinitamente el mismo sacrificio hipotético. En armed se escribe ACTION_INTENT antes y action_result=... después (el campo real es event=action_result result=sent|failed). Los errores de escritura de señal consumen presupuesto para evitar reintentos ambiguos.

Recursos y fallos

Almacenamiento de trabajo fijo para 4096 procesos y 256 zonas, prefaulted por inicialización; el tamaño se registra al arrancar. No hay malloc de la aplicación en la ruta de acción, ni system, popen, exec, vmadm, ps o vmstat en el daemon. libkstat/libc sí pueden reservar memoria internamente al actualizar su cadena. No se promete cero asignaciones ni tiempo real estricto.

El recorrido /proc tiene presupuesto cooperativo de 200 ms (hasta 500 ms por configuración). Si faltan permisos, se excede capacidad/plazo o hay errores, el scan se marca incompleto y bloquea acciones. La desaparición normal de un proceso no se trata como fallo total. Se usa caché completa de como máximo 2 s en emergencias y siempre se revalida. Si no hay caché válida se intenta el recorrido antes de actuar. Una llamada del kernel bloqueada puede exceder el presupuesto; no es un límite duro. Los overruns quedan registrados.

Muestras obligatorias ausentes/invalidas o demasiado lentas inhiben señales; ARC/swap/pageout opcionales ausentes se marcan con *_ok=0 y no bloquean por sí solos. Un fallo de log inhibe las acciones hasta reiniciar y también se anuncia en stderr/SMF. Los logs se escriben síncronamente al archivo sin fsync por muestra; el I/O o ZFS bloqueado puede retrasar este daemon. Tampoco garantiza que las últimas líneas sobrevivan a pérdida de alimentación o panic. Es persistencia en disco, no un journal de transacciones de señales. No se cambia swap, ARC ni caps de zona. La enumeración física admite hasta 64 dispositivos swap con almacenamiento fijo; si el nodo supera ese límite, swap_physical_used_ok=0 y esa señal diagnóstica se omite sin autorizar acciones.

Un lock fcntl evita dos instancias (incluso una manual y otra SMF); el PID guardado es diagnóstico, nunca se usa para señalizar. Config/locks/logs exigen propietario root, archivo regular de un enlace, destino sin symlink y directorios resueltos sin escritura para grupo/otros. Directorios /opt con symlinks legítimos de root se admiten al resolver su destino. SIGHUP sólo reabre el log; la configuración se recarga reiniciando el servicio. SIGTERM/SIGINT al daemon solicitan salida limpia.

Logging y mantenimiento

/opt/custom/bin/o6h-memory-guard
/opt/custom/etc/o6h-memory-guard.conf
/opt/custom/smf/o6h-memory-guard.xml
/opt/custom/var/run/o6h-memory-guard.lock
/opt/custom/var/log/o6h-memory-guard/guard.log
/opt/custom/var/log/o6h-memory-guard/guard.log.1 ... guard.log.5

Rotación interna: 10 MiB por archivo y 5 copias, aproximadamente 60 MiB máximos. El volumen de líneas por segundo puede hacer que la retención sea inferior a un día; aumentar LOG_MAX_MIB/LOG_KEEP o recoger logs externamente si se necesita un estudio de varios días. No configurar copytruncate/logadm simultáneamente. Snapshots de todas las zonas y top 20 procesos cada 60 s normal / 5 s en presión. Las muestras de 1 s posteriores a las acciones permiten revisar la recuperación en 1/2/5/10 s, siempre que no haya overruns. No se registran argumentos de procesos. logs/policy-tests.log y logs/runtime-tests.log proceden de pruebas locales; no son datos del nodo. logs/example.log es un ejemplo sintético de formato.

Preparación de armed (no ejecutar durante la evaluación inicial)

Se necesitan deliberadamente dos cambios: ACTION_MODE armed en la config y retirar --force-dry-run del método start del manifiesto. Mantener las reglas UUID allow mínimas y verificar antes las protecciones y el laboratorio nativo. Después de esos cambios, validar config/manifiesto, importar, refrescar y reiniciar el servicio. Verificar event=start mode=armed; comienza la inhibición de 60 s. No se proporciona un script que arme automáticamente el servicio.

Para volver a dry-run, restaurar ACTION_MODE dry-run y --force-dry-run en el manifiesto; importar, svcadm refresh y svcadm restart la instancia.

Parada, rollback y actualización

Parar el guard sin borrar evidencia:

svcadm disable -s svc:/site/o6h-memory-guard:default
svcs -p svc:/site/o6h-memory-guard:default

Si se habilitó el arranque persistente, volver a enabled="false" e importar el manifiesto para mantenerlo deshabilitado en próximos arranques. Para retirar el servicio conservando los archivos y logs:

mkdir -p /opt/custom/var/backups/o6h-memory-guard
mv /opt/custom/smf/o6h-memory-guard.xml /opt/custom/var/backups/o6h-memory-guard/o6h-memory-guard.xml.disabled
svccfg delete svc:/site/o6h-memory-guard:default

Comprobar antes que el destino de backup no exista y elegir un nombre distinto si ya se utilizó. El manifiesto sale del directorio de importación automática; binario/config/logs permanecen recuperables. No eliminar ni truncar el lock de una instancia viva. Restaurar el manifiesto e importarlo permite reinstalar SMF.

Para actualizar: deshabilitar primero, conservar copias del binario, config y manifiesto en un directorio de backup nuevo, reemplazar el binario estando parado y validar los tres archivos. No reemplazar automáticamente la config ni perder las protecciones. El instalador inicial se niega a hacerlo por ese motivo.

Referencias revisadas

Las interfaces de atributos de zona dependen de la plataforma: confirmar con la versión exacta de SmartOS. La documentación revisada no sustituye la prueba de compilación/ejecución en ese host.