README · by ansango
← Volver al libro

Container platform design

Diseñar plataformas de containers resilientes: Twelve-Factor App methodology (12 principios), Reactive Manifesto (responsive, resilient, elastic, message-driven), y cómo aplicar ambas a arquitecturas container-native

~6 min de lectura
Resumen

Esta nota cubre cómo diseñar una plataforma de containers resiliente siguiendo dos documentos clave: The Twelve-Factor App (12 principios para aplicaciones SaaS modernas, escritos por Adam Wiggins de Heroku en 2011, antes de Docker) y The Reactive Manifesto (sistemas que son responsive, resilient, elastic, y message-driven). Ambos son anteriores a Docker pero han influenciado profundamente su diseño y el de las plataformas container-native.

Twelve-Factor App

En noviembre de 2011, Adam Wiggins (cofundador de Heroku) publicó “The Twelve-Factor App”, una metodología de 12 prácticas para aplicaciones que escalan en un entorno SaaS moderno. Docker no existía aún, pero estos principios han influido en su diseño y siguen siendo la base de cualquier plataforma container-native que se precie.

Los 12 factores

1. Codebase — Un codebase en revision control

Una sola codebase tracked en Git para cada aplicación. Todos los containers de esa app se construyen desde ese repo. Si necesitas stitch-ear código de múltiples repos, algo está mal.

Test: clean laptop + new dev

Dale a un dev nuevo un laptop limpio y un párrafo de instrucciones. Si no puede buildear tu app en menos de una hora, el proceso necesita simplificarse.

2. Dependencies — Declarar y aislar explícitamente

Nunca confíes en dependencias del sistema operativo. Todo lo que tu app necesita debe estar declarado en el Dockerfile (o package.json, Gemfile, requirements.txt, etc.) y pulled in por el build.

Imágenes base son convenientes pero peligrosas

Las imágenes oficiales (Ubuntu, Alpine) son un buen punto de partida pero enmascaran dependencias ocultas. Para apps en C/Go estáticamente compiladas, una imagen FROM scratch te obliga a saber exactamente qué necesitas. Para apps en lenguajes interpretados, sé explícito sobre qué librerías y runtimes necesitas.

3. Config — En environment variables, no en el codebase

docker container run -e ENVIRONMENT="production" \
    -e DATABASE_URL="postgresql://..." \
    -e API_KEY="..." \
    myapp:1.2.3

Mismo container, distintos env vars para staging, prod, dev. Nunca bakees secrets en el código. Las env vars son el contrato entre tu imagen y el entorno.

Secrets management

Si tienes muchos secrets o rotación frecuente, no los pongas en env vars planas. Usa:

4. Backing Services — Tratar como recursos attached

Las DBs locales no son más confiables que las de terceros. Trata todas las dependencias externas (DB, cache, message queue) como recursos que pueden caerse y diseña tu app para degradar gracefully cuando eso pase.

Con containers, high availability viene del horizontal scaling + rolling deploys, no de mantener procesos vivos para siempre. Las instancias van y vienen; tu app debe manejarlo.

5. Build, Release, Run — Separar estrictamente

Tres stages distintos:

Registry como handoff

El image registry es el handoff natural entre build y run. CI pushea la imagen al registry; deploy la pulla. No rebuildes entre entornos; cambias las env vars.

6. Processes — Stateless

Ejecuta la app como uno o más procesos stateless. Todo el estado compartido va en un backing store (DB, cache, S3). Nunca confíes en disco local del container; el container es ephemeral.

Un proceso por container

Un container = una función = fácil de escalar horizontalmente. Si tu app tiene 3 procesos acoplados, rebuild-ala en 3 containers o usa un init system (tini) dentro de uno.

7. Port Binding — Exportar servicios por port binding

La app bind directamente al port, sin inetd externo. Tú expones con --publish 80:8080 y confías en que el port 8080 del container es tu servicio.

# App Go estáticamente compilada, solo expone el binario
docker container run -d -p 80:8080 myapp:1.0
# El container solo tiene el binario. No hay nginx, no hay gunicorn.

8. Concurrency — Escalar via process model

