¿Quieres dar formación sobre rendimiento en Solaris/OpenSolaris a tus técnicos?

A raíz del interés mostrado en las últimas charlas he decidido ofrecer mis servicios para consultoría y formación a empresas.

Ponte en contacto conmigo para pedir información.

OpenSolaris: Zonas

Introducción

Debemos imaginar una zona de OpenSolaris como un contenedor de procesos, es decir la zona es una jaula cuyo contenido no puede ver lo que hay fuera de ella. Para que los procesos puedan ejecutarse el kernel debe proporcionar una serie de recursos a dicha zona, como pueden ser acceso a discos, interfaces de red, etc. Dichos recursos no pueden ser compartidos entre zonas.

La excepción es la zona global con id 0, esta se crea cuando arranca el sistema y tiene asociado todos los recursos sin restricciones de privilegios. Incluso en los sistemas que no se ha definido ninguna zona existe una zona global.

Cada zona esta identificada por un nombre y un id, el nombre global y el id 0 está reservados para la zona global.

Notas sobre el kernel

En nuestro sistema solo tenemos un Kernel ejecutándose, los procesos de las distintas zonas se ejecutan todos en el mismo que, lógicamente es el que se inicia al arrancar la zona global.

Esto tiene grandes beneficios a nivel de performance, especialmente si el diseño de las zonas ha sido bien pensado.

  • Las páginas de texto (el código de los ejecutables y librerías) que ha cargado en memoria una zona son compartidas con las demás.
  • La caché de nombres de directorios del sistema (DNLC) también es compartida.
  • Las tareas que periódicamente se ejecutan en el kernel (p.ej. todas las de la callout table) no se duplican por cada zona.

Teniendo en cuenta lo anterior interesa, desde el punto de vista de la performance, que las distintas zonas sean los mas parecidas posible y compartan el mayor número de directorios posibles.

P. ej. mientras que levantar el sistema consume en mi equipo 339 megas de la memoria física, arrancar una zona adicional consume solo 79 mb.

####sistema solo con la zona global
bash-3.00# mdb -k
Loading modules: [ unix genunix specfs dtrace uppc pcplusmp scsi_vhci ufs ip
hook neti sctp arp usba fctl nca lofs mpt audiosup zfs random cpc crypto fcip
nsctl ptm sppp ipc ]
> ::memstat 
Page Summary                Pages                MB  %Tot
------------     ----------------  ----------------  ----
Kernel                      15066                58   12%
Anon                        52276               204   41%
Exec and libs               11141                43    9%
Page cache                   5927                23    5%
Free (cachelist)            24358                95   19%
Free (freelist)             20142                78   16%

Total                      128910               503
Physical                   128909               503
###iniciamos una nueva zona
> ::memstat
Page Summary                Pages                MB  %Tot
------------     ----------------  ----------------  ----
Kernel                      17944                70   14%
Anon                        69002               269   54%
Exec and libs               11305                44    9%
Page cache                   6139                23    5%
Free (cachelist)            18639                72   14%
Free (freelist)              5881                22    5%

Total                      128910               503
Physical                   128909               503
>

Notas sobre la seguridad

Si bien ejecutar todos los hilos en el mismo kernel es un beneficio para la performance también supone un reto de cara a la seguridad. El modelo de permisos de OpenSolaris va más allá del tradicional en *nix, root (id =0) todos los privilegios, los demás usuarios ninguno. En su lugar tenemos un modelo mas granular, donde podemos asignar a los procesos/usuarios un set de privilegios específicos para realizar las tareas que necesiten.

Por ejemplo, ya no es necesario arrancar un servidor web como root, en su lugar podemos dar a un usuario privilegios para que sus procesos puedan hacer un bind al puerto 80. El hecho que cada proceso pueda ejecutarse con los mínimos privilegios necesarios, en contra del todo o nada tradicional, favorece enormemente la seguridad global del sistema.

Los privilegios que tienen los procesos dentro de una zona no global están prefijados y no pueden ser alterados por el administrador, este set está diseñado de manera que evita la interacción de los threads entre zonas , así como el uso de algún privilegio para conseguir otros adicionales. Incluso los hilos que se ejecutan con id 0 tienen este set. Estas restricciones no se aplican en la zona global.

