Skip to content

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 .env in 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 sua deploy-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:

VariabileCosa
ECR_REPOSITORYil 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
PIPELINEil catalogo del deploy, che ETL_PIPELINE_WORKSPACES deve assegnare per intero
ENV_OBBLIGATORIE, ENV_VIETATEvariabili che il .env deve avere, o non deve avere
SUBMODULEsubmodule che l'immagine copia, verificati al commit registrato
AWS_ACCOUNT_ATTESO, PIATTAFORMEl'account su cui fermarsi se si è sbagliato profilo, e le architetture
CLIENTE_NOMEil nome leggibile nell'intestazione del deploy
CLIENTE_SLUGlo 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:

  1. AWS: account atteso, repository ECR e servizio ECS esistenti. Non li crea: vanno creati prima (sotto, Un cliente su ECS).
  2. Git: in production si deploya da main, in staging da staging, a working tree pulito e con HEAD uguale a origin/<branch>. L'immagine è sempre un commit pubblicato.
  3. Submodule dichiarati dal cliente, al commit che HEAD registra e senza modifiche.
  4. .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 in s3://<bucket>/.env. Senza, controlla quello già nel bucket. I controlli: NOEVA_API_BASE_URL, NOEVA_API_KEY e il workspace; l'URL di Noeva non locale; le obbligatorie e le vietate del cliente; ETL_PIPELINE_WORKSPACES che assegna tutte e sole le pipeline del cliente (ADR-0049); niente export né virgolette, che gli environmentFiles di ECS non tolgono.
  5. Gate: etl ci, lo stesso della CI del repo (--skip-check lo salta, sconsigliato).
  6. Build e push con esattamente due tag: quello di deploy (latest in produzione, staging in staging) e quello di rilascio AAAAMMGG-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.
  7. Tag git deploy/<cliente>/<ambiente>/<tag di rilascio>, pushato.
  8. 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:

  1. il .env del cliente, preparato da .env.example con ETL_PIPELINE_WORKSPACES sulle sue pipeline, va nel bucket al primo deploy (--env);
  2. pnpm exec etl deploy-ecr <cliente> --dry-run deve chiudere verde;
  3. 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.

Noeva è un marchio registrato di 4D S.R.L.