DevOps

Chaos Engineering para microservicios Go: cómo romper el sistema intencionalmente para hacerlo más confiable

Ruslan Ismailov Publicado 14 min de lectura
C

Introducción: qué es Chaos Engineering y por qué no es solo caos

Chaos Engineering es una disciplina, no vandalismo. La esencia del enfoque consiste en realizar experimentos controlados sobre un sistema en producción o staging con el objetivo de identificar vulnerabilidades ocultas antes de que lo hagan los fallos reales. En el mundo de los microservicios Go distribuidos, donde cualquier Pod en Kubernetes puede caer, la red puede degradarse y las dependencias pueden bloquearse, esta práctica ha dejado de ser un privilegio de Netflix para convertirse en una necesidad para cualquier equipo de ingeniería maduro.

La definición formal de los Principles of Chaos Engineering es la siguiente:

«Chaos Engineering es la disciplina de experimentar sobre un sistema con el fin de generar confianza en la capacidad del sistema para soportar condiciones turbulentas en producción.»

La palabra clave aquí es confianza. No se trata solo de romper el sistema; se trata de formular una hipótesis, realizar el experimento, medir el resultado y extraer conclusiones. Es un enfoque científico aplicado a la confiabilidad.

Los principios del Chaos Monkey de Netflix y su vigencia en 2026

Netflix lanzó Chaos Monkey en 2011, una herramienta que apagaba instancias aleatoriamente en producción. A lo largo de los años, el ecosistema creció hasta convertirse en el Simian Army completo, y los principios se extendieron mucho más allá de una sola empresa. En 2026, estos principios se han adaptado a entornos nativos de Kubernetes:

  • Steady State First: define cómo se ve el funcionamiento «normal» del sistema —latencia p99, tasa de errores, throughput— antes de romper cualquier cosa.
  • Experimentos basados en hipótesis: «Si una de las tres réplicas cae, el SLO seguirá cumpliéndose» es una hipótesis verificable.
  • Minimizar el blast radius: comienza con entornos aislados y acércate progresivamente a producción.
  • Automatizar los experimentos: el caos manual no es chaos engineering, es simplemente un accidente. La automatización mediante CI/CD convierte los experimentos en una práctica sistemática.

En el contexto de microservicios Go en Kubernetes, los principios de Netflix siguen siendo relevantes, pero el conjunto de herramientas disponibles se ha vuelto significativamente más rico y declarativo.

Herramientas para Chaos Engineering en Kubernetes

Chaos Mesh

Chaos Mesh es un proyecto de la CNCF que proporciona una plataforma nativa de Kubernetes para la inyección de fallos. Funciona a través de Custom Resource Definitions (CRD) y admite una amplia gama de escenarios: latencia de red, pérdida de paquetes, eliminación de Pods, estrés de CPU y memoria, e inyección de errores en llamadas al sistema mediante eBPF.

LitmusChaos

LitmusChaos es otro proyecto de la CNCF orientado a experimentos basados en workflows. Ofrece una biblioteca lista para usar llamada ChaosHub con cientos de experimentos y una interfaz visual cómoda para la orquestación. LitmusChaos es ideal para equipos que recién comienzan a implementar chaos engineering, gracias a su bajo umbral de entrada.

En este artículo nos centraremos en Chaos Mesh como la herramienta más flexible y ampliamente utilizada en clústeres Kubernetes de nivel productivo.

Escenarios típicos de fallos en microservicios Go

Network Latency

Los servicios Go que operan mediante gRPC o HTTP/2 son extremadamente sensibles a la latencia de red. Un aumento de 200ms puede convertir el p99 en una catástrofe debido al efecto acumulativo en las cadenas de llamadas. Este escenario es el primer candidato para un experimento.

Pod Failure

La eliminación aleatoria de Pods verifica la correcta configuración de las sondas liveness/readiness, la velocidad de reinicio, el comportamiento del load balancer y la resiliencia de los clientes ante un connection reset. En Go es especialmente importante asegurarse de que los clientes HTTP manejen correctamente io.EOF y connection refused.

CPU Throttling

El CPU throttling en Kubernetes ocurre cuando un contenedor supera los límites definidos en resources.limits.cpu. El runtime de Go, especialmente el GC, puede comportarse de forma impredecible bajo throttling. Esta prueba ayuda a identificar límites mal configurados y pausas del GC que afectan la latencia.

Memory Pressure y OOMKill

Los servicios Go con goroutine leaks o un pool de conexiones mal configurado pueden ser eliminados por el OOM Killer. Los experimentos de caos con estrés de memoria permiten detectar estas vulnerabilidades antes de que ocurra un incidente.

Práctica: configuración de Chaos Mesh y primer experimento

Instalación de Chaos Mesh

La instalación se realiza mediante Helm en unos pocos comandos:

helm repo add chaos-mesh https://charts.chaos-mesh.org
helm repo update
kubectl create ns chaos-testing
helm install chaos-mesh chaos-mesh/chaos-mesh \
  --namespace=chaos-testing \
  --set chaosDaemon.runtime=containerd \
  --set chaosDaemon.socketPath=/run/containerd/containerd.sock \
  --version 2.6.3

