Blog

Pipeline Magento2

Lettura 7 minuti

Oggi vengo a raccontare la mia esperienza riguardo alla configurazione di una pipeline con Bitbucket per poter compilare asset statici e DI (Dependency injection) per poi eseguire il deploy sui webserver.

Pipeline e CI/CD

Di cosa stiamo parlando? Pipeline, Continuous Integration (CI), Continuous Delivery (CD) sono termini che avrete già sentito nominare nel mondo DevOps, ma anche da sviluppatori prima o poi ne avrete a che fare.

Con Pipeline si intende il sistema dove il codice sorgente di un’applicazione viene scaricato dal repository dove risiede e viene eseguita una serie di step o task. Questo viene fatto in un ambiente isolato (di solito utilizzando un container Docker) il più simile possibile a quello che ospiterà l’applicazione vera e propria.

Continuous Integration (CI) è il procedimento automatico che porta le modifiche da un repository (server di controllo versione) verso un ambiente comune, di solito l’ambiente di staging. Questa operazione viene eseguita anche più volte al giorno e spesso vi sono collegati dei test di funzionalità per validare il lavoro. Questo per prevenire un disallineamento e incompatibilità delle versioni su vari rami di sviluppo e delle eventuali librerie esterne.

Continuous Delivery (CD) invece è un procedura che compila il codice proveniente da un repository e attraverso vari step si occupa di distribuire l’applicazione fino all’utente finale o server web di produzione.

Magento 1

Utilizzando la versione di Magento 1.x sicuramente molti di voi avranno configurato una pipeline, con qualsiasi servizio di CI/CD come Bitbucket, Gitlab, Jenkins, Trevis o qualsiasi altro servizio senza grossi problemi. La parte fondamentale che agevola questo lavoro è l’utilizzo di composer per l’installazione dei moduli utilizzando magento-hackathon/magento-composer-installer spiegato da Nadia nel suo articolo.

Il grosso cambiamento

Con la versione 2.x di Magento avrete sicuramente avuto a che a fare con il deploy di asset statici, compilazione delle DI e le tante ottimizzazioni che si porta dietro la versione 2.x.

Questo richiede uno step successivo all’installazione dei moduli, ovviamente cerchiamo di eseguire questa procedura nella pipeline per non appesantire il server durante il deploy. Alla versione attuale (2.1.7) per eseguire i comandi necessari alla compilazione è necessaria la connessione al database, questo aggiunge una complicazione: essendo il container della pipeline isolato non è possibile connettersi al database dell’ambiente di destinazione e, anche potendo, non sarebbe una buona pratica.

Bitbucket Pipeline

Il primo requisito necessario è un repository su Bitbucket, nel caso usiate un altro servizio come GitLab potete usare il suo sistema di pipeline, molto ben fatto, oppure con GitHub un servizio esterno.

Aggiungete il file di configurazione della pipeline di Bitbucket, chiamato “bitbucket-pipelines.yml”.

La struttura del file è molto semplice, come prima riga indichiamo quale immagine Docker utilizzare per creare il container della pipeline, nel nostro caso abbiamo cucinato un’immagine molto simile al sistema usato sui server e in locale bitbull/webserver

image: bitbull/webserver

Aggiungiamo ora la dichiarazione della pipeline, qui ci sono due possibilità: usarla in modo automatico, ovvero ad ogni commit viene eseguita (filtrabile in base a tag o branch) oppure lasciare a noi il compito, con un semplice click. Nella maggior parte dei casi si utilizza un solo server di staging e per non sovrascrivere accidentalmente le modifiche di qualcun altro del team, che al momento sta testando una feature, è bene seguire la seconda strada.

Dichiariamo dunque che vogliamo una pipeline eseguita in modo manuale

custom:
    01_staging:

    02_production:

Abbiamo ora dichiarato due pipeline, una per ogni ambiente. Un consiglio utile è quello di anteporre al nome un numero incrementale, questo perchè Bitbucket le mostrerà in ordine alfabetico e non vogliamo che la prima selezionata sia “Production” invece di “Staging”.

