Anti-pattern FinOps: cosa (non) fare per avere più budget per innovare
22 Ottobre 2025 - 9 min. read
Andrea Pusineri
& Nicola Ferrari
& Nicola Ferrari

La Continuous Delivery è oggi una delle più note metodologie di rilascio del software. Grazie ad essa, qualsiasi commit che abbia superato la fase di test, potrà essere rilasciato e distribuito in produzione in modo automatico.
Automatizzare le fasi di rilascio permette, tra gli altri vantaggi, di ottenere un flusso di lavoro impeccabile negli ambienti di sviluppo, test e produzione rendendo le distribuzioni semplici e sicure.
Nel nostro ultimo articolo abbiamo parlato dei microservizi, dei loro vantaggi e di come configurare una distribuzione di tipo blue/green su AWS per un servizio ECS.
Riassumendo, con la tecnica del blue/green deployment la vecchia infrastruttura (blue) coesiste temporaneamente con la nuova infrastruttura (green). Dopo aver effettuato i test di integrazione/validazione, l’infrastruttura aggiornata verrà promossa a produzione, il traffico verrà reindirizzato con un processo totalmente seamless e la vecchia infrastruttura sarà eliminata definitivamente.
Come promesso, con questo articolo faremo un passo avanti: vi mostreremo come automatizzare il processo definendo una pipeline di Continuous Deployment che, da un semplice git push, sarà in grado di gestire in autonomia l'intero flusso di rilascio di un nuovo pacchetto software in modalità blue/green su ECS.
Infine, come bonus, mostreremo come automatizzare i test sugli ambienti “green” e come semplificare la creazione di un boilerplate complesso di infrastruttura sfruttando il servizio AWS CloudFormation. Presto capiremo come potrebbe esserci utile.
Siete pronti? Si comincia!
Prima di entrare nel vivo della creazione della pipeline, occorre assicurarsi che tutti questi requisiti siano soddisfatti:
Per quest'ultimo prerequisito riassumiamo di seguito gli step che abbiamo descritto più dettagliatamente nell’articolo precedente. Se volete seguire la guida completa, leggete qui prima di proseguire.
Spostiamoci sull’account AWS e cerchiamo ECS utilizzando la barra di ricerca disponibile. Selezioniamo “Clusters” nel pannello di sinistra e, nella finestra successiva, clicchiamo su “Create Cluster”. Siccome utilizzeremo Fargate, manteniamo “Networking only” come opzione e clicchiamo su “next”.

Inseriamo un nome per il cluster che stiamo creando lasciando invariate le altre opzioni e, se vogliamo, aggiungiamo alcuni tag significativi. Clicchiamo su “Create” per generare il nuovo cluster.
Occupiamoci ora della Task Definition che ospiterà i nostri container Docker.
Andiamo su “Task Definitions” sotto al menu “Amazon ECS”, clicchiamo su “Create new Task Definition” e selezioniamo “Fargate” come mostrato nell’immagine. Clicchiamo poi su “Next Step”.

Per il momento possiamo assegnare i ruoli di default sia al Task Role, che al Task Execution Role. Per le operazioni che andremo ad eseguire saranno sufficienti. Selezioniamo i valori minimi per Memoria e CPU (per questo esempio, 0.5GB e 0.25vCPU).

Creiamo ora un Container e associamolo all’immagine Docker che abbiamo salvato su ECR (lo abbiamo spiegato qui) configurando vCPU e memoria con gli stessi valori che abbiamo indicato nella Task Definition.
Selezioniamo “Add Container”.
Nella sidebar impostiamo un nome per il container e per l’Image Uri, apriamo una nuova Tab a spostiamoci sulla dashboard di ECR. Selezioniamo l'immagine creata in precedenza e copiamone l’URL per inserirlo nel campo “Image URI”.

A questo punto, aggiungiamo 3000 per tpc protocol in “Port mapping” e lasciamo le altre opzioni invariate prima di cliccare su “Add”.
Muoviamoci nel Cluster creato nel servizio ECS, clicchiamo sul suo nome e, sul fondo della dashboard, clicchiamo su “Create” sotto la tab “Services”.

