În Azure Container Apps există un parametru care arată complet inofensiv și care, pus pe valoarea greșită, poate costa mai mult decât tot restul aplicației la un loc. Se numește minReplicas, are valoarea implicită 0 și aproape toată lumea îl schimbă pe 1 în prima săptămână.
Motivul e întotdeauna același și e perfect rezonabil: nu vreau cold start pe primul request. Prețul acelei decizii, însă, e rareori calculat.
În articolul anterior am făcut calculul pentru Azure Functions. Acum trecem la containere, unde modelul de facturare e diferit — și unde risipa e mai ușor de produs, pentru că e mai bine ascunsă.
Cum se facturează de fapt Container Apps
Pe profilul Consumption, plătești per secundă pentru vCPU și memoria alocate replicilor tale. Partea pe care mulți o ratează e că există două rate diferite pentru exact același vCPU:
| Stare | Când se aplică | Rată orientativă (East US) |
|---|---|---|
| Active | Replica pornește sau procesează cel puțin o cerere | ~$0,086 / vCPU-oră |
| Idle | Replica rulează, dar nu are trafic | ~$0,011 / vCPU-oră |
| Zero | Aplicația a scalat la 0 replici | $0 |
Există și un nivel gratuit lunar per subscripție — în jur de 180.000 vCPU-secunde, 360.000 GiB-secunde și 2 milioane de cereri — care acoperă integral aplicațiile mici.
Rata idle pare neglijabilă. Hai să o înmulțim.
Aritmetica lui minReplicas: 1
Un serviciu cu 0,5 vCPU și 1 GiB, cu minReplicas: 1, rulează 730 de ore pe lună. Dacă are trafic real 2 ore pe zi, restul de ~670 de ore sunt facturate la rata idle.
Per serviciu, pe lună, vorbim de câțiva dolari. Nimic alarmant — și exact de asta trece neobservat.
Acum înmulțește cu realitatea: șase microservicii, trei medii (dev, staging, prod), fiecare cu minReplicas: 1 pus reflex la creare. Optsprezece replici care rulează permanent ca să deservească, în cazul mediilor non-prod, un trafic de aproximativ zero.
Costul idle nu e mare per unitate. E mare pentru că se înmulțește cu numărul de servicii, cu numărul de medii și cu 730 de ore pe lună — iar niciunul dintre cei trei multiplicatori nu apare în decizia inițială.
Regula practică pe care o aplic: în dev și staging, minReplicas e 0. Fără excepții. Un cold start de câteva secunde în staging nu deranjează pe nimeni. În producție, decizia se ia per serviciu, pe baza traficului real — nu pe baza fricii.
KEDA: scalezi după ce contează, nu după CPU
Container Apps folosește KEDA pentru scalare, iar asta e mai important decât sună. Modelul clasic de autoscaling reacționează la CPU și memorie — adică la simptome. KEDA reacționează la cauză: lungimea cozii, numărul de mesaje neprocesate, rândurile dintr-un tabel.
Pentru un worker care consumă dintr-o coadă, diferența e fundamentală. Scalarea pe CPU e un indicator întârziat — coada crește de un minut până când CPU-ul reacționează. Scalarea pe lungimea cozii e imediată.
resource app 'Microsoft.App/containerApps@2024-03-01' = {
name: 'order-processor'
properties: {
configuration: { ... }
template: {
scale: {
minReplicas: 0
maxReplicas: 10
rules: [
{
name: 'queue-depth'
custom: {
type: 'azure-servicebus'
metadata: {
queueName: 'orders'
messageCount: '20'
}
auth: [ ... ]
}
}
]
}
}
}
}
Aici messageCount: 20 înseamnă: pornește o replică nouă pentru fiecare 20 de mesaje în așteptare. Parametrul ăsta e locul unde se produce a doua mare risipă din Container Apps.
Praguri prea agresive
Dacă pui messageCount: 1, un vârf de 30 de mesaje — care ar fi fost procesat în cinci secunde de o singură replică — declanșează pornirea a zece replici. Fiecare pornește, fiecare e facturată la rata active în timpul pornirii, fiecare procesează două mesaje și apoi stă degeaba până expiră perioada de cooldown.
Ai plătit de zece ori costul de pornire ca să economisești trei secunde. Și, mai rău, ai creat zece conexiuni simultane la baza de date pentru o rafală trivială.
Pragul corect se calculează invers: cât durează procesarea unui mesaj și care e latența acceptabilă? Dacă un mesaj se procesează în 200 ms și accepți 10 secunde de întârziere, o replică poate duce 50 de mesaje. Ăla e pragul, nu 1.
App Service: autoscale pe reguli, nu pe speranță
App Service funcționează pe alt model — capacitate rezervată, cu scalare orizontală între instanțe de același tier. Nu scalează la zero. Aici optimizarea are trei straturi.
1. Tier-ul corect, verificat cu date
Cea mai frecventă risipă din App Service e un tier ales la lansare „ca să fie sigur” și nemaiatins de doi ani. Azure Advisor semnalează asta explicit, dar raportul nu se deschide singur. Un plan la 12% CPU constant e un plan supradimensionat, indiferent cât de liniștitor arată.
2. Scale-out cu reguli simetrice
Greșeala clasică e să configurezi regula de creștere și să o uiți pe cea de scădere. Aplicația urcă la 5 instanțe într-un vârf de la ora 11 și rămâne acolo până la restart.
Fiecare regulă de scale-out are nevoie de perechea ei de scale-in, cu praguri asimetrice, ca să eviți oscilația: crește la peste 70% CPU, scade sub 30%. Dacă pragurile sunt prea apropiate, sistemul intră într-un ciclu de pornire și oprire continuă — care, evident, se facturează.
3. Scalare programată pentru trafic previzibil
Dacă aplicația ta e un instrument de business folosit între 8 și 18, de luni până vineri, scalarea reactivă e instrumentul greșit. Reacția vine întotdeauna cu întârziere — utilizatorii simt lentoarea înainte ca instanța nouă să fie gata.
Regulile bazate pe program rezolvă ambele probleme: capacitate mărită dimineața, redusă seara și în weekend. E mai ieftin și mai rapid decât autoscale-ul reactiv, pentru că nu aștepți metrica.
Mediile non-producție: cea mai profitabilă oră de muncă
Am menționat asta în primul articol, dar merită repetat cu cifre.
Dev și staging sunt folosite realist 40–50 de ore pe săptămână. Se facturează 168. Peste 70% din costul mediilor non-prod se consumă în timp ce nimeni nu se uită la ele — nopți, weekenduri, concedii.
Soluțiile, în ordinea efortului:
- Container Apps:
minReplicas: 0. Atât. Costul devine aproape zero automat. - App Service: regulă de autoscale programată care coboară la o instanță din tier-ul minim în afara orelor de program.
- Orice altceva (VM-uri, baze de date): un Automation Runbook sau un job de GitHub Actions care oprește resursele la 20:00 și le pornește la 7:30 în zilele lucrătoare.
Ultimul punct e cam o oră de muncă. Randamentul lui anual e, procentual, mai bun decât aproape orice altceva ai face în infrastructură.
Checklist de configurare
Ce verific de fiecare dată când creez sau moștenesc un serviciu containerizat:
minReplicase 0 în dev și staging? Dacă nu, de ce?- În producție, valoarea reflectă traficul real măsurat sau o presupunere de acum șase luni?
maxReplicasare o limită rezonabilă? E plasa de siguranță care oprește o buclă infinită să genereze o factură de mii de dolari.- Pragul KEDA e calculat din durata de procesare și latența acceptabilă, sau e o cifră rotundă aleasă intuitiv?
- Fiecare regulă de scale-out are perechea ei de scale-in, cu praguri asimetrice?
- Traficul e previzibil? Atunci folosesc scalare programată, nu reactivă.
- Resursele de test se opresc singure în afara orelor de program?
Concluzie
Auto-scaling-ul nu e o caracteristică pe care o activezi. E un set de parametri pe care îi calibrezi — iar valorile implicite sunt optimizate pentru a nu te supăra, nu pentru a te costa puțin.
Cele mai scumpe două numere dintr-o configurație de Container Apps sunt minReplicas și pragul de scalare KEDA. Amândouă se pun în treizeci de secunde și rămân neschimbate ani de zile. Merită cele zece minute de gândire.
În articolul următor trecem de la configurare la măsurare: Azure Cost Management, tagging, budgets, anomaly detection și cum îți construiești un audit lunar de cost care durează douăzeci de minute și chiar se face.
Notă: ratele indicate sunt orientative, pentru regiunea East US, la momentul scrierii. Verifică întotdeauna prețurile curente pentru regiunea ta.