Aggiungiamo ora i comandi che devono essere eseguiti, all’interno di “step” (al momento solo uno step per pipeline è supportato in futuro sarà possibile eseguirne più di uno in parallelo) e utilizziamo delle variabili d’ambiente per rendere tutto più configurabile. Un consiglio è quello di utilizzare lo stesso nome all’interno degli script e valorizzarle in base alla pipeline che vogliamo usare.

01_staging:
      - step:
          script:
            - export LOCALES="en_US it_IT"
            - chmod +x deploy-scripts/Build.sh && ./deploy-scripts/Build.sh

Notiamo che tutti i comandi verranno eseguiti da un file contenuto nel repository, questo per rendere tutto più leggibile.

È possibile configurare le variabili d’ambiente dal pannello di impostazioni e offuscare quelle più a rischio come le credenziali dell’utente AWS.

Andiamo ora a scrivere i comandi bash necessari per la compilazione creando il file ”./deploy-scripts/Build.sh”

#!/usr/bin/env bash

set -e # interrompe l’esecuzione in caso di errore

TAG=$BITBUCKET_COMMIT

#Configurazione di composer

COMPOSER_AUTH=$(cat <<EOF { "github-oauth": { "github.com": "$COMPOSER_AUTH_GITHUB_TOKEN" }, "http-basic": { "repo.magento.com": { "username": "$COMPOSER_AUTH_MAGENTO_REPO_USERNAME", "password": "$COMPOSER_AUTH_MAGENTO_REPO_PASSWORD" } } } EOF ) mkdir -p /root/.composer && echo "$COMPOSER_AUTH" > /root/.composer/auth.json

composer install --no-dev

Con questi comandi ora verranno installati i moduli di Magento e il suo stesso core, consiglio di tenere sotto controllo versione il file composer.lock per non avere sorprese durante questo step ma sapere in anticipo quali versioni verranno installate.

Ora manca qualcosa, il database a cui collegarsi! Ci viene quindi in aiuto Bitbucket che permette la creazione di un servizio, ovvero un secondo container docker, collegato a quello che esegue i comandi.

Torniamo sul file di configurazione e aggiungiamo al fondo del file bitbucket-pipelines.yml quanto segue

definitions:
  services:
    mysql:
      image: mysql
      environment:
        MYSQL_DATABASE: 'magento'
        MYSQL_ROOT_PASSWORD: 'root'

In questo modo stiamo aggiungendo un container MySQL con un database già creato “magento” e una password per l’utente root.

Aggiungiamo questo servizio alla dichiarazione della pipeline

01_staging:
      - step:
          script:
            ...
          services:
            - mysql

Ora possiamo collegarci al nostro database temporaneo e caricare un dump preso dall’ambiente di staging e alleggerito dalla tabelle di log.

# Connessione al database ed esecuzione del dump

M2_DB_HOSTNAME=127.0.0.1
M2_DB_NAME=magento
M2_DB_USERNAME=root
M2_DB_PASSWORD=root

aws s3 cp s3://$CODEDEPLOY_S3_BUCKET/$CODEDEPLOY_ENVIRONMENT/dump.sql /tmp/dump-$TAG.sql # scarichiamo da un bucket S3 il file di dump
mysql -h $M2_DB_HOSTNAME -u $M2_DB_USERNAME -p"$M2_DB_PASSWORD" $M2_DB_NAME < /tmp/dump-$TAG.sql # applichiamo il dump

ENV_FILE=$(cat <<EOF ADMIN_FRONTNAME="admin" APP_KEY="$APP_KEY" SESSION_SAVE="files" DB_HOSTNAME="$M2_DB_HOSTNAME" DB_NAME="$M2_DB_NAME" DB_USERNAME="$M2_DB_USERNAME" DB_PASSWORD="$M2_DB_PASSWORD" MAGE_MODE="production" CACHE_MODE="disable" WEBSERVER_USER="www-data" EOF ) echo "$ENV_FILE" > .env
composer run-script generate-config # usiamo un modulo sviluppato da noi per creare il file di configurazione env.php 

