syntaxis/docs/provision/multi-layer-validation.md
Jesús Pérez 9cef9b8d57 refactor: consolidate configuration directories
Merge _configs/ into config/ for single configuration directory.
Update all path references.

Changes:
- Move _configs/* to config/
- Update .gitignore for new patterns
- No code references to _configs/ found

Impact: -1 root directory (layout_conventions.md compliance)
2025-12-26 18:36:23 +00:00

26 KiB

🔗 Validación Multi-Capas: KCL → Terraform → Ansible

Fecha: 2025-11-20 Clasificación: Estrategia de validación end-to-end Enfoque: Aprovechar validaciones nativas de cada herramienta


📋 Tabla de Contenidos

  1. Arquitectura Multi-Capas
  2. Validación KCL (Definitions)
  3. Validación Terraform (IaC)
  4. Validación Ansible (Configuration)
  5. Orquestación NuShell
  6. Flujos de Validación End-to-End

🏗️ Arquitectura Multi-Capas

┌──────────────────────────────────────────────────────────┐
│ CAPA 0: DEFINICIONES (KCL)                               │
│ ═══════════════════════════════════════════════════════  │
│ • services.k (definiciones de servicios)                 │
│ • patterns.k (patrones de despliegue)                    │
│ • policies.k (políticas de validación)                   │
│                                                           │
│ VALIDACIÓN: kcl compile                                  │
│   ✅ Tipos correctos                                     │
│   ✅ Sin ciclos de dependencias                          │
│   ✅ Referencias válidas                                 │
│   ✅ Esquema completo                                    │
└──────────────────────────────────────────────────────────┘
                      ↓ (genera)
┌──────────────────────────────────────────────────────────┐
│ CAPA 1: INFRAESTRUCTURA AS CODE (Terraform)             │
│ ═══════════════════════════════════════════════════════  │
│ • kubernetes.tf (generado desde KCL)                     │
│ • docker-compose.tf (generado desde KCL)                 │
│ • networking.tf (generado desde KCL)                     │
│ • variables.tf (configuración)                           │
│                                                           │
│ VALIDACIÓN: terraform validate                           │
│   ✅ Sintaxis HCL correcta                               │
│   ✅ Recursos válidos                                    │
│   ✅ Dependencias de recursos                            │
│   ✅ Variables requeridas                                │
└──────────────────────────────────────────────────────────┘
                      ↓ (planifica)
┌──────────────────────────────────────────────────────────┐
│ CAPA 2: PLAN DE INFRASTRUCTURE (Terraform Plan)         │
│ ═══════════════════════════════════════════════════════  │
│ • terraform.tfplan (plan de cambios)                     │
│ • impacto en recursos                                    │
│ • dependencias detectadas                                │
│                                                           │
│ VALIDACIÓN: terraform plan -json                         │
│   ✅ Cambios permitidos                                  │
│   ✅ Sin errores de aplicación                           │
│   ✅ Impacto predecible                                  │
└──────────────────────────────────────────────────────────┘
                      ↓ (genera)
┌──────────────────────────────────────────────────────────┐
│ CAPA 3: CONFIGURACIÓN (Ansible)                          │
│ ═══════════════════════════════════════════════════════  │
│ • site.yml (playbook principal)                          │
│ • roles/
│ │   ├─ api-server/                                       │
│ │   ├─ database/                                         │
│ │   └─ monitoring/                                       │
│ • group_vars/
│ │   └─ production.yml (variables generadas)              │
│                                                           │
│ VALIDACIÓN: ansible-playbook --syntax-check              │
│   ✅ Sintaxis YAML correcta                              │
│   ✅ Tasks bien formados                                 │
│   ✅ Roles válidos                                       │
│   ✅ Variables resuelven                                 │
└──────────────────────────────────────────────────────────┘
                      ↓ (genera)
┌──────────────────────────────────────────────────────────┐
│ CAPA 4: DESPLIEGUE FINAL                                 │
│ ═══════════════════════════════════════════════════════  │
│ • Infrastructure aplicada                                │
│ • Servicios configurados                                 │
│ • Monitoreo activo                                       │
└──────────────────────────────────────────────────────────┘

FLUJO: Cada capa valida su propio dominio usando herramientas nativas

Validación KCL (Definitions)

1. KCL Native Validation

# Validación nativa - sin código personalizado
$ kcl compile provisioning/services.k

# Detecta automáticamente:
# ❌ Errores de tipo
services = [
    {
        id: 123  # ERROR: Esperaba str
    }
]

# ❌ Campos requeridos
service = {
    id: "api"
    # ERROR: name requerido
}

# ❌ Referencias inválidas
depends_on: ["service-inexistente"]  # ERROR

# ❌ Ciclos de dependencias
api = { depends_on: ["db"] }
db = { depends_on: ["api"] }  # ERROR: CICLO

# ❌ Puertos duplicados
ports: [3000, 3000]  # ERROR

2. Script NuShell: Ejecutar Validación KCL

#!/usr/bin/env nu

# kcl-validate.nu - Validar definiciones KCL

def validate_kcl [kcl_file: path] {
    print "🔍 Validando KCL: $kcl_file"

    let result = (
        kcl compile $kcl_file 2>&1
    )

    if ($result | str contains "Error:") {
        print "❌ Errores encontrados:"
        print $result
        return {
            success: false
            error: ($result | str trim)
        }
    }

    print "✅ KCL válido"
    return {
        success: true
        output: $result
    }
}

# Uso
validate_kcl "provisioning/services.k"

3. Qué Valida KCL Automáticamente

✅ TIPO-SEGURIDAD
   └─ id: str, port: int, replicas: int
   └─ Rechaza valores con tipo incorrecto

✅ REQUERIMIENTOS
   └─ Campos obligatorios deben estar presentes
   └─ Rechaza estructuras incompletas

✅ CICLOS DE DEPENDENCIAS
   └─ Función validate_no_dependency_cycles en policies.k
   └─ Rechaza A→B→A

✅ REFERENCIAS
   └─ Los servicios en depends_on deben existir
   └─ Rechaza referencias a servicios inexistentes

✅ UNICIDAD
   └─ Puertos únicos por host/namespace
   └─ IDs únicos en catálogo

✅ RESTRICCIONES DE RECURSOS
   └─ CPU/Memory en formato válido (100m, 512Mi)
   └─ Límites >= requests

✅ RELACIONES
   └─ Service debe existir antes de referenciar
   └─ Patterns referencian servicios existentes

🏗️ Validación Terraform (IaC)

1. Terraform Native Validation

# Validación nativa de HCL
$ terraform validate

# Detecta automáticamente:
# ❌ Sintaxis HCL inválida
resource "kubernetes_service" "api" {
    metadata {
        name = "api"
        # ERROR: bloque mal formado
    }
}

# ❌ Tipos incorrectos
resource "kubernetes_deployment" "api" {
    spec {
        replicas = "2"  # ERROR: Esperaba número
    }
}

# ❌ Variables no definidas
resource "kubernetes_service" "api" {
    metadata {
        namespace = var.undefined_var  # ERROR
    }
}

# ❌ Referencias inválidas
resource "kubernetes_service" "api" {
    spec {
        selector {
            app = kubernetes_deployment.undefined.spec.selector.match_labels.app  # ERROR
        }
    }
}

2. Terraform Plan Validation

# Validación de plan
$ terraform plan -json

# Analiza:
# ✅ Cambios válidos
# ✅ Dependencias correctas
# ✅ Recursos creables
# ❌ Cambios destructivos
# ❌ Errores de aplicación
# ❌ Conflictos de estado

3. Script NuShell: Validar Terraform

#!/usr/bin/env nu

# terraform-validate.nu - Validar generado Terraform

def validate_terraform [tf_dir: path] {
    print "🔍 Validando Terraform: $tf_dir"

    # Cambiar a directorio
    cd $tf_dir

    # Validar sintaxis
    let validate_result = (
        terraform validate -json 2>&1 | from json
    )

    if not $validate_result.valid {
        print "❌ Errores de sintaxis Terraform:"
        print $validate_result
        return {
            success: false
            errors: $validate_result.diagnostics
        }
    }

    print "✅ Sintaxis Terraform válida"

    # Validar plan
    let plan_result = (
        terraform plan -json 2>&1 | from json
    )

    # Buscar cambios destructivos
    let destructive = (
        $plan_result.resource_changes
        | filter { |rc| $rc.change.actions | any { |a| $a == "delete" } }
    )

    if ($destructive | length) > 0 {
        print "⚠️  CAMBIOS DESTRUCTIVOS DETECTADOS:"
        $destructive | each { |rc|
            print $"  - ($rc.type).($rc.name) será eliminado"
        }
    }

    return {
        success: true
        plan_summary: $plan_result.summary
        destructive_changes: ($destructive | length)
    }
}

# Uso
validate_terraform "generated/terraform"

4. Ejemplo: Terraform Generado desde KCL

# Generado automáticamente desde services.k

variable "namespace" {
  type    = string
  default = "production"
}

variable "docker_registry" {
  type    = string
  default = "docker.io"
}

# Kubernetes Deployment - Generado desde service.id="api"
resource "kubernetes_deployment" "api" {
  metadata {
    name      = "api-server"
    namespace = var.namespace
    labels = {
      app       = "syntaxis"
      component = "api"
      tier      = "backend"
    }
  }

  spec {
    replicas = 2  # Desde service.replicas

    selector {
      match_labels = {
        app = "api-server"
      }
    }

    template {
      metadata {
        labels = {
          app = "api-server"
        }
      }

      spec {
        container {
          name  = "api"
          image = "${var.docker_registry}/syntaxis:api-latest"  # Desde service.image
          port {
            name          = "http"
            container_port = 3000  # Desde service.port
          }

          # Recursos - Desde service.resources
          resources {
            requests = {
              cpu    = "100m"
              memory = "256Mi"
            }
            limits = {
              cpu    = "500m"
              memory = "512Mi"
            }
          }

          # Health check - Desde service.healthCheck
          liveness_probe {
            http_get {
              path = "/health"
              port = "http"
            }
            initial_delay_seconds = 10
            period_seconds        = 10
          }

          # Env vars - Desde service.env
          env {
            name  = "RUST_LOG"
            value = "info"
          }
          env {
            name  = "DATABASE_URL"
            value = "postgresql://postgres:5432/syntaxis"
          }
        }

        # Depends on - Desde service.depends_on
        init_container {
          name = "wait-for-db"
          image = "${var.docker_registry}/busybox:latest"
          command = ["sh", "-c", "until nc -z database 5432; do sleep 1; done"]
        }
      }
    }
  }
}

# Kubernetes Service - Generado automáticamente
resource "kubernetes_service" "api" {
  metadata {
    name      = "api-service"
    namespace = var.namespace
  }

  spec {
    selector = {
      app = "api-server"
    }

    port {
      name       = "http"
      port       = 80
      target_port = "http"
    }

    type = "LoadBalancer"
  }
}

# Validación que Terraform hace automáticamente:
# ✅ name es string requerido
# ✅ namespace referencia variable existente
# ✅ replicas es número (2)
# ✅ image es string válido
# ✅ port es número (3000)
# ✅ resources.requests.cpu es formato válido
# ✅ init_container referencias imagen válida
# ✅ selector.app coincide con deployment.labels

5. Qué Valida Terraform Automáticamente

✅ SINTAXIS HCL
   └─ Bloques, atributos, tipos correctos
   └─ Comillas, llaves, punto y coma

✅ TIPOS DE DATOS
   └─ Numbers, strings, booleans correctos
   └─ Listas y objetos bien formados

✅ REFERENCIAS
   └─ Variables referenciadas existen
   └─ Recursos referenciados están definidos
   └─ Módulos válidos

✅ DEPENDENCIAS
   └─ Orden de creación correcto
   └─ Ciclos detectados

✅ PROVEEDORES
   └─ Proveedor Kubernetes configurado
   └─ Versiones compatibles

✅ PLAN
   └─ Cambios aplicables
   └─ Sin conflictos de estado
   └─ Dependencias resuelven

✅ VALIDACIONES CUSTOMIZADAS
   └─ Nombre debe contener patrón
   └─ Replicas dentro de rango
   └─ CPU requests < limits

⚙️ Validación Ansible (Configuration)

1. Ansible Native Validation

# Validación de sintaxis YAML
$ ansible-playbook site.yml --syntax-check

# Detecta automáticamente:
# ❌ YAML inválido
- hosts: all
  tasks:
    - name: Start service
      systemd:
        name: api  # ERROR: indentación incorrecta

# ❌ Tasks malformados
- name: Invalid task
  copy:
    src: file.txt
    dest: /tmp/
    # ERROR: falta 'dest' o 'src'

# ❌ Roles inexistentes
- import_role:
    name: undefined_role  # ERROR: rol no existe

# ❌ Variables sin resolver
- name: Use variable
  debug:
    msg: "{{ undefined_var }}"  # ERROR: variable no existe

2. Script NuShell: Validar Ansible

#!/usr/bin/env nu

# ansible-validate.nu - Validar playbooks Ansible

def validate_ansible [playbook_file: path] {
    print "🔍 Validando Ansible: $playbook_file"

    # Validar sintaxis
    let syntax_check = (
        ansible-playbook $playbook_file --syntax-check 2>&1
    )

    if ($syntax_check | str contains "ERROR") {
        print "❌ Errores de sintaxis Ansible:"
        print $syntax_check
        return {
            success: false
            error: ($syntax_check | str trim)
        }
    }

    print "✅ Sintaxis Ansible válida"

    # Validar roles
    let roles_dir = "roles"
    let roles = (
        ls $roles_dir | get name | map { |r| $r | path basename }
    )

    print $"📦 Roles detectados: ($roles | str join ', ')"

    # Validar references en playbook
    let playbook_content = (open $playbook_file)
    for role in $roles {
        if not ($playbook_content | str contains $role) {
            print $"⚠️  Rol ($role) no utilizado"
        }
    }

    return {
        success: true
        roles_count: ($roles | length)
        roles: $roles
    }
}

# Uso
validate_ansible "site.yml"

3. Ejemplo: Playbook Generado desde KCL

# Generado automáticamente desde services.k

---
- name: Deploy syntaxis services
  hosts: all
  become: yes
  vars_files:
    - group_vars/{{ environment }}.yml
  vars:
    # Variables generadas desde KCL
    services:
      - name: api-server
        port: 3000
        image: "{{ docker_registry }}/syntaxis:api-latest"
        replicas: 2
      - name: database
        port: 5432
        image: "{{ docker_registry }}/postgres:15-alpine"
        replicas: 1

  pre_tasks:
    - name: Validate Docker is installed
      command: docker --version
      register: docker_version
      changed_when: false

    - name: Validate connectivity to services
      wait_for:
        host: "{{ item.host }}"
        port: "{{ item.port }}"
        timeout: 5
      loop:
        - { host: database, port: 5432 }

  roles:
    # Generado desde service.id="api-server"
    - role: api-server
      vars:
        service_port: 3000
        service_image: "{{ docker_registry }}/syntaxis:api-latest"
        service_replicas: 2
        service_env:  # Desde service.env
          RUST_LOG: info
          DATABASE_URL: "postgresql://postgres:5432/syntaxis"
        service_resources:  # Desde service.resources
          cpu: 100m
          memory: 256Mi

    # Generado desde service.id="database"
    - role: database
      vars:
        service_port: 5432
        service_image: "{{ docker_registry }}/postgres:15-alpine"
        service_replicas: 1
        postgresql_databases:
          - name: syntaxis
            user: postgres

  post_tasks:
    - name: Verify all services are running
      uri:
        url: "http://api-server:3000/health"
        method: GET
        status_code: 200
      retries: 3
      delay: 10

4. Qué Valida Ansible Automáticamente

✅ SINTAXIS YAML
   └─ Indentación correcta
   └─ Estructura válida

✅ TASKS VÁLIDOS
   └─ Modules existen
   └─ Parámetros correctos
   └─ Handlers referencian nombres válidos

✅ ROLES
   └─ Directorio structure correcto
   └─ Tasks, handlers, templates presentes

✅ VARIABLES
   └─ Sintaxis de variable correcta {{ var }}
   └─ Tipos de dato correctos

✅ INVENTARIOS
   └─ Hosts válidos
   └─ Grupos existentes

✅ HANDLERS
   └─ Listeners referencian names válidos
   └─ Sin handlers huérfanos

✅ IMPORTS/INCLUDES
   └─ Archivos referencian existen
   └─ Variables presentes

🎯 Orquestación NuShell

1. validate-all.nu - Orquestación Completa

#!/usr/bin/env nu

# validate-all.nu - Validación multi-capa completa

const GREEN = "[32m"
const RED = "[91m"
const YELLOW = "[33m"
const BLUE = "[34m"
const RESET = "[0m"

def separator [title: string] {
    print ""
    print $"($BLUE)════════════════════════════════════════($RESET)"
    print $"($BLUE)($title)($RESET)"
    print $"($BLUE)════════════════════════════════════════($RESET)"
}

# CAPA 0: KCL VALIDATION
separator "CAPA 0: Validación KCL (Definiciones)"

let kcl_result = (
    kcl compile "provisioning/services.k" 2>&1
)

if ($kcl_result | str contains "Error:") {
    print $"($RED)❌ KCL inválido($RESET)"
    print $kcl_result
    exit 1
} else {
    print $"($GREEN)✅ KCL válido($RESET)"
    print $"   - Tipos correctos"
    print $"   - Sin ciclos"
    print $"   - Referencias válidas"
}

print ""

# CAPA 1: TERRAFORM VALIDATION
separator "CAPA 1: Validación Terraform (IaC)"

# Generar Terraform desde KCL si no existe
if not (path exists "generated/terraform/main.tf") {
    print "📝 Generando Terraform desde KCL..."
    # Ejecutar generador
    nu generate-terraform.nu
}

cd "generated/terraform"

let tf_validate = (
    terraform validate -json 2>&1 | from json
)

if not $tf_validate.valid {
    print $"($RED)❌ Terraform inválido($RESET)"
    print $tf_validate.diagnostics
    exit 1
} else {
    print $"($GREEN)✅ Terraform válido($RESET)"
    print $"   - Sintaxis HCL correcta"
    print $"   - Variables resuelven"
    print $"   - Recursos válidos"
}

# Plan
let plan_result = (
    terraform plan -json 2>&1
    | from json
)

# Analizar cambios
let changes = (
    $plan_result.resource_changes
    | map { |rc| {type: $rc.type, name: $rc.name, actions: $rc.change.actions} }
)

print $"   Plan summary:"
for change in $changes {
    print $"   - ($change.type).($change.name): ($change.actions | str join '→')"
}

cd - # volver al directorio anterior
print ""

# CAPA 2: ANSIBLE VALIDATION
separator "CAPA 2: Validación Ansible (Configuration)"

# Generar Ansible desde KCL si no existe
if not (path exists "generated/ansible/site.yml") {
    print "📝 Generando Ansible desde KCL..."
    # Ejecutar generador
    nu generate-ansible.nu
}

let ansible_check = (
    ansible-playbook "generated/ansible/site.yml" --syntax-check 2>&1
)

if ($ansible_check | str contains "ERROR") {
    print $"($RED)❌ Ansible inválido($RESET)"
    print $ansible_check
    exit 1
} else {
    print $"($GREEN)✅ Ansible válido($RESET)"
    print $"   - Sintaxis YAML correcta"
    print $"   - Roles válidos"
    print $"   - Variables resuelven"
}

print ""

# RESUMEN FINAL
separator "📊 RESUMEN DE VALIDACIÓN"

print $"($GREEN)TODAS LAS CAPAS VALIDADAS EXITOSAMENTE($RESET)"
print ""
print "✅ Capa 0 - KCL (Definiciones)"
print "✅ Capa 1 - Terraform (Infraestructura)"
print "✅ Capa 2 - Ansible (Configuración)"
print ""
print $"($GREEN)Listo para deployment($RESET)"
print ""

2. Flujo en CI/CD

# .github/workflows/validate-all.yml equivalent

print "🚀 Iniciando validación multi-capas..."

# 1. KCL validation
if (!(kcl compile provisioning/services.k)) {
    print "❌ KCL failed"
    exit 1
}

# 2. Terraform validation
if (!(terraform validate)) {
    print "❌ Terraform failed"
    exit 1
}

# 3. Ansible validation
if (!(ansible-playbook site.yml --syntax-check)) {
    print "❌ Ansible failed"
    exit 1
}

# 4. All passed
print "✅ All layers validated"

🔄 Flujos de Validación End-to-End

Flujo 1: Cambio en Service Definition

Developer modifica services.k:
├─ Agrega nuevo servicio "monitoring"

Git commit + push
     ↓
GitHub CI Pipeline (validate-all.nu):
     ├─ CAPA 0: kcl compile
     │  └─ ✅ KCL válido
     │
     ├─ Generar Terraform desde KCL (actualizadas)
     │
     ├─ CAPA 1: terraform validate
     │  └─ ✅ Nuevo recurso válido
     │  └─ ✅ Sin conflictos
     │
     ├─ Generar Ansible desde KCL (actualizadas)
     │
     ├─ CAPA 2: ansible-playbook --syntax-check
     │  └─ ✅ Nuevo role incorporado
     │  └─ ✅ Variables resuelven
     │
     └─ ✅ APROBADO para merge

Resultado:
└─ Validación hecha por herramientas nativas
└─ Sin código personalizado de validación
└─ Confianza en cada capa

Flujo 2: Validación de Compatibilidad

Cambio detectado: Se rompe dependencia
├─ database → service inexistente

Pipeline CI:
├─ CAPA 0: kcl compile
│  └─ ❌ ERROR: "service-b depends on non-existent service-c"
│
└─ ❌ RECHAZADO inmediatamente

Developer notificado:
└─ Corrije el error
└─ Reintenta

Flujo 3: Validación de Impacto

Cambio: Puerto de database 5432 → 5433

Pipeline CI:
├─ CAPA 0: kcl compile
│  └─ ✅ KCL válido
│
├─ Generar Terraform
│
├─ CAPA 1: terraform plan
│  └─ Detecta: "RDS port change" (breaking)
│  └─ ⚠️ Manual approval required
│
└─ ⚠️ ESPERANDO APROBACIÓN MANUAL

DevOps revisa:
└─ "Esto requiere downtime"
└─ Aprueba manualmente
└─ Deployment continúa

📊 Comparación: Validación Manual vs Multi-Capa

Aspecto Manual Multi-Capa (Nativo)
Errores de tipo KCL Descubiertos al deploy Rechazados al commit
Sintaxis Terraform Descubiertos al deploy Rechazados al commit
Errores Ansible Descubiertos al deploy Rechazados al commit
Ciclos de dependencias Requiere revisión manual Detectado automáticamente
Conflictos de puerto Requiere revisión manual Detectado automáticamente
Cambios destructivos Riesgo en producción Detectado antes de apply
Tiempo de descubrimiento Horas (en deploy) Segundos (en commit)
Costo de error Alto (downtime) Bajo (cambio rechazado)

🎯 Resumen: Cada Herramienta Valida Su Dominio

KCL       → Valida definiciones (servicios, dependencias)
  ↓
Terraform → Valida infraestructura (HCL, recursos, plan)
  ↓
Ansible   → Valida configuración (playbooks, roles)
  ↓
NuShell   → Orquesta validación + reporta resultados

Beneficio:

  • Cada herramienta hace lo que sabe hacer mejor
  • Validaciones nativas = confiables
  • Sin duplicación de lógica
  • Errores descubiertos temprano (en commit, no en deploy)
  • Confianza en cada capa del pipeline

📋 Checklist de Implementación

  • Crear validate-all.nu script principal
  • Crear kcl-validate.nu para KCL
  • Crear terraform-validate.nu para Terraform
  • Crear ansible-validate.nu para Ansible
  • Integrar en .github/workflows/validate.yml
  • Setup pre-commit hooks para validación local
  • Documentar flujos de validación para DevOps
  • Crear dashboard que muestre validaciones
  • Test con cambios válidos e inválidos

Conclusión: Las herramientas modernas (KCL, Terraform, Ansible) ya incluyen validación nativa de sus dominios. La clave es orquestarlas inteligentemente con NuShell para crear un pipeline de validación completo end-to-end.