Skip to content

Playbook de planificación de capacidad para eventos

Versiones compatibles: Kubernetes 1.28+, KEDA 2.14+, Karpenter 0.37+ Última actualización: April 25, 2026

< Anterior: Operaciones de actualización de EKS | Tabla de contenidos | Siguiente: Visibilidad de costos FinOps >


Descripción general

Las ventas flash, campañas de marketing y eventos estacionales requieren una planificación proactiva de la capacidad de infraestructura para gestionar picos de tráfico de forma confiable. Este playbook sirve tanto a ingenieros de operaciones como a stakeholders no técnicos (PMs, especialistas en marketing) con orientación práctica y accionable.

Al combinar KEDA para el escalado a nivel de Pod y Karpenter para el aprovisionamiento a nivel de Node, puedes crear una estrategia sólida de escalado para eventos que use preescalado basado en cron como piso de seguridad, mientras las métricas de negocio impulsan la elasticidad en tiempo real.

Objetivos de aprendizaje

  • Comprender patrones de tráfico y estrategias de escalado para distintos tipos de eventos
  • Usar la hoja de trabajo de planificación de capacidad para estimar necesidades de infraestructura
  • Configurar triggers compuestos de KEDA con cron + métricas de negocio
  • Aprovechar warm pools de Karpenter y EC2 Capacity Reservations
  • Implementar coordinación de extremo a extremo entre KEDA + Karpenter
  • Crear runbooks y procesos operativos específicos para eventos

1. Marco de planificación de capacidad para eventos

1.1 Clasificación del tipo de evento

Tipo de eventoMultiplicador de tráficoTiempo de ramp-upDuraciónPredictibilidadEjemplos
Ventas flash10-50xSegundos1-4 horasAltaOfertas por tiempo limitado, lanzamientos de edición limitada
Lanzamientos de producto5-20xMinutos1-7 díasAltaLanzamientos de nuevas funcionalidades, actualizaciones de apps
Campañas de marketing3-10xMinutos-HorasHoras-DíasMediaAnuncios de TV, viralidad en redes sociales
Eventos estacionales5-30xHoras1-7 díasAltaBlack Friday, ventas de fin de año
Picos inesperados5-100xSegundosInciertaBajaViralidad en noticias/SNS, ataques de bots

1.2 Hoja de trabajo de planificación de capacidad

Una hoja de trabajo estándar que los PMs y planificadores pueden completar y entregar al equipo de infraestructura.

Entrada (completada por PM/Planificador):

CampoDescripciónValor de ejemplo
Tipo de eventoVenta flash / Marketing / EstacionalVenta flash
Usuarios concurrentes esperadosConexiones simultáneas en pico100,000
RPM pico esperadoMáximo de requests por minuto600,000
Hora de inicio del eventoFecha y hora (TZ local)2026-05-01 12:00 KST
Duración del eventoHoras en tráfico pico2 horas
Tiempo de ramp-upTiempo para alcanzar el tráfico pico10 segundos
Tráfico baseRPM del período normal30,000

Fórmula de cálculo:

Required Pods  = Peak RPM ÷ RPM per Pod
Required Nodes = Required Pods ÷ Pods per Node
Estimated Cost = Nodes × Hourly Cost × (Event Hours + Pre-scale + Cooldown)

Example:
- RPM per Pod: 3,000 (measured via load test)
- Required Pods: 600,000 ÷ 3,000 = 200 Pods
- Pods per Node: 10 (c5.4xlarge)
- Required Nodes: 200 ÷ 10 = 20 Nodes
- Safety margin (+30%): 26 Nodes
- Cost: 26 × $0.68/hr × (2hr event + 1hr pre + 1hr cooldown) = ~$70.72

1.3 Checklist de cronograma de D-30 a D+1

