Volver al blog
Cloud MigrationInfrastructureMulti-Cloud

Checklist de migración a la nube: de on-prem a multi-cloud

Checklist de migración a la nube por fases, con sus comandos: descubrir dependencias, landing zone, mover los datos, runbook de cutover y trigger de rollback.

P
Davi Nunes
February 15, 2026Actualizado October 30, 202611 min de lectura

Cada punto de esta checklist termina en algo que puedes comprobar: una lista, el resultado de una consulta, un paso del runbook con un responsable. Los ejemplos asumen hosts Linux y PostgreSQL, y un destino con uno o varios proveedores cloud.

Fase 1: descubrimiento — lo que de verdad se ejecuta

Conexiones: quién habla con quién

Las conexiones establecidas con el proceso que las abre y, después, los puertos en escucha:

bash
sudo ss -tnp state established
sudo ss -tlnp

Una instantánea no ve el job que se conecta a las 03:00. Toma una muestra cada cinco minutos durante una semana que incluya un cierre de mes:

text
# /etc/cron.d/conn-sample (delete after discovery)
PATH=/usr/sbin:/usr/bin:/sbin:/bin
*/5 * * * * root ss -tnp state established >> /var/tmp/conn-$(hostname).log

Después cuenta los pares de proceso y peer:

bash
grep -h 'users:' /var/tmp/conn-*.log |
  awk -F'"' '{n = split($1, a, " "); sub(/:[0-9]+$/, "", a[n-1]); print $2, "<->", a[n-1]}' |
  sort | uniq -c | sort -rn | head -50

Cada peer que no sepas identificar es una dependencia sin documentar. El muestreo no ve las conexiones cortas, así que activa también log_connections en PostgreSQL durante esa semana.

Tareas programadas

bash
# as root
for u in $(cut -d: -f1 /etc/passwd); do crontab -u "$u" -l 2>/dev/null | sed "s/^/$u: /"; done
cat /etc/crontab; ls /etc/cron.d /etc/cron.hourly /etc/cron.daily /etc/cron.weekly /etc/cron.monthly
systemctl list-timers --all

Cada job necesita un responsable y un destino; los que escriben en una base de datos siguen escribiendo en la copia antigua después del cutover si nadie los detiene.

Direcciones hardcodeadas, montajes, entradas de hosts

bash
sudo grep -rIEno '([0-9]{1,3}[.]){3}[0-9]{1,3}' /etc /opt /srv 2>/dev/null |
  grep -vE ':(127[.]0[.]0[.]1|0[.]0[.]0[.]0)$' | sort -u
findmnt -t nfs,nfs4,cifs
grep -v '^#' /etc/hosts

Añade los directorios de tus aplicaciones y repite la búsqueda con tu nombre de dominio interno. Cada resultado es algo que cambia con la migración.

Registros DNS y sus TTL

bash
dig +short NS example.com
dig +noall +answer app.example.com @ns1.example.com
dig +short TXT example.com

Consulta al servidor autoritativo: un resolver recursivo muestra el tiempo que le queda en caché, no el TTL configurado. El TTL de cada registro que vaya a cambiar marca con cuánta antelación tienes que bajarlo. En la respuesta TXT, busca entradas SPF ip4: de los relays de correo del centro de datos.

Licencias ligadas al hardware

bash
lscpu | grep -E '^ *(Socket|Core|Thread)'
sudo dmidecode -s system-uuid
ip -br link

El software licenciado por socket o por core, o vinculado a una dirección MAC o a un host ID, no pasa tal cual a una VM nueva. Lee esas condiciones antes de elegir estrategia.

Volumen de datos y crecimiento

bash
df -hT -x tmpfs -x devtmpfs
sql
SELECT datname, pg_size_pretty(pg_database_size(datname)) AS size
FROM pg_database ORDER BY pg_database_size(datname) DESC;

Compara con hace tres meses para ver el crecimiento y haz la cuenta de la transferencia. Supón 2 TB por un enlace de 1 Gbit/s del que puedes usar la mitad: 16.000 gigabits a 0,5 Gbit/s son 32.000 segundos, casi nueve horas antes de que empiece ninguna restauración. Ese número decide la opción de migración de datos de la fase 4.

Resultado: una línea por carga de trabajo: responsable, hosts, peers, jobs, nombres DNS, volumen de datos, licencias.

