Gestión de Configuración
La gestión de configuración separa lo que cambia por entorno de cómo la automatización lo aplica. Los proyectos de infraestructura en Python utilizan plantillas, archivos de configuración en capas y almacenes de secretos para que los entornos de desarrollo, staging y producción permanezcan estructuralmente idénticos sin copiar pilas completas.
Receta
Tarjeta de receta de referencia rápida, lista para copiar y pegar.
from pydantic_settings import BaseSettings, SettingsConfigDict
class AppSettings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
environment: str = "dev"
database_url: str
log_level: str = "info"
settings = AppSettings() # lee variables de entorno y opcionalmente .env localmente# app.env.j2
APP_ENV={{ environment }}
DATABASE_URL={{ database_url }}
LOG_LEVEL={{ log_level }}Cuándo usar esto:
- Misma plantilla, diferentes valores entre desarrollo/staging/producción
- Secretos fuera de git con referencias inyectadas en el momento del despliegue
- Validación de configuración antes de la aplicación con modelos Pydantic
- Indicadores de funciones y límites que difieren por entorno
- Ansible/Pulumi/Terraform consumiendo todos un esquema de configuración
Ejemplo de Trabajo
Configuración YAML en capas, renderizado Jinja2 y stub de búsqueda de secretos para un script de despliegue.
"""render_config.py - carga la configuración del entorno, fusiona secretos, renderiza plantilla."""
from __future__ import annotations
import json
from pathlib import Path
import yaml
from jinja2 import Environment, FileSystemLoader, select_autoescape
ROOT = Path(__file__).parent
CONFIG_DIR = ROOT / "config"
def load_yaml(path: Path) -> dict:
with path.open() as f:
return yaml.safe_load(f) or {}
def load_config(environment: str) -> dict:
base = load_yaml(CONFIG_DIR / "base.yaml")
env = load_yaml(CONFIG_DIR / f"{environment}.yaml")
merged = {**base, **env, "environment": environment}
return merged
def fetch_secret(name: str) -> str:
# Producción: boto3.client("secretsmanager").get_secret_value(...)
return f"SECRET::{name}"
def render(template_name: str, context: dict) -> str:
env = Environment(
loader=FileSystemLoader(ROOT / "templates"),
autoescape=select_autoescape(),
)
template = env.get_template(template_name)
return template.render(**context)
if __name__ == "__main__":
cfg = load_config("staging")
cfg["database_password"] = fetch_secret("db/password")
output = render("service.env.j2", cfg)
print(output)
print(json.dumps({"rendered_for": cfg["environment"]}))# config/base.yaml
log_level: info
service_port: 8080# config/staging.yaml
database_host: staging.db.internal
feature_new_ui: true# templates/service.env.j2
APP_ENV={{ environment }}
LOG_LEVEL={{ log_level }}
PORT={{ service_port }}
DATABASE_HOST={{ database_host }}
DATABASE_PASSWORD={{ database_password }}
FEATURE_NEW_UI={{ feature_new_ui }}Lo que esto demuestra:
- La fusión de YAML base + entorno mantiene la estructura DRY (Don't Repeat Yourself)
- Los secretos se obtienen en el momento de la renderización, nunca se incluyen en las plantillas
- Jinja2 renderiza los archivos de entorno finales para la inyección en contenedores o el módulo
templatede Ansible - La línea de registro JSON registra qué entorno se renderizó para auditoría
Análisis Profundo
Cómo Funciona
- Capa de datos: YAML/JSON/TOML por entorno + validación de esquema
- Capa de secretos: SSM, Secrets Manager, Vault o almacén de secretos de CI
- Capa de plantillas: Jinja2, envsubst o variables de Terraform/Pulumi
- Capa de aplicación: Ansible, Kubernetes o systemd cargan los artefactos renderizados
- Flujo de cambios: editar configuración en git → validar → renderizar → desplegar → verificar
Configuración vs. Secretos
| Elemento | Almacén | Ejemplo |
|---|---|---|
| Región, tamaño de instancia | Git (YAML de entorno) | instance_type: t3.small |
| Contraseña de BD, claves API | Gestor de secretos | arn:aws:secretsmanager:... |
| Indicadores de funciones | Git o servicio de indicadores remoto | feature_x: false |
| Sobrescrituras en tiempo de ejecución | Variables de entorno | LOG_LEVEL=debug |
Notas de Python
from pydantic import Field, SecretStr
from pydantic_settings import BaseSettings
class DeploySettings(BaseSettings):
environment: str
db_password: SecretStr = Field(alias="DATABASE_PASSWORD")
def redacted(self) -> dict:
return self.model_dump(mode="json", exclude={"db_password"})Errores Comunes
- Secretos en
prod.yaml: credenciales en el historial de git para siempre. Solución: almacenar ARN o nombres de secretos, resolver en tiempo de ejecución. - Plantillas bifurcadas por entorno: deriva cuando un entorno recibe una corrección. Solución: una plantilla, solo datos específicos del entorno.
- Falta de validación: los errores tipográficos llegan a producción como despliegues fallidos. Solución: modelos Pydantic o JSON Schema en CI.
- Archivos renderizados legibles por cualquiera:
chmod 644en.envcon contraseñas. Solución: propiedad0600solo para el usuario del servicio. - Plantillas con entrada no confiable: SSTI (Server-Side Template Injection) si las plantillas incluyen contenido del usuario. Solución: mantener las plantillas internas; nunca incrustar cadenas de usuario en Jinja2.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| Variables de entorno codificadas solo en CI | Proyectos pequeños de un solo entorno | Múltiples entornos con estructura compartida |
| Configuración en vivo de Consul/etcd | Ajuste dinámico en tiempo de ejecución | La infraestructura desea cambios versionados y revisados |
| Solo configuración de Pulumi | Todos los ajustes son parámetros de recursos en la nube | La configuración a nivel de aplicación también es necesaria en los hosts |
.env incluido por desarrollador | Conveniencia solo para desarrollo local | Cualquier entorno compartido o de producción |
Preguntas Frecuentes
¿Debería la configuración vivir en git o en un almacén de secretos?
Los parámetros no secretos pertenecen a git con superposiciones de entorno. Los secretos pertenecen a un gestor con rotación; git solo almacena referencias.
¿Por qué Jinja2 para equipos de infraestructura?
Ansible lo usa de forma nativa, la sintaxis es familiar y admite condicionales para bloques opcionales sin duplicar archivos completos.
¿Cómo encaja pydantic-settings?
Las aplicaciones leen variables de entorno en tiempo de ejecución con validación. La infraestructura renderiza esos archivos de entorno a partir de YAML en capas + secretos durante el despliegue.
¿Cómo evito que la configuración de staging afecte a las bases de datos de producción?
Cadenas de conexión separadas por archivo de entorno, convenciones de nomenclatura (staging.db.internal) y comprobaciones de CI que fallan si los nombres de host de prod aparecen en archivos que no son de producción.
¿Pueden las variables de Terraform reemplazar esto?
Para recursos en la nube, sí. Los archivos de entorno de la aplicación en máquinas virtuales/contenedores aún se benefician de la renderización fuera de las salidas de Terraform.
¿Con qué frecuencia debo rotar los secretos?
Automatiza la rotación en Secrets Manager/SSM y vuelve a renderizar las configuraciones. Documenta el rollback si la rotación rompe servicios dependientes.
¿Qué pasa con la configuración de 12 factores?
Almacena la configuración en variables de entorno en tiempo de ejecución. Tu paso de renderización produce esas variables para Kubernetes, Lambda o systemd.
¿Cómo pruebo las plantillas?
pytest con diccionarios de contexto fijos afirma que las cadenas renderizadas contienen las claves esperadas y nunca filtran tokens de marcador de posición como {{.
¿Deberían los valores predeterminados vivir en base.yaml o en el código?
Valores predeterminados operativos seguros en base.yaml; valores predeterminados de código solo para sustituciones de desarrollo local. Documenta las sobrescrituras en README por entorno.
¿Cómo audito quién cambió la configuración de producción?
git blame en el YAML del entorno más los registros de despliegue que muestran el SHA renderizado y la identidad del operador a partir de las afirmaciones OIDC de CI.
Relacionado
- Conceptos Básicos de Automatización de Infraestructura - separación de entornos
- Ansible - módulo
templateygroup_vars - Secrets Manager & SSM - resolución de secretos de AWS
- Configuración y Secretos en Producción - inyección en tiempo de ejecución
- Mejores Prácticas de Automatización de Infraestructura - reglas de configuración versionada
Versiones de la pila: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, y uv 0.6+.