DE LOS NAMESPACES EN LINUX (II)

EL NAMESPACE DE UTS

El espacio de nombres de UTS permite gestionar dos recursos: el hostname y el NIS. El primero de ellos, hostname o nombre de equipo, sirve para identificar una máquina en una red.Se puede interactuar con él a través de comandos como [set/get]hostname o uname Gracias al namespace, cada proceso o grupo de procesos puede disponer de su propio hostname dentro de la máquina. En un ejemplo:

use strict;
use Eixo::Zone;

my $pid = Eixo::Zone->init(

    init=>sub {

        `hostname MAQUINA-HIJO`;

        print “El hostname [en el espacio de hijo] es ” . `hostname`;

        exit (0);
    },

    self_uts=>1
);

waitpid($pid, 0); 

print “El hostname [en el espacio de padre es] es ” . `hostname`;

Lo cual producirá la siguiente salida (dependiendo del hostname real de la máquina)

El hostname [en el espacio de hijo] es MAQUINA-HIJO

El hostname [en el espacio de padre] es ejemplos

¿QUÉ HA PASADO?

¿Qué ha pasado? Se ha creado un proceso con clone pasando el flag CLONE_NEWUTS. Esto implica que el proceso hijo “disfruta” de su propio espacio de UTS La llamada a sethostname se realiza desde el espacio UTS del hijo, por lo que el nombre que se modifica es el de ese espacio. Si se llama a gethostname desde el espacio del padre, se obtendrá el hostname original. Se puede concluir que: el Kernel contextualiza las llamadas a [set/get]hostname en función del espacio desde donde se llamen. En un diagrama:

Llamadas desde ambos namespaces al Kernel

CREACIÓN DE UN PROCESO “VISITANTE”

Imaginémonos que queremos saber cuál es el hostname empleado dentro del namespace UTS privado de un proyecto. Linux nos va a permitir que un proceso se “introduzca” dentro del espacio de un proceso o grupo de procesos y pueda ejecutar llamadas al Kernel dentro del mismo. Veamos el siguiente código:

use strict;
use Eixo::Zone;

my $pid = Eixo::Zone->init(

    init=>sub {
        `hostname MAQUINA-HIJO`;
        print “El hostname [en el espacio de hijo] es ” . `hostname`;
        sleep (2);
    },

    self_uts=>1
);

# nos introducimos en el espacio UTS del hijo

sleep (1);

Eixo::Zone->join(
    
    $pid,
    uts=>1
);

# Ahora estamos dentro del hostname del proceso

print “El hostname [en el espacio del hijo] es ” . `hostname`;

waitpid($pid, 0);

Esto produce la siguiente salida:

El hostname [en el espacio de hijo] es MAQUINA-HIJO

El hostname [en el espacio del hijo] es MAQUINA-HIJO

¿CÓMO SE EXPLICA ESTO?

El proceso hijo ha ejecutado la misma operación de la sección anterior. Esto es:

Se crean ambos procesos y un nuevo namespaceLa diferencia estriba en el proceso padre. Mediante la ejecución de una llamada a setns, el proceso padre se ha unido al namespace de UTS del hijo. Desde ese momento, su llamada gethostname, es relativa al mismo. En un diagrama: 

El proceso padre se “une” al espacio UTS del hijoEn otras palabras, mediante llamadas a setns un proceso puede “visitar” otros namespaces.

DE LOS NAMESPACES EN LINUX (I)

EL NAMESPACE DE PIDS

El identificador de proceso (PID) es un número empleado por el Kernel para referirse de manera unívoca a los procesos que corren en un máquina. Los PIDs se emplean para interactuar con los procesos, por ejemplo, mediante el envío de señales o su repriorización con la utilidad nice. Cada proceso tiene asignado un PID que es único (ningún otro proceso activo puede tener el mismo PID) No obstante, con la introducción del namespace de PIDs (en Linux 2.6.24) esta situación ha variado. En un ejemplo en Perl:

# Un proceso clona un proceso hijo y espera su terminación.

use strict;

use Eixo::Zone; 

my $pid_hijo = Eixo::Zone->init(

    init=>sub {
        print “Soy el proceso hijo y (en mi namespace) tengo pid ” . &Eixo::Zone::getPid() . “\n“;
        sleep (1);
        exit (0);
    }

);

print “Soy el proceso padre y mi hijo tiene pid $pid_hijo \n“;

waitpid($pid_hijo, 0);

Salida:

Soy el proceso padre y mi hijo tiene pid 26708

Soy el proceso hijo y (en mi namespace) tengo pid 1

¿QUÉ HA PASADO?

A) CREACIÓN TRADICIONAL DE UN PROCESO HIJO

Empecemos por una creación de hijo normal a través de fork. 

Fork tradicional de un procesoEl proceso hijo es una copia exacta de su padre que se inicia desde la llamada a fork().

B) CREACIÓN DE UN PROCESO HIJO A TRAVÉS DE CLONE

En el ejemplo de Perl, se ha empleado clone. Esta llamada, entre otras cosas, nos permite crear nuevos namespaces a la hora de producir un proceso hijo. Esto es, indicamos en la creación del hijo qué namespaces propios debe tener Si le indicamos que cree su propio namespace para PIDs: 