Fase 2: decide por carga de trabajo

Asigna a cada carga de trabajo un único destino:

  • Retire: sin responsable y sin usuarios reales en una semana de registros de conexiones. Apágala antes de migrar.
  • Retain: latencia hacia equipos en las instalaciones, una licencia que prohíbe la nube o un fin de vida poco después de la migración.
  • Rehost: la opción por defecto con una fecha de salida fija. Mismo sistema operativo y configuración, VM nueva.
  • Replatform: cambia el componente que más tiempo de operación te cuesta por uno gestionado, como PostgreSQL gestionado, y no toques nada más.
  • Refactor: solo cuando la carga de trabajo va a cambiar de todos modos, nunca como condición previa para salir.
  • Repurchase: funciones commodity como el correo, el ticketing o una wiki. Comprueba antes la exportación de datos y dónde se guardan.
  • Relocate: mueve la capa de virtualización entera a una versión alojada del mismo hipervisor cuando hay demasiadas VMs ante una fecha de salida fija. Compra tiempo, no ahorro.

En un destino multi-cloud, añade a cada línea el proveedor, la región y el motivo: un proveedor por carga de trabajo, con una razón explícita. Nuestra guía de estrategia multi-cloud analiza si un segundo proveedor compensa. Las cargas que más hablan entre sí en el registro de la fase 1 comparten oleada y proveedor.

Una evaluación gratuita de migración a la nube empieza justo aquí: hacemos el inventario de conexiones y de cron en tus hosts, asignamos a cada carga de trabajo un destino y una oleada, y dimensionamos la migración de datos frente a tu enlace. Nuestro servicio de migración a la nube se encarga del resto.

Fase 3: la landing zone, antes de la primera carga de trabajo

Nada se migra hasta que esto exista en código. En un destino multi-cloud, constrúyela por proveedor, con un único proveedor de identidad y un único conjunto de claves de etiquetas.

  • Cuentas o proyectos: uno por entorno, más uno para los logs de auditoría y otro para la red compartida. Nada se ejecuta en la cuenta de gestión.
  • Red: rangos de direcciones que no se solapen con el centro de datos, una VPN site-to-site de vuelta a él, un enlace privado dedicado solo si el tráfico de la fase 1 lo justifica, y reenvío DNS en ambos sentidos.
  • Identidad: federación desde tu proveedor de identidad por SAML u OIDC, MFA, ninguna clave de larga duración para personas, roles para los servicios y una cuenta break-glass que genera una alerta cada vez que se usa.
  • Infraestructura como código: todo en Terraform desde el primer día, estado remoto con bloqueo, estado separado por entorno y por proveedor, cambios mediante merge requests revisadas. Cuando falta esta capa, la consultoría de Terraform es por donde empezamos.
  • Guardrails: políticas a nivel de organización que limitan las regiones a la UE (residencia de datos según el RGPD, impuesta y no solo prometida), deniegan los buckets públicos, exigen cifrado en reposo y protegen el registro de auditoría.
  • Logging: logs de auditoría de todas las cuentas en la cuenta de logs, con una retención explícita; métricas y logs (Prometheus, OpenTelemetry) funcionando antes de la primera carga de trabajo, como línea base.
  • Etiquetas de coste: owner, cost-center, environment, app, migration-wave, impuestas por política. En minúsculas y con guiones: al menos un gran proveedor solo acepta claves de etiqueta en minúsculas.

La comprobación de solapamientos, incluidos los pools de clientes VPN y las redes de partners:

python
import ipaddress
on_prem = ["10.0.0.0/16", "172.16.8.0/22", "192.168.50.0/24"]
cloud = ["10.0.128.0/17", "10.40.0.0/16"]
for a in on_prem:
    for b in cloud:
        if ipaddress.ip_network(a).overlaps(ipaddress.ip_network(b)):
            print("overlap:", a, b)

La landing zone está terminada cuando una merge request crea una cuenta con red, identidad, guardrails y logging, y nadie abre una consola.

Fase 4: mueve los datos

Dump y restore

El tiempo de inactividad es dump, transferencia, restore y verificación. Ensáyalo sobre una copia y cronométralo; si cabe en la ventana con margen, no necesitas más.

