CI/CD Con GitLab

Per parlare di CI/CD, riteniamo necessario introdurre un concetto più ampio che, per ovvi motivi di spazio e di coerenza, non possiamo approfondire oltre a un breve paragrafo. Il concetto che dobbiamo accennare è DevOps. Vediamo che cosa si intende con DevOps.

DevOps: le basi

Il termine “DevOps” combina “Development” (sviluppo) e “Operations” (operazioni). DevOps include molti aspetti e pratiche per la gestione dei processi di produzione del software. Tecnicamente, DevOps indica una serie di approcci e di valori, più che una lista di pratiche concrete da utilizzare. DevOps dev’essere declinato in un contesto concreto, nei vari team di produzione e gestione del software.

Come riportato qui da RedHat:

DevOps descrive gli approcci da adottare per accelerare i processi che consentono a un’idea (come una nuova caratteristica software, una richiesta di miglioramento o un bug fix) di passare dallo sviluppo al deployment in un ambiente di produzione, dove può fornire valore all’utente. Tali approcci richiedono una comunicazione frequente fra i team operativi e di sviluppo.

In questo contesto, il CI/CD è un approccio che fa parte del DevOps.

Cosa significa CI/CD

Il CI/CD (Continuous Integration/Continuous Delivery – Deployment) è un approccio di sviluppo del software che prevede di eseguire build, test e delivery del codice sviluppato in maniera continuativa e automatica. Il CI/CD permette l’integrazione automatica delle modifiche integrate nel codice sorgente mediante l’utilizzo di pipeline, ossia di automatizzazioni di operazioni di build, test e deployment del software.

Come indica qui RedHat:
L’approccio CI/CD è una componente fondamentale della metodologia DevOps, il cui scopo è incrementare la collaborazione tra i team di sviluppo e quelli operativi. Entrambi i metodi sono incentrati sull’automazione dei processi di integrazione del codice, accelerando così il passaggio di un’idea (come una nuova funzione, la correzione di un bug o una richiesta di potenziamento) dalla fase di sviluppo a quella di deployment, in un ambiente di produzione in cui può offrire un valore aggiunto all’utente.

Il CI/CD prevede degli step che integrano le operazioni da effettuare per verificare, compilare e integrare nuovi sviluppi in un software; il workflow del team si adatta all’utilizzo delle pipeline per ottimizzare la propria produttività e minimizzare i tempi e gli errori in fase di rilascio del software.
Vediamo alcuni dei classici step che si integrano in una pipeline di CI/CD, tenendo presente che non sono gli unici possibili e che la pipeline deve adattarsi alle esigenze del software sviluppato e del team che lo lavora.

Build

La build è lo step che dal codice sorgente dell’applicativo crea qualche tipo di eseguibile.
Un tipo oggi spesso usato di software è detto dockerizzato. Docker si basa su alcune feature del kernel Linux per permettere l’esecuzione di uno stesso software e delle sue dipendenze in ambienti diversi. La build del software genera spesso immagini Docker, che sono il software che verrà poi eseguito da Docker; esse hanno il vantaggio di essere portabili, self-contained e facili da replicare.

Test

La fase di test nelle pipeline di CI/CD è fondamentale: essa garantisce infatti che il codice integrato nell’applicativo non contenga regressioni o bug facilmente individuabili. Solitamente si esegue la fase di test sul sorgente che viene creato dalla fase di build o si premette la fase di test alla build stessa, a seconda delle caratteristiche del software stesso.
La pipeline prevede solitamente che, se i test falliscono, il rilascio venga bloccato; altrimenti, la pipeline continua alla fase successiva.

Versioning

Il versionamento del software prevede il salvataggio di uno storico delle modifiche effettuate sul software. Solitamente si usa qualche sistema simile a Git per versionare il codice sorgente; essendo Git distribuito, ci si affida spesso a qualche sistema centralizzato come GitHub o GitLab per mantenere una versione ufficiale del progetto.

Inoltre, il versionamento prevede l’assegnazione di una versione, solitamente rappresentata in qualche forma alfanumerica, al codice sorgente e agli eseguibili generati a partire da esso. È possibile integrare la fase di versionamento in modo automatico in una pipeline.

