Plataformas como Kubernetes, Nomad o cualquier plataforma como servicio (Paas) alojada en la nube ofrecen una variedad de capacidades poderosas. Desde el escalado de cargas de trabajo hasta la gestión de secretos y las estrategias de implementación, estos orquestadores de cargas de trabajo están optimizados para ayudar a escalar la infraestructura de diferentes maneras.
Pero, ¿los operadores siempre deben pagar el costo de la máxima escalabilidad? A veces, el costo de la complejidad y la abstracción superan sus beneficios. En cambio, muchos constructores llegan a confiar en arquitecturas de implementación radicalmente simples para facilitar la administración. Dos servidores privados virtuales detrás de un equilibrador de carga son una pila drásticamente más sencilla de administrar en comparación con un grupo de microservidores en expansión en una flota de hosts de contenedores. Esto puede empezar a dar sus frutos cuando hay menos piezas móviles que depurar cuando surgen problemas o actualizar cuando llega el momento de darles mantenimiento.
La base de muchas distribuciones modernas de Linux es systemd y viene con un sólido conjunto de características que a menudo son comparables a los orquestadores de contenedores o sistemas PaaS. En este artículo, exploraremos cómo puede aprovechar las últimas características de systemd para obtener muchas de las capacidades de esos otros sistemas grandes sin el dolor de cabeza de administración y potenciar cualquier servidor Linux común para que sea una plataforma de aplicaciones muy capaz.
En un solo host, escribir un archivo systemd .service
es una forma ideal de ejecutar un proceso administrado. La mayoría de las veces, ni siquiera es necesario cambiar la aplicación: systemd admite una variedad de diferentes tipos de servicios y puede adaptarse en consecuencia.
Por ejemplo, considere este .service
simple que define cómo ejecutar un servicio web simple:
[Unit] Description=a simple web service [Service] ExecStart=/usr/bin/python3 -m http.server 8080
Recuerde los valores predeterminados para los servicios systemd: ExecStart=
debe ser una ruta absoluta, los procesos no deben bifurcarse en segundo plano y es posible que deba configurar las variables de entorno necesarias con la opción Environment=
.
Cuando se coloca en un archivo como /etc/systemd/system/webapp.service
, crea un servicio que puedes controlar con systemctl
:
systemctl start webapp
iniciará el proceso.systemctl status webapp
mostrará si el servicio se está ejecutando, su tiempo de actividad y el resultado de stderr
y stdout
, así como el ID del proceso y otra información.systemctl stop webapp
finalizará el servicio.
Además, todos los resultados impresos en stderr
y stdout
se agregarán mediante diario y se podrá acceder a ellos a través del diario del sistema (con journalctl
) o se dirigirán específicamente mediante el indicador --unit
:
journalctl --unit webapp
Debido a que el diario rota y administra su almacenamiento de forma predeterminada, recopilar registros a través del diario es una buena estrategia para administrar el almacenamiento de registros.
El resto de este artículo explorará opciones para mejorar un servicio como este.
Los orquestadores de contenedores como Kubernetes admiten la capacidad de inyectar secretos de forma segura: valores extraídos de almacenes de datos seguros y expuestos a cargas de trabajo en ejecución. Los datos confidenciales, como claves API o contraseñas, requieren un tratamiento diferente al de las variables de entorno o archivos de configuración para evitar una exposición involuntaria.
Laopción LoadCredential=
systemd admite la carga de valores confidenciales de archivos en el disco y exponerlos a servicios en ejecución de forma segura. Al igual que las plataformas alojadas que administran secretos de forma remota, systemd tratará las Credenciales de manera diferente a los valores como las variables de entorno para garantizar que se mantengan seguras.
Para inyectar un secreto en un servicio systemd, comience colocando un archivo que contenga el valor secreto en una ruta del sistema de archivos. Por ejemplo, para exponer una clave API a una unidad .service
, cree un archivo en /etc/credstore/api-key
para conservar el archivo después de los reinicios o en /run/credstore/api-key
para evitar que el archivo persista permanentemente (el La ruta puede ser arbitraria, pero systemd tratará estas rutas credstore
como predeterminadas). En cualquier caso, el archivo debería tener permisos restringidos mediante un comando como chmod 400 /etc/credstore/api-key
.
En la sección [Service]
del archivo .service
, defina la opción LoadCredential=
y pásele dos valores separados por dos puntos ( :
): el nombre de la credencial y su ruta. Por ejemplo, para llamar “token” a nuestro archivo /etc/credstore/api-key
, defina la siguiente opción de servicio systemd:
LoadCredential=token:/etc/credstore/api-key
Cuando systemd inicia su servicio, el secreto se expone al servicio en ejecución bajo una ruta del formato ${CREDENTIALS_DIRECTORY}/token
donde ${CREDENTIALS_DIRECTORY}
es una variable de entorno completada por systemd. El código de su aplicación debe leer cada secreto definido de esta manera para su uso en bibliotecas o códigos que requieren valores seguros como tokens API o contraseñas. Por ejemplo, en Python, puedes leer este secreto con un código como el siguiente:
from os import environ from pathlib import Path credentials_dir = Path(environ["CREDENTIALS_DIRECTORY"]) with Path(credentials_dir / "token").open() as f: secret = f.read().strip()
Luego puede usar la variable secret
con el contenido de su secreto para cualquier biblioteca que pueda requerir un token API o una contraseña.
Otra capacidad de los orquestadores como Nomad es la capacidad de reiniciar automáticamente una carga de trabajo que ha fallado. Ya sea debido a un error de aplicación no controlado o alguna otra causa, reiniciar aplicaciones fallidas es una capacidad muy útil que suele ser la primera línea de defensa al diseñar una aplicación para que sea resistente.
La opción Restart=
systemd controla si systemd reiniciará automáticamente un proceso en ejecución. Hay varios valores potenciales para esta opción, pero para los servicios básicos, la configuración on-failure
es adecuada para satisfacer la mayoría de los casos de uso.
Otra configuración a considerar al configurar el reinicio automático es la opción RestartSec=
, que dicta cuánto tiempo esperará systemd antes de iniciar el servicio nuevamente. Normalmente, este valor debe personalizarse para evitar reiniciar servicios fallidos en un bucle cerrado y consumir demasiado tiempo de CPU dedicado a reiniciar procesos. Un valor corto que no espere demasiado , como 5s
, suele ser suficiente.
Opciones como RestartSec=
que aceptan períodos de duración o valores basados en el tiempo pueden analizar una variedad de formatos como 5min 10s
o 1hour
según sus necesidades. Consulte el manual de systemd.time para obtener información adicional.
Finalmente, otras dos opciones dictan la agresividad con la que systemd intentará reiniciar las unidades fallidas antes de darse por vencido. StartLimitIntervalSec=
y StartLimitBurst=
controlarán la frecuencia con la que se permite que una unidad arranque dentro de un período de tiempo determinado. Por ejemplo, las siguientes configuraciones:
StartLimitBurst=5 StartLimitIntervalSec=10
Solo permitirá que una unidad intente arrancar un máximo de 5 veces durante un período de 10 segundos. Si el servicio configurado intenta iniciarse por sexta vez en un período de 10 segundos, systemd dejará de intentar reiniciar la unidad y la marcará como failed
.
Combinando todas estas configuraciones, puede incluir las siguientes opciones para que su unidad .service
configure reinicios automáticos:
[Unit] StartLimitBurst=5 StartLimitIntervalSec=10 [Service] Restart=on-failure RestartSec=1
Esta configuración reiniciará un servicio si falla (es decir, se cierra inesperadamente, como con un código de salida distinto de cero) después de esperar un segundo y dejará de intentar reiniciar el servicio si intenta iniciarlo más de cinco veces en el transcurso. de 10 segundos.
Uno de los principales beneficios de ejecutar dentro de un contenedor es la zona de pruebas de seguridad. Al segmentar un proceso de aplicación del sistema operativo subyacente, es mucho más difícil que cualquier vulnerabilidad que pueda estar presente en el servicio se convierta en un compromiso total. Los tiempos de ejecución como Docker logran esto mediante una combinación de cgroups y otras primitivas de seguridad.
Puede habilitar varias opciones de systemd para imponer restricciones similares que pueden ayudar a proteger un host subyacente contra comportamientos impredecibles de la carga de trabajo:
ProtectSystem=
puede restringir el acceso de escritura a rutas sensibles del sistema como /boot
y /usr
. La documentación para esta opción enumera todas las opciones disponibles, pero en términos generales, establecer esta opción en full
es un valor predeterminado razonable para proteger estas rutas del sistema de archivos.ProtectHome=
puede configurar los directorios /home
, /root
y /run/user
en solo lectura con la configuración read-only
o, cuando se establece en true
, montarlos en el sistema de archivos del servicio como directorios vacíos. A menos que su aplicación tenga una necesidad específica de acceder a estos directorios, establecer esto en true
puede proteger de forma segura el sistema contra el acceso ilegítimo a esos directorios.PrivateTmp=
mantiene un /tmp
y /var/tmp
separados para el servicio configurado, de modo que los archivos temporales para este servicio y otros procesos permanezcan privados. A menos que exista una razón convincente para que los procesos compartan información a través de archivos temporales, esta es una opción útil para habilitar.NoNewPrivileges=
es otra forma segura y sencilla de reforzar un servicio garantizando que el proceso ejecutado no pueda elevar sus privilegios. Si no está seguro de poder utilizar otras opciones de refuerzo, esta suele ser una de las menos problemáticas de habilitar.
La página del manual de systemd.exec es un recurso útil para explorar las diferentes opciones que se aplican a cargas de trabajo ejecutables como servicios.
Las páginas del manual del proyecto systemd son extensas y útiles para conocer todas las opciones disponibles para ejecutar sus propias aplicaciones. Ya sea que esté ejecutando un servicio persistente como un servidor web o una unidad .timer
periódica para reemplazar un trabajo cron, la documentación de systemd puede ofrecerle una guía útil.