# serverless

## Vad det är

Serverless är ett sätt att köra kod där teamet skriver och driftsätter applikationen medan en
leverantör driver, skalar och underhåller maskinerna under den. På svenska talar man också om
serverlösa tjänster. Whitepapern från CNCF Serverless Working Group använder det längre namnet
serverless computing och beskriver det som att bygga och köra applikationer utan att någon i teamet
behöver hantera en server.

Namnet tar i, och whitepapern säger det rakt ut: det behövs fortfarande servrar för att köra en
serverless-plattform. Servrarna finns kvar, och det gör driftarbetet också. Det som ändras är vem
som utför det. Teamet väljer inte längre maskinstorlekar, installerar inte operativsystemet, patchar
det inte och bestämmer inte hur många kopior som ska köras. Det lämnar över kod eller en
containeravbildning och en beskrivning av när koden ska köras.

Whitepapern delar upp fältet i två delar. Functions-as-a-service, förkortat FaaS, är händelsedriven
databehandling: små stycken logik som körs när något händer. Det som startar en funktion kallas en
trigger, och den är oftast resultatet av en händelse, till exempel en inkommande HTTP-förfrågan
eller en ändring i en databas. Backend-as-a-service, förkortat BaaS, är den andra delen:
tredjepartstjänster som man når via ett API och som ersätter hela delar av en applikations
funktionalitet. Funktionerna förväntas vara tillståndslösa och kortlivade, så det är på BaaS-sidan
som tillståndet bor.

Två produkter visar spännvidden. AWS Lambda beskriver sig själv som en serverless beräkningstjänst
som kör kod utan att man behöver förbereda eller hantera servrar, och dess ursprungliga form är
event-funktionen: en handler plus en trigger. Azure Container Apps kallar sig en
serverless-plattform för att köra containerbaserade applikationer utan att hantera infrastrukturen
under dem. Den första tar emot en funktion, den andra en hel container, och båda lämnar maskinerna
åt leverantören.

## Varför det spelar roll

Debiteringsmodellen är det första skälet till att team tittar på det. Whitepapern beskriver
prissättningen som pay-as-you-go, där man bara betalar för tiden koden körs och inte betalar något
medan den står stilla. Lambda debiterar en event-funktion per förfrågan plus den körtid den
använder, mätt i gigabytesekunder. En tjänst som har fullt upp en timme om dygnet betalar för den
timmen och inte för en server som väntar de övriga 23.

Det andra skälet är skalning åt båda hållen. Lambda skapar och tar bort körmiljöer i takt med att
trafiken ökar och minskar, och whitepapern beskriver funktioner vars antal instanser sjunker till
noll när det inte finns något arbete. Microsoft skriver att Container Apps skalar på HTTP-trafik,
händelser eller belastning på processor och minne, och att de flesta applikationer kan skala hela
vägen ned till noll.

Det tredje skälet är arbetet som försvinner från teamets bord. AWS räknar upp serverunderhåll,
kapacitetsplanering, skalning och patchning bland uppgifterna som Lambda sköter. För ett litet team
är det just de sysslorna som kan fylla en hel vecka utan att något levereras, och på en
serverless-plattform blir de leverantörens ansvar.

Användningsfallen i whitepapern visar var modellen passar bäst: att reagera på ändringar i en
databas, behandla sensordata från enheter, hantera dataströmmar och korta schemalagda uppgifter av
det slag som annars hade varit ett cron-jobb. Microsofts lista för Container Apps lägger till
API-slutpunkter, bakgrundsjobb och mikrotjänster.

Samma whitepaper varnar också för att serverless kan se ut som det billigaste alternativet i början,
och ber team att se på hela kostnadsbilden innan de bestämmer sig. Förbrukningsbaserad debitering
lönar sig för arbete som kommer i skurar och står stilla större delen av tiden. Arbete som håller en
maskin sysselsatt dygnet runt är en annan kalkyl, och felsökning, som enligt whitepapern kan vara
svårare än på IaaS och PaaS, är också en kostnad.

## Så ser det ut i praktiken

