Come creare una pipeline CI/CD robusta con GitHub Actions

  • GitHub Actions consente di creare pipeline CI/CD complete con flussi di lavoro YAML, integrando test, build e distribuzione nello stesso repository.
  • Le pipeline moderne combinano rapida integrazione continua, distribuzione automatizzata e best practice quali il riutilizzo del flusso di lavoro e la gestione sicura dei segreti.
  • È possibile orchestrare pipeline complesse per backend, frontend e microservizi, distribuendo su Kubernetes, GAE, Cloud Functions o PaaS esterni.
  • Osservabilità, sicurezza (scansione del codice e delle dipendenze) e notifiche sono componenti chiave affinché una pipeline CI/CD sia affidabile in produzione.

Pipeline CI/CD con GitHub Actions

Creare una buona pipeline CI/CD con GitHub Actions Non è più un "per quando c'è tempo": nei team moderni, è praticamente un requisito per un deployment rapido e affidabile. Ciononostante, trovare un esempio completo, generico e ben congegnato che possa essere adattato alla propria azienda è spesso molto più complicato di quanto sembri.

Nelle righe seguenti mescoleremo la teoria classica di CI/CD con esempi di implementazione nel mondo reale utilizzando GitHub Actions, pipeline riutilizzabili, attività, script bash, Moduli PnP di PowerShellImplementazioni su Kubernetes, Google Cloud e Kinsta, insieme alle best practice per sicurezza, monitoraggio e scalabilità. L'idea è che sia possibile prendere questi elementi, adattarli al proprio contesto ed evitare molte delle tipiche insidie.

Perché hai bisogno di una pipeline CI/CD ben costruita

Nello sviluppo professionale attuale, CI/CD è il sistema circolatorio del codiceIntegra modifiche, esegue test, crea artefatti e distribuisce nuove versioni con un intervento minimo. Senza questo flusso di lavoro, ogni distribuzione diventa un calvario manuale lento e soggetto a errori.

L'integrazione continua (CI) si concentra sulla convalida delle modifiche Non appena vengono caricati nel repository, vengono eseguiti test unitari, linter e analisi statiche per individuare i bug il più rapidamente possibile. Prima si riceve un feedback, prima si possono correggere i bug e meno dolorosa sarà qualsiasi regressione.

Continuous Deployment (CD nel senso di Continuous Deployment) o Continuous Delivery (a seconda del livello di automazione) aggiunge l'automazione della parte di rilascio: creazione di immagini, pubblicazione di pacchetti, distribuzione in ambienti di test, staging o produzione e persino modifica del traffico utilizzando strategie blue-green o canary.

Nelle aziende con molto codice legacyUna buona pipeline è una delle leve migliori per modernizzare l'ecosistema: consente di introdurre test nei servizi legacy, automatizzare attività che in precedenza venivano eseguite manualmente e ridurre i costi di manutenzione di infrastrutture come Jenkins o Nexus che sono diventate obsolete.

Che cosa sono GitHub Actions e perché si adattano così bene a CI/CD?

GitHub Actions è la piattaforma di automazione integrata in GitHub. Permette di definire flussi di lavoro in file YAML all'interno del repository stesso. Con esso, è possibile compilare, testare, analizzare e distribuire il software senza dover configurare server CI esterni.

Un flusso di lavoro è un insieme di lavori e passaggi che viene innescato da eventi come push, pull_request, schedule (CRON), workflow_dispatch (manuale) o anche azioni su problemi. Ogni lavoro viene eseguito in un runner (ad esempio, ubuntu-latest) e consiste in passaggi che utilizzano azioni o comandi riutilizzabili run.

GitHub offre un enorme mercato per le azioni dove hai integrazioni pronte all'uso per quasi tutto: Docker, Kubernetes, AWS, Azure, Google Cloud, SonarCloud, Slack, Jira, analisi di sicurezza, linter per mille linguaggi, ecc. Ciò riduce notevolmente il tempo necessario per impostare pipeline avanzate.

