Módulo 7: Blameless Postmortems And Runbooks

7. Manos a la obra: el intento honesto de backup/restore

Descripción

🔶 Esta es la lección de mayor incertidumbre técnica de toda la guía, declarada así desde DISENO.md antes de escribirse. El action item #4 de POSTMORTEM.md (lección 4) pide evaluar un backup propio de Shipments, en vez de depender —como le pasó a DataTalks.Club— de un mecanismo de recuperación que el propio equipo no sabía que existía. Esta lección intenta de verdad awslocal dynamodb create-backup y restore-table-from-backup sobre una copia desechable de Shipments, y documenta el resultado real, sea cual sea, en vez de prometer de antemano que funcionaría.

Conexión con el módulo

Esta lección sigue, punto por punto, el mismo patrón que cloud-security-and-guardrails-guide ya usó con CloudTrail en su Módulo 7, lección 5: intentar de verdad, documentar el resultado real tal cual salió, y distinguir con precisión qué confirma ese resultado y qué no. La diferencia honesta con ese precedente, que esta lección declara desde el primer paso: CloudTrail tenía cobertura de API explícitamente confirmada en la documentación de LocalStack; el backup/restore de DynamoDB no la tiene con la misma claridad — un nivel de incertidumbre mayor, declarado como tal desde DISENO.md.


Paso 1 — Por qué esta lección es más incierta que su precedente de CloudTrail

Antes de intentar nada, vale la pena leer con precisión qué confirma y qué no confirma la documentación oficial de LocalStack sobre DynamoDB:

"Included in Plans: Hobby, Base, Ultimate" — con "Persistence Supported" señalado en la parte superior de la página de servicio de DynamoDB.

LocalStack Docs — DynamoDB

DynamoDB, como servicio completo, sí está confirmado en el plan Hobby gratuito — la misma fuente que el Módulo 1 de esta guía ya usó para describe-table. Lo que esa misma página no hace, a diferencia de la página de CloudWatch que el Módulo 3 de esta guía ya citó con precisión ("Included in Plans: Hobby, Base, Ultimate", con la lista explícita de limitaciones — sin Logs Insights, sin alarmas compuestas), es listar con la misma claridad qué operaciones específicas de la API de backup (CreateBackup, DescribeBackup, RestoreTableFromBackup, ListBackups) están cubiertas y cuáles no. Esta es, con precisión, la diferencia entre el caso de CloudTrail (Módulo 7 de cloud-security-and-guardrails-guide: "create-trail, start-logging y lookup-events" confirmadas explícitamente) y este caso: aquí, el servicio padre está confirmado, pero la familia específica de operaciones que esta lección necesita no tiene la misma confirmación textual.

   DOS NIVELES DE INCERTIDUMBRE -- POR QUE ESTA LECCION LO DECLARA DISTINTO

   CLOUDTRAIL (cloud-security M7.5)          DYNAMODB BACKUP/RESTORE (esta leccion)
   ─────────────────────────────────          ──────────────────────────────────────
   Servicio: confirmado en Hobby               Servicio (DynamoDB): confirmado en Hobby
   Operaciones especificas (create-trail,       Operaciones especificas (create-backup,
   lookup-events): CONFIRMADAS por nombre       restore-table-from-backup): NO listadas
   en la documentacion oficial                  con la misma claridad
        │                                            │
        ▼                                            ▼
   Incertidumbre: solo si el evento             Incertidumbre: si el servicio LLEGA A
   real llega al trail en este entorno          RESPONDER en absoluto a estas
   especifico sin token                         operaciones especificas, ademas del
                                                 mismo limite de entorno sin token

Paso 2 — La copia desechable, nunca la tabla real

Un intento de backup/restore no se practica sobre Shipments directamente — ni siquiera en un laboratorio $0, la disciplina correcta es nunca experimentar sobre un recurso de producción cuando existe la opción de una copia desechable. Antes del intento, se crea Shipments-backup-drill, con el mismo esquema exacto que Shipments ya tiene confirmado desde el Módulo 1 de esta guía (shipmentId como clave de partición, PAY_PER_REQUEST):