Experimento 1: Network Latency para un servicio Go

Supongamos que tenemos un servicio Go order-service en el namespace production que se comunica con inventory-service. Vamos a inyectar un retraso de 300ms en las conexiones salientes:

apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
  name: order-service-latency
  namespace: production
spec:
  action: delay
  mode: one
  selector:
    namespaces:
      - production
    labelSelectors:
      app: order-service
  delay:
    latency: "300ms"
    correlation: "25"
    jitter: "50ms"
  direction: to
  target:
    mode: all
    selector:
      namespaces:
        - production
      labelSelectors:
        app: inventory-service
  duration: "5m"

Experimento 2: Pod Failure

apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
  name: inventory-pod-failure
  namespace: production
spec:
  action: pod-failure
  mode: fixed-percent
  value: "33"
  selector:
    namespaces:
      - production
    labelSelectors:
      app: inventory-service
  duration: "3m"

Tras aplicar el manifiesto, abre Grafana de inmediato y observa las métricas. Si tu servicio Go implementa correctamente el circuit breaker, la tasa de errores debería subir temporalmente y luego estabilizarse.

Patrones de resiliencia en Go: implementación y pruebas bajo caos

Circuit Breaker

La librería gobreaker es la implementación de circuit breaker más popular para Go. A continuación, un ejemplo de integración con un cliente HTTP:

package resilience

import (
	"errors"
	"net/http"
	"time"

	"github.com/sony/gobreaker"
)

type ResilientClient struct {
	client  *http.Client
	breaker *gobreaker.CircuitBreaker
}

func NewResilientClient() *ResilientClient {
	settings := gobreaker.Settings{
		Name:        "inventory-service",
		MaxRequests: 3,
		Interval:    10 * time.Second,
		Timeout:     30 * time.Second,
		ReadyToTrip: func(counts gobreaker.Counts) bool {
			failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
			return counts.Requests >= 5 && failureRatio >= 0.6
		},
		OnStateChange: func(name string, from, to gobreaker.State) {
			// Enviamos la métrica a Prometheus
			circuitBreakerStateGauge.WithLabelValues(name, to.String()).Set(1)
		},
	}

	return &ResilientClient{
		client:  &http.Client{Timeout: 5 * time.Second},
		breaker: gobreaker.NewCircuitBreaker(settings),
	}
}

func (c *ResilientClient) Get(url string) (*http.Response, error) {
	result, err := c.breaker.Execute(func() (interface{}, error) {
		resp, err := c.client.Get(url)
		if err != nil {
			return nil, err
		}
		if resp.StatusCode >= 500 {
			return nil, errors.New("server error")
		}
		return resp, nil
	})
	if err != nil {
		return nil, err
	}
	return result.(*http.Response), nil
}

Retry con Exponential Backoff

El circuit breaker funciona en conjunto con la lógica de retry. Importante: el retry sin jitter en microservicios es el camino al thundering herd. Aquí la implementación correcta:

package resilience

import (
	"context"
	"math"
	"math/rand"
	"time"
)

type RetryConfig struct {
	MaxAttempts int
	BaseDelay   time.Duration
	MaxDelay    time.Duration
}

func WithRetry(ctx context.Context, cfg RetryConfig, fn func() error) error {
	var lastErr error
	for attempt := 0; attempt < cfg.MaxAttempts; attempt++ {
		if err := ctx.Err(); err != nil {
			return err
		}
		lastErr = fn()
		if lastErr == nil {
			return nil
		}
		if attempt == cfg.MaxAttempts-1 {
			break
		}
		// Exponential backoff con full jitter
		expDelay := float64(cfg.BaseDelay) * math.Pow(2, float64(attempt))
		maxDelay := math.Min(expDelay, float64(cfg.MaxDelay))
		jitter := time.Duration(rand.Float64() * maxDelay)
		select {
		case <-time.After(jitter):
		case <-ctx.Done():
			return ctx.Err()
		}
	}
	return lastErr
}

Propagación de Timeout mediante Context

En Go, el manejo correcto de timeouts se basa en context.WithTimeout. Al atravesar varios niveles de microservicios, el timeout debe reducirse, no reiniciarse en cada nivel:

func (s *OrderService) CreateOrder(ctx context.Context, order Order) error {
	// Obtenemos el tiempo restante del contexto y establecemos el presupuesto
	deadline, ok := ctx.Deadline()
	if !ok || time.Until(deadline) > 2*time.Second {
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, 2*time.Second)
		defer cancel()
	}

	// Verificamos la disponibilidad del producto con el presupuesto de tiempo
	return s.inventoryClient.CheckStock(ctx, order.Items)
}

Integración de pruebas de caos en el pipeline CI/CD

La verdadera madurez del Chaos Engineering llega cuando los experimentos se ejecutan automáticamente dentro del CI/CD. Un pipeline típico para microservicios Go se ve así:

  1. Build & Unit Tests — el clásico go test ./...
  2. Integration Tests — pruebas contra dependencias reales en un namespace efímero
  3. Deploy to Staging — despliegue de la nueva versión del servicio
  4. Chaos Experiment — ejecución de escenarios de caos predefinidos mediante la API de Chaos Mesh
  5. SLO Verification — verificación de que las métricas no superaron los límites del SLO durante el experimento
  6. Gate Decision — si el SLO es violado, el pipeline se detiene y el despliegue no continúa