La versione del software è spesso assegnata usando il Semantic Versioning, che ha la forma M.m.p, dove M è detta major version, m è la minor version e p è la patch.

Pipeline

Una pipeline, come premesso sopra, è una procedura che automatizza il processo di CI/CD e con esso il rilascio del software a partire dal nuovo codice sorgente.
L’obiettivo della pipeline è rendere gli sviluppatori in grado di effettuare in modo automatico rilasci, alleggerendo il carico di lavoro sui team Ops e Sistemistico.

Pipeline in GitLab

GitLab è una piattaforma DevOps che permette di integrare facilmente delle pipeline nei progetti. GitLab integra il versionamento del codice sorgente mediante Git, il salvataggio di questo su server centralizzati che garantiscono versioni condivise e ufficiali, strumenti e pipeline di CI/CD e pratiche di controllo e sicurezza del software.

Includere una pipeline in un progetto GitLab è molto semplice: basta creare un file chiamato .gitlab-ci-yml e posizionarlo nella root directory del progetto.
Il file .gitlab-ci.yml descrive la pipeline in termini di stage, job e i vari script corrispondenti.

Lo stage è l’implementazione delle fasi come sono state presentate sopra: build, generazione di versione, test o altre; un job è un task concreto che esegue una parte dello stage. Uno stage si può comporre di diversi job, che vengono eseguiti in parallelo; i diversi stage, invece, vengono portati avanti sequenzialmente. Infine, lo script implementa i comandi specifici relativi a ciascun job; quest’ultimo, oltre allo script, include indicazioni relative all’ambiente e alle modalità di esecuzione.

Gli stage della pipeline ed i relativi script sono eseguiti di default da runner GitLab (processi che mettono a disposizione un ambiente in cui eseguire i comandi previsti dallo script). Ovviamente, è possibile configurare runner on-premises. A tal fine, si rimanda alla documentazione di GitLab.

Un esempio di pipeline che ha unicamente lo scopo di introdurre alla sintassi del file è:

stages:
- build
- test
- deploy

build_job:
stage: build
script:
- echo "Building"

version_job:
stage: build
script:
- echo "Versioning"

test_job:
stage: test
script:
- echo "Testing"

deploy_job:
stage: deploy
script:
- echo "Deploying"

Il file della pipeline indica tre stage, nel nostro caso: build, test e deploy. Si possono inserire un numero arbitrario di stage con il nome desiderato.
Ogni sezione descrive poi dei job. Il job deploy_job, ad esempio, viene eseguito nello stage deploy e consiste nello script echo “Deploying” (che stampa in standard output la stringa Deploying).
È possibile decidere di eseguire il job deploy solamente per un certo branch del codice versionato. A tal fine, la configurazione dovrà essere:

deploy:
only:
- master
stage: deploy
script:
- echo "Deploying"

Un Esempio

Un esempio di pipeline che versiona il codice quando si effettua una merge request nel seguente file .gitlab-ci.yml.

stages:
- build
- test
- deploy

build:
only:
– master
stage: build
script:
– echo “Building”

version:
stage: build
before_script:
– git config user.email “${GITLAB_USER_EMAIL}”
– git config user.name “${GITLAB_USER_NAME}”
– git remote add versioning-origin https://oauth2:${GITLAB_ACCESS_TOKEN}@gitlab.com/${CI_PROJECT_PATH}
script:
– echo “Versioning”
– git fetch –tags
– OLD_VERSION=`git describe –abbrev=0 –tags`
– NEW_VERSION=`bash change_version.sh $OLD_VERSION`
– git tag -a $NEW_VERSION -m “Auto-tagged”
– git push versioning-origin $NEW_VERSION
rules:
– if: $CI_MERGE_REQUEST_ID

test:
only:
– master
stage: test
script:
– echo “Testing”

deploy:
only:
– master
stage: deploy
script:
– echo “Deploying”

Come si può vedere, i comandi seguono la sintassi di Bash.
La sezione

rules:
- if: $CI_MERGE_REQUEST_ID

indica a GitLab di controllare la variabile d’ambiente predefinita CI_MERGE_REQUEST_ID, che specifica se la pipeline corrente è stata lanciata da una Merge Request.
In questa pipeline si richiama lo script change_version.sh che dovrebbe essere salvato nella repository del progetto per essere correttamente eseguito.