bash
pg_dump -Fd -j 4 -f /backup/appdb appdb
rsync -a /backup/appdb/ migrate@TARGET_HOST:/restore/appdb/
pg_restore -h NEW_DB_HOST -U app -d appdb -j 4 --no-owner --no-privileges /restore/appdb

Las bases de datos gestionadas no suelen darte superusuario; de ahí los flags de propiedad.

Replicación y un cutover corto

Cuando la copia dura más que la ventana, replica con antelación y haz el cutover en minutos. La replicación lógica de PostgreSQL funciona entre versiones mayores:

sql
-- Source: needs wal_level = logical (changing it requires a restart)
CREATE PUBLICATION migration FOR ALL TABLES;

-- Target: load the schema first with pg_dump --schema-only, then
CREATE SUBSCRIPTION migration
  CONNECTION 'host=10.0.2.7 dbname=appdb user=replicator password=REPLACE_ME'
  PUBLICATION migration;

Los cambios de esquema no se replican, así que la congelación tiene que cubrir el DDL. Los valores de las secuencias se fijan en el cutover (paso 5 del runbook). Y en cuanto se publica una tabla sin clave primaria ni replica identity, los UPDATE y DELETE sobre ella fallan en el origen, y eso rompe producción. Localiza antes esas tablas:

sql
SELECT n.nspname, c.relname FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r' AND n.nspname NOT IN ('pg_catalog', 'information_schema')
  AND NOT EXISTS (SELECT 1 FROM pg_constraint p WHERE p.conrelid = c.oid AND p.contype = 'p');

Los archivos van en dos pasadas: una copia masiva días antes y un delta final durante la congelación.

bash
rsync -aHAX --numeric-ids /srv/data/ migrate@TARGET_HOST:/srv/data/
rsync -aHAX --numeric-ids --delete /srv/data/ migrate@TARGET_HOST:/srv/data/

Verifica la copia

Cuenta las filas de todas las tablas en ambos lados. En las tablas que contienen dinero o identidades, compara un hash:

sql
SET TimeZone = 'UTC';
SELECT count(*), md5(string_agg(o::text, '|' ORDER BY o.id)) FROM orders o;

Si las dos sesiones no usan la misma zona horaria, las marcas de tiempo se imprimen distinto y los hashes nunca coinciden. Las tablas grandes, por rangos de id. Para los archivos, un dry run con checksums lista todas las diferencias:

bash
rsync -aHAXnci --delete /srv/data/ migrate@TARGET_HOST:/srv/data/

Cuánto tiempo sigue abierta la vía de rollback

Mientras no se reabran las escrituras en el lado nuevo, el rollback es un cambio de DNS sin pérdida de datos. A partir de ahí, el lado antiguo se queda atrás. O configuras replicación inversa en el cutover (eliminas la suscripción de ida, publicas desde la base de datos nueva y suscribes la antigua con copy_data = false), o acuerdas por escrito que un rollback tardío implica volver a introducir datos a mano. Cierra la ventana con un evento de negocio, como que la primera facturación de fin de mes salga bien en el lado nuevo.

Fase 5: el runbook de cutover

Antes del día

  • T-14 días: ensayo completo sobre un clon, rollback incluido, con cada paso cronometrado.
  • Un TTL antiguo más margen antes: baja a 300 segundos todos los registros que cambian y confírmalo con dig contra el servidor autoritativo. Un registro a 86400 necesita al menos un día; una semana es más seguro.
  • T-3 días: congelación de cambios en ambos lados: ni deploys, ni cambios de esquema ni de configuración.
  • T-1 día: reunión de go/no-go. Ensayo superado, lag casi a cero, último backup probado con una restauración, rollback por escrito y una persona con nombre que decide el rollback.

El día, en orden

  1. 1Activa la página de mantenimiento y detén las escrituras de la aplicación.
  2. 2Detén cron y los timers en los hosts antiguos: systemctl stop cron (o crond, según la distribución) y systemctl stop para cada unidad .timer de tu inventario de la fase 1.
  3. 3En el origen, espera a que el lag llegue a cero: SELECT application_name, pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) FROM pg_stat_replication;
  4. 4Ejecuta el delta final de rsync.
  5. 5Ajusta cada secuencia, por ejemplo SELECT setval(pg_get_serial_sequence('orders', 'id'), (SELECT max(id) FROM orders));, y después ejecuta las consultas de verificación.
  6. 6Haz los smoke tests del lado nuevo con su hostname real antes de tocar el DNS (comando abajo).
  7. 7Cambia el DNS o el load balancer y repite los smoke tests desde fuera.
  8. 8Decide go o no-go a la hora fijada de antemano: el go reabre las escrituras, el no-go ejecuta el rollback.
