Når man utvikler moderne webapplikasjoner, er varsler til brukerne ofte en essensiell del av tjenesten. Enten det gjelder å sende e-poster, SMS eller annen type varsling, er det viktig å sikre at systemet er både effektivt og pålitelig. Å håndtere slike varsler synkront kan føre til forsinkelser i hovedapplikasjonen, spesielt når det er behov for å sende et stort volum av e-poster eller SMS-meldinger. En løsning på dette problemet er å bruke en asynkron prosess som kan håndtere varslene i bakgrunnen, uten å blokkere brukerens opplevelse. I denne sammenhengen vil vi utforske hvordan Celery og Twilio kan brukes til å håndtere både e-post- og SMS-varsler.

Når en bruker utfører en handling som krever et varsel – for eksempel registrering på en nettside – kan det være fristende å sende e-posten umiddelbart. Dette kan imidlertid skape problemer med ytelse, spesielt i høytrafikkerte applikasjoner. Derfor er det ofte bedre å legge varslene i en oppgavekø, som deretter blir behandlet asynkront. I vårt tilfelle bruker vi Celery, en distribuert arbeidskø, til å håndtere oppgavene.

Automatisert e-postsending med Celery

For å sette opp en systematisk og pålitelig måte å sende e-poster, kan Celery brukes til å plassere e-postsendingsoppgaver i en Redis-kø. Når en hendelse skjer – som når en bruker registrerer seg – vil systemet legge e-postsendingsoppgaven i køen i stedet for å sende e-posten umiddelbart. For å gjøre dette, kan man bruke en FastAPI-rute som legger e-posten i køen:

python
from fastapi import APIRouter from app.tasks import send_email_task router = APIRouter() @router.post("/register") def register_user(email: str): subject = "Welcome!" html_body = "<h1>Thank you for signing up!</h1>" send_email_task.delay(email, subject, html_body) return {"msg": "Registration complete. Please check your email."}

Ved å bruke send_email_task.delay(), legges oppgaven i køen og Celery vil ta hånd om å sende e-posten i bakgrunnen. Dette gir raskere svar til brukeren uten å blokkere applikasjonens hovedlogikk.

Overvåking av Celery-oppgaver med Flower

En viktig komponent når man bruker Celery i produksjon er overvåkning. Flower er et web-basert verktøy for å overvåke Celery-oppgaver i sanntid. Dette verktøyet gir deg innsyn i statusen på aktive oppgaver, antall vellykkede og mislykkede oppgaver, og hvilke oppgaver som er blitt forsøkt på nytt. Dette gjør det enkelt å feilsøke problemer og sørge for at varslene dine alltid blir sendt som de skal. For å bruke Flower, kan du installere det ved hjelp av pip og starte det som følger:

bash
pip install flower
celery -A app.celery_worker.celery_app flower --port=5555

Ved å åpne http://localhost:5555 kan du få tilgang til Flower-grensesnittet, hvor du kan se detaljer om alle dine Celery-oppgaver.

SMS-varsler med Twilio

I tillegg til e-post, er SMS et viktig verktøy for å sende varsler i sanntid. SMS er mer umiddelbar og lettere å få med seg, noe som gjør det til et populært valg for viktige varsler som to-faktor-autentisering eller påminnelser. Twilio er en av de ledende tjenestene for å sende og spore SMS over hele verden. For å sende SMS ved hjelp av Twilio i vår applikasjon, kan vi bruke HTTPX til å sende asynkrone forespørsler:

python
import httpx from config import TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_FROM_NUMBER, TWILIO_API_URL async def send_sms_via_twilio(to_number: str, body: str, status_callback_url: str = None): data = { "From": TWILIO_FROM_NUMBER, "To": to_number, "Body": body, } if status_callback_url: data["StatusCallback"] = status_callback_url async with httpx.AsyncClient() as client: resp = await client.post( TWILIO_API_URL, data=data, auth=(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN) ) resp.raise_for_status() return resp.json()

Ved å bruke httpx kan SMS-ene sendes raskt og effektivt uten å blokkere hovedtråden i applikasjonen. For å håndtere store mengder SMS-varsler asynkront, kan vi definere en Celery-oppgave som behandler SMS-ene i bakgrunnen:

python
from app.celery_worker import celery_app
from app.sms_utils import send_sms_via_twilio import asyncio @celery_app.task(bind=True, max_retries=4, default_retry_delay=40)
def send_sms_task(self, to_number, message, status_callback_url=None):
try: loop = asyncio.get_event_loop() result = loop.run_until_complete( send_sms_via_twilio(to_number, message, status_callback_url) ) return result except Exception as exc: raise self.retry(exc=exc)

Webhooks for å håndtere Twilio-status

