Saltearse al contenido

Stack LAMP con Ansible & Docker

Este proyecto moderniza el despliegue de aplicaciones PHP (Legacy), evolucionando de una configuración manual a una Infraestructura Inmutable. A diferencia de los despliegues básicos de contenedores, esta solución aborda problemas del mundo real: la necesidad de compilar extensiones de PHP personalizadas (mysqli), la gestión segura de credenciales fuera del código y la estandarización del entorno de desarrollo.

  1. Inmutabilidad: Construcción de imágenes Docker personalizadas (custom builds) en tiempo de despliegue para inyectar dependencias del sistema.
  2. Seguridad (12-Factor App): Desacoplamiento total de credenciales. El código fuente no contiene secretos; estos se inyectan como variables de entorno.
  3. Estandarización: Implementación de un Makefile como wrapper para abstraer la complejidad de Python/Ansible y facilitar el onboarding.

El diseño implementa un patrón de Build & Deploy Remoto orquestado por Ansible. El nodo de control prepara la configuración y los secretos, mientras que el host remoto construye y ejecuta los contenedores.

graph LR
    %% --- Definición de Estilos ---
    classDef ansibleNode fill:#fff9c4,stroke:#fbc02d,stroke-width:2px,color:#000000;
    classDef dockerNode fill:#e0f7fa,stroke:#0288d1,stroke-width:2px,color:#000000;
    classDef secretNode fill:#ffcdd2,stroke:#c62828,stroke-width:2px,color:#000000;

    subgraph Control_Node ["Nodo de Control (Dev)"]
        style Control_Node fill:#eceff1,stroke:#cfd8dc,color:#000000
        Make[("Makefile Wrapper")]:::ansibleNode
        Ansible[("Ansible Playbook")]:::ansibleNode
        Secrets[("secrets.yml<br/>(GitIgnored)")]:::secretNode
        
        Make --> Ansible
        Secrets -.-> Ansible
    end

    subgraph Remote_Host ["Servidor de Aplicaciones (Ubuntu 24.04)"]
        style Remote_Host fill:#0288d1,stroke:#cfd8dc,stroke-width:2px,color:#000000
        
        subgraph Docker_Runtime ["Docker Engine"]
            style Docker_Runtime fill:#eceff1,stroke:#455a64,color:#000000
            
            Build[("Docker Build<br/>(PHP + Mysqli)")]:::dockerNode
            App[("Container Web<br/>Apache")]:::dockerNode
            DB[("Container DB<br/>MariaDB")]:::dockerNode
            
            Ansible -- "1. Upload Dockerfile" --> Build
            Build -- "2. Image Created" --> App
            Ansible -- "3. Inject ENV Vars" --> App
            App -- "Internal DNS" --> DB
        end
    end
    
    linkStyle default stroke:#333,stroke-width:2px,fill:none;

La solución utiliza una estructura profesional que separa configuración, secretos y lógica de despliegue.

  1. Wrapper de Ejecución (Makefile)

    Para evitar problemas de versiones y entornos virtuales, encapsulamos los comandos en un Makefile. Esto garantiza que cualquier desarrollador use las mismas herramientas.

    Makefile
    setup:
    python3 -m venv .venv
    .venv/bin/pip install ansible-core docker
    .venv/bin/ansible-galaxy install -r requirements.yml
    deploy:
    .venv/bin/ansible-playbook playbook/deploy-lamp.yml
  2. Gestión de Secretos (Patrón de Sobreescritura)

    Ansible carga primero las variables públicas (all.yml) y luego sobreescribe con un archivo de secretos local que está excluido de Git (.gitignore).

    playbook/deploy-lamp.yml
    - hosts: app_servers
    vars_files:
    - "../group_vars/all.yml" # Estructura pública (Placeholders)
    - "../group_vars/secrets.yml" # Secretos locales (Real Passwords)
  3. Lógica del Playbook (Build & Run)

    El playbook no solo descarga imágenes, sino que sube un Dockerfile y compila una imagen personalizada para soportar mysqli.

    playbook/deploy-lamp.yml
    tasks:
    - name: Construir imagen PHP personalizada
    community.docker.docker_image:
    name: "{{ custom_image_name }}"
    tag: latest
    build:
    path: "{{ remote_project_path }}" # Ruta remota del Dockerfile
    source: build
    force_source: true
    - name: Desplegar Web Container
    community.docker.docker_container:
    name: "{{ project_name }}-web"
    image: "{{ custom_image_name }}:latest" # Usamos la imagen recién creada
    env:
    # Inyección segura de variables de entorno
    DB_HOST: "{{ project_name }}-db"
    DB_USER: "{{ mysql_user }}"
    DB_PASSWORD: "{{ mysql_pass }}"
  4. Código Agnóstico (PHP)

    El código fuente (src/index.php) no contiene ninguna credencial. Lee la configuración del entorno, cumpliendo con los principios de The Twelve-Factor App.

    src/index.php
    <?php
    // Lectura segura desde variables de entorno
    $servername = getenv('DB_HOST');
    $username = getenv('DB_USER');
    $password = getenv('DB_PASSWORD');
    $conn = new mysqli($servername, $username, $password, $dbname);
    if ($conn->connect_error) {
    die("❌ Fallo de conexión: " . $conn->connect_error);
    }
    echo "✅ Conectado exitosamente a MariaDB.";
    ?>
  5. Ejecución Estandarizada

    Gracias al Makefile, el despliegue se reduce a dos comandos, independientemente de la complejidad subyacente.

    Ventana de terminal
    # Inicialización (Solo la primera vez)
    make setup
    # Despliegue idempotente
    make deploy
CaracterísticaScript Bash TradicionalAnsible + Docker Pro
CredencialesHardcoded en archivos (.php)Inyectadas en Memoria (ENV)
DependenciasInstalación manual (apt-get)Docker Build Automático
Portabilidad”Funciona en mi máquina”Estandarizado vía Makefile

Este proyecto sirve como base para la migración a la nube. Hemos documentado este proceso en la sección de Operaciones Cloud:

  1. Migración IaaS: Replicar esta arquitectura utilizando Máquinas Virtuales en Azure.

  2. Azure Container Registry (ACR): Mover el proceso de build a la nube.

  3. Azure Key Vault: Gestión de secretos centralizada.