Información blog

Linux, tutoriales, noticias, sistemas, redes y seguridad informática, entre otras cosas.

lunes, 15 de enero de 2018

Cómo usar los playbooks con Ansible en Linux

Continuando con el anterior artículo, en esta ocasión vamos a profundizar un poco más en el uso de la herramienta Ansible. Hasta ahora hemos podido mandar ordenes individuales desde el controlador al resto de equipos, a los que llamaremos nodos, pero el problema radica en que por el momento lo único que estamos haciendo es mandar una orden individual a todos... Una orden que puede ser muy práctica y que puede ahorrarnos mucho tiempo, pero que no es lo más práctico cuando queremos gestionar varios servicios y tareas... Es por ello que en este aparado vamos a centrarnos en uno de los aspectos más importantes que ofrece Ansible: Los playbooks. Antes de continuar, es recomendable que, si no se tienen noción alguna de Ansible, os leáis el anterior artículo.

playbooks_portada

Un playbook no es más que un fichero que contiene las ordenes correspondientes que queremos enviar a todos los nodos; ordenes que se irían ejecutando secuencialmente según hallamos escrito en el fichero. Dicho playbook, estará escrito en lenguaje YMAL y tendrá una extensión .yml. La lógica es parecida a la seguida con las ordenes y los módulos usados en el anterior apartado, pero con más opciones y con la posibilidad de crear un conjunto de ordenes de forma estructurada y ordenada. La mejor forma de entenderlo es con un pequeño ejemplo inicial. Para ello, para tener todo bien organizado, vamos a crear un directorio llamado PLAYBOOKS dentro de /etc/ansible y dentro de dicho directorio vamos a crear un fichero llamado master.yml.
mkdir /etc/ansible/PLAYBOOKS
touch /etc/ansible/PLAYBOOKS/master.yml
Dicho fichero tendrá el contenido de a continuación:

---
#Este es el playbook maestro
- hosts: all
  remote_user: root
  tasks:
  - name: Asegurarse que NTP esta en marcha
    service: name=ntp state=started enabled=yes
...

Puede parecer complicado, pero si lo desgranamos, veremos que es muy sencillo:

  • Los primeros tres guiones indican el comienzo del fichero. Todo fichero YMAL debe llevarlo para que Ansible pueda leerlo correctamente.
  • Cualquier línea que comience con una # será considerado un comentario, tal y como ocurre en los scripts de bash.
  • Después comenzaremos con la primera sección de todas, que sería de a qué grupo (especificado previamente dentro del fichero /etc/ansible/hosts) queremos hacer referencia. Allí podemos poner el nombre de un grupo que hayamos creado o, en caso de quererlo, decir que se envíen a todas las IPs y hosts que aparezcan dentro del fichero. En nuestro caso hemos optado por dicha opción, lo cual se representa mediante - hosts: all. Es muy importante comenzar con dicho - pues marcaría el inicio de las acciones y reglas dirigidas a dicho grupo.
  • Tras decir eso, habría que decir a qué usuario remoto nos queremos conectar. Aquí podemos escoger el que queramos, pero en mi caso, ya que he preparado el usuario root del otro equipo para poder conectarme directamente a él sin contraseña alguna, optaré por usar dicho usuario.
  • Ahora habría que pasar a la parte de las tareas... Dichas tareas se definen mediante, el nombre tasks: y después debajo de éste empezaríamos a mencionar las tareas una por una; tareas que siempre empezarían con un -.
  • Toda tarea está compuesta por, cómo mínimo, 2 partes. La primera parte sería meramente informativa. Se pondría un nombre descriptivo que nos mostraría Ansible antes de ejecutar la tarea. Dicho nombre se especificaría mediante - name. La segunda parte sería la tarea a ejecutar, que constaría de: Módulo: argumentos. En este caso hemos usado el módulo service, y los argumentos usados serían name, state y enabled. Se pueden poner tantas tareas como se quieran, siempre y cuando se siga la misma estructura que la que acabo de mencionar.
  • Por último, siempre al final de cada playbook habría que escribir tres puntos.