Configuriamo le opzioni così come mostrato:
1. Launch Type: FARGATE
2. Task Definition: <YOUR_TASK_DEFINITION>
3. Cluster: <YOUR_CLUSTER>
4. Service Name: <A_NAME_FOR_THE_SERVICE>
5. Number of Tasks: 1
6. Deployment Type: Blue/Green
7. Deployment Configuration: CodeDeployDefault.ECSAllAtOnce
8. Service Role for CodeDeploy: <A_SUITABLE_ROLE_WITH_ECS_PERMISSIONS>
Lasciamo invariate le altre opzioni e clicchiamo su “Next Step”. Nella sezione successiva selezioniamo una VPC appropriata, una o più subnet e abilitiamo “auto-assign IP”.
Occorre ora configurare un Application LoadBalancer per il nostro Cluster. Selezioniamone uno esistente oppure creiamone uno nuovo dalla console di EC2. Scegliamo poi il nostro Container assicurandoci che mostri la porta mappata.

Dopo aver selezionato il Container, clicchiamo su “Add to load balancer”.
Indichiamo 8080 per “Production Listener Port” e 8090 per “Test Listener Port”. Selezioniamo il nostro LoadBalancer target group come mostrato in figura (se non lo avete configurato prima, potete farlo ora in una nuova tab seguendo questa guida).

Proseguiamo lasciando spento l’autoscaling, per questo esempio non ci servirà. Dopo la revisione, il servizio è creato!
Ora che abbiamo finalmente tutti i blocchi fondamentali per la nostra Pipeline, procediamo con la creazione!
Cominciamo con il “pushare” la nostra applicazione di prova sul nostro repository GitHub.
Andiamo poi sul nostro account AWS, selezionando AWS CodePipeline dalla lista dei servizi. Dalla dashboard clicchiamo su “Create pipeline”.

Nella schermata seguente diamo un nome alla pipeline e, se non abbiamo già un ruolo adeguato alle operazioni, lasciamo “New service role” selezionato e le altre opzioni come default; clicchiamo su “next”.

Nel source stage selezioniamo “GitHub version 2” e quindi connettiamoci al nostro repository GitHub. Seguiamo le istruzioni che ci verranno presentate dopo aver selezionato “Connect to GitHub”. Ricordiamoci di autorizzare solo il repository della nostra soluzione e di eseguire le operazioni come owner di quel repository, altrimenti non saremo in grado di completare il processo.
Dopo esserci connessi a GitHub, potremo completare i passaggi come mostrato di seguito, selezionando repository e branch:

Clicchiamo “next” e potremo proseguire con lo stage di build dove creeremo il nostro progetto CodeDeploy da aggiungere alla pipeline.
Per mantenere il codice sempre aggiornato nella nostra pipeline, abbiamo bisogno di questo step per generare un’immagine sempre aggiornata di Docker.
Cominciamo col dare un nome al nostro stage di Build, selezioniamo CodeBuild come “Action provider”, la region e SourceArtifact come “Input Artifact”.
Dobbiamo ora creare un nuovo progetto di build.
Cliccando su “Add project”, ci troveremo davanti ad una schermata simile a questa:

Diamo un nome al progetto, quindi lasciamo Managed Image con tutte le proprietà del container come suggerito. Proseguiamo cliccando (questo è estremamente importante) su “Privileged option” per permettere alla pipeline di generare le immagini Docker per noi. Verificate i vostri settaggi con l’immagine sottostante:

Per l’opzione di buildspec, selezioniamo l’editor inline e copiamo questi comandi:
version: 0.2
phases:
pre_build:
commands:
- REPOSITORY_URI=YOUR_ECR_URI
- echo $CODEBUILD_RESOLVED_SOURCE_VERSION
- COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION)
- IMAGE_TAG=${COMMIT_HASH}:latest
- $(aws ecr get-login --no-include-email --region YOUR_REGION)
install:
runtime-versions:
java: corretto11
build:
commands:
- printf '{"ImageURI":"%s"}' $REPOSITORY_URI:latest > imageDetail.json
- docker build -t YOUR_ECR_URI:latest .
- docker push YOUR_ECR_URI:latest
artifacts:
files: imageDetail.json
Nota: in grassetto abbiamo evidenziato le variabili che dovete adeguare al vostro progetto.
Dopo aver copiato i comandi, cliccate “ok”, quindi aggiungete il vostro progetto di Build allo stage.

