📋 Contexto

En 2020, AWS lanzó GP3 (General Purpose SSD), la nueva generación de volúmenes EBS que ofrece mejor rendimiento a menor costo que GP2. Sin embargo, muchas organizaciones siguen usando GP2 por inercia o desconocimiento de los beneficios.

Recientemente enfrenté el desafío de migrar decenas de volúmenes GP2 a GP3 de forma masiva, minimizando downtime y riesgos. Este post documenta el proceso y comparte el script de automatización.

💰 Ahorro esperado: Aproximadamente 20% de reducción en costos de almacenamiento EBS, manteniendo o mejorando el rendimiento.

🎯 Objetivo

Crear un script bash que permita:

  • Identificar todos los volúmenes GP2 en una cuenta AWS
  • Migrar volumes de forma segura y controlada
  • Validar el estado antes y después de la migración
  • Generar logs detallados de cada operación
  • Manejar errores y permitir rollback si es necesario
  • Minimizar impacto en servicios productivos

📊 GP2 vs GP3: Comparación Técnica

Característica GP2 GP3
Precio (por GB/mes) $0.10 $0.08 (-20%)
IOPS Base 3 IOPS por GB (mín 100) 3,000 IOPS (fijos)
IOPS Máximo 16,000 IOPS 16,000 IOPS (gratis)
Hasta 64,000 (pagando extra)
Throughput Hasta 250 MB/s 125 MB/s base
Hasta 1,000 MB/s (pagando extra)
Burst Performance Sí (con créditos) No necesario (IOPS constantes)
✅ Ventaja clave de GP3: IOPS y throughput son independientes del tamaño del volumen. Un volumen GP3 de 10 GB tiene los mismos 3,000 IOPS base que uno de 1 TB.

🛠️ Implementación del Script

Estructura del Script

El script sigue esta lógica:

  1. Validar permisos y perfil AWS
  2. Listar todos los volúmenes GP2
  3. Para cada volumen:
    • Verificar si está attached (en uso)
    • Obtener configuración actual (size, IOPS, AZ)
    • Calcular configuración GP3 equivalente
    • Ejecutar modificación
    • Validar resultado
    • Log detallado
  4. Generar reporte final (CSV)

Script Base (Simplificado)

#!/bin/bash
# migrate_gp2_to_gp3.sh
# Script para migración masiva de volúmenes EBS GP2 a GP3

set -euo pipefail

# Configuración
AWS_PROFILE="${AWS_PROFILE:-default}"
AWS_REGION="${AWS_REGION:-us-east-1}"
LOG_FILE="migration_$(date +%Y%m%d_%H%M%S).log"
REPORT_FILE="migration_report_$(date +%Y%m%d_%H%M%S).csv"

# Colores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}

log_success() {
    echo -e "${GREEN}✓${NC} $*" | tee -a "$LOG_FILE"
}

log_error() {
    echo -e "${RED}✗${NC} $*" | tee -a "$LOG_FILE"
}

log_warning() {
    echo -e "${YELLOW}⚠${NC} $*" | tee -a "$LOG_FILE"
}

# Verificar AWS CLI y permisos
check_prerequisites() {
    log "Verificando prerequisitos..."
    
    if ! command -v aws &> /dev/null; then
        log_error "AWS CLI no encontrado"
        exit 1
    fi
    
    if ! aws sts get-caller-identity --profile "$AWS_PROFILE" &> /dev/null; then
        log_error "No se puede autenticar con AWS"
        exit 1
    fi
    
    log_success "Prerequisites OK"
}

# Listar volúmenes GP2
list_gp2_volumes() {
    log "Listando volúmenes GP2 en $AWS_REGION..."
    
    aws ec2 describe-volumes \
        --profile "$AWS_PROFILE" \
        --region "$AWS_REGION" \
        --filters "Name=volume-type,Values=gp2" \
        --query 'Volumes[*].[VolumeId,Size,State,Attachments[0].InstanceId,AvailabilityZone]' \
        --output text
}

