Skip to content
AWS App Runner după șase luni în producție: un review sincer
← ← Înapoi la Idei Cloud

AWS App Runner după șase luni în producție: un review sincer

Rulăm trei aplicații Next.js pe AWS App Runner din martie 2026, în conturi de dev, staging și producție, pentru o platformă de client cu utilizatori reali și bani reali care trec prin ea. Șase luni sunt suficiente ca să dai de toate muchiile ascuțite. Acesta e review-ul pe care am fi vrut să-l citim înainte să-l alegem.

Versiunea scurtă: App Runner e alegerea corectă pentru o echipă mică ce vrea containere pe AWS fără să dețină un ALB, un layout de VPC și ciclul de viață al task definition-urilor ECS. E alegerea greșită dacă ai nevoie de control fin peste rețea, de request-uri de lungă durată sau de un comportament la deploy care depășește „înlocuiește imaginea asta cu aia". L-am alege din nou pentru proiectul ăsta, cu avertismentele de mai jos scrise pe prima pagină a runbook-ului.

Între ce alegeam

Platforma fusese planificată inițial pentru Amplify Hosting. Când am reconstruit infrastructura în CDK, trei opțiuni erau pe masă:

  • ECS Fargate în spatele unui ALB. Standardul industriei. Control total, instalații complete: VPC, subneturi, security groups, target groups, reguli de listener, task definitions, auto-scaling de serviciu. L-am acoperit într-un ghid separat.
  • Amplify Hosting. Zero operațiuni pentru Next.js, dar vrea să dețină integrarea cu Git și build-ul, iar runtime-ul lui server-side e o cutie neagră pe care n-o poți regla.
  • App Runner. Împingi o imagine în ECR, primești un URL HTTPS. Fără ALB, fără VPC obligatoriu pentru calea publică, auto-scaling nativ, o resursă CloudFormation pe care o administrezi din CDK ca pe oricare alta.

App Runner a câștigat la suprafața operațională. Pentru o echipă de două-trei persoane care scrie și codul aplicației, fiecare bucată de rețea pe care n-o deții e o bucată pe care nu trebuie s-o depanezi la 11 noaptea.

Ce e bun

HTTPS și un URL din prima. Fiecare serviciu primește un hostname *.awsapprunner.com cu certificat administrat. Domeniile custom sunt un CNAME plus un record de validare. N-am atins ACM deloc pentru hostname-urile aplicațiilor.

Resursa CloudFormation e completă. AWS::AppRunner::Service expune identificatorul imaginii, dimensiunea instanței, configurația de auto-scaling, health check-ul, variabilele de mediu și secretele. Asta înseamnă că rollout-ul se întâmplă în interiorul CloudFormation când se schimbă tag-ul imaginii, ceea ce are un beneficiu subtil: rolul de execuție al CloudFormation deține permisiunea iam:PassRole, pe care utilizatorul uman de deploy nu are nevoie s-o aibă. Deploy-urile concurente se serializează la nivel de stack, deci doi ingineri nu-și pot suprascrie unul altuia rollout-ul.

Injectarea secretelor e făcută bine. runtimeEnvironmentSecrets trage valorile din Secrets Manager la pornirea containerului și acordă rolului de instanță GetSecretValue exact pe acele ARN-uri. Valorile sensibile nu ajung niciodată în template. Folosim asta pentru credențialele bazei de date, token-urile API ale terților și cheia privată de web push.

Auto-scaling-ul e plictisitor în cel mai bun sens. Configurația implicită (minim 1, maxim 25, 100 de request-uri concurente per instanță) a fost suficientă pentru aplicațiile interne. Pentru aplicația publică am setat un minim de două instanțe calde, care au absorbit traficul din ziua lansării fără un scale-out la rece.

Dimensionarea instanței e o schimbare de o linie. Când calculul server-side al câștigurilor din aplicația de finanțe s-a dovedit a fi JavaScript limitat de CPU, iar jumătate de vCPU făcea o pagină să dureze 20 de secunde, am mutat-o la 1 vCPU / 2 GB în config.ts. Deploy, gata. Aceeași schimbare pe ECS e o revizie de task definition plus un update de serviciu plus o așteptare.

Ce e rău

Fiecare punct de aici ne-a costat cel puțin o după-amiază.

