GitLab Pipeline: Gestire Dipendenze Job

Se stai costruendo pipeline CI/CD con GitLab, probabilmente ti sei chiesto come far eseguire i job in un ordine specifico. Magari vuoi che i test vengano eseguiti prima del build, o che il deploy in produzione avvenga solo dopo aver superato tutti i controlli di qualità.

In questa guida completa sulle dipendenze tra job GitLab CI/CD scoprirai tutto quello che c’è da sapere: cos’è la keyword needs, come funziona, quando usarla e come costruire pipeline complesse ma efficienti. Che tu stia gestendo un piccolo progetto o un’infrastruttura enterprise, questa guida ti aiuterà a ottimizzare i tempi di esecuzione delle tue pipeline.

Cos’è una Dipendenza tra Job in GitLab CI/CD

Immagina di dover preparare una cena: non puoi servire il piatto prima di averlo cucinato, e non puoi cuocerlo prima di aver comprato gli ingredienti. Esiste un ordine logico che deve essere rispettato.

Lo stesso vale per le pipeline CI/CD. Una dipendenza tra job è semplicemente una regola che dice: “questo job può partire solo quando quest’altro job è completato con successo”.

Il Problema delle Pipeline Sequenziali Tradizionali

Per impostazione predefinita, GitLab CI/CD organizza i job in stage (fasi) che vengono eseguiti in sequenza:

stages:
  - test
  - build
  - deploy

test_job:
  stage: test
  script:
    - npm test

build_job:
  stage: build
  script:
    - npm run build

deploy_job:
  stage: deploy
  script:
    - npm run deploy

Come funziona questo approccio:

  1. Tutti i job dello stage test vengono eseguiti in parallelo
  2. Solo quando tutti i job di test finiscono, parte lo stage build
  3. Solo quando tutti i job di build finiscono, parte lo stage deploy

Il problema: Se hai 10 job di test che durano 1 minuto ciascuno, ma uno solo dura 10 minuti, gli altri 9 job devono aspettare inutilmente prima che inizi lo stage successivo!

La Soluzione: La Keyword needs

La keyword needs ti permette di creare dipendenze esplicite tra job specifici, ignorando completamente gli stage. Questo significa:

  • Job indipendenti partono subito in parallelo
  • Job dipendenti partono appena i loro prerequisiti finiscono
  • Nessuna attesa inutile
  • Pipeline più veloci

Esempio pratico:

build_frontend:
  script:
    - npm run build

deploy_frontend:
  needs: [build_frontend]
  script:
    - deploy frontend

In questo caso, deploy_frontend parte immediatamente dopo che build_frontend finisce, senza aspettare altri job dello stesso stage!

Come Funziona la Keyword needs in GitLab CI/CD

Vediamo nel dettaglio come usare needs per creare dipendenze tra job.

Sintassi Base della Keyword needs

La sintassi più semplice è questa:

nome_job:
  needs: [job_prerequisito]
  script:
    - echo "Questo job parte dopo job_prerequisito"

Caratteristiche fondamentali:

  • needs accetta una lista di nomi di job
  • Il job parte solo quando tutti i job nella lista sono completati con successo
  • Se un job prerequisito fallisce, il job dipendente viene automaticamente cancellato
  • Puoi specificare più job nella lista

Esempio Pratico: Pipeline di Test e Build

Creiamo una pipeline realistica per un’applicazione web:

stages:
  - test
  - build
  - deploy

# Test unitari veloci (2 minuti)
unit_tests:
  stage: test
  script:
    - npm run test:unit

# Test di integrazione lenti (10 minuti)
integration_tests:
  stage: test
  script:
    - npm run test:integration

# Test end-to-end molto lenti (15 minuti)
e2e_tests:
  stage: test
  script:
    - npm run test:e2e

# Build del frontend - dipende solo dai test unitari
build_frontend:
  stage: build
  needs: [unit_tests]
  script:
    - npm run build:frontend
    - echo "Frontend pronto dopo soli 2 minuti!"

# Build del backend - dipende dai test unitari e di integrazione
build_backend:
  stage: build
  needs: [unit_tests, integration_tests]
  script:
    - npm run build:backend
    - echo "Backend pronto dopo 10 minuti"

# Deploy - dipende da entrambi i build
deploy_production:
  stage: deploy
  needs: [build_frontend, build_backend]
  script:
    - deploy production

Vantaggi di questa configurazione:

  • Il frontend può essere buildato dopo soli 2 minuti (non aspetta i test E2E)
  • Il backend parte dopo 10 minuti (non aspetta i test E2E)
  • Il deploy parte appena entrambi i build finiscono
  • I test E2E continuano in parallelo per validazione completa