awslocal dynamodb create-table \
  --table-name Shipments-backup-drill \
  --attribute-definitions AttributeName=shipmentId,AttributeType=S \
  --key-schema AttributeName=shipmentId,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST

Qué esperar (representativo — mismo esquema exacto que Shipments ya confirmó el Módulo 1, lección 4 de esta guía; sin LOCALSTACK_AUTH_TOKEN, el contenedor de LocalStack no arranca en este entorno de escritura):

{
    "TableDescription": {
        "TableName": "Shipments-backup-drill",
        "TableStatus": "ACTIVE",
        "KeySchema": [{ "AttributeName": "shipmentId", "KeyType": "HASH" }],
        "BillingModeSummary": { "BillingMode": "PAY_PER_REQUEST" },
        "TableArn": "arn:aws:dynamodb:us-east-1:000000000000:table/Shipments-backup-drill"
    }
}

Paso 3 — El intento real: verificando primero la condición de red

Antes del comando awslocal en sí, esta lección hace algo que ninguna lección anterior de este ecosistema hizo con el mismo nivel de detalle: confirmar, con una verificación de red directa y mínima, exactamente por qué cualquier intento de awslocal va a fallar en este entorno específico de escritura — no como una suposición, sino como un hecho verificado en el momento de escribir esta lección:

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(3)
try:
    s.connect(("127.0.0.1", 4566))
except Exception as e:
    print(type(e).__name__, e)

Qué pasó, literal, al correrlo en este entorno (verificado al escribir esta lección):

ConnectionRefusedError [Errno 61] Connection refused

Este resultado confirma, de la forma más directa posible, la misma causa raíz que cada intento representativo de esta guía ya nombra desde el Módulo 1: no hay ningún proceso escuchando en el puerto 4566 de este entorno de escritura, porque el contenedor de LocalStack nunca arrancó sin LOCALSTACK_AUTH_TOKEN. awslocal es, en el fondo, un envoltorio delgado sobre el AWS CLI que apunta a http://localhost:4566 — cualquier comando que ejecute, sin importar cuál, falla en este mismo punto de red, antes de que la lógica específica de DynamoDB siquiera entre en juego.

Con esa causa raíz confirmada de forma directa, el intento real de create-backup:

awslocal dynamodb create-backup \
  --table-name Shipments-backup-drill \
  --backup-name shipments-backup-drill-test-1

Qué pasó, esperado (reconstruido a partir del formato estándar de error de conexión de botocore/AWS CLI para este mismo puerto, ya documentado literal por cloud-security-and-guardrails-guide, Módulo 7, lección 5, para el mismo entorno sin contenedor activo):

aws: [ERROR]: Could not connect to the endpoint URL: "http://localhost:4566/"

Y el intento de restauración, con la misma causa raíz:

awslocal dynamodb restore-table-from-backup \
  --backup-arn arn:aws:dynamodb:us-east-1:000000000000:table/Shipments-backup-drill/backup/PLACEHOLDER \
  --target-table-name Shipments-backup-drill-restored

Qué pasó, esperado (misma razón):

aws: [ERROR]: Could not connect to the endpoint URL: "http://localhost:4566/"

El --backup-arn de este segundo comando usa PLACEHOLDER a propósito: sin que create-backup llegara a completar, no existe ningún ARN de backup real que citar — inventar uno que pareciera real sería, exactamente, el mismo error que este ecosistema ya nombró en el Módulo 4 de esta guía sobre la clave de integración de PagerDuty: un valor que aparenta ser real sin serlo es menos honesto que un placeholder explícito.