CuándoResponsableTareaHecho
D-30PM + InfraCompartir detalles del evento (completar hoja de trabajo)
D-30InfraSolicitar Capacity Reservations (si es necesario)
D-30FinanzasAprobar presupuesto adicional de infraestructura
D-14InfraEjecutar prueba de carga al 120% del RPM objetivo
D-14InfraIdentificar y resolver cuellos de botella
D-7InfraDesplegar KEDA ScaledObject (preescalado cron)
D-7InfraDesplegar NodePool de Karpenter para el evento
D-7InfraConfigurar dashboards de monitoreo
D-3InfraDry-run de preescalado (verificar sin tráfico en vivo)
D-1Infra + PMVerificación final: conteos de Pod/Node, canales de alerta, URLs de dashboard
D-1InfraPreparar war room, asignar on-call
D-DayInfraVerificar preescalado (30 min antes del evento)
D-DayInfraMonitoreo en tiempo real y standby para intervención manual
D+1InfraConfirmar scale-down, limpiar recursos sobrantes
D+1Infra + PMPost-mortem: previsto vs real, análisis de costos

2. Estrategias de preescalado

2.1 Preescalado basado en cron con KEDA

Escala Pods antes del evento para absorber el pico inicial de tráfico.

Ventana de evento único:

yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: flash-sale-prescale
  namespace: ecommerce
spec:
  scaleTargetRef:
    name: order-service
  minReplicaCount: 5
  maxReplicaCount: 300
  triggers:
    # Pre-scale 30 min before the event
    - type: cron
      metadata:
        timezone: Asia/Seoul
        start: "30 11 1 5 *"    # May 1st, 11:30
        end: "30 14 1 5 *"      # May 1st, 14:30
        desiredReplicas: "200"

Ventanas de evento multirregión:

yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: global-sale-prescale
  namespace: ecommerce
spec:
  scaleTargetRef:
    name: order-service
  minReplicaCount: 5
  maxReplicaCount: 500
  triggers:
    # Korea flash sale (KST 12:00-14:00)
    - type: cron
      metadata:
        timezone: Asia/Seoul
        start: "30 11 1 5 *"
        end: "30 14 1 5 *"
        desiredReplicas: "200"
    # Japan flash sale (JST 18:00-20:00)
    - type: cron
      metadata:
        timezone: Asia/Tokyo
        start: "30 17 1 5 *"
        end: "30 20 1 5 *"
        desiredReplicas: "150"
    # US flash sale (EST 09:00-11:00)
    - type: cron
      metadata:
        timezone: America/New_York
        start: "30 8 1 5 *"
        end: "30 11 1 5 *"
        desiredReplicas: "180"

2.2 Warm pools de Karpenter

Preaprovisiona Nodes antes del evento para eliminar retrasos de scheduling de Pods.

yaml
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: event-warm-pool
spec:
  template:
    metadata:
      labels:
        workload-type: event
        pool: warm
    spec:
      requirements:
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64"]
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["on-demand"]
        - key: node.kubernetes.io/instance-type
          operator: In
          values:
            - c5.4xlarge
            - c5.2xlarge
            - m5.4xlarge
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: event-nodes
  limits:
    cpu: "640"
    memory: 1280Gi
  disruption:
    consolidationPolicy: WhenEmpty
    consolidateAfter: 30m
  weight: 100   # Higher weight = preferred over default NodePool
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: event-nodes
spec:
  amiSelectorTerms:
    - alias: al2023@latest
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: my-cluster
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: my-cluster
  blockDeviceMappings:
    - deviceName: /dev/xvda
      ebs:
        volumeSize: 100Gi
        volumeType: gp3
        iops: 5000
        throughput: 250
  tags:
    event: flash-sale-2026-05
    cost-center: marketing

2.3 EC2 Capacity Reservations

Garantiza la disponibilidad de instancias para eventos a gran escala.

Terraform HCL:

hcl
resource "aws_ec2_capacity_reservation" "flash_sale" {
  instance_type     = "c5.4xlarge"
  instance_platform = "Linux/UNIX"
  availability_zone = "ap-northeast-2a"
  instance_count    = 15
  instance_match_criteria = "targeted"

  end_date_type = "limited"
  end_date      = "2026-05-01T15:00:00Z"

  tags = {
    Name       = "flash-sale-2026-05"
    Event      = "flash-sale"
    CostCenter = "marketing"
  }
}