# Migrar un volumen específico
migrate_volume() {
    local volume_id=$1
    local current_size=$2
    
    log "Migrando volumen $volume_id ($current_size GB)..."
    
    # Calcular IOPS para GP3 (mínimo 3000, equivalente a GP2)
    local gp2_iops=$((current_size * 3))
    local gp3_iops=3000
    
    if [ $gp2_iops -gt 3000 ]; then
        gp3_iops=$gp2_iops
    fi
    
    # Limitar a 16000 IOPS (máximo gratuito de GP3)
    if [ $gp3_iops -gt 16000 ]; then
        gp3_iops=16000
    fi
    
    # Ejecutar modificación
    if aws ec2 modify-volume \
        --profile "$AWS_PROFILE" \
        --region "$AWS_REGION" \
        --volume-id "$volume_id" \
        --volume-type gp3 \
        --iops "$gp3_iops" \
        --output json > /dev/null; then
        
        log_success "Volumen $volume_id migrado (IOPS: $gp3_iops)"
        echo "$volume_id,$current_size,SUCCESS,$gp3_iops" >> "$REPORT_FILE"
        return 0
    else
        log_error "Error migrando volumen $volume_id"
        echo "$volume_id,$current_size,FAILED,N/A" >> "$REPORT_FILE"
        return 1
    fi
}

# Verificar estado de modificación
check_modification_status() {
    local volume_id=$1
    
    aws ec2 describe-volumes-modifications \
        --profile "$AWS_PROFILE" \
        --region "$AWS_REGION" \
        --volume-ids "$volume_id" \
        --query 'VolumesModifications[0].ModificationState' \
        --output text
}

# Main
main() {
    log "═══════════════════════════════════════"
    log "  Migración GP2 → GP3"
    log "  Región: $AWS_REGION"
    log "  Perfil: $AWS_PROFILE"
    log "═══════════════════════════════════════"
    
    check_prerequisites
    
    # Crear header del reporte
    echo "VolumeID,Size(GB),Status,GP3_IOPS" > "$REPORT_FILE"
    
    # Listar y procesar volúmenes
    local count=0
    local success=0
    local failed=0
    
    while IFS=$'\t' read -r vol_id size state instance_id az; do
        count=$((count + 1))
        
        log ""
        log "[$count] Volumen: $vol_id"
        log "    Tamaño: ${size}GB"
        log "    Estado: $state"
        log "    Instancia: ${instance_id:-N/A}"
        log "    AZ: $az"
        
        # Advertencia si está attached
        if [ -n "$instance_id" ]; then
            log_warning "Volumen attached a instancia. La migración es online."
        fi
        
        # Confirmar antes de migrar
        read -p "¿Migrar este volumen? (y/n): " -n 1 -r
        echo
        if [[ $REPLY =~ ^[Yy]$ ]]; then
            if migrate_volume "$vol_id" "$size"; then
                success=$((success + 1))
            else
                failed=$((failed + 1))
            fi
        else
            log "Volumen $vol_id omitido"
            echo "$vol_id,$size,SKIPPED,N/A" >> "$REPORT_FILE"
        fi
        
    done < <(list_gp2_volumes)
    
    # Resumen final
    log ""
    log "═══════════════════════════════════════"
    log "  RESUMEN DE MIGRACIÓN"
    log "═══════════════════════════════════════"
    log "Total procesados: $count"
    log_success "Exitosos: $success"
    if [ $failed -gt 0 ]; then
        log_error "Fallidos: $failed"
    fi
    log ""
    log "Reporte guardado en: $REPORT_FILE"
    log "Log completo en: $LOG_FILE"
}

main
⚠️ Importante: Este script es una versión simplificada para fines educativos. En producción, agrega validaciones adicionales, manejo de errores más robusto, y pruebas en staging primero.

🚀 Uso del Script

Prerequisitos

# Instalar AWS CLI
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install