Rispetto a soluzioni come Jenkins o ConcourseGitHub Actions offre diversi vantaggi evidenti: è un servizio gestito (non è necessario gestire server), è strettamente legato al codice, utilizza un modello di pagamento a consumo ed è supportato da una community molto ampia. Inoltre, molti sviluppatori lo utilizzano già da progetti personali, il che riduce significativamente la curva di apprendimento.

Componenti di base di un flusso di lavoro di GitHub Actions

Tutto inizia con un file YAML in .github/workflows/, per esempio ci.yml o build-test-deploy.ymlSebbene la sintassi possa crescere considerevolmente, la struttura di base è relativamente semplice.

Le sezioni chiave di YAML sono: name (nome del flusso di lavoro), on (eventi che lo innescano), jobs (insieme di compiti logici), e all'interno di ogni lavoro, runs-on (corridore), steps (passi), env (variabili globali) e if (condizioni per l'esecuzione di passaggi o lavori).

I lavori rappresentano blocchi di lavoro che può essere eseguito in parallelo o in una catena utilizzando needsAll'interno di ogni lavoro, i passaggi utilizzano azioni (uses:) o comandi (run:Un esempio tipico include: checkout del codice, installazione delle dipendenze, esecuzione del linter, test e build.

Segreti e variabili d'ambiente Sono gestiti a livello di repository, organizzazione o ambiente. Nei flussi di lavoro, sono referenziati con ${{ secrets.MI_SECRET }} e consentono di lavorare con chiavi API, token di distribuzione o credenziali cloud senza esporli nel repository.

YAML consente anche di creare array di esecuzione con strategy.matrix, molto utile per testare il tuo codice su diverse versioni di Node, Python o Java, o anche su diversi sistemi operativi senza dover scrivere lo stesso blocco più volte.

Progettare una pipeline CI/CD moderna utilizzando le migliori pratiche

Una pipeline sana è solitamente divisa in fasi chiare: controlli rapidi (lint, test unitari), creazione di artefatti, rilascio (versione, etichettatura, changelog, pubblicazione nel repository degli artefatti) e distribuzione in uno o più ambienti.

La fase di integrazione continua dovrebbe essere la più rapida possibile. Ciò garantisce che qualsiasi richiesta push o pull riceva un feedback pressoché immediato. Una pratica comune è quella di eseguire i vari controlli in parallelo utilizzando array o job separati, ipotizzando un costo leggermente più elevato in cambio della riduzione del tempo di attesa complessivo.

Per disaccoppiare la pipeline dal linguaggio concretoPuoi usare uno strumento di attività come Task (simile a Make ma con una sintassi più intuitiva). In questo modo, il flusso di lavoro di GitHub Actions richiama solo attività generiche (task test, task lintecc.) e ogni repository definisce come vengono implementati internamente a seconda che si tratti di Node, Java, Python, ecc.

Nella fase di rilascio entrano in gioco il controllo delle versioni e gli artefatti.Qui puoi creare un'immagine Docker, un file jar/war, un pacchetto npm o qualsiasi altro artefatto, caricarlo nel registro corrispondente (registro Docker, Maven, npm nel registro degli artefatti, ecc.), taggare i commit e generare rilasci GitHub o changelog con strumenti come git-cliff o azioni di rilascio.

Infine, la fase di distribuzione Sposta quell'artefatto nell'ambiente di runtime: Kubernetes (GKE), Google App Engine, Cloud Functions, servizi su Kinsta, i tuoi server tramite SSH, ecc. Qui puoi concatenare i passaggi successivi, come i test funzionali dopo la distribuzione o le notifiche Slack con i dettagli della versione.

Esempio: pipeline completa con ESLint, test e distribuzione su Kinsta

Un esempio molto illustrativo è l'utilizzo di GitHub Actions Per convalidare un'applicazione React con ESLint e test unitari, e poi distribuirla su Kinsta utilizzando la sua API, tutto è orchestrato in un unico flusso di lavoro CI/CD.