resource "aws_ec2_capacity_reservation" "flash_sale_2b" {
  instance_type     = "c5.4xlarge"
  instance_platform = "Linux/UNIX"
  availability_zone = "ap-northeast-2b"
  instance_count    = 11
  instance_match_criteria = "targeted"
  end_date_type = "limited"
  end_date      = "2026-05-01T15:00:00Z"
  tags = {
    Name       = "flash-sale-2026-05-2b"
    Event      = "flash-sale"
    CostCenter = "marketing"
  }
}

Apuntar Capacity Reservations en Karpenter:

yaml
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
  name: event-reserved
spec:
  amiSelectorTerms:
    - alias: al2023@latest
  subnetSelectorTerms:
    - tags:
        karpenter.sh/discovery: my-cluster
  securityGroupSelectorTerms:
    - tags:
        karpenter.sh/discovery: my-cluster
  capacityReservationSelectorTerms:
    - tags:
        Event: flash-sale

2.4 Pause Pods (escalado con placeholders)

Usa Pause Pods de baja prioridad como placeholders para precalentar Nodes. Se expulsan automáticamente cuando las cargas de trabajo reales necesitan la capacidad.

yaml
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: pause-priority
value: -10
globalDefault: false
description: "Pause pods for pre-warming nodes - evicted by real workloads"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: event-pause-pods
  namespace: ecommerce
  labels:
    purpose: node-warm-pool
spec:
  replicas: 20
  selector:
    matchLabels:
      app: pause-placeholder
  template:
    metadata:
      labels:
        app: pause-placeholder
    spec:
      priorityClassName: pause-priority
      terminationGracePeriodSeconds: 0
      containers:
        - name: pause
          image: registry.k8s.io/pause:3.9
          resources:
            requests:
              cpu: "14"
              memory: 24Gi
            limits:
              cpu: "14"
              memory: 24Gi
      tolerations:
        - key: event-workload
          operator: Exists
          effect: NoSchedule

Cómo funciona:


3. Escalado impulsado por métricas de negocio

3.1 Escalado basado en tasa de pedidos

Escala en función de métricas de negocio recopiladas en Prometheus.

PrometheusRule (Recording Rules):

yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: business-metrics
  namespace: monitoring
spec:
  groups:
    - name: business.rules
      interval: 15s
      rules:
        - record: business:orders_per_minute:rate5m
          expr: sum(rate(order_completed_total[5m])) * 60
        - record: business:page_views_per_minute:rate5m
          expr: sum(rate(nginx_http_requests_total{path=~"/product.*"}[5m])) * 60
        - record: business:cart_additions_per_minute:rate5m
          expr: sum(rate(cart_add_total[5m])) * 60

KEDA ScaledObject (tasa de pedidos):

yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: order-service-scaler
  namespace: ecommerce
spec:
  scaleTargetRef:
    name: order-service
  pollingInterval: 10
  cooldownPeriod: 60
  minReplicaCount: 5
  maxReplicaCount: 300
  triggers:
    - type: prometheus
      metadata:
        serverAddress: http://prometheus.monitoring.svc:9090
        query: business:orders_per_minute:rate5m
        threshold: "150"
        activationThreshold: "10"
  advanced:
    horizontalPodAutoscalerConfig:
      behavior:
        scaleUp:
          stabilizationWindowSeconds: 0
          policies:
            - type: Percent
              value: 100
              periodSeconds: 15
        scaleDown:
          stabilizationWindowSeconds: 300
          policies:
            - type: Percent
              value: 10
              periodSeconds: 60

3.2 Escalado basado en Page Views / Session

Usa métricas de CloudWatch para escalar el frontend.

yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: frontend-pageview-scaler
  namespace: ecommerce
spec:
  scaleTargetRef:
    name: frontend-web
  pollingInterval: 15
  cooldownPeriod: 120
  minReplicaCount: 3
  maxReplicaCount: 200
  triggers:
    - type: aws-cloudwatch
      metadata:
        namespace: ECommerce/Frontend
        dimensionName: Service
        dimensionValue: frontend-web
        metricName: PageViewsPerMinute
        targetMetricValue: "5000"
        minMetricValue: "100"
        metricStatPeriod: "60"
        metricStatType: Sum
        awsRegion: ap-northeast-2
      authenticationRef:
        name: keda-aws-credentials
---
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
  name: keda-aws-credentials
  namespace: ecommerce