# Configurar credenciales
aws configure
# O usar AWS SSO
aws sso login --profile my-profile

Ejecución

# Hacer ejecutable
chmod +x migrate_gp2_to_gp3.sh

# Ejecutar en región específica
AWS_REGION=us-east-1 ./migrate_gp2_to_gp3.sh

# Con perfil específico
AWS_PROFILE=production AWS_REGION=eu-west-1 ./migrate_gp2_to_gp3.sh

# Modo dry-run (solo listar, sin modificar)
# Comentar la línea de migrate_volume en el script

🐛 Troubleshooting

Problema 1: "VolumeInModification" Error

Causa: El volumen ya tiene una modificación en progreso.

Solución: Esperar a que termine la modificación anterior (puede tomar horas).

# Ver modificaciones en progreso
aws ec2 describe-volumes-modifications \
    --volume-ids vol-xxxxx \
    --query 'VolumesModifications[0].[ModificationState,Progress]'

Problema 2: "InvalidParameterValue" con IOPS

Causa: IOPS solicitados exceden el máximo para el tamaño del volumen.

Solución: GP3 permite hasta 16,000 IOPS gratis. Para más, se paga extra.

Problema 3: Downtime Durante Migración

Realidad: La migración GP2 → GP3 es online. No hay downtime.

Nota: Puede haber ligera degradación de performance durante la migración (1-2 horas típicamente).

📊 Resultados Reales

En mi caso de uso, migré ~30 volúmenes con los siguientes resultados:

Ahorro mensual: $180 USD (~20% de reducción)
Tiempo total: ~2 horas (incluyendo validaciones)
Downtime: 0 minutos
Problemas: 0 (después de testing en staging)

Métricas de Performance

Monitoreando con CloudWatch después de la migración:

  • Latencia: Sin cambios significativos
  • IOPS: Mejora en volúmenes pequeños (ahora tienen 3000 IOPS mínimos)
  • Throughput: Equivalente o mejor
  • Burst performance: Ya no necesaria (GP3 tiene IOPS constantes)

✅ Mejores Prácticas

  1. Prueba primero en staging - Valida el script en entorno no productivo
  2. Migra fuera de horas pico - Aunque el downtime es cero, reduce riesgo
  3. Monitorea CloudWatch - Observa métricas antes y después
  4. Documenta todo - Guarda logs y reportes para auditoría
  5. Comunica al equipo - Avisa sobre la migración aunque sea transparente
  6. Snapshots previos - Crea snapshots antes de migrar (extra seguridad)
  7. Migración gradual - No migres todo a la vez, hazlo por lotes

💡 Lecciones Aprendidas

  • GP3 no siempre es mejor - Para volúmenes muy grandes (>5TB) con pocos IOPS, GP2 podía ser más económico (esto cambió en 2023)
  • Automatización es clave - Migrar manualmente 50+ volúmenes sería tedioso y propenso a errores
  • Validación constante - Verificar el estado de cada modificación antes de proceder con la siguiente
  • Logs detallados - Indispensables para troubleshooting y auditoría
  • Comunicación - Aunque no haya downtime, el equipo debe estar informado

🚀 Próximos Pasos

Mejoras futuras para el script:

  • Integración con Terraform/CloudFormation para IaC
  • Rollback automático en caso de error
  • Estimación de ahorro antes de ejecutar
  • Notificaciones vía SNS/Slack
  • Dashboard de progreso con Lambda + DynamoDB

💭 Conclusión

La migración de GP2 a GP3 es una de las optimizaciones de costo más fáciles y seguras que puedes hacer en AWS. Con un script bien diseñado, puedes automatizar el proceso y ahorrar significativamente sin afectar servicios productivos.

Si aún tienes volúmenes GP2, considera migrarlos. El beneficio es claro: menos costo, igual o mejor performance.

En el próximo post, profundizaré en el uso avanzado de JMESPath para queries complejas en AWS CLI, una habilidad esencial para cualquier DevOps Engineer.