Ga naar inhoud

Infrastructuur

Deze pagina beschrijft de AWS-infrastructuur van Remedice op hoofdlijnen. De beschrijving volgt de actuele hostingroadmap en de Terraform-modules in infra/.

Technische architectuur

Remedice draait in eu-central-1 en gebruikt gescheiden AWS-accounts voor staging en productie. De diagrammen hieronder tonen de productieomgeving. Staging bestaat als aparte omgeving met dezelfde module-opzet, maar heeft hier geen eigen diagram.

flowchart LR
    subgraph prod["Prod account"]
        foundation@{ img: "/assets/aws/aws-iam.svg", label: "foundation<br/>OIDC, Config, alerts", pos: "b", w: 46, h: 46, constraint: "on" }
        network@{ img: "/assets/aws/aws-vpc.svg", label: "network<br/>VPC, subnets, SGs", pos: "b", w: 46, h: 46, constraint: "on" }
        stateful@{ img: "/assets/aws/aws-rds.svg", label: "stateful<br/>Aurora, S3, KMS, ECR, secrets", pos: "b", w: 46, h: 46, constraint: "on" }
        cdn@{ img: "/assets/aws/aws-cloudfront.svg", label: "cdn<br/>CloudFront app + media", pos: "b", w: 46, h: 46, constraint: "on" }
        stateless@{ img: "/assets/aws/aws-ecs.svg", label: "stateless<br/>ALB, WAF, ECS services", pos: "b", w: 46, h: 46, constraint: "on" }
        scrapers@{ img: "/assets/aws/aws-eventbridge.svg", label: "scraper-runner<br/>EventBridge + ECS tasks", pos: "b", w: 46, h: 46, constraint: "on" }
    end

    foundation --> network
    network --> stateful
    stateful --> cdn
    stateful --> stateless
    stateful --> scrapers

Publieke request path

flowchart LR
    user[Gebruiker]
    zorgid[ZORG-ID OIDC]
    dns@{ img: "/assets/aws/aws-route53.svg", label: "Route 53", pos: "b", w: 46, h: 46, constraint: "on" }

    subgraph edge["Edge"]
        appCf@{ img: "/assets/aws/aws-cloudfront.svg", label: "CloudFront app", pos: "b", w: 46, h: 46, constraint: "on" }
        mediaCf@{ img: "/assets/aws/aws-cloudfront.svg", label: "CloudFront media", pos: "b", w: 46, h: 46, constraint: "on" }
        waf@{ img: "/assets/aws/aws-waf.svg", label: "WAF", pos: "b", w: 46, h: 46, constraint: "on" }
        alb@{ img: "/assets/aws/aws-elb.svg", label: "ALB", pos: "b", w: 46, h: 46, constraint: "on" }
    end

    subgraph storage["S3 origins"]
        frontend@{ img: "/assets/aws/aws-s3.svg", label: "S3 frontend", pos: "b", w: 46, h: 46, constraint: "on" }
        media@{ img: "/assets/aws/aws-s3.svg", label: "S3 general", pos: "b", w: 46, h: 46, constraint: "on" }
    end

    api@{ img: "/assets/aws/aws-ecs.svg", label: "ECS backend", pos: "b", w: 46, h: 46, constraint: "on" }

    user --> dns
    user --> zorgid
    zorgid --> alb
    dns --> appCf --> frontend
    dns --> mediaCf --> media
    dns --> waf --> alb --> api

Applicatie en data

flowchart LR
    api@{ img: "/assets/aws/aws-ecs.svg", label: "ECS backend", pos: "b", w: 46, h: 46, constraint: "on" }
    worker@{ img: "/assets/aws/aws-ecs.svg", label: "Celery worker", pos: "b", w: 46, h: 46, constraint: "on" }
    beat@{ img: "/assets/aws/aws-ecs.svg", label: "Celery beat", pos: "b", w: 46, h: 46, constraint: "on" }
    redis@{ img: "/assets/aws/aws-elasticache.svg", label: "ElastiCache Redis", pos: "b", w: 46, h: 46, constraint: "on" }

    subgraph data["Stateful resources"]
        db@{ img: "/assets/aws/aws-rds.svg", label: "Aurora user-data", pos: "b", w: 46, h: 46, constraint: "on" }
        buckets@{ img: "/assets/aws/aws-s3.svg", label: "S3 buckets", pos: "b", w: 46, h: 46, constraint: "on" }
        secrets@{ img: "/assets/aws/aws-secrets-manager.svg", label: "Secrets Manager", pos: "b", w: 46, h: 46, constraint: "on" }
        kms@{ img: "/assets/aws/aws-kms.svg", label: "KMS", pos: "b", w: 46, h: 46, constraint: "on" }
        logs@{ img: "/assets/aws/aws-cloudwatch.svg", label: "CloudWatch Logs", pos: "b", w: 46, h: 46, constraint: "on" }
    end

    api --> db
    worker --> db
    api --> redis
    worker --> redis
    beat --> worker
    api --> buckets
    worker --> buckets
    api --> secrets --> kms
    buckets --> kms
    api --> logs
    worker --> logs

Achtergrondtaken en AI

