Tota l’evidència
AWSFinOpsTerraformECS

Reduir costos a ECS: de Fargate a EC2 amb desplegaments d’hosts immutables i sense downtime

Resum

Migrar els serveis d’ECS en producció de Fargate a EC2 i capacitat spot va reduir al voltant d’un 44 % el cost de còmput per unitat de càrrega de treball, i els equips d’aplicació no van haver de canviar res.

Fargate cobra un sobrepreu perquè ningú no hagi de gestionar hosts. Vam conservar la major part d’aquesta comoditat sobre capacitat més barata: els hosts són immutables i se substitueixen automàticament sense downtime, les interrupcions de spot es drenen de manera ordenada i cada tasca manté la seva pròpia interfície de xarxa i el seu propi security group.

L’estalvi és més petit que la diferència de preu bruta entre Fargate i EC2, i aquest cas d’èxit explica per què.

El repte

Els serveis d’ECS en producció s’executaven a Fargate, i la factura de còmput creixia amb cada nou servei, fins a uns 1.700 $ al mes.

El preu de Fargate inclou la gestió dels hosts: no hi ha AMIs per aplicar pedaços, ni instàncies per dimensionar, ni capacitat per planificar. A Frankfurt (eu-central-1) això costa 0,04656 $ per vCPU-hora i 0,00511 $ per GB-hora. EC2, i spot en particular, és molt més barat per al mateix còmput, però només si assumeixes la feina que feia Fargate.

Així que la veritable pregunta era quanta d’aquesta feina podíem automatitzar perquè l’estalvi no se n’anés en operacions.

Restriccions

  1. Paritat amb Fargate. Els equips d’aplicació no haurien de notar res: el mateix model de xarxa, el mateix flux de desplegament, cap runbook nou.
  2. Res de flocs de neu. Tot viu a Terraform sobre l’stack existent de terraform-aws-modules (VPC, ALB, ECS, Auto Scaling). Cap host no es configura mai a mà.
  3. Construir de manera incremental. Vam començar amb una VPC mínima, un ALB, un clúster sobre EC2 i un servei nginx, i després vam afegir les funcions de producció una a una, validant cadascuna abans de passar a la següent.

Hosts immutables: substituir, mai aplicar pedaços

A Fargate, AWS aplica els pedaços als hosts. A EC2 aquesta feina va passar a ser nostra, així que la vam convertir en una tasca de substitució en lloc de manteniment: a cap host no se li apliquen mai pedaços in situ.

Una nova AMI optimitzada per a ECS implica una nova versió del launch template, cosa que dispara un instance refresh d’Auto Scaling que substitueix tots els hosts. Els contenidors es mouen sense downtime perquè cada host es drena abans de terminar-se:

  1. Terraform publica una versió del launch template amb la nova AMI, i comença l’instance refresh.
  2. Auto Scaling tria els hosts antics que substituirà, en lots limitats pel seu percentatge mínim d’instàncies sanes.
  3. Cada host triat passa pel drenatge gestionat d’ECS. ECS reprograma les seves tasques en altres hosts respectant el percentatge mínim de tasques sanes de cada servei, i l’ALB dona de baixa els targets antics.
  4. El host només es termina quan ja no executa cap tasca, i els hosts nous amb la nova AMI n’ocupen el lloc.

El parany: la protecció davant de l’scale-in. La protecció de terminació gestionada marca com a protegit cada host que executa tasques, cosa que impedeix que l’scale-in mati tasques. L’instance refresh té una preferència específica per a les instàncies protegides, i cada configuració equivocada falla d’una manera diferent. Per defecte, el refresh se salta els hosts protegits i pot acabar amb AMIs antigues encara servint trànsit. Wait es queda bloquejat, perquè ECS només retira la protecció d’un host buit. Substituir hosts protegits sense drenatge gestionat els termina amb tasques encara en execució.

Vam configurar ScaleInProtectedInstances = Refresh i ens recolzem en el drenatge gestionat: els hosts protegits continuen substituint-se, però només després que ECS n’hagi mogut les tasques.

Aquest bucle és el que substitueix l’aplicació de pedaços que abans feia Fargate per nosaltres, i és el nucli de la paritat amb Fargate.

Base on-demand més spot

El clúster executa un host on-demand estable i posa tota la resta a spot. Hi havia dues maneres de muntar-ho, i només una garanteix on acaben les tasques crítiques.

Opció A: un ASG amb política d’instàncies mixtesOpció B: dos capacity providers
EstructuraUn Auto Scaling group que barreja on-demand i spotUn grup on-demand i un grup spot, cadascun amb el seu propi capacity provider
La base on-demand comptaInstànciesTasques (base a l’estratègia)
ECS distingeix on-demand de spotNoSí
Tasques crítiques garantides a on-demandNoSí

Vam triar l’opció B. Amb un únic grup mixt, ECS veu un únic conjunt d’hosts, així que una tasca crítica pot acabar a spot. Amb dos capacity providers, l’estratègia de capacity providers de cada servei col·loca les seves primeres base tasques a on-demand i reparteix la resta segons weight.

El detall que confon molta gent: base compta tasques, no instàncies. Una base de 2 vol dir dues tasques en capacitat on-demand, que poden compartir un mateix host.