La prima parte dello YAML definisce il trigger e il nome della pipeline. Ad esempio, che venga eseguito su ogni push y pull_request alla filiale maine persino programmati con processi CRON (ad esempio, ogni giorno a mezzanotte o ogni lunedì alle 8:00 UTC) utilizzando l'evento schedule.

Il primo lavoro in cantiere può essere chiamato eslint ed è responsabile del controllo della sintassi del codice. Viene eseguito in ubuntu-latest e utilizza una serie di versioni di Node (ad esempio, 18.x, 20.x), con passaggi per verificare e configurare Node con actions/setup-node, cache dipendenze npm, installazione con npm ci e lanciare npm run lint.

Il secondo lavoro, testsDipende da eslint attraverso needs: eslintquindi viene eseguito solo se il controllo della sintassi ha esito positivo. All'interno, lo schema viene ripetuto: checkout, installazione delle dipendenze ed esecuzione di npm run test su una versione specifica di Node.

Il terzo lavoro, deployÈ incatenato dopo entrambi i lavori utilizzando needs: e utilizza un passaggio con curl per chiamare l'API di Kinsta. Per fare ciò, la chiave API e l'ID dell'applicazione vengono configurati come segreti in GitHub (KINSTA_API_KEY y APP_ID) e sono esposti nel lavoro tramite env per creare la richiesta POST che attiva la distribuzione.

È importante capire che questo lavoro di distribuzione Kinsta considera la semplice accettazione dell'API un successo; tuttavia, se la distribuzione dovesse successivamente fallire internamente a Kinsta, il flusso di lavoro di GitHub potrebbe comunque mostrare uno stato verde. È opportuno tenere presente questo aspetto per evitare l'autocompiacimento e integrare il processo con il monitoraggio post-distribuzione.

Gestione cron avanzata e pianificazione del flusso di lavoro

La sintassi CRON in GitHub Actions Si basa sul formato UNIX a cinque campi: minuto, ora, giorno del mese, mese e giorno della settimana. Ogni campo può utilizzare asterischi, intervalli, elenchi e passaggi (*, 1-5, 1,15,30, */5), che consente di programmare attività di manutenzione, backup, pulizie o controlli periodici.

Ad esempio, 0 0 * * * eseguire il flusso di lavoro ogni mezzanotte (UTC), mentre 0 8 * * 1 Lo fa ogni lunedì alle 8:00. Questo si combina perfettamente con i soliti trigger di push y pull_requestin modo che lo stesso YAML possa reagire sia alle modifiche del codice sia alle esecuzioni pianificate.

Questa capacità è ideale per le attività che non hanno senso rilasciare a ogni commit: scansioni di sicurezza intensive (ad esempio, con OWASP Dependency Check in Java), audit delle dipendenze, controlli di copertura dei test o pulizia di vecchi artefatti nel registro.

Riutilizzo del flusso di lavoro: scalabilità di CI/CD su centinaia di repository

Quando la tua organizzazione ha decine o centinaia di repositoryCopiare e incollare lo stesso YAML ovunque è la ricetta per il caos. Qualsiasi modifica richiederebbe di modificare metà di GitHub Enterprise, rendendo quasi impossibile mantenere coerenza e best practice.

La soluzione sta nella progettazione di flussi di lavoro riutilizzabili centralizzati in un repository "template" CI/CD. Questi flussi di lavoro espongono input e output e ogni servizio definisce solo un piccolo YAML che li richiama, passando parametri come il tipo di artefatto (Docker, libreria Java, pacchetto npm), il runtime di distribuzione (GKE, GAE, Cloud Function, ecc.) o gli elementi Task che devono essere eseguiti.

Un modello comune è quello di separare tre grandi flussi di lavoro riutilizzabili: uno di build-check-task (integrazione continua), un altro di build-release-dockerfile o altri artefatti e una terza distribuzione (deploy-gke, deploy-gaeecc.), in modo che ogni repository costruisca la propria pipeline combinandoli.