Ejemplo de paso para GitHub Actions usando Chaos Mesh CLI (chaosctl):

- name: Run Chaos Experiment
  run: |
    kubectl apply -f chaos/network-latency-experiment.yaml
    sleep 300  # Esperamos 5 minutos
    kubectl delete -f chaos/network-latency-experiment.yaml

- name: Verify SLO
  run: |
    ERROR_RATE=$(curl -s "$PROMETHEUS_URL/api/v1/query" \
      --data-urlencode 'query=rate(http_requests_total{status=~"5..",service="order-service"}[5m])' \
      | jq '.data.result[0].value[1]' | tr -d '"')
    if (( $(echo "$ERROR_RATE > 0.01" | bc -l) )); then
      echo "SLO violated: error rate $ERROR_RATE exceeds 1%"
      exit 1
    fi

Métricas y observabilidad durante los experimentos de caos

Sin una observabilidad adecuada, el chaos engineering se convierte en destrucción ciega. Para microservicios Go son necesarios los siguientes instrumentos y métricas:

Prometheus + Grafana

Conjunto básico de métricas a monitorear durante el experimento:

  • http_request_duration_seconds — latencia por percentiles (p50, p95, p99)
  • http_requests_total{status=~"5.."} — tasa de errores
  • circuit_breaker_state — estado actual del circuit breaker
  • go_goroutines — número de goroutines (fugas bajo carga)
  • go_gc_duration_seconds — pausas del GC bajo CPU throttling
  • process_resident_memory_bytes — consumo de memoria

Distributed Tracing con OpenTelemetry

El tracing es fundamental para entender exactamente qué hop en la cadena de llamadas se degradó. Ejemplo de instrumentación de un servicio Go con OpenTelemetry:

package main

import (
	"context"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/trace"
)

var tracer = otel.Tracer("order-service")

func (s *OrderService) ProcessOrder(ctx context.Context, orderID string) error {
	ctx, span := tracer.Start(ctx, "ProcessOrder",
		trace.WithAttributes(
			attribute.String("order.id", orderID),
		),
	)
	defer span.End()

	if err := s.validateOrder(ctx, orderID); err != nil {
		span.RecordError(err)
		span.SetStatus(codes.Error, err.Error())
		return err
	}
	return nil
}

Dashboard de Chaos Mesh

Chaos Mesh proporciona una interfaz integrada que muestra los experimentos activos, su estado y marcas de tiempo. Se puede acceder a ella mediante kubectl port-forward:

kubectl port-forward -n chaos-testing svc/chaos-dashboard 2333:2333

Hoja de ruta para implementar Chaos Engineering en el equipo

Implementar chaos engineering es un cambio organizacional, no solo instalar herramientas. Aquí una hoja de ruta realista para un equipo de 5 a 15 ingenieros:

Fase 1: Fundamentos (1-2 meses)

  • Configuración del stack de observabilidad: Prometheus, Grafana, Jaeger/Tempo
  • Definición de SLOs para cada servicio
  • Instalación de Chaos Mesh en el clúster de staging
  • Primeros experimentos manuales con servicios aislados

Fase 2: Sistematización (2-3 meses)

  • Biblioteca de experimentos en un repositorio Git
  • Runbook para cada tipo de escenario de caos
  • Implementación de circuit breaker y retry en todos los servicios Go
  • Chaos Game Day — simulacros trimestrales con todo el equipo

Fase 3: Automatización (3-6 meses)

  • Integración de pruebas de caos en el pipeline CI/CD para servicios críticos
  • Verificación automática de SLOs tras cada experimento
  • Extensión gradual de la práctica a producción con un blast radius mínimo

Fase 4: Madurez (6+ meses)

  • Chaos engineering como parte del Definition of Done para nuevos servicios
  • Caos continuo en producción con salvaguardas automáticas
  • Compartir aprendizajes entre equipos mediante internal tech talks

Conclusión

Chaos Engineering para microservicios Go en Kubernetes no es una moda ni un entretenimiento. Es una disciplina de ingeniería que permite transformar lo desconocido (cuándo y cómo caerá el sistema) en algo conocido (conocemos sus puntos débiles y están bajo control). Usando Chaos Mesh para la inyección de fallos, patrones de circuit breaker y retry correctamente implementados en Go, la integración de experimentos en CI/CD y un stack de observabilidad completo, tu equipo obtiene una confianza que ningún code review ni ningún load test puede ofrecer.

Empieza por algo pequeño: un servicio, un escenario, métricas bien configuradas. El primer experimento encontrará algo inesperado sin falta — y en eso reside todo el sentido.

Tecnologías

Etiquetas

Ruslan Ismailov

Desarrollador Senior Web / Backend. Desarrollador senior web/backend con 9 años de experiencia. Stack: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, microservicios, CI/CD. Más sobre mí →