Ora Magento è collegato al database, non ci resta che eseguire i comandi della CLI per la compilazione vera e propria

# Magento deploy
php bin/magento module:enable --all # abilitiamo tutti i moduli

php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento setup:static-content:deploy $LOCALES

Diamo una pulizia al repository

rm app/etc/env.php
rm .env
rm -r var/cache/*
rm -r .git/
rm docker-compose.yml
rm Vagrantfile

Parte fondamentale delle CI/CD è la creazione dell’artefatto, ovvero un archivio contenente la versione dell’applicazione pronta per essere eseguita.

zip -r /tmp/release-$TAG.zip .

Adesso la nostra versione è pronta per il deploy. Per questo ultimo step in Bitbull utilizziamo un servizio AWS apposito chiamato CodeDeploy che con qualche configurazione ci permette di portare la nuova versione all’interno dei nostri server. È possibile con un paio di righe aggiuntive

aws s3 cp /tmp/release-$TAG.zip s3://$CODEDEPLOY_S3_BUCKET/$CODEDEPLOY_ENVIRONMENT/release-$TAG.zip
aws deploy register-application-revision --application-name $CODEDEPLOY_APP_NAME --s3-location bundleType=zip,bucket=$CODEDEPLOY_S3_BUCKET,key=$CODEDEPLOY_ENVIRONMENT/release-$TAG.zip --region $AWS_REGION

aws deploy create-deployment --application-name $CODEDEPLOY_APP_NAME --deployment-group-name $CODEDEPLOY_DEPLOYMENT_GROUP --s3-location bundleType=zip,bucket=$CODEDEPLOY_S3_BUCKET,key=$CODEDEPLOY_ENVIRONMENT/release-$TAG.zip --region $AWS_REGION

Questo comando è asincrono quindi qui la pipeline terminerà senza aspettare CodeDeploy, quindi per monitorare il progresso e controllare eventuali errori è necessario accedere alla console di AWS. Noi abbiamo collegato le notifiche di CodeDeploy al nostro Slack così da essere notificati per eventuali errori.

Salvate il file di script e quello di configurazione delle pipelines e fate una commit.

Esecuzione manuale della pipeline

Ora che la nostra pipeline è pronta non ci rimane che eseguirla.

Andiamo ora su Bitbucket e scegliamo la commit da cui fare partire la pipeline, è possibile anche selezionare un ramo e verrà usata l’ultima commit su di esso. Troverete sulla vostra destra il comando “Run pipeline”, cliccate su di esso e verranno mostrate le pipeline dichiarate nel file di configurazione.

Una volta lanciata si aprirà un pannello di controllo apposito che mostrerà l’output dei comandi, nel caso di errori questo è il posto giusto per controllare qual è il problema.

Durante l’esecuzione apparirà a sinistra un bottone “stop” con cui è possibile bloccarne l’esecuzione, in caso sia stata lanciata per errore. Al termine, invece, tramite il pulsante “rerun” è possibile eseguirla un’altra volta partendo dallo stesso commit.

Tips & Tricks

Cache

Per velocizzare lo scaricamento del moduli composer è comodo utilizzare la cache della pipeline. Attenzione ad utilizzare quella di default di Bitbucket, altrimenti verranno messi in cache i moduli sotto /vendor e non verranno creati i symlinks al core di Magento creati durante il composer install. In Bitbull utilizziamo un soluzione che in parte aiuta ma non ancora ottimizzata.

Al fondo del file bitbucket-pipelines.yml aggiungiamo

definitions:
  services:
    ...
  caches:
    composercache: ~/.composer/cache

e poi all’interno della dichiarazione della pipeline

01_staging:
      - step:
          caches:
            - composercache

così da applicare la configurazione.

Notifiche

Nel caso usiate Slack come app di messaggistica troverete sicuro comodo creare un webhook per inviare le notifiche nel canale usato per il progetto.

Fabio Gollinucci