flowchart LR
    api@{ img: "/assets/aws/aws-ecs.svg", label: "ECS backend", pos: "b", w: 46, h: 46, constraint: "on" }
    worker@{ img: "/assets/aws/aws-ecs.svg", label: "Celery worker", pos: "b", w: 46, h: 46, constraint: "on" }
    schedule@{ img: "/assets/aws/aws-eventbridge.svg", label: "EventBridge Scheduler", pos: "b", w: 46, h: 46, constraint: "on" }
    scraper@{ img: "/assets/aws/aws-ecs.svg", label: "ECS RunTask<br/>scraper-runner", pos: "b", w: 46, h: 46, constraint: "on" }
    dlq@{ img: "/assets/aws/aws-sqs.svg", label: "SQS DLQ", pos: "b", w: 46, h: 46, constraint: "on" }
    alert@{ img: "/assets/aws/aws-sns.svg", label: "SNS alert", pos: "b", w: 46, h: 46, constraint: "on" }
    artifacts@{ img: "/assets/aws/aws-s3.svg", label: "S3 scraped-content", pos: "b", w: 46, h: 46, constraint: "on" }
    bedrock@{ img: "/assets/aws/aws.svg", label: "Bedrock", pos: "b", w: 46, h: 46, constraint: "on" }
    kb@{ img: "/assets/aws/aws.svg", label: "Bedrock KB", pos: "b", w: 46, h: 46, constraint: "on" }
    vector@{ img: "/assets/aws/aws-rds.svg", label: "Aurora pgvector", pos: "b", w: 46, h: 46, constraint: "on" }
    speech@{ img: "/assets/aws/aws.svg", label: "Transcribe", pos: "b", w: 46, h: 46, constraint: "on" }
    ses@{ img: "/assets/aws/aws-ses.svg", label: "SES", pos: "b", w: 46, h: 46, constraint: "on" }
    messagebird@{ img: "/assets/aws/message-text.svg", label: "MessageBird SMS", pos: "b", w: 46, h: 46, constraint: "on" }

    schedule --> scraper
    scraper --> artifacts --> kb --> vector
    scraper -. failure .-> dlq --> alert
    api --> bedrock
    api --> kb
    api --> speech
    worker --> ses
    worker --> messagebird

Kerncomponenten

  • DNS en edge: Route 53 beheert remedice.nl en de delegatie naar staging.remedice.nl. CloudFront bedient de webapp en media. De API loopt via WAF en een Application Load Balancer.
  • Compute: Django, Daphne en Celery draaien op ECS Fargate. De prod-scrapers draaien via EventBridge als losse Fargate Spot-taken op een aparte scraper-cluster.
  • Database: Aurora Serverless v2 PostgreSQL bevat de tenantdata. Remedice gebruikt django-tenants, waarbij elke apotheek een eigen schema heeft.
  • Cache en queue: Redis ondersteunt Django Channels, caching en Celery. Productie gebruikt ElastiCache. Staging gebruikt een goedkopere Redis-task op Fargate.
  • Opslag en encryptie: S3 bewaart media, frontendbestanden, chatarchieven, scraper-artifacts en auditdata. KMS versleutelt S3-objecten en gevoelige applicatiedata.
  • Statische sites: de landingspagina (apex en www), de documentatiesite en de contactpagina draaien als losse statische sites op S3 met CloudFront ervoor (module static-site). Ze staan los van de webapp, hebben een eigen CloudFront-distributie met OAC en een eigen Content Security Policy, en kosten in rust vrijwel niets.
  • Berichtenverkeer: SES verstuurt e-mail vanaf het aparte afzenddomein mail.remedice.nl, gescheiden van de Zoho-mailboxen op het hoofddomein. MessageBird (Bird) verstuurt sms (onder andere de tweede-factor- en uitnodigingscodes) onder de alfanumerieke afzender Remedice; deze externe partij is gekozen omdat AWS SNS nog geen sms-productietoegang heeft verleend, en SNS blijft als terugvaloptie geconfigureerd. Telefoonnummers worden bewust niet gelogd; foutsignalering loopt via de applicatie.
  • Secrets en configuratie: Secrets Manager bevat alleen gevoelige waarden, waaronder de OIDC- en OAuth-clientgegevens voor ZORG-ID, Google en Apple. Niet-geheime configuratie blijft in de settings en Terraform.
  • ZORG-ID: VZVZ redirect naar de bestaande API-callbacks https://api.staging.remedice.nl/api/auth/zorgid/callback/ en https://api.remedice.nl/api/auth/zorgid/callback/. De backend haalt discovery, token en userinfo op via acceptatie.zorg-id.nl/oauth of productie.zorg-id.nl/oauth.

AWS AI-laag

De AI-stack voor patientdata blijft binnen AWS. Bedrock levert LLM-aanroepen en embeddings. De kennisbank gebruikt een aparte Aurora pgvector-database in het productieaccount. Staging kan deze kennisbank alleen lezen via cross-account rechten.

Transcribe verwerkt spraak naar tekst voor de anamnese. De tekst-naar-spraak van de werkafspraken-podcast loopt buiten AWS via Google Cloud Chirp 3 HD en verwerkt geen patientdata.

Omgevingen

  • Staging: draait in een eigen AWS-account met dezelfde Terraform-modules als productie. De omgeving is bedoeld voor validatie van backend, frontend, CDN, database en CI/CD.
  • Productie: draait in een apart AWS-account. De gebruikersroute blijft warm. Achtergrondtaken en AI-onderdelen worden waar mogelijk pay-per-use ingericht.