Skip to main content

El teu primer Lambda: què passa de veritat quan l'invoques

Aquesta lliçó no fa servir cap framework. Ni CDK, ni SAM, ni Serverless Framework. Només l'AWS CLI, perquè vegis quines peces hi ha realment abans que una eina te les amagui.

Necessites l'AWS CLI configurada (aws configure) i Node 20 o superior.


Les tres peces mínimes

Una funció Lambda no és només codi. Són tres coses:

  1. El codi, empaquetat en un ZIP o en una imatge de contenidor.
  2. Un rol d'IAM que la funció assumeix quan s'executa. Sense rol no hi ha funció; AWS no te la deixa crear.
  3. La configuració: runtime, handler, memòria, timeout, variables d'entorn.

Comencem pel rol, que és on tothom es queda encallat.

cat > confianca.json <<'JSON'
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}
JSON

aws iam create-role \
--role-name hola-lambda-rol \
--assume-role-policy-document file://confianca.json

aws iam attach-role-policy \
--role-name hola-lambda-rol \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

Aquest document de confiança diu una sola cosa: «el servei Lambda pot actuar com aquest rol». La política que hi enganxem, AWSLambdaBasicExecutionRole, només dona permís per escriure logs a CloudWatch. Res més. Si la teva funció ha de llegir d'una taula de DynamoDB, li hauràs d'afegir aquell permís explícitament — ho veurem a la lliçó 06.


El codi

// src/index.ts
let invocacions = 0;
const arrencat = Date.now();

export const handler = async (event: unknown) => {
invocacions += 1;

return {
missatge: "Hola des de Lambda",
invocacionsEnAquestEntorn: invocacions,
edatDeLEntornMs: Date.now() - arrencat,
memoriaMb: process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE,
regio: process.env.AWS_REGION,
};
};

Lambda no executa TypeScript. Cal compilar-lo abans. Amb esbuild és una línia:

npx esbuild src/index.ts \
--bundle --platform=node --target=node22 \
--format=cjs --outfile=dist/index.js

cd dist && zip -r ../funcio.zip . && cd ..

Fixa't en --format=cjs. És el punt on peta més gent: si generes ESM, el fitxer ha d'acabar en .mjs o el paquet ha de portar un package.json amb "type": "module". Amb CommonJS no hi ha discussió possible.


Crear-la i invocar-la

COMPTE=$(aws sts get-caller-identity --query Account --output text)

aws lambda create-function \
--function-name hola \
--runtime nodejs22.x \
--handler index.handler \
--role "arn:aws:iam::${COMPTE}:role/hola-lambda-rol" \
--zip-file fileb://funcio.zip \
--timeout 10 \
--memory-size 256 \
--region eu-west-1

--handler index.handler vol dir: al fitxer index.js, exporta una cosa que es diu handler. Si canvies el nom del fitxer o de l'export i no toques aquest paràmetre, la invocació respon Runtime.HandlerNotFound i el missatge no és gens obvi.

Ara invoca-la:

aws lambda invoke \
--function-name hola \
--payload '{}' --cli-binary-format raw-in-base64-out \
--region eu-west-1 \
resposta.json

cat resposta.json

Torna-hi tres o quatre cops seguits. Veuràs que invocacionsEnAquestEntorn va pujant i que edatDeLEntornMs creix: és el mateix entorn d'execució reutilitzat. Espera deu minuts, torna a invocar, i els dos números tornen a l'inici. Acabes de veure un cold start amb les teves mans.


Què passa entre que crides i que respon

Tres fases, i només pagues dues:

  • INIT. AWS crea l'entorn, descarrega el codi, arrenca Node i executa tot el que hi ha fora del handler. Aquí és on van les connexions i els clients d'SDK, perquè es reaprofiten. Per a funcions normals aquesta fase no es factura.
  • INVOKE. S'executa el handler. Això és el que pagues, mil·lisegon a mil·lisegon.
  • SHUTDOWN. Passats uns minuts sense feina, AWS mata l'entorn. No t'avisa i no pots dependre'n.

Ho pots veure als logs:

aws logs tail /aws/lambda/hola --follow --region eu-west-1

Cada invocació deixa una línia REPORT així:

REPORT RequestId: 4f0e... Duration: 3.42 ms  Billed Duration: 4 ms
Memory Size: 256 MB Max Memory Used: 68 MB Init Duration: 142.31 ms

Aquesta línia és la més útil de tot AWS i gairebé ningú la mira:

  • Billed Duration és el que pagues, arrodonit a mil·lisegons.
  • Max Memory Used et diu si has assignat massa memòria. Aquí, 68 MB de 256: pots baixar.
  • Init Duration només surt als cold starts. Si és de 800 ms, tens el bundle massa gros.

La memòria és la CPU

Aquest és el detall que canvia la manera de dimensionar funcions: a Lambda no tries CPU, la tries indirectament amb la memòria. A 1.769 MB tens l'equivalent a un vCPU sencer; per sota, una fracció proporcional.

Conseqüència contraintuïtiva: pujar la memòria pot sortir més barat. Una funció que triga 1.000 ms a 512 MB (0,5 GB-segon) potser triga 400 ms a 1.024 MB (0,4 GB-segon). Menys temps × més memòria = factura més petita i resposta més ràpida. No ho suposis: mesura-ho amb la línia REPORT abans i després.


Què costa això

Tota aquesta lliçó, executada unes quantes desenes de vegades, et costa 0 €. No t'hi acostes ni de lluny al nivell gratuït d'1.000.000 de peticions i 400.000 GB-segon mensuals.

El que sí que et pot passar factura és el que no mires:

ConceptePreuNivell gratuït
Peticions Lambda0,20 $ per milió1M/mes
Còmput Lambda0,0000166667 $ per GB-segon400.000 GB-s/mes
CloudWatch Logs (ingesta)~0,50 $ per GB5 GB/mes
CloudWatch Logs (retenció)~0,03 $ per GB i mes5 GB

El paranys real són els logs. Per defecte, el grup de logs d'una Lambda no caduca mai. Una funció xerraire que escriu un console.log per petició i rep tràfic de debò t'acumula gigabytes que pagues cada mes per sempre. Arregla-ho el primer dia:

aws logs put-retention-policy \
--log-group-name /aws/lambda/hola \
--retention-in-days 14 \
--region eu-west-1

Errors habituals

  • The role defined for the function cannot be assumed by Lambda. El rol acabat de crear triga uns segons a propagar-se per IAM. No has fet res malament: espera deu segons i torna a llançar el create-function.
  • Runtime.HandlerNotFound. El valor de --handler no quadra amb el que hi ha al ZIP. Comprova-ho amb unzip -l funcio.zip: hi ha d'haver un index.js a l'arrel del ZIP, no dins d'una carpeta dist/.
  • Timeout de 3 segons per defecte. Si crees la funció sense --timeout, qualsevol crida externa una mica lenta la talla a mitges. I si la invocació és asíncrona, Lambda reintenta dos cops més tot sol: la teva feina s'executa tres vegades i tu només veus errors.
  • La regió. Si crees la funció a eu-west-1 i després llances aws logs tail sense --region, la CLI fa servir la regió del teu perfil i et dirà que el grup de logs no existeix. No hi és, a la regió on mires.

T'aviso quan publiqui

Un correu per setmana com a molt, i només quan publiqui alguna cosa. De tant en tant t'hi explico el que faig: cursos, sessions, producte. Te'n pots donar de baixa quan vulguis, amb un clic.