Senza needs: La pipeline durerebbe almeno 15 minuti (aspettando tutti i test)

Con needs: La pipeline può completare in ~12 minuti, risparmiando tempo prezioso!

Dipendenze Multiple: Quando un Job Dipende da Più Job

Puoi specificare più job come prerequisiti. Il job dipendente parte solo quando tutti i prerequisiti sono completati.

# Questi job sono indipendenti e partono in parallelo
lint_code:
  script:
    - npm run lint

security_scan:
  script:
    - npm audit

unit_tests:
  script:
    - npm test

# Questo job parte solo quando tutti e tre i precedenti finiscono
build_app:
  needs: [lint_code, security_scan, unit_tests]
  script:
    - npm run build
    - echo "Build avviato solo dopo validazione completa"

Rappresentazione visiva del flusso:

[lint_code]  ─┐
              │
[security]   ─┼──> [build_app] ──> [deploy]
              │
[unit_tests] ─┘

Tutti e tre i job di validazione vengono eseguiti in parallelo, e build_app parte non appena tutti e tre sono completati con successo.

needs vs stage: Differenze Fondamentali

Capire la differenza tra needs e stage è cruciale per costruire pipeline efficienti.

Come Funzionano gli Stage Tradizionali

Gli stage creano una dipendenza implicita: ogni stage attende il completamento di tutti i job dello stage precedente.

stages:
  - test
  - build

test_fast:     # Dura 1 minuto
  stage: test
  script: fast test

test_slow:     # Dura 20 minuti
  stage: test
  script: slow test

build:
  stage: build
  script: build app

Timeline di esecuzione:

Minuto 0:  [test_fast] [test_slow] ──┐
                                      │ (Aspetta 20 minuti)
Minuto 1:  [test_fast completato]   │
Minuto 20: [test_slow completato]   │
                                     ↓
Minuto 20: [build] inizia

Il job build deve aspettare 20 minuti anche se test_fast finisce dopo 1 minuto!

Come Funziona needs per Ottimizzare il Flusso

Con needs, puoi creare dipendenze esplicite ignorando gli stage:

stages:
  - test
  - build

test_fast:
  stage: test
  script: fast test

test_slow:
  stage: test
  script: slow test

build:
  stage: build
  needs: [test_fast]  # Dipende SOLO da test_fast
  script: build app

Timeline di esecuzione migliorata:

Minuto 0:  [test_fast] [test_slow] ──┐
                │                     │
Minuto 1:      └──> [build]          │ (Continua in parallelo)
                                      │
Minuto 20: [test_slow completato]   ─┘

Il job build parte dopo solo 1 minuto, risparmiando 19 minuti di attesa!

Tabella Comparativa: stage vs needs

Caratteristica stage needs
Dipendenza Implicita da tutti i job dello stage precedente Esplicita solo dai job specificati
Esecuzione parallela Limitata agli stage Massima tra job indipendenti
Flessibilità Bassa (struttura rigida) Alta (dipendenze granulari)
Tempo di esecuzione Più lungo (attese inutili) Ottimizzato (zero attese inutili)
Complessità configurazione Bassa (semplice) Media (più controllo)
Quando usarlo Pipeline semplici e lineari Pipeline complesse con ottimizzazione

needs con artifacts: Condividere File tra Job

Una delle funzionalità più potenti di needs è la possibilità di condividere file (artifacts) tra job dipendenti.

Come Funzionano gli Artifacts con needs

Sintassi completa:

job_che_usa_artifacts:
  needs:
    - job: job_che_genera_artifacts
      artifacts: true  # Scarica gli artifacts
  script:
    - ls artifacts/

Cosa succede:

  1. Il job job_che_genera_artifacts esegue e crea artifacts
  2. GitLab salva gli artifacts nello storage
  3. Il job job_che_usa_artifacts parte quando il prerequisito finisce
  4. GitLab scarica automaticamente gli artifacts prima di eseguire lo script
  5. I file sono disponibili nella working directory del job

Esempio Pratico: Build, Test, Deploy con Artifacts

# Compila l'applicazione e salva il risultato
compile:
  script:
    - gcc -o myapp main.c
  artifacts:
    paths:
      - myapp
    expire_in: 1 day

# Test che usa l'eseguibile compilato
test_executable:
  needs:
    - job: compile
      artifacts: true
  script:
    - ./myapp --test
    - echo "Test passed!"

# Package che crea un archivio dell'eseguibile
package:
  needs:
    - job: compile
      artifacts: true
    - test_executable  # Non serve artifacts da questo
  script:
    - tar -czf release.tar.gz myapp README.md
  artifacts:
    paths:
      - release.tar.gz