Un timeout de gateway de 120 de secunde, neconfigurabil. App Runner închide cu 504 orice request care durează mai mult de 120 de secunde. Aplicația de finanțe avea un grid de date care, când un backlog de materializare depășea vreo 250 de rânduri, cădea pe un calcul complet la citire și depășea limita. Rezolvarea a fost la noi (materializare incrementală, o rută de health care raportează „not ready" cât timp se încălzesc cache-urile), dar e genul de constrângere pe care vrei s-o știi înainte să-ți proiectezi rutele de API. Tot ce durează mult ține de o Lambda sau de o coadă, nu de App Runner.

Re-deploy-ul aceluiași tag de imagine nu face nimic. Fixăm fiecare serviciu pe o imagine etichetată cu SHA-ul de git. Dacă reconstruiești același SHA cu o configurație de build diferită, string-ul tag-ului e neschimbat, CloudFormation nu vede niciun diff pe ImageIdentifier, iar App Runner continuă să servească imaginea pe care a tras-o deja — deși tag-ul din ECR indică acum un digest diferit. Ne-a mușcat când am schimbat un feature flag prin context CDK fără o schimbare de cod. Regula acum: fiecare deploy mută SHA-ul. Comite schimbarea de config.

Mediul de build versus mediul de runtime e problema ta. Next.js încorporează variabilele NEXT_PUBLIC_* la build. Variabilele de mediu de runtime de pe serviciul App Runner nu le pot schimba. Scriptul nostru de deploy citește acum mediul de build din template-ul CloudFormation proaspăt sintetizat, îl transmite per build către CodeBuild și apoi verifică căutând în chunk-urile JavaScript servite. Până am făcut asta, o variabilă publică schimbată avea nevoie de două deploy-uri ca să ajungă în browser.

O instanță nouă durează cam un minut. Trage imaginea, pornește Node, încălzește cache-urile. Cu un health check TCP, instanța intră în rotație imediat ce se deschide portul, ceea ce pentru aplicația de finanțe însemna un prim request rece și lent. Trecerea la un health check HTTP pe o rută dedicată /api/health, care întoarce 503 până se termină încălzirea, a rezolvat problema. Fă asta din prima zi.

Primele 120 de secunde ale unei instanțe noi 0 s 30 s 60 s 90 s 120 s imagine trasă · Node pornit instanță cerută health check TCP: în rotație, rece HTTP /api/health: 503 cât se încălzește → în rotație, caldă timeout de gateway · orice request încă în execuție primește 504

Implicit, health check-urile sunt TCP. Vezi mai sus. E o capcană pentru orice aplicație care încălzește cache-uri la pornire.

Fără GitHub Actions? Atunci nici deploy din consolă. Pentru că CloudFormation deține serviciul, butonul „Deploy" din consolă e practic interzis: orice schimbare manuală e suprascrisă de următorul update al stack-ului. A trebuit să ne construim propriul script de deploy pentru cazul în care CI-ul nu era disponibil (motive de facturare, o vreme). E un script shell de 300 de linii cu un worktree fixat pe branch-ul remote, un upload git archive în S3, job-uri CodeBuild în paralel și un deploy CDK. Funcționează, dar e al nostru de întreținut.

Pipeline-ul care a rezultat

git push branch → cont rol OIDC cdk synth eșuează rapid emite env-ul de build CodeBuild ×3 docker build amd64 tag ECR = SHA git cdk deploy fixează ImageIdentifier CFN face rollout-ul migrare Lambda, idempotentă verifică 200 ×3 Deploy complet în prod: 25–35 de minute, majoritatea build-uri Docker. Synth rulează primul, ca un template stricat să pice în secunde, nu după 15 minute de build-uri.

Build-urile Docker se întâmplă în CodeBuild, nu pe laptopuri, parțial pentru că build-urile cross-arhitectură pe Apple Silicon sunt dureroase și parțial pentru că ține fluxul „construiește imaginea pentru App Runner" în interiorul contului, cu un rol IAM care poate împinge doar în repository-urile ECR ale acelui cont. Trei build-uri rulează în paralel. Synth-ul rulează înaintea build-urilor, pentru că un template stricat trebuie să pice în secunde, nu după cincisprezece minute de Docker.

Costul

Nu putem publica factura clientului, dar îi putem descrie forma. App Runner taxează memoria provizionată cât timp o instanță există (caldă, inactivă) și vCPU-ul doar cât timp procesează activ request-uri. Pentru aplicațiile interne la dimensiune minimă cu o instanță, e o eroare de rotunjire. Pentru aplicația publică, cu două instanțe calde de 2 vCPU / 4 GB, e cea mai mare linie individuală din factura de compute, mai mare decât Lambda și Aurora la un loc, dar tot mai puțin decât un setup ECS Fargate echivalent odată ce adaugi taxa orară a ALB-ului și NAT gateway-ul de care ai avea nevoie pentru subneturi private.

Comparația sinceră: App Runner e puțin mai scump per vCPU-oră decât Fargate și considerabil mai ieftin per oră de inginer.

Verdict

Notă Observație
Configurare și suport CDK 5/5 Resursă CloudFormation completă, secrete făcute bine
Operare de zi cu zi 4/5 Liniște; valoarea implicită a health check-ului e singurul lucru pe care l-am schimba
Model de deploy 3/5 Fixarea pe tag de imagine e deterministă, dar capcana aceluiași tag nu iartă
Constrângeri pe request-uri 2/5 Timeout-ul de 120 de secunde e dur; planifică în jurul lui
Cost 4/5 Mai mult per vCPU decât Fargate, mult mai puțin per inginer
L-am alege din nou Da Pentru o echipă mică ce livrează Next.js pe AWS

Dacă workload-ul tău e o aplicație web cu request-uri sub câteva secunde, echipa e mică și ai prefera să scrii cod de produs în loc de cod de rețea, App Runner e unul dintre serviciile cu cel mai bun raport valoare-preț pe care le are AWS. Dacă ai nevoie de websocket-uri cu viață lungă, request-uri de peste două minute sau ingress exclusiv privat cu control granular, du-te direct la ECS Fargate și acceptă instalațiile.

Ai nevoie de ajutor să decizi sau să migrezi o aplicație existentă? Te putem ajuta — am făcut-o în ambele direcții.