Per incapsulare la logica condivisa, è possibile definire anche azioni personalizzate. en .github/actionsAd esempio, per configurare Gradle, Java, Node o Task, per ottenere metadati di build, per pubblicare immagini Docker, per taggare le versioni in Git con uno script bash o per inviare notifiche a Slack. La regola d'oro è che i repository di servizi dovrebbero utilizzare solo flussi di lavoro riutilizzabili, non direttamente queste azioni, in modo che la compatibilità con le versioni precedenti sia più gestibile.

Integrazione continua e rapida con attività, matrici e analisi statica

Durante la fase di compilazione o di controllo, è consigliabile attivare più cose in parallelo.Test unitari, analisi statica (PMD, Checkstyle, SpotBugs in Java; ESLint in JS/TS), scansione con SonarCloud, ecc. In questo modo il tempo totale della pipeline rimane ragionevole anche in basi di codice di grandi dimensioni.

Task (Taskfile.yml) funge da livello di astrazione su comandi specifici, consentendo al flusso di lavoro CI di chiamare semplicemente task check, task test o task lintPer un progetto Java, queste attività possono essere delegate a Gradle con JUnit, PMD, Checkstyle e SpotBugs; per un progetto Node, a Jest, ESLint e strumenti di sicurezza come npm audit o simili.

GitHub Actions aggiunge il pezzo array Per eseguire le stesse attività su diverse versioni del runtime: ad esempio, testare una libreria Node su 16, 18 e 20, o un progetto Python su 3.10 e 3.12. È semplice come dichiarare un array di versioni e utilizzarlo nella configurazione del job.

Questo approccio è particolarmente utile nelle organizzazioni che desiderano supportare più stack. (Java, Node, TypeScript, Python, ecc.) senza dover riscrivere la logica della pipeline per ogni repository: l'attività si adatta a ciascun linguaggio e i flussi di lavoro riutilizzabili rimangono praticamente gli stessi.

Fase di rilascio: controllo delle versioni, tagging e pubblicazione degli artefatti

Una volta superati i controlli, è il momento di costruire l'artefatto che verrà effettivamente distribuito.Immagine Docker, file JAR, pacchetto npm, qualsiasi cosa sia appropriata. Questo include sia gli strumenti linguistici che i registri e la politica di versioning dell'organizzazione.

Alcuni progetti Java utilizzano plugin come Gradle Axion. Per gestire le versioni in base ai tag Git. In contesti misti (Java, Node, ecc.) potrebbe essere più semplice utilizzare uno script bash personalizzato che calcoli la versione successiva (ad esempio utilizzando SemVer), crei il tag, lo invii al server remoto e generi la release corrispondente.

Strumenti come git-cliff Aiutano a generare i changelog In base ai messaggi di commit, le modifiche vengono classificate per tipologia (funzionalità, correzione, interruzione, ecc.). Integrandole nella pipeline, si garantisce che ogni release venga fornita con un changelog chiaro, senza che nessuno debba scriverlo manualmente.

Per pubblicare gli artefatti, vengono combinate azioni e credenziali appropriate.Registri Docker (Docker Hub, GitHub Container Registry, Artifact Registry), repository Maven, registri npm, ecc. Anche in questo caso, le credenziali vengono archiviate come segreti e inserite nei processi solo quando necessario.

Distribuzione continua su Kubernetes, GCP, Kinsta e altri ambienti

La distribuzione è il punto in cui CI/CD interagisce con l'infrastrutturaQui, GitHub Actions si integra perfettamente con quasi tutte le piattaforme: Kubernetes, App Engine, Cloud Functions, server tradizionali, piattaforme come Kinsta, ecc.

Per Kubernetes (ad esempio in GKE) il modello usuale Si tratta di: autenticare con Google Cloud (utilizzando azioni ufficiali), configurare kubectl All'interno del contesto del cluster, applicare i manifesti o i grafici Helm e, se necessario, eseguire un rollout controllato (ad esempio, con canary o blu-verde) e verificare lo stato con i comandi da kubectl rollout status.

Nel caso di App Engine o Cloud FunctionsLa pipeline crea l'immagine o l'artefatto, lo pubblica nell'Artifact Registry e quindi richiama i comandi di distribuzione. gcloud appropriato, utilizzando ancora una volta credenziali gestite come segreti e runner effimeri.