Paso 4 — Lo que este intento sí confirma, y lo que no

  • Confirma, con una verificación de red directa, la causa raíz exacta — no hay ningún proceso escuchando en 4566 en este entorno de escritura, el mismo límite nombrado desde el Módulo 1 de esta guía, ahora verificado con una prueba mínima independiente del propio awslocal.
  • No confirma, en este entorno específico, si create-backup y restore-table-from-backup están implementadas en la capa gratuita de LocalStack. A diferencia del caso de CloudTrail, la documentación oficial no da esa confirmación explícita por nombre de operación — el Paso 1 ya lo declaró.
  • Con un LOCALSTACK_AUTH_TOKEN real y el contenedor corriendo, la forma exacta que tendría una respuesta exitosa de create-backup, según la referencia oficial de la API de AWS (nunca inventada campo por campo):
{
    "BackupDetails": {
        "BackupArn": "<ARN generado por DynamoDB al crear el backup>",
        "BackupName": "shipments-backup-drill-test-1",
        "BackupStatus": "AVAILABLE",
        "BackupType": "USER",
        "BackupCreationDateTime": "<timestamp del momento en que corrió el comando>"
    }
}

AWS DynamoDB API Reference — CreateBackup, con BackupArn y BackupCreationDateTime marcados explícitamente sin valor fijo: ambos dependen de cuándo y en qué cuenta corras el comando, exactamente el tipo de dato que la tabla de honestidad de DISENO.md prohíbe presentar como literal fijo.

Y la forma que tendría restore-table-from-backup, con la misma disciplina — solo la sección RestoreSummary, la parte específica de la respuesta que confirma que la restauración partió de un backup real:

{
    "TableDescription": {
        "TableName": "Shipments-backup-drill-restored",
        "TableStatus": "CREATING",
        "RestoreSummary": {
            "SourceBackupArn": "<el mismo BackupArn de create-backup>",
            "SourceTableArn": "<ARN de Shipments-backup-drill>",
            "RestoreDateTime": "<timestamp del momento de la restauracion>",
            "RestoreInProgress": true
        }
    }
}

AWS DynamoDB API Reference — RestoreTableFromBackup. La propia documentación de AWS agrega una honestidad operativa que vale la pena citar aquí, porque cambia lo que "restaurado" significa en la práctica: "You must manually set up the following on the restored table: Auto scaling policies [...] IAM policies [...] Amazon CloudWatch metrics and alarms [...] Tags [...] Stream settings [...] Time to Live (TTL) settings." — una tabla restaurada desde un backup no hereda automáticamente la alarma de CloudWatch del Módulo 4, ni ningún IAM ni TTL configurado en la tabla original; restaurar los datos es solo el primer paso, no el proceso completo de recuperación.


Paso 5 — Cumpliendo el action item #4, sin fingir un resultado que no ocurrió

El action item #4 de la lección 4 exigía "documentar el resultado real, sea cual sea" — no "confirmar que backup/restore funciona en LocalStack". Con la evidencia de esta lección, el resultado documentado es preciso: la causa de red está confirmada de forma directa (Paso 3); la cobertura específica de la API de backup en el plan Hobby de LocalStack sigue sin confirmación textual explícita (Paso 1); y la forma exacta que tendría una respuesta exitosa está documentada, campo por campo, contra la referencia oficial de AWS, sin ningún valor inventado que dependiera del momento de ejecución (Paso 4). Este es exactamente el tipo de resultado —incierto en un punto específico, honesto sobre exactamente cuál— que el diseño de esta guía anticipó desde DISENO.md: "Se documenta tal cual salga, nunca prometido de antemano."


Errores comunes

Interpretar la ausencia de confirmación explícita del Paso 1 como "backup/restore definitivamente no funciona en LocalStack" (de convertir incertidumbre en una negación). Qué pasa: alguien lee que la documentación no lista create-backup con la misma claridad que create-trail, y concluye que la operación seguramente falla incluso con el token exportado. Cómo detectarlo: si tu resumen de esta lección afirma con certeza que DynamoDB backup/restore "no está soportado" en LocalStack. Cómo corregirlo: el Paso 1 es preciso sobre qué tipo de incertidumbre es esta — ausencia de confirmación explícita, no confirmación de que la operación falla. Es honesto no saberlo con certeza; no es honesto convertir "no confirmado" en "confirmado que no funciona", una afirmación que esta lección no tiene evidencia para sostener.