AWS dokumentation för handlers i Python har ett exempel som tar emot en order, skriver ett kvitto
som text och sparar det i en Amazon S3-bucket. En förkortad version ser ut så här:

```python
import os

import boto3

# Körs i Init-fasen, en gång per körmiljö.
s3_client = boto3.client("s3")


def lambda_handler(event, context):
    order_id = event["Order_id"]
    bucket_name = os.environ.get("RECEIPT_BUCKET")
    receipt_content = (
        f"OrderID: {order_id}\n"
        f"Amount: ${event['Amount']}\n"
        f"Item: {event['Item']}"
    )
    key = f"receipts/{order_id}.txt"
    s3_client.put_object(Bucket=bucket_name, Key=key, Body=receipt_content)
    return {"statusCode": 200, "message": "Receipt processed successfully"}
```

Handlern tar exakt två argument. Det första innehåller de data som ska behandlas, och en händelse i
JSON kommer in som en Python-dictionary, så den här funktionen väntar sig indata med följande form:

```json
{"Order_id": "12345", "Amount": 199.99, "Item": "Wireless Headphones"}
```

Det andra argumentet, kontextobjektet, innehåller uppgifter om själva anropet. Heter filen
`lambda_function.py` får handlern namnet `lambda_function.lambda_handler`, vilket är standardnamnet
som Lambda-konsolen ger en ny funktion. Bucketnamnet kommer från en miljövariabel och inte från
koden, så som AWS rekommenderar att driftinställningar skickas in, och koden innehåller inga
inloggningsuppgifter: Lambda ger dem till SDK:t genom sina egna miljövariabler.

Var klienten skapas spelar roll. Lambda för varje körmiljö genom tre faser: Init, Invoke och
Shutdown. Kod utanför handlern körs en gång under Init, och AWS begränsar den fasen till 10
sekunder. Därefter körs handlern för varje händelse inom funktionens tidsgräns. När en miljö
återanvänds för nästa händelse, det AWS kallar en warm start, finns klienten från Init kvar och
byggs inte om.

Serverless behöver inte betyda funktioner. Portföljinlägget om
[Azure-infrastruktur provisionerad som kod](/sv/portfolio/provisioned-azure-infrastructure-as-code/)
nämner två tjänster, `api` och `www`, som körs på Azure Container Apps och vars
containeravbildningar ligger i projektets eget Azure Container Registry. Microsoft kallar Container
Apps en serverless-plattform för containrar, så det är de två tjänsterna som utgör serverless-delen
av helheten. Varje miljö beskrivs av en enda uppsättning Bicep-mallar med parametrar, och därför kan
inlägget behandla en ny miljö som en pull request.

Den motsatta vägen kan också vara den rätta. Inlägget om
[en tiondel av molnräkningen](/sv/portfolio/a-tenth-of-the-cloud-bill-and-nothing-lost/) börjar med
en AWS-miljö som var långt större än verksamheten behövde och som fortfarande bar på tjänster som
ingen längre använde. De oanvända tjänsterna togs bort, resten anpassades till den belastning den
faktiskt hade, och driften flyttades stegvis från AWS till billigare hosting. Serverless-debitering
är ett sätt att slippa betala för ledig kapacitet; inlägget visar ett annat, och bara en mätning av
den verkliga belastningen kan avgöra vilket av de två som kostar minst.

## Var det går fel

De flesta fel med serverless kommer av att man behandlar en funktion som en liten server som alltid
är igång, vilket den inte är.

**Kallstarter fördröjer en del förfrågningar.** När ingen miljö står redo måste Lambda hämta koden,
sätta upp en miljö och köra Init-koden innan handlern ser händelsen. Enligt AWS drabbar kallstarter
vanligtvis under 1 % av anropen och varar från under 100 millisekunder till över en sekund; de är
vanligare i utvecklings- och testfunktioner, och Init-tiden debiteras. Lösningen är att hålla Init
liten, bara importera det som funktionen behöver och läsa in sällan använda objekt först när de
behövs. Där fördröjningen spelar roll håller provisioned concurrency ett valt antal miljöer
initierade i förväg.

