1 puntos por GN⁺ 2023-10-13 | 1 comentarios | Compartir por WhatsApp
  • The Twelve-Factor App es una metodología para operar y escalar apps web y SaaS a largo plazo, y aborda en conjunto la automatización de configuración, la portabilidad, el despliegue en la nube y la entrega continua
  • No está atada a una combinación específica de lenguaje de programación ni de servicios de respaldo como bases de datos, colas o cachés en memoria, por lo que puede aplicarse a diversas aplicaciones como servicio
  • Se basa en la experiencia de haber participado directamente en el desarrollo y despliegue de cientos de apps en la plataforma Heroku, y de haber observado indirectamente el desarrollo, la operación y el escalado de cientos de miles de apps
  • La preocupación central es ofrecer un vocabulario común para reducir los costos de colaboración y la erosión del software que surgen cuando una app crece orgánicamente
  • Puede servir como criterio práctico no solo para desarrolladores que crean aplicaciones de servicio, sino también para ingenieros de operaciones que las despliegan y administran

12 principios operativos para apps SaaS

  • El software moderno suele ofrecerse como app web o en formato SaaS, y Twelve-Factor App es una metodología para crear este tipo de aplicaciones
  • El objetivo es hacer más predecibles los procesos de desarrollo, despliegue y operación de una app
    • Usa automatización declarativa de configuración para reducir el tiempo y costo de incorporar a nuevos desarrolladores al proyecto
    • Establece un contrato claro con el sistema operativo subyacente para aumentar la portabilidad entre entornos de ejecución
    • Está diseñado para reducir la carga de administración de servidores y sistemas, y adaptarse al despliegue en plataformas modernas de nube
    • Reduce las diferencias entre el entorno de desarrollo y el de producción para hacer posible la entrega continua
    • Permite escalar sin cambiar de forma drástica las herramientas, la arquitectura ni las prácticas de desarrollo
  • Su alcance no se limita a un stack tecnológico específico
    • Puede aplicarse a apps escritas en cualquier lenguaje de programación
    • Los servicios de respaldo incluyen bases de datos, colas, cachés en memoria, etc.

Contexto resumido a partir de la experiencia en Heroku

  • Los contribuidores participaron directamente en el desarrollo y despliegue de cientos de apps en la plataforma Heroku, y observaron indirectamente el desarrollo, la operación y el escalado de cientos de miles de apps
  • A partir de la experiencia y las observaciones obtenidas en apps SaaS reales, organizaron las prácticas ideales para el desarrollo de aplicaciones
    • Pusieron atención en cómo las apps crecen orgánicamente con el tiempo
    • Abordan la forma en que varios desarrolladores colaboran sobre una misma base de código
    • Consideran evitar el costo de la erosión del software como un objetivo importante
  • El formato se inspira en Patterns of Enterprise Application Architecture y Refactoring de Martin Fowler

Los 12 factores