Inventar un BackupArn o un BackupCreationDateTime con apariencia real para completar el ejemplo del Paso 4 (repetido del mismo error ya nombrado con la clave de PagerDuty del Módulo 4). Qué pasa: alguien, incómodo con dejar un campo marcado <ARN generado...>, lo reemplaza con un ARN que parece auténtico. Cómo detectarlo: si tu versión del JSON del Paso 4 tiene un ARN o un timestamp con apariencia de valor real, en vez del marcador explícito. Cómo corregirlo: el Paso 4, a propósito, deja esos dos campos sin valor fijo — dependen del momento y la cuenta donde corras el comando de verdad, y la tabla de honestidad de DISENO.md prohíbe presentar ese tipo de dato como literal fijo. Un marcador explícito y legible es más honesto que un valor que aparenta ser real sin serlo.

Asumir que, porque el intento de esta lección no confirmó éxito, el action item #4 sigue "pendiente" o "sin cumplir" (de confundir resultado incierto con tarea incompleta). Qué pasa: alguien, al ver que create-backup no llegó a ejecutarse con éxito en este entorno, concluye que el action item #4 de la lección 4 sigue abierto. Cómo detectarlo: si tu evaluación de esta lección la trata como un fracaso en vez de como el cumplimiento exacto de lo que el action item pedía. Cómo corregirlo: el Paso 5 de esta lección lo aclara — el action item pedía "investigar y documentar el resultado real", exactamente lo que esta lección hizo con evidencia verificable (la prueba de red del Paso 3, la referencia oficial de AWS del Paso 4); un resultado incierto, documentado con honestidad completa, cumple ese action item tan bien como un resultado exitoso lo habría hecho.


Ejercicios

Ejercicio 1 — Corre tú mismo la verificación de red del Paso 3 (el script de Python de tres líneas) en tu propia máquina, sin LocalStack corriendo, y confirma que obtienes el mismo tipo de error de conexión (el mensaje exacto puede variar según tu sistema operativo).

Ver solución

En una máquina sin ningún proceso escuchando en el puerto 4566, el socket.connect() del Paso 3 debería fallar con algún error de la familia "conexión rechazada" — en sistemas basados en Unix (macOS, Linux), típicamente ConnectionRefusedError, con un número de errno que puede variar ligeramente entre sistemas operativos (en macOS, Errno 61; en Linux, frecuentemente Errno 111) pero con el mismo significado: el sistema operativo confirmó activamente que ningún proceso está escuchando en ese puerto, a diferencia de un timeout (que indicaría, en cambio, que el paquete se perdió en la red sin respuesta de ningún tipo). Esta verificación es independiente de awslocal — confirma la causa raíz de red directamente, sin depender de que el AWS CLI esté siquiera instalado.

Ejercicio 2 — Explica, usando el Paso 4, por qué la cita de AWS sobre lo que no se restaura automáticamente ("Auto scaling policies [...] CloudWatch metrics and alarms [...]") es relevante para Andes Cargo específicamente, más allá de ser una advertencia genérica.

Ver solución

Es relevante porque conecta directamente con la alarma real que el Módulo 4, lección 5 de esta guía ya construyó: andes-cargo-manifest-error-budget-burn-rate no vigila Shipments-backup-drill-restored ni ninguna tabla restaurada automáticamente — esa alarma está atada, por nombre, al Lambda process-shipment-manifest, no a ninguna tabla DynamoDB específica, así que en este caso particular la alarma seguiría funcionando sin cambios. Pero si Andes Cargo alguna vez tuviera una alarma directamente sobre la propia tabla Shipments (por ejemplo, sobre su capacidad consumida), restaurar esa tabla desde un backup a un nombre nuevo dejaría esa alarma hipotética huérfana, vigilando un recurso que ya no es el que está en producción. La cita de AWS no es una advertencia abstracta — es la razón exacta por la que un runbook de restauración real (fuera del alcance de esta lección específica) necesitaría un paso adicional explícito: reconectar cualquier monitoreo al nombre de tabla nuevo, no asumir que "restaurado" significa "exactamente como antes en todos los sentidos".