Cominciamo selezionando “Amazon ECS (Blue/Green)” alla voce “Deploy Provider”, la region voluta per il progetto, quindi clicchiamo su “Create application”.

Diamo un nuovo nome al progetto e selezioniamo “Amazon ECS” come Compute Provider. Comparirà la schermata di creazione di un nuovo Development Group.
Nominiamolo, dopodiché selezioniamo, nell’ordine:
- Un service role con permessi appropriati
- Il cluster ECS creato in precedenza
- Il servizio ECS creato in precedenza
- L’Application Load Balancer creato prima con, rispettivamente, 8080 e TargetGroup 1 per produzione e 8090 e TargetGroup 2 per l’ambiente di test
- Una strategia per il traffico; per il nostro esempio useremo “Specify when to reroute traffic” e selezioneremo 5 minuti.
Clicchiamo su “Create” e torniamo a CodePipeline. Selezioniamo l’applicazione CodeDeploy e il CodeDeploy deployment group appena creati.

Per “Input Artifacts” aggiungiamo BuildArtifact affianco a “SourceArtifact”.
Per Amazon ECS Task Definition e AWS CodeDeploy AppSpec file selezioniamo “Source Artifact”, dopodiché aggiungiamo BuildArtifact e IMAGE come ultime opzioni. Clicchiamo su “Next”, facciamo la review e clicchiamo infine su “Create pipeline”.
Ci siamo quasi: per completare la nostra pipeline abbiamo bisogno di aggiungere una task definition e un appspec.yml alla nostra applicazione.
Creiamo dunque un nuovo file appspec.yml nella root del progetto e aggiungiamo al suo interno questo codice:
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: "<TASK_DEFINITION>"
LoadBalancerInfo:
ContainerName: "YOUR_ECS_CLUSTER_NAME"
ContainerPort: 3000
Passiamo ora al file task definition.
Per questo useremo un trucco per semplificarne la creazione. Come ricorderete, abbiamo già creato una task definition in fase di preparazione dei prerequisiti all’inizio di questo articolo. Andiamo a recuperare quest'ultima da AWS e clicchiamo su “Edit”. Arriveremo all’editor JSON. Copiamo il testo e incolliamolo in un nuovo file taskdef.json all'interno della root del nostro progetto. Fatto ciò, andiamo a modificare così le seguenti righe:3."image": "<IMAGE>" (rimuovere URL e mettere <Image>)
"image": "<IMAGE>" "taskDefinitionArn": "<TASK_DEFINITION>"
Carichiamo la nuova versione del codice sul nostro repo.

Testiamo l’applicazione prima di promuoverla a Produzione
Per verificare che tutto funzioni correttamente, apportiamo una piccola modifica al testo contenuto nella root principale dell’applicazione, facciamo un commit e aspettiamo che la Pipeline esaurisca tutti i task. A questo punto, verifichiamo che solo l’URL sulla porta 8090 riporti la modifica di test. Sulla porta 8080 dovremmo invece continuare a vedere la vecchia versione dell’infrastruttura. Dopo 5-6 minuti (il tempo impostato per il routing del traffico), anche l’ambiente di produzione dovrebbe aggiornarsi mostrando la versione corretta.
La nostra Pipeline funziona!