Como consecuencia hay una serie de privilegios no podrán adquirir los procesos ejecutándose en una zona no global:

Privilege 			Description
PRIV_NET_RAWACCESS  		Allows a process to have direct access to the 
				network layer.
PRIV_PROC_CLOCK_HIGHRES 	Allows process to create high-resolution timers.
PRIV_PROC_LOCK_MEMORY 		Allows process to lock pages in physical memory.
PRIV_PROC_PRIOCNTL 		Allows process to change scheduling priority or 
				class.
PRIV_PROC_ZONE 			Allows process to control/signal other processes 
				in different zones.
[...]
De esta manera los procesos se hallan aislados de forma efectiva de aquellos que no se están ejecutando en su misma zona.

Obviamente, a parte de la restricción nivel de privilegios, es vital para la seguridad de las zonas, que los derechos de acceso a los distintos recursos sean correctos. Es decir, los privilegios nos indican que "acciones" podemos realizar (p. ej. Un chown), pero donde realizarlas está controlado a nivel de objetos (p. ej. Usando zonecfg damos acceso a los filesystem que nos interesa.).

Es la combinación de ambas cosas, privilegios y restricciones de acceso , lo que finalmente garantiza la seguridad entre zonas.

Como pequeño ejemplo prático podemos comprobar como desde la zona global vemos los pids de las demás y no así a viceversa. En ambos casos el id del usuario es 0 (root).

#ps dentro de la zona test
bash-3.00# id
uid=0(root) gid=0(root)
bash-3.00# uname -a
SunOS test 5.11 snv_70b i86pc i386 i86pc
bash-3.00# ps
   PID TTY         TIME CMD
   11227 console     0:01 bash
   29665 console     0:02 sh
   26806 console     0:00 ps
   
#ps desde la zona global con un grep para ver el proceso bash de la zona test
bash-3.00# id
uid=0(root) gid=0(root)
bash-3.00# ps -ef | grep 11227
    root 11227 29665   1   Jan 18 zoneconsole    0:01 bash
    root 26809   555   1 09:45:14 pts/2       0:00 grep 11227

#ps desde la zona global

bash-3.00# ps
   PID TTY         TIME CMD
   555 pts/2       0:03 bash
   551 pts/2       0:00 sh
   26812 pts/2       0:00 ps

#ps desde la zona test buscando el pid del bash de la zona global

bash-3.00# ps -ef | grep 555 | grep -v grep
bash-3.00#

Como consecuencia de lo anterior existen algunas tareas que no están permitidas ejecutarse dentro de una zona:

  • Modificar interfaces de red o tablas de rutas.
  • Acceder al dispositivo /dev/kmem.
  • Rebotar o apagar todo el sistema.
  • Cargar módulos personalizados del kernel.

Procesos que gestionan una Zona

Existen dos nuevos procesos por cada zona no global que tenemos en el sistema, su función gestionar los recursos de ellas:

El primero es el zoneadmd. Sus tareas son las siguientes:

  • Crear las estructuras del kernel necesarias y iniciar un proceso zsched.
  • Configurar el control a los recursos de dicha zona (como pools de CPU, memoria o privilegios).
  • Configurar los dispositivos de la zona con el comando devfsadmd.
  • Crear y destruir las interfaces de red virtuales.
  • Montar los filesystems.
  • Proporcionar un servidor de consola para el comando zconsole.
  • Ejecutar el proceso init de la zona.
  • Proporcionar un Door Server, los clientes como el zoneadm o el propio kernel se conectan a el para enviar mensajes de cambio de estado a la zona como halt, reboot, ...

Hay una completa descripción de este proceso en el fichero zoneadmd.c del código fuente.

El otro proceso es el zsched, sus tareas son:

  • Es el padre de todos los threads del kernel de su zona.
  • Lanza el proceso init de la zona cuando arranca.

Ciclo de vida de una zona

Estos son los estados (a nivel de kernel) en los que se puede encontrar una zona, ordenados por el flujo natural de arranque a parada.