Les tasques crítiques van primer a on-demand; la resta, a spot. L’estratègia de capacity providers d’un servei d’ECS envia les seves tasques base al capacity provider on-demand i tota la resta, segons el seu pes, al capacity provider spot. Cada provider es recolza en el seu propi Auto Scaling group: un host on-demand estable, i spot repartit entre diversos tipus d’instància i zones de disponibilitat.

L’estratègia de cada servei omple on-demand fins a la seva base i després envia les tasques restants a spot.

Mantenir la sensació de Fargate

Xarxa. Tots els serveis continuen en mode awsvpc, així que cada tasca té la seva pròpia ENI i el seu propi security group, exactament com a Fargate. El preu a EC2 és la densitat: cada tasca fa servir una ENI, i cada tipus d’instància limita quantes en pot associar. L’ENI trunking eleva aquest límit, però només en els tipus d’instància compatibles, cosa que redueix el ventall de spot. Fem servir hosts xlarge (4 vCPU, 16 GB) amb trunking activat, i cadascun porta entre 6 i 8 tasques, així que el límit el marca la CPU, no les ENIs.

Interrupcions de spot. El drenatge gestionat als capacity providers, juntament amb ECS_ENABLE_SPOT_INSTANCE_DRAINING=true a la configuració de l’agent, converteix l’avís d’interrupció de spot de dos minuts en un drenatge ordenat. ECS reprograma les tasques i l’ALB les dona de baixa abans que el host desaparegui.

Marge. Un target_capacity del managed scaling per sota del 100 % manté espai lliure al clúster, de manera que les tasques noves arrenquen a l’instant en lloc d’esperar en PROVISIONING que hi hagi un host. El vam fixar en el 90 %, canviant capacitat ociosa per latència d’escalat.

Resiliència de spot. El grup spot es reparteix entre quatre tipus d’instància (m5.xlarge, m5d.xlarge, m6i.xlarge, m6id.xlarge) i tres zones de disponibilitat, així que una recuperació de capacitat en un pool mai no fa caure un servei sencer. Fa servir l’estratègia d’assignació price-capacity-optimized, i el capacity rebalancing substitueix els hosts en risc abans que AWS els recuperi.

Mesurar l’escalat

Un nou host spot triga uns 170 segons, una mica menys de tres minuts, des que se sol·licita la tasca fins que hi ha una tasca sana darrere de l’ALB. «Quant triga un host en fred?» era la preocupació més gran a l’hora de deixar Fargate, així que ho vam mesurar en lloc de discutir-ho.

La prova va ser una petita aplicació Next.js amb renderització al servidor, escalada fins que ECS va haver de demanar un nou host spot.

FaseTemps (s)
De la sol·licitud de la tasca a l’arrencada del host~75
De l’arrencada del host al registre al clúster~50
Del registre a la tasca en RUNNING~25
De RUNNING a sana al target group de l’ALB~20
Total~170

Aquesta xifra va determinar el marge: com més lent és un host en fred, més capacitat lliure ha de reservar target_capacity.

Resultats

Per unitat de càrrega de treball, el cost de còmput va baixar de 0,05678 $ a uns 0,032 $ per tasca-hora, al voltant d’un 44 % menys: uns 1.720 $ al mes a Fargate davant de 970 $ a EC2 i spot. La referència és una tasca d’1 vCPU i 2 GB amb les tarifes de Fargate a Frankfurt (0,04656 $ + 2 × 0,00511 $).

MètricaFargateEC2 + spot
Cost mensual de còmput, mateixa càrrega~1.720 $~970 $
Cost per tasca-hora, 1 vCPU / 2 GB0,05678 $~0,032 $
Reserva de CPU / memòria del clústerEs factura per tasca~74 % / ~46 %
Desplegament complet d’AMI, 14 hostsHo gestiona AWS~35 min, 0 peticions fallides a l’ALB
Interrupcions de spot al mesCapD’1 a 3, cap de visible per als usuaris

Perquè la comparació sigui honesta, la xifra d’EC2 és el cost de tot el clúster, EBS inclòs, dividit entre les vCPU-hora i GB-hora que les tasques van reservar realment. El marge ociós, el malbaratament del bin-packing i la sobrecàrrega dels hosts són dins d’aquesta xifra.

A què vam renunciar

L’estalvi és real, però més petit que la diferència de preu bruta, per quatre motius:

  • Malbaratament del bin-packing. Ara paguem capacitat d’host que cap tasca no fa servir, una cosa que Fargate no cobrava mai.
  • Sobrecàrrega dels hosts. L’agent d’ECS i els daemons que hi hagi es queden una part de cada instància.
  • Responsabilitat. El ritme d’actualització de les AMIs i el flux de substitució són ara feina nostra, no d’AWS.
  • Interrupcions. Que AWS recuperi capacitat spot és un esdeveniment normal, i tots els serveis l’han de tolerar.

Preferim dir-ho d’entrada abans que deixar que un percentatge en un titular suggereixi que res d’això no té cost.

Què faríem diferent

  • Instàncies Graviton, per a un preu per vCPU més baix tant a on-demand com a spot.
  • Un Compute Savings Plan que cobreixi la base on-demand, que és prou estable per comprometre-s’hi.
  • Un flux automatitzat d’AMIs que llanci un instance refresh tan bon punt AWS publiqui una nova AMI optimitzada per a ECS, perquè el ritme d’aplicació de pedaços ja no depengui que algú se n’adoni.