# Deploy che usa il package
deploy:
  needs:
    - job: package
      artifacts: true
  script:
    - scp release.tar.gz server:/releases/
    - ssh server "cd /releases && tar -xzf release.tar.gz"

Ottimizzare l’Uso degli Artifacts: artifacts: false

A volte non hai bisogno degli artifacts dal job prerequisito, ma vuoi comunque aspettare che finisca. Usa artifacts: false per risparmiare tempo e bandwidth:

test_unit:
  script:
    - npm test
  artifacts:
    paths:
      - coverage/  # Genera report di copertura

deploy:
  needs:
    - job: test_unit
      artifacts: false  # Non scarica i report di copertura
  script:
    - deploy to production

Quando usare artifacts: false:

  • Il job dipendente non ha bisogno dei file generati
  • Gli artifacts sono grandi e rallenterebbero il download
  • Vuoi solo aspettare il completamento del job prerequisito

Artifacts di Lunga Durata vs Temporanei

GitLab ti permette di controllare quanto tempo conservare gli artifacts:

build:
  script:
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 week  # Conserva per 1 settimana

build_release:
  script:
    - npm run build:production
  artifacts:
    paths:
      - release/
    expire_in: never  # Conserva per sempre

Best practice per expire_in:

  • Build temporanei: 1 hour o 1 day
  • Release candidate: 1 week o 1 month
  • Release ufficiali: never (o molti mesi)

Questo aiuta a risparmiare storage e mantenere il sistema pulito!

Gestione degli Errori e Fallback con needs

Cosa succede quando un job prerequisito fallisce? Vediamo come gestire questi scenari.

Comportamento Predefinito: Cancellazione Automatica

Quando un job specificato in needs fallisce, tutti i job dipendenti vengono automaticamente cancellati:

test:
  script:
    - npm test  # FALLISCE!

build:
  needs: [test]
  script:
    - npm run build  # NON VIENE ESEGUITO

deploy:
  needs: [build]
  script:
    - deploy  # NON VIENE ESEGUITO

Risultato: Se test fallisce, sia build che deploy vengono cancellati automaticamente. Questo previene deploy di codice non testato!

Keyword allow_failure: Continuare Anche con Errori

A volte vuoi che la pipeline continui anche se un job non critico fallisce:

test_unit:
  script:
    - npm run test:unit

test_experimental:
  script:
    - npm run test:experimental
  allow_failure: true  # Non blocca la pipeline se fallisce

build:
  needs:
    - test_unit  # Deve passare
    - test_experimental  # Può fallire
  script:
    - npm run build

Cosa succede:

  • Se test_unit fallisce → build viene cancellato
  • Se test_experimental fallisce → build continua normalmente
  • Se entrambi passano → build continua normalmente

Keyword when: Eseguire Job in Scenari Specifici

La keyword when controlla quando eseguire un job in base al risultato dei prerequisiti:

test:
  script:
    - npm test

# Eseguito solo se test ha successo (default)
deploy_success:
  needs: [test]
  when: on_success
  script:
    - deploy to production

# Eseguito solo se test fallisce
cleanup_failed:
  needs: [test]
  when: on_failure
  script:
    - send alert
    - cleanup resources

# Eseguito sempre, indipendentemente dal risultato
notify_team:
  needs: [test]
  when: always
  script:
    - send notification to Slack

Valori possibili per when:

  • on_success (default) - Esegui solo se i prerequisiti hanno successo
  • on_failure - Esegui solo se almeno un prerequisito fallisce
  • always - Esegui sempre, indipendentemente dal risultato
  • manual - Richiede approvazione manuale
  • delayed - Esegui dopo un ritardo specificato

Pattern: Retry Automatico su Fallimento

Combina retry con needs per gestire fallimenti temporanei:

deploy_to_server:
  needs: [build]
  script:
    - deploy to production
  retry:
    max: 3
    when:
      - runner_system_failure
      - stuck_or_timeout_failure

Questo job verrà ritentato fino a 3 volte se fallisce per problemi infrastrutturali!

Pattern: Rollback Automatico

Crea un job di rollback che parte solo se il deploy fallisce:

deploy:
  needs: [build]
  script:
    - deploy to production

rollback:
  needs: [deploy]
  when: on_failure
  script:
    - rollback to previous version
    - send alert

Se deploy fallisce, rollback parte automaticamente per ripristinare la versione precedente!

Se deploy fallisce, rollback parte automaticamente per ripristinare la versione precedente!

Link Utili e Risorse

Documentazione Ufficiale GitLab

GitLab CI/CD - Keyword needs:

Artifacts e Trasferimento Dati:

Pipeline Configuration:

Guide Correlate

Tutorial GitLab CI/CD su CloudOpsItalia:

Tool Utili per GitLab CI/CD

Validazione e Testing: