Tema
Runbook: un cliente su ECR + ECS
L'artefatto di un repo cliente è l'immagine su GHCR (deployment.md). Un cliente può scegliere invece ECR, con il suo servizio ECS e il suo
.envin un bucket S3 (ADR-0094 §1, ADR-0104). Lo strumento arriva col pacchetto:etl deploy-ecr, lanciato dalla radice del repo cliente, che porta la suadeploy-ecr.conf.
Build & pubblicazione (ECR)
Si deploya un cliente per volta, dalla radice del suo repo:
bash
pnpm exec etl deploy-ecr <cliente> [--ambiente production|staging] [--env <file>] [--attendi] [--dry-run]Lo script è il core del deploy, uguale per tutti: sta nel pacchetto, in deploy/deploy-ecr.sh, accanto al Dockerfile dell'artefatto. Ciò che è del cliente sta nella deploy-ecr.conf alla radice del suo repo, che dichiara soltanto:
| Variabile | Cosa |
|---|---|
ECR_REPOSITORY | il repository dell'immagine del cliente |
ECS_CLUSTER_<amb>, ECS_SERVICE_<amb>, ENV_BUCKET_<amb> | servizio e bucket del .env per ambiente; senza, il cliente pubblica solo l'immagine |
PIPELINE | il catalogo del deploy, che ETL_PIPELINE_WORKSPACES deve assegnare per intero |
ENV_OBBLIGATORIE, ENV_VIETATE | variabili che il .env deve avere, o non deve avere |
SUBMODULE | submodule che l'immagine copia, verificati al commit registrato |
AWS_ACCOUNT_ATTESO, PIATTAFORME | l'account su cui fermarsi se si è sbagliato profilo, e le architetture |
CLIENTE_NOME | il nome leggibile nell'intestazione del deploy |
CLIENTE_SLUG | lo slug del cliente: il deploy si ferma se <cliente> è un altro |
Il file è codice bash che lo script legge con source: solo assegnazioni, e niente segreti, che stanno nel .env del bucket. Un esempio:
bash
CLIENTE_NOME="Acme"
CLIENTE_SLUG="acme"
AWS_ACCOUNT_ATTESO="123456789012"
ECR_REPOSITORY="noeva-etl-acme"
PIATTAFORME="amd64"
ECS_CLUSTER_production="cluster-production"
ECS_SERVICE_production="noeva-etl-acme-production"
ENV_BUCKET_production="noeva-etl-acme-env-production"
PIPELINE=(acme-ordini)
ENV_OBBLIGATORIE=(ACME_ORDINI_DESTINATARI)Cosa fa, in ordine:
- AWS: account atteso, repository ECR e servizio ECS esistenti. Non li crea: vanno creati prima (sotto, Un cliente su ECS).
- Git: in
productionsi deploya damain, instagingdastaging, a working tree pulito e conHEADuguale aorigin/<branch>. L'immagine è sempre un commit pubblicato. - Submodule dichiarati dal cliente, al commit che
HEADregistra e senza modifiche. .env: con--env <file>lo controlla, mostra le chiavi aggiunte e tolte rispetto a quello nel bucket (mai i valori), chiede conferma e lo carica ins3://<bucket>/.env. Senza, controlla quello già nel bucket. I controlli:NOEVA_API_BASE_URL,NOEVA_API_KEYe il workspace; l'URL di Noeva non locale; le obbligatorie e le vietate del cliente;ETL_PIPELINE_WORKSPACESche assegna tutte e sole le pipeline del cliente (ADR-0049); nienteexportné virgolette, che glienvironmentFilesdi ECS non tolgono.- Gate:
etl ci, lo stesso della CI del repo (--skip-checklo salta, sconsigliato). - Build e push con esattamente due tag: quello di deploy (
latestin produzione,stagingin staging) e quello di rilascioAAAAMMGG-hhmmss-<sha7>. Il push del tag di deploy fa partire la CodePipeline del servizio, che aggiorna ECS. Un terzo tag romperebbe la sua CodeBuild, che ricava il tag di rilascio per esclusione: lo script lo verifica dopo il push. - Tag git
deploy/<cliente>/<ambiente>/<tag di rilascio>, pushato. - Con
--attendi, aspetta la fine della CodePipeline e un servizio stabile.
bash
pnpm exec etl deploy-ecr acme --dry-run # solo i controlli
pnpm exec etl deploy-ecr acme --env ~/segreti/acme.env --attendi
pnpm exec etl deploy-ecr acme --solo-env --env ~/segreti/acme.env # .env nuovo, stessa immagine
pnpm exec etl deploy-ecr acme --solo-ecr # solo ECR, servizio non ancora creato
pnpm exec etl deploy-ecr --help # tutte le opzioni--solo-ecr pubblica l'immagine senza guardare il servizio ECS e il suo bucket: serve per avere l'immagine su ECR prima che il servizio esista in Terraform. Salta il controllo del .env nel bucket; con --env <file> il file si controlla ma non si carica. Restano i controlli su git e submodule, il gate e i due tag: se la CodePipeline del servizio esiste già, il push del tag di deploy la fa partire comunque. Non si combina con --attendi né con --solo-env.
--solo-env carica il .env e riavvia il servizio (--force-new-deployment) senza build: serve per cambiare i destinatari di una mail o ruotare una chiave. Il .env da cui si parte non sta nel repo: è del cliente, e ne esiste uno per ambiente.
La piattaforma di default è quella del cliente (amd64 se non la dichiara), --platform la cambia. AWS_PROFILE e AWS_REGION (default default, eu-west-1) vengono dall'ambiente: il comando non carica il .env del repo.
Il gate comprende quello delle versioni di pipeline (ADR-0086), che confronta il checkout con origin/dev o origin/main: per questo lo script fa git fetch origin dev main prima del gate, e senza rete verso GitHub si ferma lì. Vedi versioni di pipeline.
Il Dockerfile
L'immagine si costruisce col Dockerfile del pacchetto (deploy/Dockerfile, lo stesso di image.yml). Un cliente che ha bisogno di altro nell'immagine, un interprete o un CA aziendale, mette il suo Dockerfile alla radice del repo e lo dichiara in unmanaged: lo script usa quello. Se l'immagine copia dei submodule, vanno dichiarati in SUBMODULE e scaricati prima del deploy (git submodule update --init <percorso>); lo script si ferma se mancano, sono a un altro commit o hanno modifiche locali.
Requisiti: AWS CLI v2, Docker con buildx, jq, git, bash, pnpm, e i submodule del cliente scaricati.
Un cliente su ECS
Il repository ECR si crea prima, con la AWS CLI (aws ecr create-repository, tag mutabili, scan on push, AES256). Bucket del .env, servizio e CodePipeline si creano in noeva-terraform, con lo stesso modulo ecs_service degli altri servizi. Un runner ETL non riceve traffico (prende i job dal poller su Noeva), quindi niente load balancer (health_check_path e hostnames a null, come noeva-worker-api), e un task solo: due serve prenderebbero gli stessi job due volte.
hcl
module "noeva_etl_<cliente>_production" {
source = "./modules/ecs_service"
providers = { aws = aws.production }
aws_region = var.aws_region
environment = var.environment_production
service_name = "noeva-etl-<cliente>-production"
service_port = 9010 # ETL_HTTP_PORT di `etl serve`
host_port = 0
service_cpu = 256
service_memory_reservation = 512
service_memory = 1024
desired_task_count = 1
launch_type = "EC2"
minimum_healthy_percent = 0 # mai due worker insieme durante un deploy
maximum_healthy_percent = 100
health_check_path = null # niente load balancer: il runner non riceve traffico
hostnames = null
create_env_bucket = true
env_bucket_versioning = true
env_bucket_name = "<org>-noeva-etl-<cliente>-env-production"
create_ecr_repository = false # il repository esiste già
ecr_repository_name = "<repository ECR del cliente>"
deployment_tag = "latest"
sns_topic_arn = var.sns_topic_arn_production
vpc_bastion_outputs = module.vpc_bastion
kms_outputs = module.kms
alb_outputs = module.alb
ecs_cluster_outputs = module.ecs_cluster_production
}I nomi devono coincidere con quelli della deploy-ecr.conf del cliente. service_name ha al massimo 37 caratteri: il modulo ne ricava il ruolo IAM <service_name>-eventbridge-start-pipeline, e un nome di ruolo ne ha al massimo 64. Per questo il servizio può chiamarsi diversamente dal suo repository ECR. Dopo l'apply:
- il
.envdel cliente, preparato da.env.exampleconETL_PIPELINE_WORKSPACESsulle sue pipeline, va nel bucket al primo deploy (--env); pnpm exec etl deploy-ecr <cliente> --dry-rundeve chiudere verde;- il primo deploy vero, con
--attendi, poi una run di prova da Noeva.
La release nel report
L'immagine porta ETL_RELEASE=git:<sha12>, il commit buildato (sempre un commit pubblicato e a working tree pulito), così il report registra quale release ha eseguito la run (campo release; la versione di pipeline sta in pipelineVersion, ADR-0086). Accanto alla release il report porta la versione del core (core) e il cliente (client): vedi report di run § Versione di pipeline e release.