ZONE_IS_UNINITIALIZED: la zona se ha añadido a la lista de zonas activas pero aun no es accesible.

ZONE_IS_READY: el proceso zsched esta preparado.

ZONE_IS_BOOTING: es un estado de transición el proceso zsched esta tratando de lanzar el init de la zona.

ZONE_IS_RUNNING: zsched a lanzado correctamente el init, la zona esta lista para trabajar. Permanece en este estado hasta que se le haga un shutdown.

ZONE_IS_SHUTTING_DOWN: se ha ejecutado la llamada zone_shutdown(), el sistema esta matando todos los procesos dentro de la zona.

ZONE_IS_EMPTY: no quedan mas procesos de esta zona ejecutándose.

ZONE_IS_DOWN: todos los threads del kernel relacionados con la zona han terminado.

La estructura zone_t

Por cada zona existente en el sistema hay una estructura zone_t en el kernel, en ella tenemos información acerca del estado y configuración de esta.

Podemos ver su definición en el fichero zone.h de nuestro sistema.

typedef struct zone {
	/*
	 * zone_name is never modified once set.
	 */
	char		*zone_name;	/* zone's configuration name */
	/*
	 * zone_nodename and zone_domain are never freed once allocated.
	 */
	char		*zone_nodename;	/* utsname.nodename equivalent */
	char		*zone_domain;	/* srpc_domain equivalent */
	/*
	 * zone_lock protects the following fields of a zone_t:
	 * 	zone_ref
	 * 	zone_cred_ref
	 * 	zone_ntasks
	 * 	zone_flags
	 * 	zone_zsd
	 */
	kmutex_t	zone_lock;
	/*
	[...]

Dentro de ella encontramos los parámetros con los que hemos configurado la zona, entre ellos los que limitan el consumo, como por ejemplo el máximo de memoria, el número de semáforos, etc. Vamos hacer un breve repaso a los mas destacados:

*zone_name;     /* nombre de la zona */
zoneid_t        zone_id;        /* ID de la zona */
rctl_qty_t      zone_locked_mem_ctl;    /* límite máximo de
memoria*/
rctl_qty_t      zone_max_swap_ctl;      /* límite máximo de
swap */
[...]

También hay información de la zona, como por ejemplo:

char            *zone_rootpath; /* path de la raíz de la zona */
zone_status_t   zone_status;    /* en que estado esta actualmente la
zona*/
rctl_qty_t      zone_nlwps;     /* número de hilos ejecutándose */
[...]

Podemos acceder fácilmente a ella usando mdb:

bash-3.00# mdb -k
Loading modules: [ unix genunix specfs dtrace uppc pcplusmp scsi_vhci ufs ip
hook neti sctp arp usba fctl nca lofs mpt audiosup zfs random sppp crypto ptm
md nfs cpc fcip fcp logindmux nsctl sdbc sv ii rdc ]
> ::walk zone
fec7f028
d5072b40
d884ac40
> ::walk zone
fec7f028
d5072b40
d884ac40
> d5072b40 ::zone
    ADDR     ID NAME                 PATH
d5072b40      1 linux                /zone/root/
> d884ac40 ::zone
    ADDR     ID NAME                 PATH
d884ac40      3 test                 /zone_OS/root/
> d884ac40 ::print zone_t
{
    zone_name = 0xd7ce69c0 "test"
    zone_nodename = 0xd86e5400 "test"
    zone_domain = 0xd86e57c0 ""
    zone_lock = {
        _opaque = [ 0, 0 ]
    }
    zone_linkage = {
        list_next = zone_active+8
        list_prev = 0xd5072b54
    }
    zone_id = 0x3
    zone_ref = 0x13
    zone_cred_ref = 0x1e
    zone_rootvp = 0xd5074240
    zone_rootpath = 0xd884ed10 "/zone_OS/root/"
    zone_flags = 0
    zone_status = 3 (ZONE_IS_RUNNING)
    zone_ntasks = 0x11
    zone_nlwps_lock = {
        _opaque = [ 0, 0 ]
    }
   [...]
   
Creative Commons License
Esta obra está bajo una licencia de Creative Commons.