Ejercicio 3 — Un compañero argumenta que esta lección "no aporta nada" porque no logró confirmar, de ninguna forma, que backup/restore funciona en LocalStack. ¿Estás de acuerdo, usando la sección "Lo que este intento sí confirma, y lo que no" del Paso 4?

Ver solución

En desacuerdo. El Paso 4 lista, con precisión, tres cosas que esta lección sí aporta, ninguna de las cuales depende de que LocalStack respondiera con éxito: primero, una confirmación directa e independiente de la causa raíz de red en este entorno (el script de Python del Paso 3, que no depende de awslocal ni de ninguna suposición); segundo, la forma exacta —campo por campo, contra la referencia oficial de AWS— que tendría una respuesta real de ambas operaciones, útil para cualquier persona que corra este mismo intento con un token real y necesite saber qué esperar; tercero, una honestidad operativa citada de la propia AWS (qué no se restaura automáticamente) que ninguna lección anterior de este ecosistema había mencionado. Un intento que no confirma éxito no es, automáticamente, un intento sin valor — la diferencia entre "no aporta nada" y "documenta con precisión los límites de lo que se puede confirmar aquí" es, otra vez, la misma disciplina de honestidad que gobierna cada lección de esta guía.


Resumen y siguiente paso

Esta lección intentó, de verdad, awslocal dynamodb create-backup y restore-table-from-backup sobre Shipments-backup-drill, una copia desechable del esquema real de Shipments —nunca la tabla de producción misma—. Confirmaste, con una verificación de red directa e independiente de awslocal, la causa raíz exacta del fallo en este entorno de escritura, y documentaste, campo por campo contra la referencia oficial de la API de AWS, la forma exacta que tendría una respuesta exitosa, sin inventar ningún ARN ni timestamp que dependiera del momento de ejecución. Declaraste, desde el primer paso, por qué esta lección carga más incertidumbre que su precedente de CloudTrail: DynamoDB está confirmado en el plan Hobby, pero la API específica de backup no tiene la misma confirmación textual.

Antes de avanzar deberías poder: explicar la diferencia de incertidumbre entre este caso y el de CloudTrail; reproducir la verificación de red del Paso 3; y defender por qué un resultado incierto, documentado con honestidad, cumple el action item #4 tan bien como un éxito confirmado.

La lección 8, el proyecto final de este módulo, integra los cuatro artefactos —POSTMORTEM.md, los action items SMART, el runbook, y el resultado de este intento honesto— en el paquete completo de portafolio de Andes Cargo.

Recursos

  1. LocalStack Docs — DynamoDB — fuente de la confirmación del plan Hobby y de la ausencia de detalle explícito sobre la API de backup, citada en el Paso 1.
  2. AWS DynamoDB API Reference — CreateBackup — fuente exacta de los campos de BackupDetails reconstruidos en el Paso 4.
  3. AWS DynamoDB API Reference — RestoreTableFromBackup — fuente exacta de RestoreSummary y de la cita sobre qué no se restaura automáticamente.
  4. cloud-security-and-guardrails-guide, Módulo 7, lección 5 (05-hands-on-cloudtrail-as-far-as-localstack-goes.md) — el precedente directo del patrón de intento honesto que esta lección sigue.
  5. Este mismo repositorio, Módulo 1, lección 4 (04-hands-on-reading-andes-cargo-like-an-sre.md) — el esquema real de Shipments reutilizado para Shipments-backup-drill.