Bonus 1: automatizzare i test sull’infrastruttura green con AWS Lambda
Nella fase di rilascio è possibile associare una o più Lambda function col compito di verificare il buon funzionamento della nostra applicazione prima di promuovere la nuova versione in un ambiente di produzione. Questo procedimento lo si svolge durante la configurazione del Deployment lifecycle hooks. Occorrerà solo aggiungere un Lambda hook a AfterAllowTraffic.
Seguite queste guide per un esempio di configurazione:
Uno dei prerequisiti necessari consisteva nella creazione di un cluster ECS e dei suoi componenti. Di tutta la configurazione, questo è sicuramente uno dei passaggi più complessi… ma che possiamo semplificare e addirittura rendere ripetibile! Come?
Creando un CloudFormation template!
Ecco un semplice snippet di esempio che vi aiuterà ad impostarlo:
LoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Ref ProjectName
LoadBalancerAttributes:
- Key: 'idle_timeout.timeout_seconds'
Value: '60'
- Key: 'routing.http2.enabled'
Value: 'true'
- Key: 'access_logs.s3.enabled'
Value: 'true'
- Key: 'access_logs.s3.prefix'
Value: loadbalancers
- Key: 'access_logs.s3.bucket'
Value: !Ref S3LogsBucketName
- Key: 'deletion_protection.enabled'
Value: 'true'
- Key: 'routing.http.drop_invalid_header_fields.enabled'
Value: 'true'
Scheme: internet-facing
SecurityGroups:
- !Ref LoadBalancerSecurityGroup
Subnets:
- !Ref SubnetPublicAId
- !Ref SubnetPublicBId
- !Ref SubnetPublicCId
Type: application
HttpListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- RedirectConfig:
Port: '443'
Protocol: HTTPS
StatusCode: 'HTTP_301'
Type: redirect
LoadBalancerArn: !Ref LoadBalancer
Port: 80
Protocol: HTTP
HttpsListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
Certificates:
- CertificateArn: !Ref LoadBalancerCertificateArn
DefaultActions:
- Type: forward
TargetGroupArn: !Ref TargetGroup
LoadBalancerArn: !Ref LoadBalancer
Port: 443
Protocol: HTTPS
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Ref ProjectName
HealthCheckIntervalSeconds: 30
HealthCheckPath: !Ref HealthCheckPath
HealthCheckProtocol: HTTP
HealthCheckPort: !Ref NginxContainerPort
HealthCheckTimeoutSeconds: 10
HealthyThresholdCount: 2
UnhealthyThresholdCount: 2
Matcher:
HttpCode: '200-299'
Port: 8080
Protocol: HTTP
TargetType: ip
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: '30'
VpcId: !Ref VpcId
Cluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: !Ref ProjectName
Service:
Type: AWS::ECS::Service
Properties:
Cluster: !Ref Cluster
DeploymentConfiguration:
MaximumPercent: 200
MinimumHealthyPercent: 100
DesiredCount: 3
HealthCheckGracePeriodSeconds: 60
LaunchType: FARGATE
LoadBalancers:
- ContainerName: ContainerOne
ContainerPort: !Ref ContainerPort
TargetGroupArn: !Ref TargetGroup
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: DISABLED
SecurityGroups:
- !Ref ContainerSecurityGroupId
Subnets:
- !Ref SubnetPrivateNatAId
- !Ref SubnetPrivateNatBId
- !Ref SubnetPrivateNatCId
ServiceName: !Ref ProjectName
TaskDefinition: !Ref TaskDefinition
DependsOn: HttpsListener
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Ref ProjectName
ContainerDefinitions:
- Cpu: 2048
Image: !Ref ContainerImageUri
Memory: 4096
MemoryReservation: 4096
PortMappings:
- ContainerPort: !Ref ContainerPort
Protocol: tcp
Name: ContainerOne
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: !Ref ContainerLogGroup
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: ContainerOne
Cpu: '2048'
Memory: '4096'
ExecutionRoleArn: !GetAtt ExecutionContainerRole.Arn
TaskRoleArn: !GetAtt TaskContainerRole.Arn
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
Questo snippet di codice è puramente informativo; dovrete adattare da voi la parte di gestione dei parametri facendo riferimento alle specifiche del vostro progetto. In caso di necessità si può fare riferimento a questi due link:
In questo articolo abbiamo visto come creare una Pipeline completamente automatica di Deploy in modalità Blue/Green di un servizio su ECS.
Abbiamo anche capito come le Lambda function possono essere utilizzate per automatizzare le fasi di test sull’infrastruttura “green”.
Per completezza del tutorial, vi abbiamo anche mostrato come utilizzare un AWS CloudFormation template per semplificare la creazione di una infrastruttura standard complessa minimizzando i tempi e rendendola facilmente replicabile.
Il nostro intento era creare una traccia per far comprendere utilizzi e potenzialità di questa modalità automatica di rilascio del software lasciando comunque spazio per tutte le personalizzazioni necessarie a seconda dello specifico caso in cui si andrà ad applicarla.
Cosa ne pensate? Avete realizzato particolari configurazioni per le vostre Pipeline?
Siamo curiosi di scoprirlo!
Per oggi è tutto. #Proud2beCloud vi dà appuntamento come sempre a tra 14 giorni!