Este ejemplo es muy sencillo y puede evolucionar a mucho más, poniendo diferentes hosts y tareas, pero sirve de buena base introductoria... Para ejecutar dicho playbook, simplemente habría que escribir el comando:
ansible-playbook /etc/ansible/PLAYBOOKS/master.yml
El contenido del playbook, al hacer referencia a una única tarea, sería lo equivalente al siguiente comando de Ansible:
ansible all -m service -a \
"name=ntp state=started enabled=yes"
Vamos a poner un ejemplo más completo en esta ocasión; supongamos que queramos asegurarnos de que el servicio NTP tenga la última versión; además también vamos a verificar que el servicio Asterisk está en marcha. Por otro lado vamos a crear una tarea exclusiva para el grupo llamado PRUEBAS; una tarea que consistirá en revisar que tenemos la última versión del paquete apache2. Al final sería aplicara los mismos conceptos que hemos visto arriba, pero de forma un poco más amplia, dejando el fichero con el siguiente aspecto:

---
#Este es el playbook maestro

#Reglas para todos los hosts
- hosts: all
  remote_user: root
  tasks:
  - name: Asegurarse que NTP esta en marcha
    service: name=ntp state=started enabled=yes
  - name: Verificar ultima version NTP
    apt: name=ntp state=latest
  - name: Asegurarse que Asterisk esta en marcha
    service: name=asterisk state=started enabled=yes

# Reglas para el grupo PRUEBAS
- hosts: PRUEBAS
  remote_user: root
  tasks:
  - name: Verificar ultima version Apache2
    service: name=apache2 state=started enabled=yes
...

Como podéis ver siempre mantendrá la misma lógica... Es decir primero irían los hosts de destino, luego el usuario con el que queremos hacer las acciones y por último las tareas a realizar.

Una característica muy interesante que podemos aprovechar en los playbooks, es el uso de handlers. Un handler es una tarea que se ejecuta únicamente en caso de que una tarea concreta (especificada por nosotros) haya realizado un cambio de estado. Por ejemplo si tuviésemos un handler preparado para cuando hubiese un cambio de estado en Apache2, y una tarea que se asegurase de que apache2 tuviese la última versión, en caso de que Apache2 no la tuviese, el handler se ejecutaría. Veamos un ejemplo usando como referencia el mencionado Apache2:

---
#Este es el playbook maestro
- hosts: all
  remote_user: root
  tasks:
  - name: Verificar ultima version Apache2
    apt: name=apache2 state=latest
    notify: "Reiniciar Apache2"
  handlers:
  - name: Reiniciar apache si es necesario
    service: name=apache2 state=restarted
    listen: "Reiniciar Apache2"
...

Como podéis observar, el handler está a la escucha de que le llegue una notificación; notificación que enviaríamos desde la tarea de verificación de la última versión de Apache2. Con lo que siempre que trabajásemos con handlers, trabajaríamos de la misma forma que con las tareas, pero con la diferencia de que desde la tarea tendríamos que notificar al handler mediante un notify, y desde el handler tendríamos que escuchar a que nos llegase una notificación concreta mediante un listen.

Gracias a las tareas (tasks) y handlers, podemos realizar playbooks muy completos donde podemos agrupar una enorme cantidad de tareas y handlers para luego así llamarlos a todos a la vez; ahorrándonos tener que escribir los comandos uno a uno para cada tarea que queramos realizar, haciendo que nos ahorremos muchísimo tiempo una vez tengamos todas las tareas correctamente definidas. Aún así, a nivel de gestión, el tener todas las tareas y handlers en un solo playbook puede ser una locura a nivel de mantenimiento... Cuando son pocas tareas y servicios no pasa nada, pero y si queremos gestionar 50 servicios, con diferentes grupos, handlers, etc... Técnicamente al agruparlo todo en un solo playbook es perfectamente viable, pero a nivel mantenerlo en el tiempo puede ser realmente costoso, especialmente si se quieren hacer cambios en el futuro... Es por ello que lo ideal es tener un playbook maestro y que este vaya llamando a diferentes playbooks dependiendo de las tareas que se quieran realizar. La llamada a dichos playbooks desde el playbook maestro se realiza mediante la sentencia include, y podemos crear una estructura perfectamente definida para que con el paso del tiempo se pueda mantener sin demasiados problemas.

Vamos a suponer que queremos poner un firewall para todos los equipos; un firewall común que todos van a tener; además también vamos a verificar que tanto Mysql como Apache2 están en marcha, pues tenemos un servidor web; por otro lado también verificaremos qué Asterisk tiene la última versión y que en dicho caso lo reiniciaremos. Estas tareas se podrían considerar como diferentes entre sí y por ello lo suyo sería tener: El playbook maestro, un playbook para el firewall, otro playbook para el apartado web y otro más para Asterisk. Comencemos con el aspecto del playbook maestro, master.yml:

---
#Este es el playbook maestro
- hosts: all
  remote_user: root
  tasks:
  - include: /etc/ansible/PLAYBOOKS/Firewall.yml
  - include: /etc/ansible/PLAYBOOKS/Web.yml
  - include: /etc/ansible/PLAYBOOKS/Asterisk.yml
  handlers:
  - include: /etc/ansible/HANDLERS/Firewall_handler.yml
  - include: /etc/ansible/HANDLERS/Web_handler.yml
  - include: /etc/ansible/HANDLERS/Asterisk_handler.yml
...

Como podéis ver todo queda alojado en ficheros separados, tanto las tareas como los handlers; handlers que como podéis ver se alojarían en otro directorio llamado /etc/ansible/HANDLERS/. Estos ficheros tendrían una ligera variación con respecto a lo que hemos visto hasta ahora, pues a diferencia de un playbook "normal", en estos no haría falta ponerles las etiquetas, solamente las tareas a realizar, pues ya estarían asignadas previamente por el master.yml. Veamos por ejemplo el contenido de uno de los playbooks de tareas; concretamente el contenido del fichero Firewall.yml que posee un módulo que no hemos visto hasta ahora; el módulo copy en el que copiaremos un fichero desde nuestro controlador al resto de nodos.

---
#ESTE ES EL PLAYBOOK DEL FIREWALL
- name: COPIAR IPTABLES DEL CONTROLADOR A LOS NODOS
  copy: src=/usr/src/iptables.sh dest=/etc/init.d/iptables.sh
  notify: "CORTAFUEGOS"
...

Como veis directamente empezaríamos con el - name; sin mencionar los hosts, ni el usuario remoto, ni el hecho de que es una tarea y no un handler, pues todo eso ya ha sido especificado por el anterior playbook. En este caso lo que hacemos simplemente es copiar un fichero desde la máquina controladora al resto de equipos y que en caso de que haya una diferencia entre el iptables.sh del equipo controlador y el del nodo, mande un notify llamado "CORTAFUEGOS". Obviamente si el fichero iptables.sh no existiese en el nodo, también mandaría dicho notify.

El proceso de llamamiento de un handler mediante un include no difiere apenas con el realizado con las tareas. Buen ejemplo de ello sería el handler Firewall_handler.yml que estaría relacionado con la tarea arriba mostrada; en este caso usaremos otro módulo nuevo llamado file, capaz de modificar los permisos de un fichero para que sea ejecutable:

---
#HANDLERS FIREWALL
- name: Dar permisos firewall
  file: dest=/etc/init.d/iptables.sh mode=755 state=touch
  listen: "CORTAFUEGOS"
- name: Arrancar cortafuegos
  shell: /etc/init.d/iptables.sh start
  listen: "CORTAFUEGOS"
...

En este caso el handler lo primero que haría sería darle los permisos de ejecución necesarios al nuevo script para luego ejecutarlo. El contenido del cortafuegos no es algo relevante para este ejemplo, pero a modo de prueba podría ser algo como lo siguiente:


#!/bin/bash
case "$1" in
        start)
                iptables -F
                iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
                iptables -A INPUT -m state --state NEW -j DROP
                iptables -A OUTPUT -j ACCEPT
        ;;
        stop)
                iptables -F
        ;;
esac

Con esto ya tendríamos el firewall controlado y simplemente siguiendo las mismas pautas se podría hacer exactamente lo mismo para la gestión de tanto la parte Web como Asterisk, pues la base sería la misma que la que hemos estado usando hasta ahora.

Gracias a lo que hemos visto en este artículo, tendríamos un dominio básico de los playbooks gracias al cual podríamos realizar la mayoría de las gestiones necesarias para controlar un grupo de servidores... De aquí en adelante solo sería profundizar conceptos y jugar con los diferentes módulos de Ansible, con el fin de tener los playbooks más óptimos posibles.

Espero que os haya resultado útil.

Saludos.

lunes, 8 de enero de 2018

Cómo instalar Ansible y usarlo de forma básica en Linux