**Tillstånd i miljön läcker eller försvinner.** Varje anrop körs oberoende, och en miljö kanske
återanvänds, kanske inte. AWS byter dessutom ut miljöer med några timmars mellanrum för att
uppdatera runtime, även vid jämn trafik, och en återställning efter en krasch eller en timeout
tömmer inte katalogen `/tmp`. Data från en händelse kan därför dyka upp under en annan eller
försvinna utan förvarning. Lösningen är att hålla användardata och händelser helt utanför miljön och
lägga allt som ska bevaras i en databas eller en lagringstjänst.

**Samma händelse behandlas två gånger.** En funktion kan ta emot dubblerade händelser, och AWS lista
över god praxis ber om idempotent kod som validerar varje händelse och hanterar en dubblett på samma
sätt som originalet. I exemplet ovan härleds kvittots nyckel från ordernumret, så en ny körning
skriver över samma objekt i stället för att skapa ännu ett kvitto. Varje sidoeffekt som inte tål att
upprepas, som att skicka ett e-postmeddelande eller dra pengar från ett kort, behöver en egen
registrering av vad som redan är gjort.

**Hårda gränser stoppar arbetet halvvägs.** Ett Lambda-anrop kan köras i högst 900 sekunder, alltså
15 minuter. En synkron förfrågan och dess svar är vardera begränsade till 6 MB, en asynkron
förfrågan till 1 MB och miljövariabler till 4 KB sammanlagt. Att överskrida en gräns ger ett fel,
inte en inbromsning. Lösningen är att utforma efter gränserna från början: dela upp långa jobb i
steg, lägga stora datamängder i en lagringstjänst och skicka vidare en referens, och flytta arbete
som inte ryms inom gränserna till en containerplattform eller en virtuell maskin.

**Samtidigheten tar slut före trafiken.** Lambdas standardkvot är 1 000 samtidiga körningar per
region, medan API Gateway som standard tillåter 10 000 förfrågningar per sekund, så ingången kan ta
emot fler förfrågningar än funktionerna bakom den kan köra. Nya konton börjar med sänkta kvoter för
samtidighet och minne, som AWS höjer automatiskt utifrån användningen. Lösningen är att begära en
höjning före lanseringen och att lasttesta vid den väntade toppbelastningen.

**En funktion som startar sig själv får räkningen att skena.** En funktion som anropar sig själv,
eller som sätter i gång en process som anropar den igen, kan utlösa en flod av anrop, och vart och
ett debiteras. AWS råd är att undvika rekursiva anrop och, om en oväntad volym plötsligt dyker upp,
genast sätta funktionens reserved concurrency till `0`, vilket stoppar alla anrop tills koden är
rättad.

**Programmeringsmodellen binder dig till en plattform.** Whitepapern pekar ut plattformens
programmeringsmodell, dess gränssnitt för händelser och meddelanden och dess BaaS-erbjudanden som
källor till inlåsning. Lösningen är att hålla affärslogiken i vanliga funktioner som handlern bara
anropar, och att föredra plattformar som Container Apps, som kör en vanlig containeravbildning från
valfritt register, när en senare flytt är en verklig möjlighet.

## Läs mer

- [CNCF Serverless Whitepaper v1.0](https://github.com/cncf/wg-serverless/tree/master/whitepapers/serverless-overview)
  — arbetsgruppens definitioner av serverless, FaaS och BaaS, och nackdelarna.
- [What is AWS Lambda?](https://docs.aws.amazon.com/lambda/latest/dg/welcome.html)
  — vad Lambda tar hand om, dess funktionstyper och dess debiteringsmodell.
- [Define Lambda Event Function handler in Python](https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html)
  — hela kvittoexemplet, namngivning av handlers och god praxis för koden.
- [Understanding the Lambda execution environment lifecycle](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html)
  — faserna Init, Invoke och Shutdown, kallstarter och återanvändning av miljöer.
- [Lambda quotas](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html)
  — gränser för samtidighet, minne, tidsgräns och payload, och hur de höjs.
- [Azure Container Apps overview](https://learn.microsoft.com/en-us/azure/container-apps/overview)
  — Microsofts serverless-plattform för containrar, som kan skala till noll.

<https://engineer.company/sv/glossary/serverless/>