Diseña para horizontal scaling. Añadir una instancia más es trivial con containers; añadir RAM a una instancia existente es caro y difícil de revertir. Escala horizontal es el modelo natural con Docker + scheduler.

9. Disposability — Fast startup y graceful shutdown

Containers son descartables. Diseño para que mueran y se reemplacen rápidamente:

Init system

Si tu app spawn-ea hijos, usa --init (tini) para que PID 1 sea un init que reapa hijos. Sin esto, acumulas zombies en cada restart.

10. Development/Production Parity — Minimizar divergencia

Mismas personas, mismos procesos, mismos artifacts en dev, staging, prod. Cada divergencia es un riesgo que se manifiesta en producción cuando es tarde para arreglarlo proactivamente.

Con containers, esto es más fácil: mismo Dockerfile, mismas env vars ajustadas, mismo registry. La imagen es idéntica en todos los entornos.

11. Logs — Tratar como event streams

Escribe logs a STDOUT/STDERR, no a archivos en el container. Deja que la plataforma los collecte (Docker, Kubernetes, el logging service):

# Tu app solo hace:
console.log("User logged in");
# La plataforma (Docker daemon, k8s, fluentd) se encarga del resto
No escribas logs a archivos

Si tu app escribe a /var/log/myapp/, esos logs se pierden cuando el container se destruye. STDOUT/STDERR van al driver de logging del daemon (json-file, syslog, fluentd) y persisten fuera del container.

12. Admin Processes — Como one-off processes

Tareas admin (migrations, cleanup, etc.) se corren como containers one-off con la misma imagen y config que la app:

# Migración de DB
docker container run --rm \
    -e DATABASE_URL="..." \
    myapp:1.2.3 \
    python manage.py migrate

No cron jobs en el host

No crees cron jobs en el host que ejecuten docker exec para hacer tareas admin. Usa un job scheduler (Kubernetes CronJob, Jenkins) que lance el container one-off.

The Reactive Manifesto

En julio de 2013, Jonas Bonér (cofundador/CTO de Typesafe) publicó “The Reactive Manifesto”. Describe cómo los sistemas modernos deben reaccionar predeciblemente a eventos, usuarios, carga, y fallos.

Los cuatro traits

Responsive

El sistema responde rápido si es posible. Si una operación es lenta (renderizar un PDF grande), responde inmediatamente con “job submitted” y notifica al usuario cuando esté listo. No hagas esperar al usuario.

Con containers, el sistema es responsive por diseño: los schedulers re-arrancan containers muertos, el load balancer re-routea traffic, y la app puede escalar.

Resilient

El sistema se mantiene responsive ante fallos. Cuando algo falla:

Con containers, los orchestrators re-arrancan containers muertos automáticamente. Tu trabajo como dev: diseñar la app para que la muerte de un container no sea catastrófica.

Elastic

El sistema se mantiene responsive bajo carga variable. Con Docker, esto es dinámico: deploy más containers cuando sube la carga, retira cuando baja. Sin over-provisioning permanente.

Message Driven

Sistemas reactivos usan message passing asíncrono para establecer boundaries entre componentes. Loose coupling, isolation, location transparency.

Si tu servicio A habla con B de forma síncrona y B está down, A se cuelga. Con message queues (RabbitMQ, Kafka, SQS), A publica mensajes y sigue. B los procesa cuando esté disponible.

Mensajes asíncronos no son siempre la respuesta

Async message passing añade complejidad. No es gratis. Para muchos casos, una llamada HTTP síncrona con timeout y retry es más simple. Async es para casos donde la latencia, throughput, o decoupling realmente importan.

Wrap-Up: los principios juntos

“Una service is only as reliable as its least reliable dependency”

Por eso importa incorporar estas ideas en cada componente de tu plataforma, no solo en la app principal.

Los 12 factores y los 4 traits del Reactive Manifesto se complementan:

Docker provee la infraestructura que hace ambos viables. La imagen inmutable es el artefacto compartido. El registry es el handoff. El scheduler provee resilience y elasticity. Tu trabajo es diseñar la app siguiendo estos principios.

Próximos pasos