En annen viktig komponent i SMS-håndtering er å spore statusen på utsendte meldinger. Twilio sender asynkrone oppdateringer om statusen til SMS-ene (om de ble sendt, leverte eller feilet) til en webhook. Ved å sette opp en FastAPI-rute for å håndtere disse oppdateringene, kan vi logge eller oppdatere statusen på meldingen i databasen:

python
from fastapi import APIRouter, Request, status
from fastapi.responses import JSONResponse router = APIRouter() @router.post("/webhooks/twilio-sms-status") async def twilio_sms_status_webhook(request: Request): data = await request.form() message_sid = data.get("MessageSid") message_status = data.get("MessageStatus") to_number = data.get("To")
print(f"Twilio SMS: {message_sid} to {to_number} is now {message_status}")
return JSONResponse({"status": "received"}, status_code=status.HTTP_200_OK)

Automatisk retry av SMS-oppgaver

Twilio kan noen ganger oppleve midlertidige feil, for eksempel på grunn av nettverksproblemer. Heldigvis har Celery innebygd støtte for automatisk retry av oppgaver. Hvis en SMS ikke blir sendt, for eksempel hvis Twilio returnerer en 500-feil, vil Celery forsøke å sende SMS-en på nytt opptil fire ganger med 40 sekunders mellomrom. Denne retry-mekanismen er svært nyttig for å sikre pålitelighet, spesielt i en produksjonssetting.

Viktig å forstå

Etter at du har implementert et system som håndterer både e-post og SMS asynkront, er det viktig å huske på hvordan du overvåker og vedlikeholder systemet. Celery og Flower gir god synlighet på oppgaver som kjører i bakgrunnen, men det er viktig å sørge for at systemet er riktig konfigurert for feilhåndtering og retry-mekanismer. I tillegg kan det være nyttig å vurdere skaleringsstrategier for når systemet vokser, spesielt når trafikken øker og varsler sendes til flere brukere samtidig. Husk også på sikkerheten når du håndterer API-nøkler og sensitive data som telefonnummer og e-postadresser.

Hvordan bygge et overvåkningssystem med ELK-stack og Kubernetes

Når vi bygger et moderne system, er en av de viktigste komponentene evnen til å overvåke applikasjoner og infrastruktur i sanntid. I dette kapittelet vil vi utforske hvordan du kan bruke ELK-stacken (Elasticsearch, Logstash, Kibana) for loggbehandling og visualisering, samt Kubernetes-manifestfiler for effektiv distribusjon og administrasjon av applikasjoner i en containerisert infrastruktur.

ELK Stack: En helhetlig løsning for loggbehandling og visualisering

ELK-stacken består av tre hovedkomponenter: Elasticsearch, Kibana og Filebeat. Elasticsearch fungerer som den primære lagrings- og indekseringsmotoren for loggdata, mens Kibana gir et interaktivt grensesnitt for visualisering og analyse. Filebeat er et lettvektig loggagent som kontinuerlig sender loggdata til Elasticsearch.

Når ELK-stacken er konfigurert og i drift, kan vi bruke Kibana til å få et helhetlig bilde av systemets helse, feilmeldinger og bruksmetrikker. For å opprette et dashboard i Kibana som viser feilrater og forespørselsmetrikker, kan vi begynne med å opprette et indeksmønster som samsvarer med våre loggfiler. Dette kan gjøres ved å navigere til "Stack Management → Index Patterns" i Kibana og lage et mønster som matcher loggfilene, for eksempel "myapp-logs-*". Når mønsteret er opprettet, kan vi velge tidsstempel som tidsfelt.

Visualisering av feilmeldinger

Når loggene er i Elasticsearch, kan vi begynne å visualisere feilmeldinger i Kibana. Et eksempel kan være å lage et stolpediagram som viser antall feil per tidsenhet, filtrert på feilsituasjoner som "ERROR"-nivå eller HTTP-statuskoder større enn eller lik 500. Dette diagrammet kan deretter legges til et nytt dashboard, som vi kan kalle "Application Health", for å overvåke applikasjonens tilstand kontinuerlig.

Overvåkning av forespørselsmetrikker

Ved å visualisere forespørsels-ID-er eller statuskoder kan vi oppdage anomali eller plutselige økninger i trafikken. Kibana gir muligheten til å lage tabeller eller sektordiagrammer som grupperer data etter endepunkt, feiltyper eller brukeridentifikatorer. Dette kan være nyttig for å raskt identifisere problemer med spesifikke API-er eller tjenesteinteraksjoner.

Setting opp varsler i Kibana

For å proaktivt håndtere systemfeil kan vi sette opp varsler i Kibana som varsler teamet vårt om problemer før brukerne merker dem. Ved å gå til "Stack Management → Rules and Connectors" kan vi opprette regler basert på Elasticsearch-spørringer. For eksempel kan vi sette en regel som varsler oss dersom antall feilmeldinger overskrider et visst antall på en fem-minutters periode.