Llamada a clone y creación de un namespace nuevo de pidsAhora, nuestro proceso hijo “vive” en su propio namespace y tiene, por tanto, dos PIDs: uno en el espacio de su padre y otro en el suyo propio. Para conocer su PID, un proceso realiza una llamada al Kernel: getpid(). Si el proceso hijo llama al Kernel para pedir su pid:

print “Soy el proceso hijo y (en mi namespace) tengo pid ” . &Eixo::Zone::getPid() . “\n“;

El Kernel devuelve el PID que el hijo tiene dentro de su namespace (PID = 1)

Es decir: El kernel contextualiza las llamadas de los procesos de acuerdo a su namespace El proceso padre quiere esperar a que el proceso hijo termine su ejecución. Para ello, emplea la llamada a waitpid.

waitpid ($pid_hijo, 0);

Espera mientras no termina el proceso hijo ¿Qué PID empleará? Desde su punto de vista (desde su namespace) el PID del proceso hijo será el que recibió en la llamada a clone y ese será el que emplee para esperar.

PROCESOS CON PID = 1

El primer proceso que entra en un namespace de PIDs recibirá (dentro del espacio) el PID 1. Esto tiene importancia, así:

  • El proceso se comporta, en cierto sentido, como el init del sistema
  • Los procesos huérfanos dentro del namespace creado serán adoptados por este proceso y no por el init del sistema
  • La llamada getppid(), desde dentro del namespace, para obtener el padre de este proceso devolverá siempre 0.
  • Este proceso puede “matarse” normalmente terminando todo su árbol de procesos (y el namespace)

¿QUÉ ES UN CONTAINER?

Un container es una técnica de virtualización a nivel de sistema operativo que aísla un proceso o grupo de procesos ofreciéndoles un contexto de ejecución “completo”. Se entiende por contexto o entorno de ejecución el conjunto de recursos (PIDs, red, sockets, puntos de montaje…) que le son relevantes al proceso.

 

A medio camino entre el chroot y las soluciones del tipo de KVM, el container no incurre en el coste de virtualizar el hardware o el kernel del SO ofreciendo, no obstante, un nivel de control y aislamiento muy superiores a los del chroot.

El container es más rápido en ser aprovisionado que el VM, no necesita arrancar una emulación de dispositivos ni el núcleo del sistema operativo, a costa de un nivel de aislamiento mucho menor: procesos en distintos containers comparten el mismo kernel.

Lo que sigue es una introducción a la idea general de los containers…

1. Proceso y Kernel: los recursos
El kernel de Linux tiene como misión fundamental la de mediar entre los programas que corren en una máquina, los procesos, y los distintos dispositivos físicos y virtuales que la componen. Para ello, se crea una abstracción fundamental: el recurso.

Los recursos pueden tener una disponibilidad limitada, un acceso restringido y un empleo privativo. Es tarea del kernel la de impedir que existan monopolizaciones de los mismos, que se produzcan accesos indebidos o que se simultanee el uso de recursos de utilización exclusiva.

Comunicación con los componentes del ordenador
Además, el kernel realiza un importante trabajo de homogeneización, ocultando los detalles de funcionamiento de los distintos dispositivos y ofreciendo a los procesos interfaces limpias y unificadas que estandarizan el empleo de hardware de distinta procedencia.

Podemos concluir, por tanto, que los procesos no “ven” los dispositivos que conforman la máquina donde corren sino la representación que de los mismos les ofrece el kernel.

Recursos de un proceso
Ese conjunto de recursos con los que interactúa el proceso, y que son los que le “interesan”, conforman su contexto de ejecución. En un SO funcionando de manera tradicional dicho contexto es único; esto es, todos los procesos comparten y ven el mismo entorno de SO.

Recursos compartidos por varios procesos
Así encontramos, por ejemplo, que un pid es único por máquina, como lo es un semáforo o un recurso de red. Independientemente de los derechos de acceso, los recursos existentes en una máquina son globales y, en principio, visibles para todos los procesos que viven en ella.

2. La virtualización de plataforma
Las técnicas de virtualización de plataforma (como KVM) simulan una máquina mediante software, y eventualmente algún hardware especial de emulación como el vt-x de Intel, de manera que la replican totalmente: se abstraen los dispositivos, se arranca un kernel y, sobre todo ello, se inician por fin los procesos de usuario.

Virtualización de servidores en una máquina
3. Los containers
Los containers suponen un nuevo enfoque: no se pretende virtualizar la máquina, sino el contexto de ejecución del proceso; esto es, los recursos que el sistema le expone y con los que interactúa.
El contexto ya no es único dentro de una máquina, por lo tanto varios procesos pueden tener el mismo pid (siempre que se encuentren en distintos contextos) cada proceso corriendo en un container puede realizar su propio IPC o montar sus sistemas de ficheros.

El container se centra en aislar el proceso y evita el coste de virtualizar los dispositivos o el kernel que le son indiferentes al primero puesto que ya se encuentran abstraídos por el núcleo del SO.

Dos son los fundamentos tecnológicos de los containers: los cgroups y los namespaces. Ambas tecnologías se encuentran subsumidas dentro del kernel. Las introduciremos en el siguiente artículo.