1 comentarios

 
GN⁺ 2023-10-13
Opiniones en Hacker News
  • 12-Factor App es una recomendación creada en 2011 apoyándose más en Heroku y en las limitaciones de la infraestructura basada en contenedores de la época, no parece un documento profundamente basado en principios de ingeniería.
    Por ejemplo, la afirmación de que hay que poner la configuración en variables de entorno se debe a que los autores trabajaban en Heroku, y Heroku llenaba las variables de entorno mediante campos de entrada de una app web.
    Si quieres rastrear el historial de la configuración con control de versiones, usar GitOps, usar ConfigMap de k8s o poner archivos de configuración en un volumen montado, en general todas son opciones aceptables. Porque separan el estado de la configuración del estado de despliegue de la app.
    Este documento confunde el bosque con los árboles y hace recomendaciones ajustadas a las funciones del producto de la empresa que lo escribió, más que a principios reales de ingeniería; por eso lo veo como una guía dañina.

    • Es cierto que estuvo influido en cierta medida por la forma en que funciona Heroku, pero ConfigMap y GitOps no satisfacen del mismo modo los requisitos de seguridad y usabilidad que sí cumplen la configuración/variables de entorno de Heroku.
      Si quieres almacenar configuración de forma segura en Kubernetes, al final terminas usando Secrets, y eso toma la forma de clave-valor, igual que las variables de entorno. Para obtener una seguridad similar en Git necesitas una capa de cifrado, y entonces se rompen las comparaciones de diferencias y hacen falta herramientas adicionales.
      Al final vuelves a la razón por la que se crearon herramientas de despliegue de alto nivel como Heroku.
    • Hoy ya nadie le dice 12 factor, pero gracias a eso seguimos aplicando principios que todavía son generales. 12-Factor era un documento anterior a que Docker y Kubernetes se volvieran mainstream.
      El principio de tratar los logs como flujos sigue siendo correcto. Basta con escribir los logs en STDOUT, no en archivos, y dejar que el orquestador los lea y los almacene.
      La configuración se toma del entorno. Según el método de despliegue, las apps tienden a leer la configuración desde fuentes distintas: .env en local, un almacén de secretos en producción, etc.
      Lo mismo con el enlace de puertos: la app abre un puerto y se pone algo como nginx al frente para configurar un proxy inverso. Los servicios e ingress de K8S cumplen ese rol.
      La crítica más fuerte que se le puede hacer hoy a 12-Factor es que el documento en realidad no estaba bien escrito y asume que el lector ya sabe exactamente de qué está hablando.
    • Heroku no inventó las variables de entorno, ni fue el primero en tener la idea de usarlas para la configuración de apps. Es una práctica que ya se usaba desde mucho antes de Heroku.
      Aun así, hay que reconocerle a Heroku el mérito de haber difundido ampliamente el concepto y expandido su uso.
    • “Guardar la configuración en el entorno” no significa necesariamente poner la configuración en variables de entorno. Significa que la configuración proviene del entorno de hosting, no de la app en sí.
      No se trata de agregar un settings.json a la máquina antes de iniciar la app, sino de que el mismo código fuente, si se despliega en un clúster AKS de Azure EU north, use los valores configurados en ese clúster; y si se despliega en un clúster RPi Zero Docker Swarm dentro de un marco de Ikea, use la configuración de ese clúster.
      Ese clúster se llama Gibson.
    • Las apps 12-Factor que ponían la configuración en variables de entorno fueron triviales de mover a ConfigMap. En cambio, las apps empresariales actuales con diseños complejos de ConfigMap y, en el peor de los casos, dependencias directas de la API de k8s, serán un infierno cuando haya que migrarlas a lo que venga después de k8s.
  • Siento que cada uno de estos puntos puede refutarse de forma bastante razonable.
    Primero, el principio de un repositorio por app no es fundamentalmente incorrecto. No hay problema en desarrollar en un solo repositorio varias apps que están fuertemente acopladas funcionalmente y comparten ciclo de lanzamiento, pero que deben desplegarse por separado para obtener las ventajas de procesos separados y escalado independiente. Se me vienen a la mente la separación entre API pública y procesos worker, por ejemplo Sidekiq en Ruby, Celery en Python o consumidores comunes de Kafka.
    Segundo, decir que “una app 12-Factor no depende de la existencia implícita de paquetes globales del sistema” es algo muy difícil de lograr en la práctica si no usas algo como Nix. También se filtran dependencias de la API de llamadas al sistema del kernel, y la mayoría de las apps Rust, salvo con musl, dependen implícitamente de glibc. Está presente en las principales distribuciones Linux, salvo distribuciones slim como Alpine. Creo que Docker también se necesita como apoyo para mitigar este problema.
    Tercero, guardar configuración en variables de entorno parece más frágil. Puede reducir la seguridad al forzar que secretos se pongan en el entorno, y te hace renunciar a configuraciones en archivos estructurados que ofrecen seguridad de tipos, autocompletado en el IDE y parseo automático. En variables de entorno tienes que implementar tú mismo parsers para valores de configuración complejos que no sean cadenas. En la práctica, muchas veces se guardan en forma de archivos .env y se commitean al repositorio, así que el argumento sobre la seguridad de los commits pierde sentido.

    • Esa es una mala interpretación de la guía. El texto original habla de bibliotecas de soporte instaladas mediante sistemas de empaquetado específicos del lenguaje, como CPAN o Rubygems, y explica que esas bibliotecas pueden instalarse como “site packages” globales del sistema.
      No significa que no debas depender del sistema operativo, incluida glibc, sino que no debes hacer que sea obligatorio que cierto paquete de un gestor de paquetes de un lenguaje esté instalado en la máquina.
      Estoy de acuerdo con el resto de las observaciones. En especial, lo de las variables de entorno se parece más a que los autores asumieron que una práctica común en el desarrollo Ruby de la época era la mejor opción, y lo escribieron así, que a un consejo con fundamentos reales.
    • Si se despliegan por separado, entonces no comparten ciclo de lanzamiento. Al final, tarde o temprano, puede ejecutarse una combinación de versiones que no encajen. Por ejemplo, puede fallar el despliegue de solo una parte.
      Por eso hay que prepararse para esa situación, y si quieres probar fácilmente combinaciones de versiones distintas, creo que es mejor tener repositorios separados.
      La mayoría de los lenguajes de alto nivel no dependen de una glibc específica. Si el runtime del lenguaje funciona correctamente, la app también funciona encima de él. Claro que en algunos casos terminarás usando algo como Docker. Que sea difícil no significa que no tenga valor.
    • Si esa es la parte fundamental, no estoy de acuerdo. Creo que una mayor parte del mundo está de acuerdo conmigo y con los principios 12-Factor.
    • Estoy de acuerdo con la perspectiva sobre la primera regla, y pondría con gusto varias apps relacionadas en un solo repositorio. Como no se presenta una justificación de por qué un repositorio debe contener una sola app, se puede ignorar esa regla.
      Los lenguajes dinámicos o administrados muchas veces permiten ignorar la mayor parte de las molestias relacionadas con paquetes del sistema.
    • La utilidad de un monorepo tiene forma de U. La mayoría de los proyectos están en algún punto intermedio, especialmente los proyectos gestionados por equipos de ingeniería independientes que no tienen un equipo centralizado de plataforma/DevOps/o como se le llame hoy.
  • En general me gusta, pero demasiadas veces alguien no técnico o medio técnico sacó “12 factor” como si fuera una tarjeta amarilla universal para retrasar releases, así que terminé ignorándolo casi por completo.
    La verdad, con “agile” pasó algo parecido. Entiendo la intención de estas guías, pero su valor real parece mucho más útil para personas que no pueden ofrecer más que liderazgo técnico de torre de marfil.

    • Ignorarlo por completo por principio es tan malo como tratarlo como una doctrina obligatoria.
      Me tocó ver a ingenieros junior demasiado entusiastas o aspirantes a arquitectos usar el artículo de 12-Factor como si fuera un requisito obligatorio para cada lanzamiento.
      Hay que explicar de forma firme y consistente que estas cosas son buenos objetivos a perseguir, pero que en la realidad hay que hacer concesiones para lanzar y elegir qué partes se retrasan o se posponen.
    • A eso se le llama doctrina weaponizada. Aun así, creo que es mejor que quienes insisten con DRY creando funciones helper genéricas incomprensibles para ahorrar 3 o 4 líneas de código duplicado.
    • Hay que dar ejemplos. Como casi todo, depende del contexto y hay zonas grises, pero creo que la mayoría de los desarrolladores usa 12-Factor como una estrella polar.
      No bloquearía un release por una desviación menor, pero si en general no encaja, al menos debería tratarse como deuda técnica. Si un release retrocede mucho en un punto, es justo bloquearlo o, como mínimo, exigir una revisión más detallada de por qué se consideró que esa concesión valía la pena.
    • Ninguna doctrina debería impedir un despliegue dentro del alcance de un MVP. Después de salir del MVP, todo lo que no se haya resuelto desde la perspectiva de buenas prácticas se convierte en deuda técnica.
      Si la organización adoptó 12FA como buena práctica, debería cumplirse, pero no debería bloquear el despliegue.
      12FA no es una única casilla que hay que marcar. A medida que el producto madura, cada punto puede implementarse agregándolo al producto uno por uno, y si hace falta dividiéndolo aún más; en la mayoría de los casos así debería ser.
      Si al principio se hizo buena ingeniería, es decir, si se usaron abstracciones e interfaces adecuadas y no se hardcodeó todo, no debería ser un problema.
      YAGNI se abusa tanto como 12FA. Lo que más le falta a 12FA son ejemplos concretos que puedan servir de referencia a ingenieros junior.
    • Antes tuve ese problema, y la solución fue tener un proceso claramente documentado y apuntar a ese proceso.
      Aprendí a asumir buena fe en estos problemas. Hay que ver por qué se plantea el problema, si el proceso existente no es claro, si tiene fallas o si le falta confiabilidad.
      Si se encuentra una preocupación razonable, se cambia el proceso y se actualiza la documentación.
  • Twelve-Factor App dice que se use el entorno para la configuración, y Docker dice que no se use el entorno para la configuración, porque no es seguro.
    Me gustan y uso muchos patrones de 12-Factor, pero algunos se pensaron en el contexto de VPS. En ese momento el entorno era estable, seguro y más fijo, pero en contenedores el entorno puede terminar incluido en alguna capa.
    En la era de los contenedores, este punto específico fue bastante problemático. Docker secrets tampoco siempre encaja bien, así que para hacerlo funcionar hacen falta varias acrobacias.

    • ¿Podrías explicarlo con más detalle? Me da curiosidad saber en qué sentido las variables de entorno no son seguras. Si un secreto debe existir durante toda la vida de la aplicación, no sé cuál sería una alternativa mejor.
      Inyectar secretos en la app como variables de entorno no significa que todo su manejo externo sea inseguro. Por ejemplo, los contenedores de AWS ECS tienen soporte integrado para obtener secretos de Secret Manager al iniciar y pasarlos como variables de entorno: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/...
      Los secretos se obtienen al momento de iniciar el contenedor, y se traen de Secret Manager usando las credenciales IAM de la aplicación en ejecución. Por lo tanto, debe tener permisos sobre esos secretos.
      La ventaja de usar variables de entorno parece depender totalmente de cómo se configuren al final. Con un mecanismo así, no veo grandes desventajas.
      La principal desventaja que veo es que malware común que intente volcar variables de entorno podría capturar los valores, pero si no se evita almacenar secretos de forma permanente en memoria, mitigar esa amenaza suele parecer más bien ofuscación.
    • En cambio, si no hay soporte de la plataforma, no hay muchas alternativas fáciles. Al menos fomenta la separación entre configuración y código, y permite mantener los secretos fuera del sistema de control de versiones.
    • Por este motivo nunca me gustó poner secretos en variables de entorno. Sorprendentemente a menudo, consolas de debug mal hechas expusieron variables de entorno por accidente.
      De verdad hay que evitar que las claves privadas queden expuestas así.
      No tengo mayor problema con montar secretos en el sistema de archivos usando k8s. Por supuesto, todo esto depende del entorno de despliegue.
      A veces las variables de entorno son la opción menos mala, porque los secretos siempre son difíciles.
    • Se podría argumentar que ninguna entrada debería considerarse “segura para consumir”. Si escribes el código de la app de forma defensiva, nunca asumes que ENV es seguro o que tiene la forma esperada, y lo saneas antes de usarlo.
      Si confías ciegamente en la entrada porque es single-tenant, cuando más adelante alguna decisión arbitraria del negocio cambie las condiciones, terminarás con bugs difíciles de rastrear y noches largas.
    • En retrospectiva, el nombre de esa sección habría sido mejor como separar la configuración del código, tal como se usa en el primer párrafo.
  • En los últimos años se habló mucho de las apps de 12 factores, y también vi mucha confusión. El sitio de 12-Factor es excelente, pero sirve sobre todo para quienes ya entienden por qué esos puntos son importantes.
    Para quienes no conocen las razones detrás de las reglas, hacía falta una explicación más profunda. Por eso hice el video “What are 12 Factor Apps and Why Should You Care?”[1], y supe que a varias empresas les resultó muy útil para capacitar a nuevos ingenieros/DevOps.
    Donde sea que lo aprendan, vale la pena dedicar una o dos horas a estudiar las apps de 12 factores. La mayoría de las “reglas” son cosas que requieren tomar conciencia y no resultan obvias de inmediato hasta que uno comete los errores en carne propia y sufre las consecuencias.
    [1] https://youtu.be/REbM4BDeua0

    • Es parecido a las pruebas unitarias. La idea de escribir código que prueba el código me pareció tan obvia desde que empecé a programar que me sorprendía que a otros les hubiera llevado tanto tiempo adoptarla.
      El concepto de “pruebas unitarias” empezó a ganar traction hacia fines de los 90, y fue un alivio que alguien con autoridad lo promoviera.
      La razón por la que yo no las hacía directamente hasta que Kent Beck publicó JUnit era que el código en el que trabajaba no tenía una estructura adecuada para ser controlado por otro código. Por variables globales, estado ampliamente distribuido, dependencias de sistemas externos y de un layout específico del sistema de archivos, e ignorar la modularidad, nada podía ejecutarse fuera del contexto para el que había sido diseñado.
      Todo eso era “mal diseño”, pero cumplía con las fechas de entrega, así que todos lo hacían. Esperaba que, si las pruebas unitarias ganaban traction, los programadores se alejaran de los diseños monolíticos.
      Después de 25 años de ver pruebas unitarias para getters/setters y una enorme prueba unitaria que crea una base de datos en memoria porque todas las funciones de la app necesitan una base de datos en vivo solo para intentar ejecutarse, y que finalmente falla y se comenta, perdí la fe en que las pruebas unitarias llegaran a ser algo más que una casilla sin sentido. Todos la completan porque es una “buena práctica”, pero no se detienen a pensar por qué lo hacen.
  • Siempre estuve más en desacuerdo con el consejo sobre configuración. La configuración termina siendo definida por varios actores, y muchas veces también por desarrolladores, así que con frecuencia lo mejor es incluir valores predeterminados razonables junto con la aplicación y sobrescribirlos con archivos por entorno y variables de entorno.
    Para la mayoría de las aplicaciones del lado del servidor, este enfoque es el más flexible. Muchas veces ya se sabe cuál debe ser cierta configuración, y conviene que esté bajo control de versiones, pero los valores secretos deben inyectarse en tiempo de ejecución.
    Para no gastar una cantidad enorme de tiempo configurando desarrollo, pruebas y producción, la configuración necesita sobrescritura jerárquica.

    • El problema es si los “valores predeterminados razonables” son valores por defecto de desarrollo o de producción.
      Si son de desarrollo, eventualmente romperán producción; y los valores por defecto de producción quizá no tengan ningún sentido en desarrollo.
    • En el contexto en que se escribió 12-Factor App, existía la idea de poder alternar modos con variables de entorno como RAILS_ENV=test.
      Una forma de pensar la sección de configuración es: “¿esta estrategia de configuración encaja bien con contenedores?”. Al construir una imagen, se obtiene un estado de disco estático cuyos cambios no persisten a menos que se cree una nueva imagen.
      Si la configuración es solo basada en archivos, habría que construir una imagen completamente nueva para alternar entre el comportamiento de pruebas y el de producción.
      Permitir que la configuración cambie de forma independiente del disco base ayuda a aislar los cambios. Hay que poder distinguir si la app se rompió porque falló el despliegue, es decir, la creación de la imagen, o porque la configuración estaba mal.
      Si separas la creación de la imagen de los cambios de configuración, esa pregunta desaparece.
    • Si impides de forma sistémica que una instancia de QA se conecte a recursos de producción, puede estar bien desplegar la mayor parte de la configuración junto con la app.
      Por ejemplo, no deberías permitir un error humano totalmente previsible como enviar desde QA 100 mil reintentos de rechazo de autenticación a una cola de envío de trabajos de producción.
      Dicho eso, mucha configuración no trata sobre infraestructura, sino sobre cuestiones como qué bean ResolverStrategy conectar en cada entorno.
    • Puedes, y probablemente deberías, tener valores predeterminados razonables.
      La configuración debe estar versionada, pero gestionarse por separado del código fuente. Esto se debe a que la configuración no describe la imagen usada para el despliegue, sino el despliegue en sí.
    • No veo por qué los valores por defecto no deberían expresarse también como configuración. Si a eso se referían, no creo que contradiga 12-Factor.
  • Sin duda fue una norma de ingeniería influyente. Hoy hay muchas abstracciones sencillas de hosting como Render o Vercel, pero se siente un poco extraño si uno recuerda que este documento se escribió en 2012 y que, en términos de prácticas comunes aceptadas, las apps web de esa época eran mucho más como el Lejano Oeste.

  • Lo que falta en gran medida en este documento es una justificación de las reglas. Casi todo son simplemente reglas.
    Es difícil juzgar si las reglas son buenas, y este documento no ayuda a descubrirlo.

  • Por el título, pensé que era un comentario sobre autenticación de dos factores. Algo como una app que, para iniciar sesión una sola vez, te pide foto del pasaporte, escaneo facial, licencia de conducir, SMS, Google Authenticator, enlace por correo, contraseña y huella digital.

    • Me parece que eso describe al exchange de criptomonedas centralizado promedio.
    • Es más o menos lo que pide el exchange promedio de acciones o criptomonedas antes de permitir operar.
  • En los primeros días de Docker, trabajé bastante para hacer que WordPress se comportara como una Twelve-Factor App.
    Tradicionalmente WordPress no funcionaba así, y hasta cierto punto era lógico. WordPress creció en un mundo donde eran comunes los servidores de larga vida con un disco local escribible y persistente.
    Desde entonces seguramente las cosas cambiaron bastante. Hablo de alrededor de 2016, pero fue un desafío realmente divertido.

    • Recuerdo cuando aprendí a hacer que los servidores fueran sin estado, es decir, guardar la información de sesión en la base de datos y no escribirla en disco.
      Me sorprendió cuánto se simplificaban las cosas, y permitió balancear carga entre varios nodos sin preocuparnos por la afinidad de sesión.
      Claro que se volvió más difícil en otros aspectos, como necesitar una base de datos separada para sesiones.