Kubernetes: Automatisering og skalering av applikasjoner

Med veksten av systemer som består av mange containere på flere verter, blir manuell administrasjon stadig mer utfordrende. Her kommer Kubernetes inn som en containerorkestrator, som automatiserer distribusjon, skalering, nettverksoppsett og selv-healing av tjenester på tvers av klynger av noder.

Kubernetes manifester, som er tekstfiler i YAML-format, fungerer som "kilden til sannhet" for infrastrukturen. Ved å bruke disse filene kan vi definere alt fra hvordan applikasjoner skal kjøres til hvordan de skal aksesseres, konfigureres og lagre sensitive data. En vanlig Kubernetes-manifest for en applikasjon kan for eksempel spesifisere antall pod-er (containere), tilgjengelige ressurser som CPU og minne, samt miljøvariabler som applikasjonen trenger.

Skriving av Kubernetes Deployment Manifest

Et Kubernetes Deployment er ansvarlig for å beskrive hvordan en gruppe identiske Pods (applikasjonscontainere) skal kjøres og administreres. For eksempel kan et manifest defineres for en applikasjon som krever tre replikater for høy tilgjengelighet. Her er et eksempel på hvordan manifestet kan se ut:

yaml
apiVersion: apps/v1
kind: Deployment metadata: name: myapp labels: app: myapp spec: replicas: 3 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: myapp image: myregistry/myapp:1.0.0 ports: - containerPort: 8000 env: - name: DATABASE_URL valueFrom: secretKeyRef: name: myapp-secret key: database-url resources: requests: cpu: "250m" memory: "512Mi" limits: cpu: "500m" memory: "1Gi"

I dette eksemplet definerer vi tre replikater av applikasjonen vår, som hver eksponerer port 8000. Vi har også spesifisert miljøvariabler og ressurser som applikasjonen kan bruke, samt hvordan Kubernetes skal skalere og distribuere disse containerne på tvers av nodene.

Definering av Kubernetes Service Manifest

En Kubernetes Service eksponerer Pods til andre deler av klyngen, og kan også brukes til å gjøre applikasjonen tilgjengelig for eksterne tjenester. En enkel ClusterIP-tjeneste, som bare tillater intern trafikk, kan defineres som følger:

yaml
apiVersion: v1 kind: Service metadata: name: myapp-service spec: selector: app: myapp ports: - protocol: TCP port: 80 targetPort: 8000

Her ruter tjenesten trafikk som kommer til port 80 til port 8000 på hver av Pods.

Ingress for HTTP Routing

Ingress er en Kubernetes-ressurs som gir avansert HTTP(S)-ruting til tjenestene våre. Det lar oss administrere SSL-terminering, domene-ruting og URL-stier. Et eksempel på en enkel Ingress-konfigurasjon kan være:

yaml
apiVersion: networking.k8s.io/v1
kind: Ingress metadata: name: myapp-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - host: myapp.local http: paths: - path: / pathType: Prefix backend: service: name: myapp-service port: number: 80

Med denne konfigurasjonen vil alle forespørsler til myapp.local/ bli rutet til vår tjeneste.

Konfigurasjon med ConfigMap og Secret

Kubernetes anbefaler at man lagrer ikke-sensitive konfigurasjoner i ConfigMaps og sensitive data som passord eller API-nøkler i Secrets. Dette sikrer at sensitive data ikke blir lagret i klartekst i manifestene.

Et eksempel på en ConfigMap som lagrer applikasjonskonfigurasjoner kan være:

yaml
apiVersion: v1 kind: ConfigMap metadata: name: myapp-config data: LOG_LEVEL: "info" CACHE_HOST: "redis.default.svc.cluster.local"

For sensitive data, som en database-URL, kan vi bruke en Secret:

yaml
apiVersion: v1
kind: Secret metadata: name: myapp-secret data: database-url: <base64-encoded-url>

ConfigMap og Secret kan refereres i Deployment-manifestene, og gir oss fleksibilitet til å håndtere konfigurasjoner og sensitive data på en sikker og organisert måte.

Viktige betraktninger for leseren

Å bygge et overvåkningssystem med ELK-stacken og Kubernetes krever en grundig forståelse av hvordan komponentene samhandler. Det er viktig å ha en klar plan for hvordan loggene skal håndteres og visualiseres, samt hvordan applikasjoner skal administreres gjennom Kubernetes-manifester. I tillegg er det avgjørende å kontinuerlig overvåke systemet for potensielle problemer som kan oppstå, som for eksempel feil i loggdataflyten, helseproblemer med Elasticsearch, eller problemer med Docker-volumer. En godt implementert løsning vil gjøre det lettere å finne rotårsaken til problemer og bidra til en mer stabil og pålitelig applikasjon.