bash
curl -sS -o /dev/null -w '%{http_code} %{time_total}' --resolve app.example.com:443:203.0.113.10 https://app.example.com/healthz

Criterios de go/no-go

  • Los recuentos y los hashes coinciden, y todos los smoke tests pasan.
  • La tasa de errores y la latencia p95 están dentro del rango medido en el lado antiguo la semana anterior.
  • Ningún error de replicación ni de aplicación desde el cambio.

Smoke tests

  • Inicia sesión, lee un registro conocido, crea y borra un registro de prueba.
  • Ejecuta un job en segundo plano; envía un correo y confirma que pasa SPF.
  • Una llamada por integración externa: sandbox de pagos, SFTP de partners, webhooks.

El trigger de rollback

Una frase, acordada antes del día. Por ejemplo: "Si algún criterio de go/no-go falla y no está resuelto a las 02:00, o la tasa de 5xx se mantiene por encima de la línea base antigua durante 15 minutos en las primeras 24 horas, hacemos rollback. Decide el responsable de la migración; no se debate en la llamada."

Mantén el lado antiguo respondiendo o redirigiendo durante unos días; algunos resolvers y clientes guardan en caché más allá del TTL.

Fase 6: después de la migración

  • Desmantela con método. En cada host antiguo, sudo ss -tn state established '( sport = :5432 )' lista los clientes que siguen llegando a la base de datos antigua; cada uno es una dependencia que se escapó. Mantén el host apagado dos semanas antes de borrarlo, revoca sus credenciales, túneles, reglas de firewall y registros DNS, y cancela sus licencias. Un host olvidado deja de recibir parches pero sigue accesible, y sus cron jobs siguen ejecutándose.
  • Ajusta el tamaño con datos reales. Tras unas semanas que incluyan un cierre de mes, dimensiona al p95 observado más un margen, no a la asignación on-prem. Los descuentos por compromiso, después del rightsizing.
  • Presupuestos y etiquetas. Alertas de presupuesto por cuenta y una lista semanal de recursos sin etiquetar.
  • Prueba el backup y la recuperación ante desastres en el nuevo entorno. Un job de backup en verde no es una prueba. Restaura una base de datos en una instancia nueva y cronométralo, guarda copias en otra cuenta y otra región, y compara el tiempo de recuperación y la pérdida de datos medidos con lo que acordó el negocio. Nuestro trabajo de backup y recuperación ante desastres empieza por esa restauración.

Errores comunes y cómo detectarlos pronto

  • Rangos de direcciones solapados: ejecuta la comprobación de solapamientos antes de que exista la primera red.
  • El consumidor olvidado: una semana de muestreo con ss, los logs de conexiones de la base de datos y la comprobación con ss después del cutover.
  • Allowlists ligadas a las IP antiguas: firewalls de partners, proveedores de pago, peers SFTP, tu propio registro SPF. Haz la lista de tus IP de salida en la fase 1 y pregunta entonces a cada responsable de integración.
  • Un par muy conversador partido por la VPN: la aplicación en la nube y la base de datos todavía on-prem. Multiplica el round trip por las consultas de cada página: supón 200 consultas con un round trip de 15 ms, lo que añade 3 segundos.
  • MTU del túnel: ping -M do -s 1472 CLOUD_HOST desde el centro de datos envía un paquete de 1500 bytes que no se puede fragmentar. Si falla, baja el tamaño hasta que pase y ajusta el MTU o el MSS clamping en consecuencia; si no, las respuestas grandes se cuelgan mientras las peticiones pequeñas funcionan.
  • Un rollback sin ensayar: si el ensayo se lo saltó, no tienes rollback.

Antes de la primera oleada, la evaluación gratuita de migración a la nube cubre lo que falla en silencio: el mapa de dependencias sacado de tus propios hosts, las comprobaciones de rangos de direcciones y de TTL, la cuenta de la migración de datos para tu base de datos más grande, y un borrador del runbook de la primera oleada con criterios de go/no-go y un trigger de rollback.