Quando la distribuzione viene eseguita su API esterne come quella di Kinstadi solito basta un passo curl oppure un'azione specializzata che invia la richiesta con il token di autenticazione e i parametri necessari (ID app, branch, ecc.). Il processo è considerato riuscito se l'API risponde correttamente alla richiesta di nuova versione.

L'implementazione è quasi sempre accompagnata da una notifica. a Slack, Teams o altri strumenti di comunicazione, indicando quale servizio è stato distribuito, in quale ambiente, con quale versione, chi lo ha attivato e link ai log del flusso di lavoro. In produzione, questo serve anche per l'audit e la tracciabilità.

Controllo qualità: sicurezza, monitoraggio e registri

L'automazione della build e della distribuzione è ottima, ma senza visibilità Per quanto riguarda ciò che accade, la pipeline può trasformarsi in una scatola nera. GitHub Actions offre log dettagliati per esecuzione, per job e per fase, consentendo di diagnosticare errori di compilazione, test o distribuzione.

Per esigenze più avanzate, vengono integrati servizi di osservabilità esterna. come Datadog, New Relic o Splunk, che raccolgono parametri sui flussi di lavoro, tempi di esecuzione, tassi di errore, ecc., aiutando a rilevare i colli di bottiglia e a dare priorità alle ottimizzazioni della pipeline.

Parallelamente, la sicurezza gioca un ruolo chiave: gestione dei segreti crittografati, criteri di accesso minimi necessari, revisione delle autorizzazioni di azione, integrazione di scanner di vulnerabilità del codice e dipendenze (scansione del codice, scansione dei segreti, OWASP, ecc.) all'interno dei flussi di lavoro stessi.

Molti team aggiungono anche test post-distribuzione nell'ambiente appena aggiornato: test funzionali end-to-end, controlli delle prestazioni, test di base e, in caso di problemi, meccanismi di rollback automatizzati che ripristinano la versione stabile precedente.

Governance del flusso di lavoro: rami protetti e richieste pull

Il modo di lavorare con rami e richieste pull deve essere allineato con CI/CD in modo che tutto abbia senso. La cosa più comune è proteggere il ramo principale (main o master) e richiedono che ogni modifica venga sottoposta a PR e superi i controlli della pipeline.

GitHub consente di definire regole di protezione dei rami Queste policy impongono l'uso di pull request, bloccano i commit diretti e richiedono che determinati controlli di stato (flussi di lavoro specifici di Azione) siano verdi prima di consentire l'unione. Potrebbero anche richiedere revisioni minime, regole di approvazione, ecc.

Questo modello garantisce che il codice che raggiunge la produzione Ha superato la revisione umana e tutti i controlli automatizzati della pipeline, riducendo drasticamente il rischio di incorrere in gravi errori o vulnerabilità.

Nelle aziende con più ambienti (sviluppo, staging, produzione) la distribuzione in produzione è solitamente riservata alle unioni nel ramo principale, mentre altri rami possono attivare distribuzioni in ambienti precedenti per test interni o demo.

Considerando il quadro generale, una pipeline CI/CD ben progettata con GitHub Actions Diventa la spina dorsale dello sviluppo: integrazione delle modifiche, esecuzione di suite di test complete, creazione e pubblicazione di artefatti, distribuzione su più piattaforme cloud, monitoraggio con strumenti di osservabilità e governance tramite chiare regole di branching e pull request. Grazie a flussi di lavoro riutilizzabili, azioni personalizzate, strumenti ausiliari come Task, Rease Action e Git Cliff, e a una solida gestione di segreti e permessi, è possibile supportare qualsiasi cosa, dalle semplici app Python alle complesse architetture Kubernetes, mantenendo velocità di distribuzione, qualità del codice e sicurezza senza sovraccaricare il team con attività manuali.

azzurro
Articolo correlato:
Guida pratica per gli incidenti cloud in Microsoft Azure e Microsoft 365