spec:
  podIdentity:
    provider: aws

3.3 Escalado por profundidad de cola SQS

Escala workers de procesamiento de pedidos en función del backlog de la cola.

yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: order-worker-scaler
  namespace: ecommerce
spec:
  scaleTargetRef:
    name: order-worker
  pollingInterval: 5
  cooldownPeriod: 30
  minReplicaCount: 2
  maxReplicaCount: 100
  triggers:
    - type: aws-sqs-queue
      metadata:
        queueURL: https://sqs.ap-northeast-2.amazonaws.com/123456789012/order-processing
        queueLength: "5"
        awsRegion: ap-northeast-2
        activationQueueLength: "1"
      authenticationRef:
        name: keda-aws-credentials

3.4 Configuración de triggers compuestos

Cron establece el piso, las métricas determinan el techo.

yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: order-service-composite
  namespace: ecommerce
spec:
  scaleTargetRef:
    name: order-service
  pollingInterval: 10
  cooldownPeriod: 120
  minReplicaCount: 5
  maxReplicaCount: 300
  triggers:
    # Floor: guarantee minimum 100 Pods during event window
    - type: cron
      metadata:
        timezone: Asia/Seoul
        start: "30 11 1 5 *"
        end: "30 14 1 5 *"
        desiredReplicas: "100"
    # Ceiling: scale beyond 100 based on actual order volume
    - type: prometheus
      metadata:
        serverAddress: http://prometheus.monitoring.svc:9090
        query: business:orders_per_minute:rate5m
        threshold: "150"
        activationThreshold: "10"
    # Auxiliary: also consider SQS backlog
    - type: aws-sqs-queue
      metadata:
        queueURL: https://sqs.ap-northeast-2.amazonaws.com/123456789012/order-processing
        queueLength: "10"
        awsRegion: ap-northeast-2
      authenticationRef:
        name: keda-aws-credentials

KEDA selecciona el conteo máximo de réplicas entre todos los triggers. Si cron solicita 100 y Prometheus solicita 150, el resultado es 150.


4. Coordinación de KEDA + Karpenter

4.1 Escalado a nivel de Pod y a nivel de Node

4.2 Optimización del timing de escalado

FaseDuraciónOptimización
Polling de KEDA → actualización de HPA10-30sReducir pollingInterval
HPA → creación de Pod1-5sscaleUp stabilization: 0
Pod Pending → aprovisionamiento de Node60-90sKarpenter (vs CA 3-5 min)
Node Ready → Pod Running10-30sPre-cache de imágenes
Arranque en frío total~2 minEliminado con Warm Pool

DaemonSet de pre-cache de imágenes:

yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: image-cache
  namespace: kube-system
spec:
  selector:
    matchLabels:
      app: image-cache
  template:
    metadata:
      labels:
        app: image-cache
    spec:
      initContainers:
        - name: cache-order-service
          image: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/order-service:v2.5.0
          command: ["sh", "-c", "echo cached"]
        - name: cache-frontend
          image: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/frontend:v3.1.0
          command: ["sh", "-c", "echo cached"]
      containers:
        - name: pause
          image: registry.k8s.io/pause:3.9
          resources:
            requests:
              cpu: 10m
              memory: 16Mi
      tolerations:
        - operator: Exists

4.3 Ejemplo de extremo a extremo: escenario de venta flash

Configuración completa para una venta flash el 1 de mayo a las 12:00 KST.

1) KEDA ScaledObject:

yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: flash-sale-order-service
  namespace: ecommerce
spec:
  scaleTargetRef:
    name: order-service
  pollingInterval: 10
  cooldownPeriod: 180
  minReplicaCount: 5
  maxReplicaCount: 300
  triggers:
    - type: cron
      metadata:
        timezone: Asia/Seoul
        start: "30 11 1 5 *"
        end: "00 15 1 5 *"
        desiredReplicas: "150"
    - type: prometheus
      metadata:
        serverAddress: http://prometheus.monitoring.svc:9090
        query: business:orders_per_minute:rate5m
        threshold: "150"
  advanced:
    horizontalPodAutoscalerConfig:
      behavior:
        scaleUp:
          stabilizationWindowSeconds: 0
          policies:
            - type: Percent
              value: 100
              periodSeconds: 15
        scaleDown:
          stabilizationWindowSeconds: 600
          policies:
            - type: Percent
              value: 5
              periodSeconds: 60

2) Karpenter NodePool:

yaml
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: flash-sale-pool
spec:
  template:
    metadata:
      labels:
        event: flash-sale-2026-05
    spec:
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["on-demand", "spot"]
        - key: node.kubernetes.io/instance-type
          operator: In
          values: ["c5.4xlarge", "c5.2xlarge", "c6i.4xlarge", "c6i.2xlarge"]
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: event-nodes
  limits:
    cpu: "800"
    memory: 1600Gi
  disruption:
    consolidationPolicy: WhenEmpty
    consolidateAfter: 30m
  weight: 100

3) PodDisruptionBudget:

yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: order-service-pdb
  namespace: ecommerce
spec:
  minAvailable: "80%"
  selector:
    matchLabels:
      app: order-service

4) Visualización del cronograma:

Time    11:00  11:30  12:00  12:30  13:00  13:30  14:00  14:30  15:00
        ──────────────────────────────────────────────────────────
Pods:     5     150    250    280    260    200    150     50      5
Nodes:    2      20     28     30     28     22     20      8      2
Traffic:  1x     1x    20x    35x    25x    15x    10x     2x     1x
        ──────────────────────────────────────────────────────────
             ▲                                           ▲
         Cron fires                                  Cron ends
         Pre-scaling                                 Cooldown starts

5. Runbook: playbooks específicos para eventos

5.1 Runbook de venta flash

D-7: Desplegar preconfiguración

bash
# 1. Label the event namespace
kubectl label namespace ecommerce event=flash-sale-2026-05

# 2. Deploy KEDA ScaledObject
kubectl apply -f flash-sale-scaledobject.yaml

# 3. Deploy Karpenter NodePool
kubectl apply -f flash-sale-nodepool.yaml

# 4. Deploy PDB
kubectl apply -f order-service-pdb.yaml

# 5. Deploy image cache DaemonSet
kubectl apply -f image-cache-daemonset.yaml

D-1: Verificación final

bash
# Verify ScaledObject status
kubectl get scaledobject -n ecommerce
kubectl describe scaledobject flash-sale-order-service -n ecommerce

# Verify Karpenter NodePool
kubectl get nodepool flash-sale-pool
kubectl describe nodepool flash-sale-pool

# Record baseline
echo "=== Baseline ==="
echo "Pods: $(kubectl get pods -n ecommerce -l app=order-service --no-headers | wc -l)"
echo "Nodes: $(kubectl get nodes --no-headers | wc -l)"

D-Day: Monitoreo del evento

bash
# Real-time Pod count monitoring
watch -n 5 'kubectl get pods -n ecommerce -l app=order-service --no-headers | wc -l'

# Real-time Node count monitoring
watch -n 10 'kubectl get nodes -l event=flash-sale-2026-05 --no-headers | wc -l'

# Watch HPA status
kubectl get hpa -n ecommerce -w

# Emergency manual scale (if metrics are slow)
kubectl scale deployment order-service -n ecommerce --replicas=250

D+1: Limpieza y análisis

bash
# Remove event ScaledObject (restore normal config)
kubectl delete scaledobject flash-sale-order-service -n ecommerce

# Remove event NodePool
kubectl delete nodepool flash-sale-pool

# Cost analysis via Kubecost API
kubectl port-forward -n kubecost svc/kubecost-cost-analyzer 9090:9090 &
curl -s "http://localhost:9090/model/allocation?window=2d&aggregate=label:event" | jq '.data'

5.2 Runbook de campaña de marketing

Las campañas de marketing tienen un ramp-up gradual, así que usa triggers cron por etapas.

yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: campaign-gradual-scale
  namespace: ecommerce
spec:
  scaleTargetRef:
    name: frontend-web
  minReplicaCount: 3
  maxReplicaCount: 100
  triggers:
    # Campaign start: moderate scale-up
    - type: cron
      metadata:
        timezone: Asia/Seoul
        start: "0 9 1 5 *"
        end: "0 12 1 5 *"
        desiredReplicas: "20"
    # Peak hours: full scale
    - type: cron
      metadata:
        timezone: Asia/Seoul
        start: "0 12 1 5 *"
        end: "0 22 1 5 *"
        desiredReplicas: "50"
    # Metric-based additional scaling
    - type: prometheus
      metadata:
        serverAddress: http://prometheus.monitoring.svc:9090
        query: sum(rate(nginx_http_requests_total{service="frontend-web"}[2m])) * 60
        threshold: "10000"

5.3 Runbook de respuesta de emergencia

Procedimiento de respuesta inmediata para picos de tráfico inesperados.

bash
#!/bin/bash
# emergency-scale.sh

NAMESPACE=${1:-ecommerce}
DEPLOYMENT=${2:-order-service}
TARGET_REPLICAS=${3:-100}

echo "=== Emergency Scaling ==="
echo "Target: ${NAMESPACE}/${DEPLOYMENT}"
echo "Desired Replicas: ${TARGET_REPLICAS}"

# 1. Immediate manual scale
kubectl scale deployment ${DEPLOYMENT} \
  -n ${NAMESPACE} \
  --replicas=${TARGET_REPLICAS}

# 2. Temporarily raise HPA minimum
kubectl patch hpa ${DEPLOYMENT} \
  -n ${NAMESPACE} \
  --type='json' \
  -p='[{"op":"replace","path":"/spec/minReplicas","value":'${TARGET_REPLICAS}'}]'

# 3. Apply emergency NodePool
cat <<EOF | kubectl apply -f -
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: emergency-pool
spec:
  template:
    spec:
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["on-demand"]
        - key: node.kubernetes.io/instance-type
          operator: In
          values: ["c5.4xlarge", "c6i.4xlarge", "m5.4xlarge"]
      nodeClassRef:
        group: karpenter.k8s.aws
        kind: EC2NodeClass
        name: default
  limits:
    cpu: "500"
  weight: 200
EOF

echo "=== Scaling Status ==="
kubectl get pods -n ${NAMESPACE} -l app=${DEPLOYMENT} --no-headers | wc -l
kubectl get nodes --no-headers | wc -l

6. Monitoreo y dashboards

6.1 Dashboard del período del evento

Paneles clave para monitoreo del evento en tiempo real:

PanelPromQLPropósito
RPSsum(rate(http_requests_total[1m]))Requests por segundo
Conteo de Podcount(kube_pod_info{namespace="ecommerce"})Conteo actual de Pod
Conteo de Nodecount(kube_node_info)Conteo actual de Node
Tasa de erroressum(rate(http_requests_total{code=~"5.."}[1m])) / sum(rate(http_requests_total[1m]))Tasa de errores
Latencia P99histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[1m])) by (le))Tiempo de respuesta
Profundidad de colaaws_sqs_approximate_number_of_messages_visibleBacklog de SQS
Costo/Horasum(node_cost_hourly)Costo actual de infra

JSON de dashboard de Grafana (paneles clave):

json
{
  "dashboard": {
    "title": "Event Capacity Dashboard",
    "tags": ["event", "capacity"],
    "panels": [
      {
        "title": "Requests Per Second",
        "type": "timeseries",
        "gridPos": {"h": 8, "w": 8, "x": 0, "y": 0},
        "targets": [
          {
            "expr": "sum(rate(http_requests_total{namespace=\"ecommerce\"}[1m]))",
            "legendFormat": "RPS"
          }
        ],
        "fieldConfig": {
          "defaults": {
            "thresholds": {
              "steps": [
                {"color": "green", "value": null},
                {"color": "yellow", "value": 5000},
                {"color": "red", "value": 8000}
              ]
            }
          }
        }
      },
      {
        "title": "Pod Count vs Target",
        "type": "timeseries",
        "gridPos": {"h": 8, "w": 8, "x": 8, "y": 0},
        "targets": [
          {
            "expr": "count(kube_pod_status_phase{namespace=\"ecommerce\",phase=\"Running\"})",
            "legendFormat": "Running Pods"
          },
          {
            "expr": "kube_horizontalpodautoscaler_spec_target_metric{namespace=\"ecommerce\"}",
            "legendFormat": "Target"
          }
        ]
      },
      {
        "title": "Node Count",
        "type": "stat",
        "gridPos": {"h": 8, "w": 8, "x": 16, "y": 0},
        "targets": [
          {
            "expr": "count(kube_node_info)",
            "legendFormat": "Total Nodes"
          }
        ]
      }
    ]
  }
}

6.2 Análisis post-evento

Plantilla de post-mortem:

MétricaPrevistoRealVariación
RPM pico600,000520,000-13%
Conteo máximo de Pod200175-12.5%
Conteo máximo de Node2622-15%
Latencia P99 pico200ms180ms-10%
Tasa de errores< 0.1%0.02%OK
Costo total del evento$70.72$59.84-15%
Revenue$500,000$620,000+24%
Costo de infra / Revenue0.014%0.010%OK
bash
# Cost analysis via Kubecost for the event period
curl -s "http://kubecost:9090/model/allocation" \
  --data-urlencode "window=2026-05-01T02:00:00Z,2026-05-01T08:00:00Z" \
  --data-urlencode "aggregate=namespace" \
  --data-urlencode "accumulate=true" | jq '
  .data[0] | to_entries[] | 
  select(.key == "ecommerce") | 
  {namespace: .key, totalCost: .value.totalCost, cpuCost: .value.cpuCost, memCost: .value.ramCost}'

7. Gestión de costos

7.1 Estimación de costos del evento

Tipo de instanciavCPUMemoriaCosto por hora (Seúl)Capacidad de Pod
c5.2xlarge816 GiB$0.34~5 pods
c5.4xlarge1632 GiB$0.68~10 pods
c6i.4xlarge1632 GiB$0.72~10 pods
m5.4xlarge1664 GiB$0.77~10 pods

Matriz de decisión Spot vs On-Demand:

CriterioUsar On-DemandSpot aceptable
Criticidad del eventoImpacta ingresosServicios auxiliares
Tolerancia a interrupcionesNo aceptableOK (tiene lógica de reintentos)
Duración del evento< 2 horas> 4 horas
Ahorro de Spot< 50%> 60%

7.2 Scale-down automático

yaml
# Gradual scale-down after event
advanced:
  horizontalPodAutoscalerConfig:
    behavior:
      scaleDown:
        stabilizationWindowSeconds: 600
        policies:
          - type: Percent
            value: 10
            periodSeconds: 120

Teardown de consolidación de Karpenter:

bash
# 1. Switch event NodePool to aggressive consolidation
kubectl patch nodepool flash-sale-pool --type='merge' -p '
  {"spec":{"disruption":{"consolidationPolicy":"WhenEmptyOrUnderutilized","consolidateAfter":"5m"}}}'

# 2. Remove event NodePool after 30 min
sleep 1800 && kubectl delete nodepool flash-sale-pool

# 3. Release Capacity Reservations (Terraform)
terraform destroy -target=aws_ec2_capacity_reservation.flash_sale

8. Buenas prácticas y anti-patrones

Buenas prácticas

#PrácticaDescripción
1Hacer pruebas de carga tempranoEjecutar al 120% del RPM objetivo dos semanas antes del evento
2Combinar cron + métricasCron establece el piso, las métricas determinan el techo
3Scale-down gradualReducir 10% cada vez en lugar de caídas bruscas
4Etiquetar todoEtiquetar todos los recursos con el nombre del evento para seguimiento de costos
5Incluir stakeholders no técnicosCompartir URLs de dashboard y estado con PMs
6Post-mortem en cada eventoComparar previsto vs real para mejorar la precisión
7Usar warm poolsEliminar por completo el arranque en frío de 2 minutos

Anti-patrones

#Anti-patrónProblemaAlternativa
1Solo escalado manualNo puede responder a eventos de noche/fin de semanaAutomatizar con cron de KEDA
2Sin maxReplicasExplosión de costos, agotamiento de recursosEstablecer siempre límites superiores
3Cambios de configuración de último minutoSin tiempo para validar, riesgo de interrupciónDesplegar en D-7, validar en D-3
4Olvidar hacer scale-downCosto innecesario continuo después del eventoEstablecer políticas de TTL/cooldown

9. Referencias