La gestión de múltiples equipos es una tarea que dependiendo del volumen de éstos, puede resultar una tarea abrumadora y extremadamente ineficiente; especialmente si queremos aplicar el mismo cambio a todos estos. Imaginemos que tenemos 5 servidores Linux con SSH instalado para gestionarlos remotamente; y tenemos que hacer (por ejemplo) una simple tarea de actualización, junto con añadir un nuevo DNS a/etc/resolv.conf. Ambas tareas son simples, pero no deja de requerir tiempo pues requiere replicarlas en 5 servidores. Es por ello que en ciertos entornos es recomendado tener alguna herramienta de automatización de tareas... Existen varias herramientas, tales como Chef, Puppet o Ansible, pero hoy nos vamos a centrar en la que, en mi opinión, es la que tiene la menor curva de aprendizaje y la que es más sencilla de gestionar: Ansible.

Logo_ansible

Ansible es una herramienta de automatización de tareas que puede mandar comandos a múltiples máquinas ya sea desde la consola a la fuerza, o mediante unos ficheros, cuyo formato es YAML  (conocidos como playbooks), que siguen una estructura muy definida, en la que se establecen todo tipo de comandos, comportamientos, desencadenadores ante ciertos eventos, etc... Lo más común y lo ideal, es trabajar con dichos ficheros, pues se puede dejar todo configurado en estos y luego simplemente con decirle a Ansible que realice las acciones pertinentes que le digan éstos; pero a modo de introducción, es mejor comenzar con lo más básico y realizar tareas sencillas para luego, en caso de ver que la herramienta se adapta a nuestras necesidades, pasar a usar Ansible en profundidad.

Lo primero y necesario para poder gestionar todas las máquinas, es que todas tengan el paquete openssh-server instalado. Esto sí que es necesario hacer a mano. También es necesario tener dicho paquete instalado en la máquina controladora.
apt-get install openssh-server
La necesidad de dicho paquete radica en que todas las comunicaciones se hacen vía SSH, con lo que en caso de tener un firewall entre el controlador que enviará los comandos al resto, y las máquinas, habría que asegurarse de que éste no está bloqueando el puerto correspondiente (por defecto el 22). Aún así con esto no valdría; habría que habilitar el acceso de root por SSH a las otras máquinas; esta parte es subjetiva, ya que dependiendo de qué Linux estemos usando y la versión de éste, podemos tener habilitado el acceso remoto, pero lo ideal es asegurarse... Para ello habría que editar el fichero /etc/ssh/sshd_config y comprobar que el parámetro PermitRootLogin esté puesto a yes:
PermitRootLogin yes
Por supuesto habría que reiniciar el servicio SSH.
/etc/init.d/ssh restart
Ahora bien, hay que pensar en hacer las tareas lo más automatizadas posible, con lo que vamos a usar el método de autenticación de clave pública y privada para evitar tener que introducir la contraseña a cada servidor, ahorrándonos mucho tiempo y pudiendo hacer que las tareas de Ansible se ejecuten sin tener que estar nosotros delante autenticándonos cada vez que se conecte a un nuevo servidor. Esto puede parecer tedioso, pero hay que pensar que es un proceso se hará una sola vez, al igual que la instalación de SSH. Para ello, desde el servidor que usaremos como el controlador, es decir desde el que usaremos para enviar ordenes al resto con Ansible, generaremos un par de claves (una pública y una privada) RSA.
ssh-keygen -t rsa
Es importante matizar que cuando nos pregunten sobre qué contraseña ponerle a la clave privada para poder usarla, no poner ninguna, pues dicha clave privada, en un principio, no va a salir del equipo.
.
Ahora para exportar la clave pública a otro equipo se usaría el comando ssh-copy-id tal que así:

ssh-copy-id root@IP

Por ejemplo, si uno de los equipos tuviese la IP 192.168.1.8 haríamos:
ssh-copy-id root@192.168.1.8
Este comando habría que repetirlo para cada IP que queramos controlar. Obviamente, al copiar la clave pública, el equipo al queramos copiar la clave nos pedirá la clave de root de dicho equipo. Gracias a dicho comando ya podremos entrar a dichos equipos sin necesidad de contraseña, lo cual hará que cualquier tarea sea mucho menos tediosa; ahora bien, faltaría instalar Ansible en el equipo que queremos que "controle" al resto, es decir el equipo desde el que hemos enviado nuestra clave pública al resto. Afortunadamente instalar Ansible es tan sencillo como instalarlo de los repositorios:
apt-get install ansible
Ahora que tenemos Ansible instalado, lo primero que haremos será dirigirnos al fichero /etc/ansible/hosts; dicho fichero sería el encargado de recopilar los diferentes equipos que queremos controlar. En dicho fichero se pueden crear diferentes grupos que agrupan distintos hosts; grupos que se pueden llamar de forma individual, o que también pueden ser llamados todos a la vez.  Cada grupo tendrá la siguiente estructura:

[nombre_grupo]
IP1
IP2
...

Un ejemplo puramente teórico podría ser:
[WEB]
192.168.1.7
192.168.1.8

[ASTERISK]
192.168.1.9
A modo de ejemplo vamos a crear un solo grupo que tendrá la IP 192.168.1.8 dentro del susodicho, pues sabemos que dicha IP tiene nuestra clave pública y que podremos efectuar cualquier acción sobre el equipo con dicha IP sin preocuparnos en introducir la contraseña.
[PRUEBAS]
192.168.1.8
Ahora que tenemos el fichero hosts preparado, vamos a efectuar un par de comando básicos:

El primero sería una simple comprobación del contenido del fichero /etc/resolv.conf de cada equipo controlado por Ansible:
root@debian:~# ansible all -a \ 
"cat /etc/resolv.conf"
192.168.1.8 | SUCCESS | rc=0 >>
nameserver 192.168.1.1
Esto haría que lanzásemos una consulta del contenido del fichero /etc/resolv.conf a cada equipo bajo nuestro control, y que cada uno nos devolviese como respuesta la salida de dicho comando; ahora bien; qué significa la estructura arriba mostrada? Dicha estructura es tan sencilla como:

ansible nombre_grupo -a "comando de Linux"

A la hora de poner el nombre de grupo podemos poner el nombre del grupo al que queremos hacer referencia, como por ejemplo PRUEBAS, o por el contrario, si queremos hacer referencia a todos los grupos habría que simplemente poner el nombre all, tal y como hemos hecho arriba. Es decir que en nuestro anterior comando podríamos haber puesto el nombre del grupo PRUEBAS en vez de all y haber obtenido el mismo resultado. Al igual que podemos haber mandando dicho comando, podemos lanzar cualquier otro comando común de la consola, como podría ser un "ifconfig" o incluso un "apt-get update"; es decir que prácticamente con la prueba de concepto de arriba, podrían mandarse comandos de consola simples a todos los equipos, lo cual de por sí nos puede ahorrar una cantidad de tiempo considerable.

Ansible ofrece más opciones más allá de los comandos de shell, ya que si bien he omitido este concepto a propósito en el primer ejemplo, cuando se mandan ordenes a través de Ansible, se mandan gracias al uso de módulos que son llamados gracias al parámetro -m. La lista de módulos y sus diferentes parámetros es realmente enorme; lista que se pueden encontrar en la página oficial de Ansible como podéis ver en este enlace. Mencionarlos todos es prácticamente imposible, pero hay unos pocos que pueden resultar interesantes de mencionar; en concreto habrían 3 módulos que en mi opinión son interesantes conocer:

  • shell: Este módulo es el usado por defecto. Es decir que no especificar un módulo o poner que se quiere usar este módulo viene a ser lo mismo. Este módulo ejecuta directamente ordenes de shell.
  • service: Este es un módulo muy interesante, ya que se encarga de comprobar el estado de los servicios y manipularlos. Puede pararlos, arrancarlos e incluso añadirlos y quitarlos del arranque... En definitiva, es un módulo muy interesante a la hora de mantener servidores.
  • apt: Este módulo se dedica a usar apt para realizar actualizaciones de la caché, instalaciones, actualizaciones y eliminaciones de paquetes. 

El primer módulo ya hemos visto como usarlo y que simplemente requiere tener nociones de la shell, pero ¿Cómo usamos los módulos service y apt?

El módulo service tiene tres argumentos que podrían considerarse como los más importantes: El nombre del servicio, mediante el argumento name, el estado en el que queremos que esté mediante el comando state, y si queremos que se inicio no durante el proceso de arranque mediante el comando enabled.  Los estados pueden ser: started, stopped, restarted y reloaded; mientras que la habilitación del arranque durante el inicio sería: yes o no.

La mejor forma de entender los argumentos es viendo el uso de estos:

