Cómo administro mis servidores (2022)
(blog.wesleyac.com)- Los servicios personales los opero directamente sobre una VM de DigitalOcean de $5/mes y Debian 10, y para servicios pequeños, el enfoque de instalar el software del servidor en una VM sigue siendo lo bastante simple y razonable.
- Las apps del servidor están hechas como binarios estáticos de Rust e incluyen HTML, CSS, configuración y hasta secretos, para mantener el artefacto de despliegue como un solo archivo.
- La ejecución la gestiona systemd, y el manejo de HTTPS se deja a un proxy reverso nginx y a Let’s Encrypt/certbot, para que la app no tenga que tratar TLS directamente.
- Los servicios que necesitan datos usan un archivo único de SQLite, junto con respaldos diarios en Tarsnap y respaldos en streaming hacia DigitalOcean Spaces con Litestream.
- Cuando hay varios servicios en una sola VM, se aíslan con usuarios Unix por servicio; si se necesita más seguridad, se elige una VM separada o herramientas como systemd-nspawn o firejail.
Configuración básica de servidor y despliegue
- Varios servicios como thoughts.page, hanabi.site, cgmserver y phonebridge funcionan con una configuración cercana a esta.
- La app corre en una VM de DigitalOcean; el plan es el tier de $5/mes y el sistema operativo es Debian 10.
- Algunos servicios comparten la misma VM y otros están separados en distintas VMs.
- El software del servidor está escrito en Rust.
- Está enlazado estáticamente.
- HTML, CSS, configuración y secretos, entre otros, se compilan dentro del binario.
- Se usan rust-musl-builder y rust-embed.
- Con este enfoque, el despliegue se simplifica a copiar un solo archivo al servidor.
- Considera que algo parecido también puede hacerse con Go, C++, etc.
- Si el lenguaje no facilita desplegar un solo binario, una alternativa es usar un contenedor Docker como artefacto de build.
Ejecución, proxy y conservación de datos
- systemd se encarga de iniciar los servicios.
- Hace que el binario se ejecute junto con el arranque del servidor.
- La mayoría de los archivos unit de systemd son archivos simples de 9 líneas.
- systemd en sí es complejo, pero para arrancar el servidor al iniciar casi nunca se toca esa complejidad.
- El despliegue se hace con un simple deploy script.
- Copia el binario al servidor y reinicia el servidor.
- Permite rollback y garantiza que siempre haya una versión válida ejecutándose, incluso si se corta la conexión durante el despliegue.
- Los programas que necesitan base de datos usan SQLite.
- Todo el estado de la app queda en un solo archivo.
- Cada día se crea un respaldo con el comando SQLite
.backupy se guarda en Tarsnap. - El script de respaldo se ejecuta con cron.
- Con Litestream se transmite una copia de la base de datos, con resolución de segundos, hacia el almacenamiento DigitalOcean Spaces, y se generan snapshots cada 6 horas.
- Todos los servidores corren detrás de un proxy reverso nginx.
- nginx maneja la terminación TLS, así que la app no tiene que preocuparse por HTTPS.
- Los certificados HTTPS se obtienen con Let’s Encrypt y certbot y se renuevan automáticamente.
- Hay un ejemplo de configuración de nginx para hanabi.site en otro gist.
- Los archivos estáticos pueden servirse con nginx y copiarse al servidor con
scporsync.
Mantenimiento y aislamiento de servicios
- Esta configuración apunta a una ruta operativa simple y robusta.
- Fuera de la propia app, el software del camino de serving son componentes probados y usados durante décadas.
- Mientras se siga pagando DigitalOcean, operar el sitio prácticamente no requiere mantenimiento esencial.
- Los únicos problemas detectados por el monitoreo fueron incidentes temporales de red de DigitalOcean.
- De vez en cuando sí hacen falta actualizaciones del sistema operativo y de seguridad.
- Las releases de Debian ofrecen 5 años de soporte, así que habría que actualizar a Debian 11 en unos 2 años y medio.
- Si vuelve a ocurrir algo como Heartbleed, habrá que aplicar parches.
- Este tipo de incidentes es poco frecuente.
- El costo de usar una VM de $5/mes por servicio puede ser pesado, pero varios servicios pueden ejecutarse en la misma VM.
- El aislamiento se logra creando una cuenta de usuario Unix separada para cada servicio.
- Este método existe desde los inicios de Unix, así que parece sólido.
- Si se necesita un aislamiento más fuerte, se pueden usar systemd-nspawn o firejail.
- Si de verdad es algo crítico en seguridad, se pagan $5/mes extra y se ejecuta en una VM separada.
- El proceso para configurar un proyecto nuevo es corto.
- Crear un usuario nuevo.
- Agregar un host virtual de nginx y emitir un certificado HTTPS con certbot.
- Agregar una unidad de systemd.
- Hacer commit del deploy script en el repositorio y ejecutarlo.
- Al principio hay bastante que aprender, pero esta infraestructura cambia mucho más lento que la infraestructura cloud.
- El formato de configuración de nginx se ha mantenido esencialmente igual durante los últimos 10 años.
- El último gran cambio en administración de sistemas Debian fue la transición a systemd, hace casi 10 años.
- Se está menos expuesto a que un proveedor cloud retire servicios o cambie su comportamiento silenciosamente.
- La única dependencia es el proveedor de VPS, y como el servidor es un commodity genérico, se puede mover a otro proveedor.
1 comentarios
Comentarios de Hacker News
Si ejecutas cada servicio con un usuario Unix separado para aislarlos, la función DynamicUser de systemd puede ahorrarte tiempo
Asigna un UID y crea directorios de logs/estado con los permisos correctos
https://0pointer.net/blog/dynamic-users-with-systemd.html
Basta con poner un pequeño archivo de texto en
/etc/sysusers.d/con información como el nombre de usuario y el directorio home, y luego ejecutar el comando o serviciosysusersLas funciones en la nube activadas por HTTP son lo que más me gusta estos días
Si tienes cuidado de evitar las trampas de cada vendor, reducen muchísimo la complejidad y son la única abstracción cloud-native que realmente se siente como una “forma final”
En una sola app las he desplegado más de 2000 veces, y nunca me ha pasado que se rompa el entorno de ejecución y tenga que contactar a soporte o meter comandos rarísimos en la consola
El rendimiento también es excelente tomando como referencia un App Service Plan aislado de Azure, y ya se me quitó el rechazo ideológico al cobro por solicitud
Podrías hacerlo más barato si además fueras dueño del inmueble donde están tus propios servidores, pero para implementar por tu cuenta propiedades como compliance y auditoría que te da una simple función HTTP, en la práctica tendrías que montar una empresa enorme y contratar a muchísima gente
El argumento de la dependencia del vendor ya tampoco me convence tanto. La interfaz de recibir una solicitud HTTP y devolver una respuesta HTTP es natural, y las diferencias entre vendors suelen ser contexto adicional como la firma del método del trigger o claims de OIDC/SAML, así que tendrías que esforzarte activamente para construir algo que no puedas refactorizar a otro vendor en una semana
Entiendo que para un blog personal sea más divertido rentar una VM en Hetzner y afinarla como artesano. Si estás en una industria sin ninguna regulación, también se puede defender con más fuerza evaluar con cuidado márgenes y complejidad en lugar de externalizar por completo los servidores
No sé cómo creen que hacía las cosas todo el mundo antes de la nube
Ahora llevo varios equipos en una gran empresa industrial, escalando y desplegando apps innovadoras en Azure, y los costos cloud son absurdamente altos para la cantidad de usuarios
Antes operábamos apps con 10 veces más usuarios por una centésima parte del costo y con menos complejidad
A mí en lo personal me da igual porque ese costo se le carga a otros departamentos que tomaron esta decisión dudosa, pero me cuesta entender la fe ciega en la nube
Para nosotros, operar nuestro propio hardware es lo mejor, y estamos a un nivel en el que ni siquiera se pueden tomar fotos dentro de la oficina, así que la probabilidad de confiarle algo a un proveedor cloud es 0%
Pero para que una app sea útil también necesita otras piezas, como una base de datos, ¿y ahí no se disparan también los costos?
No sé si se refiere al request handler de un servidor HTTP, a computación cloud como servicio de funciones, o simplemente a un RPC de los de antes
Si algún script kiddie empieza a hacer solicitudes 10 veces por minuto, ¿cuánto tardará en que el costo de la función supere al de un VPS?
Uso una configuración parecida para mi sitio personal y para sitios de proyectos
Uso una VM de Linode de 5 dólares al mes, Debian GNU/Linux, y el software lo escribo en Common Lisp
Para un sitio web personal o blog, genero un sitio estático con un programa en Common Lisp, y para servicios en línea o apps web hago programas en Common Lisp que manejan solicitudes HTTP y responden con Hunchentoot
Uso archivos unit de systemd para que los sitios/servicios arranquen automáticamente al iniciar o reiniciar la VM, y la mayoría tienen entre 10 y 15 líneas
Dejé codificada la configuración inicial de la VM en un script de shell: https://github.com/susam/dotfiles/blob/main/linode.sh
La configuración por proyecto y por servicio la manejo con su propio Makefile: https://github.com/susam/susam.net/blob/main/Makefile, https://github.com/susam/mathb/blob/main/Makefile
No uso contenedores. Estos sitios llevan años funcionando desde antes de que los contenedores se pusieran de moda, y hasta ahora scripts de inicialización y Makefiles han sido suficientes
También uso Nginx. Sirve archivos estáticos y, cuando hay servicios de backend, actúa como proxy inverso; además de terminar TLS, también tiene ventajas como limitar la tasa de solicitudes o configurar listas permitidas de headers HTTP para proteger el backend
En mi pequeño playbook personal tengo unos cuantos comandos como
curl LINK -o linode.sh && sh linode.sh,git clone LINK && cd PROJECT && sudo make setup httpsLos targets de
makese encargan de instalar herramientas como Nginx, certbot y sbcl, configurar Nginx, configurar certificados y demás tareas necesarias para levantar el sitio, y cuando termina el comando el sitio web queda publicado de inmediatoViendo el Makefile, parece que usa SBCL, pero me pregunto si el uso de memoria le ha dado problemas
Uso un VPS con 512MB de RAM, y una sola instancia de SBCL se come más o menos 100MB, así que solo puedo levantar unos pocos servicios al mismo tiempo
Incluso he pensado en mover a CLISP los servicios con menos tráfico, pero le falta una de las funciones que uso: alias locales de paquetes
Después de varios años puliendo la configuración, ahora me asenté en correr todo en contenedores Docker
Como orquestador uso docker-compose en lugar de systemd, y Caddy en vez de nginx como proxy
Igual que en el post original, termino escribiendo scripts de despliegue para cada proyecto que hay que ejecutar, así que en general se parece bastante
Una de las varias ventajas de Docker es que también puedes ejecutar software de terceros con la misma configuración
Llevo varios años usando esta configuración y va de maravilla; como en el post original, es sólida pero también flexible para cambiarla como quieras cuando hace falta
El único dolor que tengo ahora es el despliegue rolling. A medida que el software creció, se volvió un problema que cada despliegue genere unos segundos de downtime, y aunque todavía no tengo una solución simple, docker swarm podría ser el camino correcto
Para reducir el downtime, podrías probar
health_uri /health,lb_try_duration 30sPor ejemplo, si configuras algo como
reverse_proxy api:8089 { health_uri /health lb_try_duration 30s }, Caddy bufferiza las solicitudes durante el despliegue de la nueva versión y le da 30 segundos para que el nuevo servicio levanteIdealmente, Caddy debería empezar a usar la nueva versión solo después de que esté saludable, y recién entonces matar el contenedor viejo
Revisé https://github.com/Wowu/docker-rollout y https://github.com/lucaslorentz/caddy-docker-proxy, pero todavía no pude darles prioridad
No es perfecto, pero al menos el navegador se queda cargando unos segundos en vez de recibir un error de conexión
https://mrsk.dev/ también usa esta misma técnica
Producción empezó con compose, y después lo fuimos mejorando hasta tener un pipeline de despliegue continuo que actualiza el stack automáticamente
A medida que crecieron la empresa y la base de usuarios, empezamos a tener downtime en reinicios y despliegues, y cada vez que queríamos correr una app adicional había que preparar otro entorno de despliegue
Me daba miedo que llegara el día de tener que usar Kubernetes; ya había visto esa complejidad de cerca y no quería pasar la mayor parte del día calmando el clúster
Así que nos fuimos por el modo Swarm, y ha sido un recorrido que a veces es Jekyll y a veces Hyde
Hay bugs que nadie quiere arreglar, partes de la especificación de Docker que no están implementadas pero tampoco te lo avisan, decisiones de implementación que dan ganas de arrancarse el pelo, y la sensación de que la gente de Docker Inc no se habla entre sí o no logra mantener la atención hasta el final
Aun así, también tiene muchas cosas hermosas. Los stacks de compose siguen funcionando tal cual y pueden crecer justo donde hace falta, y si lo configuras bien, los despliegues sin downtime, las actualizaciones, el balanceo de carga y los rollbacks funcionan bien
Raft es confiable para mantener el clúster, como en otros lados, y con un poco de trabajo puedes construir una plataforma de autoservicio flexible, segura y autodistribuida con apenas una fracción del presupuesto de mantenimiento de K8s
Eso sí, hay que prepararse para hacer bien los scripts de despliegue. Pasé bastante tiempo creando una herramienta en Python para convertir archivos compose válidos según la especificación de Docker a la especificación de Swarm, además de actualizar y limpiar secretos y expandir variables de entorno
Según el proveedor de VPS, también tienes que configurar correctamente el MTU de red. Siento que esto me acortó bastante la vida
Al menos no hasta que de verdad empieces a tener problemas reales de escalado por la cantidad de clientes o usuarios
docker saveydocker importMe pregunto si administran su propio registro
En un servidor físico, pongo la base de datos PostgreSQL y la app dentro de un Pod de Podman, todo corriendo en puertos por encima del 5000 en localhost, y Caddy actúa como proxy inverso en el 443
Con un systemd Timer hago un dump de todas las bases de datos en un directorio todos los días a las 4:55 p. m.
Después DejaDup [1] hace una copia de seguridad automática diaria de
$HOMEa un HDD externo a las 5:00 p. m.; excluye los archivos de caché, pero incluye los dumps de las bases de datosEl SO es Debian con GNOME Core [2], y las reglas de firewalld solo permiten los puertos 80, 443 y un puerto SSH personalizado
SSH usa llaves y la autenticación por contraseña está desactivada
Es la forma más aburrida posible, pero simplemente funciona
1 - https://flathub.org/apps/org.gnome.DejaDup
2 - https://packages.debian.org/bookworm/gnome-core
Últimamente uso Dokku sobre un solo servidor
Es fácil de administrar, y los desarrolladores pueden desplegar con solo hacer
git pushal estilo Heroku; además, tiene muchos plugins, así que agregar una base de datos o configurar HTTPS toma como mucho 10 segundosgit pushes comodísimoAgregar apps, agregar bases de datos, gestionar variables de entorno y administrar dominios es todo muy intuitivo
Como me gustan las configuraciones simples, este enfoque parece agradable para trabajar
Probablemente sea suficiente para el 99% de los servicios
Creo que usaría Ansible para la configuración del servidor y los scripts de despliegue
Así el servidor podría quedar documentado y los scripts de despliegue también podrían simplificarse más
Aun así, no evitaría conectarme directamente al servidor para depurar o verificar cosas
Mis scripts casi no han cambiado en los últimos 7 años, pero Ansible es lento y tiende a obligarte a mantener varios archivos
En cuanto a la depuración, también se pueden exponer los logs de la app en un endpoint protegido con contraseña usando nginx
A veces parece que olvidamos que incluso antes de la nube, CI/CD y la administración eficaz de servidores ya eran prácticas comunes en el trabajo real
Sobre escribir software de servidor en Rust, enlazarlo estáticamente y compilar dentro del binario el HTML, CSS, la configuración, los secretos, etc., últimamente he estado haciendo esto en Go y me encanta porque hace que desarrollar y desplegar software que depende de archivos estáticos sea realmente fácil
Lo estándar sería usar variables de entorno o configuración; agrega un paso más antes de ejecutar, pero evita la situación de compartir un binario y olvidar que llevaba secretos dentro
Es bastante fácil descomprimir un binario y buscar cadenas
Incluir juntos los recursos estáticos del frontend y la configuración por defecto sí es una gran ventaja
En Rust, compilar de forma cruzada de Mac a Linux es relativamente doloroso, mientras que en Go es trivial y viene integrado en la herramienta
goHace que sea muy fácil eliminar la fricción al terminar y desplegar proyectos personales
Pero tengo una pregunta. En el artículo dice: “obtienen certificados de Let’s Encrypt con certbot y también gestionan la renovación automática”, pero yo uso una configuración entre regiones con dos servidores y geo-DNS, así que certbot solo funciona en el servidor de EE. UU. y tengo que copiar manualmente el certificado al servidor europeo
¿Habrá alguna forma de resolver esto?
La explicación de ClickHouse Playground está aquí: https://ghe.clickhouse.tech/
Sirvo la aplicación web desde el mismo servicio compilado estáticamente que ofrece la API del backend
Ejecuto
npm builden CI e incrusto el resultado, así que es muy fácil levantar pruebas locales o instancias de demostración