Tema 5 · 8 · Ejercicio adicional B: Euribor incremental (BCE) a RDS
Presentación del Tema 5: «Ejercicio B · ETL incremental del Euribor a RDS» (ejercicios adicionales; solo si tu docente te lo pide). Duración: a tu ritmo (el entorno se prepara en 15-20 min). Coste: prácticamente nulo en esta actividad; lo que cuesta es la infraestructura base de la carpeta 05.01 (RDS y EC2 por hora).
Etiquetas de lo que creas. Todo recurso que crees a mano (máquina, volumen, bucket, base de datos, tabla, función...) ponle estas etiquetas, al crearlo (apartado Tags / Etiquetas) o después desde su pestaña de etiquetas:
curso=G214,universidad=CUNEFyactividad=05.08-adicionalbeuribor. Si algún día hay que borrar recursos a mano, se localizan todos de golpe conaws resourcegroupstaggingapi get-resources --region eu-north-1 --tag-filters Key=actividad,Values=05.08-adicionalbeuribor. Más detalle (cómo etiquetar con la CLI, cómo buscar y qué hacer en una emergencia): README principal, sección «Etiquetas».
Qué vas a hacer
Un ETL completo sobre una fuente pública real: una Lambda propia descarga el CSV mensual del Euribor a 1 año del portal del BCE y lo carga en la tabla euribor de una base MariaDB de forma incremental (clave primaria fecha + INSERT ... ON DUPLICATE KEY UPDATE: repetir la ejecución no duplica filas). Esta actividad no trae script ni plantilla: creas tú la función Lambda con la receta de abajo. Este README prepara el entorno, te da el esqueleto corregido del código (adicional-b-euribor.py, el mismo), el SQL de la tabla y cómo comprobarlo; el diseño completo y las reglas son el entregable y los haces tú.
Qué se crea en AWS: una función Lambda propia, un rol de IAM propio y, en la RDS, la base banco y la tabla euribor. Entregable: copia del CSV en raw/euribor/, comprobación de calidad y regla de EventBridge.
Qué contiene esta carpeta
| Fichero | Para qué sirve | En qué paso se usa |
|---|---|---|
adicional-b-euribor.py |
Esqueleto corregido de la Lambda: descarga el CSV del BCE (con 3 reintentos), lo lee con csv.DictReader y hace el INSERT ... ON DUPLICATE KEY UPDATE en banco.euribor. No es la solución completa: faltan la copia del CSV en raw/euribor/, la comprobación de calidad (menos filas que la carga anterior) y la regla de EventBridge. No lo despliega ningún script. |
Paso 4 (es el código de tu función) |
adicional-b-euribor.sql |
Sentencias de la tabla banco.euribor con su clave primaria (dos opciones) y consultas de comprobación con valores de referencia. |
Paso 2 (crear la tabla) y paso 6 (comprobar) |
README.md |
Este documento. | Siempre |
Antes de empezar
- Dónde vive la base
banco. La basebancoy la tablaeuribordel Tema 3 están en la RDS del Tema 3, no en la RDS de este laboratorio. La RDS del Paso 0 solo tienetitanic(tras el Ejercicio 1) ycrypto(tras el tipo 3). Puedes usar la RDS del Tema 3 si sigue creada, o la del Paso 0 creando allí la base y la tabla conadicional-b-euribor.sql. Este README asume la RDS del Paso 0, que depende de Tema 5 · 1 · Infraestructura base; si usas la del Tema 3, sustituye$RDSpor su endpoint y la contraseña por la suya. - Región eu-north-1 (Estocolmo).
- Usa nombres de función y rol que no choquen con los del laboratorio (no reutilices
crypto-price-tracker*). Aquí se usanetl-adicional-byrol-etl-adicional-b. - La función necesita
pymysql, que AWS no trae: la empaquetas con el código (paso 4).
Cómo llevar los ficheros a CloudShell
- En tu PC, comprime esta carpeta entera (
05.08-adicionalbeuribor): clic derecho → Comprimir en archivo ZIP (Windows 11), Enviar a > Carpeta comprimida en zip (Windows 10) o Comprimir (Mac). - En CloudShell (región eu-north-1): Actions → Upload file y elige
05.08-adicionalbeuribor.zip. - Ejecuta:
cd ~
unzip -o 05.08-adicionalbeuribor.zip && cd 05.08-adicionalbeuribor && chmod -R u+rwX .
ls
Si ya subiste
g214-materiales.zip(README principal), no necesitas este zip: entra concd ~/g214-materiales/tema5/05.08-adicionalbeuribory, si algún fichero daPermission denied, ejecuta allíchmod -R u+rwX . && chmod +x *.sh.
Qué verás: adicional-b-euribor.py, adicional-b-euribor.sql y README.md. (No hay .sh, no hace falta chmod.)
Paso a paso
0. Recupera las variables del Paso 0:
OUT() { aws cloudformation describe-stacks --stack-name cunef-etl --region eu-north-1 \
--query "Stacks[0].Outputs[?OutputKey=='$1'].OutputValue" --output text; }
BUCKET=$(OUT BucketName); RDS=$(OUT RDSEndpoint); ADMINER=$(OUT AdminerURL)
echo "Bucket : $BUCKET"; echo "RDS : $RDS"; echo "Adminer: $ADMINER"
Qué verás: tres líneas con valores. Si salen vacías, el stack cunef-etl no existe: vuelve a la carpeta 05.01.
1. Prueba la API del BCE desde CloudShell. Pon la URL entre comillas y en una sola línea. La página del portal (data.ecb.europa.eu) genera el CSV con JavaScript y no sirve para una Lambda; usa esta URL de la API:
curl -s "https://data-api.ecb.europa.eu/service/data/FM/M.U2.EUR.RT.MM.EURIBOR1YD_.HSTA?format=csvdata&detail=dataonly" | head -3
curl -s "https://data-api.ecb.europa.eu/service/data/FM/M.U2.EUR.RT.MM.EURIBOR1YD_.HSTA?format=csvdata&detail=dataonly" | wc -l
Qué verás: una cabecera de 10 columnas (KEY,FREQ,REF_AREA,...,TIME_PERIOD,OBS_VALUE) y una línea por mes: 393 líneas de datos a 2026-09 (de 1994-01 en adelante; con la cabecera, wc -l da una más). La función usa solo TIME_PERIOD (formato AAAA-MM) y OBS_VALUE, y las lee por nombre con csv.DictReader.
2. Crea la base y la tabla en la RDS. Abre Adminer ($ADMINER, http://, raíz): Sistema MySQL (o MySQL / MariaDB), Servidor $RDS, usuario admin, contraseña CunefAdmin!, base de datos vacía; pulsa SQL command. Abre adicional-b-euribor.sql, copia el contenido y ejecuta solo la opción que te toque:
-
Opción 1 (la normal en la RDS del Paso 0, que no tiene la base
banco):CREATE DATABASE IF NOT EXISTS banco;y la tabla nueva con su clave primaria:CREATE DATABASE IF NOT EXISTS banco; CREATE TABLE IF NOT EXISTS banco.euribor ( fecha DATE NOT NULL, periodo VARCHAR(16) NOT NULL, valor DECIMAL(8,4) NOT NULL, PRIMARY KEY (fecha) ); -
Opción 2 (ya tienes la tabla del Tema 3, que no tiene clave primaria): vacíala y añade la clave una sola vez, fuera de la Lambda:
TRUNCATE TABLE banco.euribor;yALTER TABLE banco.euribor ADD PRIMARY KEY (fecha);. Si no la vacías y tiene meses repetidos o con otro formato de fecha, elALTERfalla o quedan filas duplicadas. Si ya tiene clave primaria, elALTERdaráMultiple primary key defined: no lo repitas.
Qué verás: Adminer confirma las sentencias; en la columna izquierda aparece la base banco.
3. Crea un rol propio (una sola vez). La RDS no necesita permiso de IAM (se conecta con usuario y contraseña); el rol solo necesita escribir logs. Si además guardas el CSV en raw/euribor/ (entregable), añade s3:PutObject sobre tu bucket como en la carpeta 05.07 (paso 2):
ROLE=rol-etl-adicional-b
cat > confianza.json <<'EOF'
{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"},"Action":"sts:AssumeRole"}]}
EOF
ROLE_ARN=$(aws iam create-role --role-name $ROLE \
--assume-role-policy-document file://confianza.json --query Role.Arn --output text)
aws iam attach-role-policy --role-name $ROLE \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
echo "$ROLE_ARN"
sleep 10
Qué verás: el ARN del rol (arn:aws:iam::<cuenta>:role/rol-etl-adicional-b).
4. Empaqueta con pymysql y crea la función. Sube el timeout a 60 s. Las variables de entorno van en un fichero JSON porque la URL del CSV lleva ?, & y = (formato --environment file:// probado en AWS real; si prefieres, define las variables en la consola, Lambda → tu función → Configuration → Environment variables):
rm -rf paquete && mkdir paquete
pip3 install pymysql==1.1.0 -t paquete --quiet
cp adicional-b-euribor.py paquete/lambda_function.py
(cd paquete && zip -q -r ../funcion.zip .)
cat > entorno.json <<EOF
{"Variables":{"DB_HOST":"$RDS","DB_USER":"admin","DB_PASS":"CunefAdmin!","CSV_URL":"https://data-api.ecb.europa.eu/service/data/FM/M.U2.EUR.RT.MM.EURIBOR1YD_.HSTA?format=csvdata&detail=dataonly"}}
EOF
aws lambda create-function --function-name etl-adicional-b --runtime python3.12 \
--handler lambda_function.lambda_handler --role "$ROLE_ARN" \
--zip-file fileb://funcion.zip --timeout 60 --memory-size 256 \
--environment file://entorno.json --region eu-north-1
Qué verás: un JSON con la configuración de la función ("FunctionName": "etl-adicional-b", "Timeout": 60). Si create-function dice que el rol no se puede asumir, espera unos segundos y repite. Las variables DB_HOST, DB_USER, DB_PASS son las del Paso 0; en el esqueleto la base banco va fija en el código. Para volver a subir el código después de cambiarlo:
aws lambda update-function-code --function-name etl-adicional-b \
--zip-file fileb://funcion.zip --region eu-north-1
aws lambda wait function-updated --function-name etl-adicional-b --region eu-north-1
5. Invócala tres veces seguidas y mira los logs:
for i in 1 2 3; do
aws lambda invoke --function-name etl-adicional-b --cli-binary-format raw-in-base64-out \
--payload '{}' --region eu-north-1 salida.json && cat salida.json
done
aws logs tail /aws/lambda/etl-adicional-b --since 10m --region eu-north-1
Qué verás: {"filas": 393} las tres veces (a 2026-10; crece 1 por mes). Si tu función lanza una excepción, invoke muestra "FunctionError": "Unhandled" y el detalle está en salida.json y en los logs.
6. Comprueba la tabla en Adminer (SQL command), con las consultas de adicional-b-euribor.sql:
SELECT COUNT(*), MIN(fecha), MAX(fecha) FROM banco.euribor;
SELECT MIN(valor), MAX(valor) FROM banco.euribor;
SELECT YEAR(fecha) AS anio, ROUND(AVG(valor), 4) AS media FROM banco.euribor GROUP BY YEAR(fecha) ORDER BY anio;
Qué verás: valores de referencia con la serie completa (1994-01 en adelante; el recuento crece en 1 cada mes nuevo): a 2026-10-03 había 393 filas, de 1994-01-01 a 2026-09-01, mínimo -0.5047 y máximo 8.0200. El recuento no debe cambiar tras las tres ejecuciones (esa es la prueba de que la carga es incremental). Si el mínimo es 0.0000 o el recuento no coincide con las líneas del CSV, hay filas vacías o cabecera cargada.
7. Entregable (lo haces tú): la copia del CSV en raw/euribor/, la comprobación de calidad (menos filas que la carga anterior) y la regla de EventBridge (ver la carpeta 05.06 para los comandos de una regla; aquí con tu función etl-adicional-b y una frecuencia mensual).
Errores frecuentes
Task timed out after 3.00 seconds. La función se creó con el tiempo máximo por defecto (3 s). Súbelo a 60 s:aws lambda update-function-configuration --function-name etl-adicional-b --timeout 60 --region eu-north-1.HTTP Error 502/504, o{"error": "el BCE no responde"}. La API del BCE a veces está saturada. El esqueleto reintenta la descarga 3 veces con 5 s de pausa y, si no hay manera, devuelve ese error sin tocar la tabla. Invócala de nuevo pasado un minuto.ValueError: el CSV no trae filas validasoKeyError: 'TIME_PERIOD'. El CSV que descarga la función no es el de la serie del BCE con formato SDMX: compruebaCSV_URLconcurl -s "<URL>" | head -3(la cabecera debe llevarTIME_PERIODyOBS_VALUE). Si la función carga 0 filas, el formato no es el que espera el código: mira la cabecera.Duplicate entry ... for key 'PRIMARY'al añadir la clave, o meses repetidos. La tabla del Tema 3 tenía datos cargados a mano con otro formato de fecha: vacíala (TRUNCATE TABLE banco.euribor;) antes delALTER.- La función se queda colgada o falla con
Can't connect ... (timed out). La RDS no es alcanzable (grupo de seguridad del puerto 3306, endpoint equivocado o RDS parada). El esqueleto conecta conconnect_timeout=10, así que falla a los 10 segundos. OperationalError (1045, "Access denied for user 'admin'..."). La contraseña de la función no es la de la RDS (CunefAdmin!en la del Paso 0). RevisaDB_PASS.OperationalError (1049, "Unknown database 'banco'"). No creaste la base: vuelve al paso 2.Unable to import module 'lambda_function'oNo module named 'pymysql'. El paquete no lleva el código o la librería. Repite el empaquetado del paso 4 y vuelve a subir el código.- Adminer no carga o no deja entrar: ver "Errores frecuentes" de la carpeta 05.01.
Limpieza
Hazla cuando termines, en este orden, y solo borra lo que hayas creado tú:
-
Regla de EventBridge (si creaste una):
aws events remove-targets --rule NOMBRE --ids 1 --region eu-north-1yaws events delete-rule --name NOMBRE --region eu-north-1(o EventBridge → Rules → Delete). -
La función y su rol:
aws lambda delete-function --function-name etl-adicional-b --region eu-north-1 aws iam detach-role-policy --role-name rol-etl-adicional-b \ --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole aws iam list-role-policies --role-name rol-etl-adicional-b aws iam delete-role --role-name rol-etl-adicional-bSi
list-role-policiesmuestra una política en línea (por ejemplo la des3:PutObject, si la añadiste), bórrala antes del rol conaws iam delete-role-policy --role-name rol-etl-adicional-b --policy-name NOMBRE. Un errorNoSuchEntitysignifica que ese elemento ya no existía. -
La base
banco(opcional): si la creaste en la RDS del Paso 0, desaparece con ella; para borrarla antes, en AdminerDROP DATABASE banco;. Si usaste la RDS del Tema 3, no la borres: es de ese tema. -
Opcional: el grupo de logs (
aws logs delete-log-group --log-group-name /aws/lambda/etl-adicional-b --region eu-north-1), la copia del CSV enraw/euribor/si la guardaste (aws s3 rm s3://$BUCKET/raw/euribor/ --recursive) y los ficheros de CloudShell (cd ~ && rm -rf 05.08-adicionalbeuribor 05.08-adicionalbeuribor.zip).
El borrado de la infraestructura base (carpeta 05.01, sección Limpieza) debe ser el último paso de todo el Tema 5.
Cómo sabes que has terminado
- [ ] Tres invocaciones seguidas devuelven
{"filas": 393}(a 2026-10; crece 1 por mes). - [ ]
SELECT COUNT(*) FROM banco.euribor;no cambia entre ejecuciones y coincide con las líneas de datos del CSV. - [ ] Entregaste lo que pide la diapositiva (código, copia del CSV en
raw/euribor/, comprobación de calidad y regla de EventBridge). - [ ] Has hecho la limpieza de esta actividad.
Anterior: Tema 5 · 7 · Adicional A (REE) · Siguiente: Tema 5 · 9 · Adicional C (clima)