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)
26 KiB
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
- Arquitectura Multi-Capas
- Validación KCL (Definitions)
- Validación Terraform (IaC)
- Validación Ansible (Configuration)
- Orquestación NuShell
- 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.nuscript principal - Crear
kcl-validate.nupara KCL - Crear
terraform-validate.nupara Terraform - Crear
ansible-validate.nupara 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.