Imaginemos que queremos parar el servicio ntp en todos los equipos que controlamos; el comando que lograría dicho resultado sería:
root@debian:~# ansible all -m service -a \
"name=ntp state=stopped"
192.168.1.8 | SUCCESS => {
    "changed": true,
    "name": "ntp",
    "state": "stopped",
Cada comando que lancemos que a cada máquina nos devolverá una serie de líneas, entre las cuales nos importará ver, por un lado que el comando se ha enviado exitosamente, lo cual se deduce al ver que devuelve un SUCCESS, y por otro lado hay una línea en la que veremos que dirá "changed"; en caso de dar como respuesta true, significará que antes se encontraba parado, mientras que al decir false nos estaría diciendo que ya se encontraba parado antes de que nosotros se lo ordenásemos. Otro ejemplo más completo, aprovechando que el servicio ntp se encuentra parado podría ser:
root@debian:~# ansible all -m service -a \
"name=ntp state=started enabled=yes"
192.168.1.8 | SUCCESS => {
    "changed": true,
    "enabled": true,
    "name": "ntp",
    "state": "started",
En este caso no solo le habríamos ordenado que arrancase, sino que además le estaríamos ordenando que se iniciase durante el proceso de arranque el equipo...  Este ejemplo se ha usado para el servicio NTP pero podría extrapolarse a cualquier otro sin problema alguno.

Veamos ahora el módulo apt. Este módulo funciona de la misma forma que el uso habitual de apt-get, con la diferencia que desde aquí podremos controlar pequeños detalles como que por ejemplo tengamos un paquete concreto siempre con la última versión. Este módulo tiene muchos parámetros, pero los más importantes serían: name, cuya utilidad es la misma que la usada en el módulo de service y que está presente en la gran mayoría de los módulos, state, que en este caso tiene unos estados diferentes a los del módulo service, y update_cache, que sería un equivalente al comando apt-get update. Los estados en este caso serían: latest (última versión del paquete), absent, present y  build-dep. El último estado simplemente comprobaría que el paquete X tiene todas las dependencias necesarias instaladas.

A sabiendas de dichos parámetros, podemos jugar con el módulo apt para, por un lado nos actualice la caché de los repositorios y que por otro lado nos instale (si no tenemos instalada ya) la última versión de NTP.
root@debian:~# ansible all -m apt -a \
"name=ntp update_cache=yes state=latest"
192.168.1.8 | SUCCESS => {
    "cache_update_time": 1515448761,
    "cache_updated": true,
    "changed": false
}
Gracias a dicho módulo podemos mantener actualizados todos los equipos sin tener que ir uno por uno realizando dicha tarea.

Existen muchos más módulos cuya importancia varía dependiendo de las herramientas que estemos usando, pero estas tres serían las más básicas y con las que se podrían realizar las tareas más sencillas sin necesidad de introducirse en el mundo de los playbooks.

Como podéis observar el uso de Ansible puede ayudar muchísimo en la administración de varios equipos. Cierto es que en este caso se ha interactuado con uno solo y que hacer la misma tarea directamente en dicho equipo habría sido más sencillo ¿Pero y si fuesen 5, o 10, o incluso 100? Esta herramienta brilla en dicho tipo de entornos y es donde realmente se le saca partido.

Espero que con este post hayáis podido ver las posibilidades que ofrece Ansible y las ventajas que puede ofrecer para automatizar ciertas tareas y ahorrarnos horas de tareas repetitivas.

Saludos.

martes, 2 de enero de 2018

Cómo controlar el runlevel en Linux con systemd

La tecnología va evolucionando de forma inexorable con el paso de los años, haciendo que algunos conceptos que se han mantenido intocables con el paso de los años, cambien; cosa inevitable. Esto hace que, nos guste o no, tengamos que adaptarnos a dichos cambios. Uno de los cambios más polémicos que han tenido los entornos de GNU/Linux, es sin lugar a dudas la implantación de systemd, que se ha aplicado a la mayoría de sistemas operativos actuales, con algunas excepciones tales como Devuan. Dicho cambio supone la obligación del aprendizaje de nuevos conceptos, pues aunque en algunos sistemas como Debian o Ubuntu, existen varios enlaces simbólicos para algunas funciones con el fin de que la curva de aprendizaje sea mucho más suave, existen algunas funcionalidades que estamos obligados a aprendernos sí o sí. Uno de dichos conceptos sería el manejo del runlevel del sistema.

systemd_portada

Ya se ha hablado de dicho concepto con anterioridad en este blog; pero en dicho caso se trató el concepto con un init de System V; init que ,para bien o para mal, va a tender a desaparecer o perder relevancia progresivamente. En este caso trataremos el el manejo del runlevel, o nivel de ejecución, en un entorno con un init systemd.

Para ello lo primero que tenemos que tener en cuenta es que para hacer referencia a dichos niveles de ejecución, no se hace numéricamente como antes (0, apagado, 1 modo monousuario, etc...), sino que se hace mediante descripciones más técnicas; descripciones a las que luego se hacen referencia mediante enlaces simbólicos, cuyos nombres son parecidos a los usados en System V (runlevel0.target, runlevel1.target...) pero que no dejan de ser enlaces simbólicos, con lo que es mejor tener claros los nombres reales de los niveles de ejecución en systemd. He aquí una pequeña tabla con los runlevels de System V, los nombre "intuitivos" usados en systemd, y los nombres reales utilizados en systemd:

tabla_runlevel
Tabla nombre runlevels

Teniendo claros dichos conceptos, nosotros nos vamos a centrar en los nombres "reales" usados en systemd, sin uso de alias, pues dichos alias puede que en un futuro desaparezcan y es mejor intentar no crear posibles malos hábitos.

Para adivinar el runlevel usado por defecto por el sistema podemos recurrir a la herramienta principal de systemd: systemctl. En concreto habría que recurrir al siguiente comando:

root@debian:~# systemctl get-default
graphical.target

Por defecto, lo más común sería que viésemos el resultado mostrado arriba; es decir el runlevel graphical.target; o lo que sería lo mismo, el runlevel5 en System V. Además, si bien lo más común es que coincidan, podemos ver el runlevel actual de diferentes formas:

La primera, y menos recomendada debido a que con el tiempo la obsoletarán, será mediante el comando usado siempre en sistemas system V; es decir mediante el comando runlevel:

root@debian:~# runlevel
5

Otra forma de conocer el runlevel, es mediante systemctl, el cual ofrece un resultado más preciso pero que desgraciadamente no muestra un valor único; veréis, si os fijas bien todos los nombres acaban en "target", y es debido a que ahora muchos conceptos son denominados así, con lo que si listamos todos los targets cargados por el sistema, tendremos que ver entre dicho listado nuestro runlevel actual; el problema es que si bien la información es fidedigna, requiere o bien poner muchos greps para mostrar únicamente una línea, o fijarse entre las líneas mostradas en aquella que hace referencia al runlevel; he aquí el comando y el resultado mostrado por este:

systemctl list-units --type target |grep loaded

target_list

Teniendo en mano la información deseada, ahora podemos proceder a cambiar el runlevel, cambio que se puede hacer de dos formas diferentes.

El primero y el más "sólido" sería cambiando el runlevel por defecto usado por el sistema. Esto haría que a partir del siguiente reinicio siempre se usase dicho runlevel; lo cual en algunos casos puede ser útil aunque habría que ser muy cuidadoso, ya que en dicho caso nunca podríamos usar los "targets" poweroff.target ni reboot.target, pues en caso contrario no podríamos arrancar con normalidad a menos que editasemos el runlevel en el propio GRUB para dicho arranque en cuestión. Dicho cambio se haría mediante el comando systemctl tal que así:

systemctl set-default nombre_target

Por ejemplo:
systemctl set-default rescue.target

Esta acción cambiará el runlevel que cargaremos por defecto y si nos fijamos bien veremos que en realidad lo que realiza es un enlace simbólico desde /lib/systemd/system/nombre_target/etc/systemd/system/default.target, con lo que en caso de no sentirnos cómodos usando la herramienta systemctl, podemos optar por cambiar el runlevel por defecto de la siguiente forma:

rm /etc/systemd/system/default.target
ln -s /lib/systemd/system/rescue.target \
/etc/systemd/system/default.target

Si en cambio quisiésemos cambiar el runlevel del sistema únicamente para la sesión actual, habría que optar por otro comando de systemctl; en concreto sería:

systemctl isolate nombre_target

Por ejemplo:
systemctl isolate rescue.target


Como veis, la manipulación del runlevel mantiene las mismas bases que en system V, con la diferencia de que se usan herramientas y nomenclaturas distintas; lo más importante dentro de los runlevels de systemd no sería el conocimiento de la herramienta de systemctl, sino la comprensión de las nuevas nomenclaturas de éstos, nomenclaturas que ahora sería "targets" y que estarían estrechamente ligadas a la definición de cada nivel de ejecución.

Espero que os haya resultado útil.

Saludos.