In questo articolo descrivo come automatizzare la distribuzione di una applicazione web sviluppata in Clojure e Luminus, limitando la quantità di dati inviati dal nostro computer al server su internet su cui installare.

Ho sentito l'esigenza di automatizzare un po' il processo, avendo sviluppato site4 basata su Luminus. I sorgenti sono sotto controllo di versione in un repository Git locale. Le modifiche sono committate e inviate ad un repository Git remoto nel cloud. L'applicazione, un sito web, gira su un server virtuale.

Luminus è un micro-framework web per Clojure. È costruito su esperienze consolidate di sviluppo web e affidabili librerie dell'ecosistema di Clojure. Clojure è un linguaggio funzionale per la Java Virtual Machine. Clojure è una piccola sfida per chi programma con linguaggi procedurali o ad oggetti, ma credo valga la pena di provare. Le strutture dati immutabili sono uno strumento importante per sviluppare applicazioni concorrenti sicure e scalabili. Alcuni concetti di Clojure sono utili anche quando si torna a programmare con linguaggi non funzionali.

Una volta sviluppata l'applicazione Luminus ho seguito le istruzioni del sito di Luminus per andare on line, configurando un server VPS Linux con Java e Nginx, quest'ultimo come frontend.

Il server è stato configurato per eseguire l'uberjar, l'eseguibile tutto in uno della web app site4, nella directory /opt/deploy. L'applicazione può essere avviata, fermata e riavviata tramite il comando standard systemctl.

L'uberjar ha una dimensione superiore a 50MB e può richiedere un bel po' di tempo per trasferirlo al server tramite una comune DSL. Per evitare di dover ritrasferire tutto l'uberjar per ogni piccolo cambiamento dell'applicazione, ho scelto di assemblare l'uberjar nel server VPS stesso, scaricando i sorgenti dal Git remoto, compilando l'uberjar, procedendo all'installazione nella directory deploy e riavviando l'applicazione.

Le prime volte ho fatto tutto a mano, poi ho cercato di automatizzare il processo usando Fabric/fabfile.

Fabric è una semplice ma potente alternativa a Chef, Puppet e Ansible per automatizzare il processo di distribuzione.

Ciò di cui avevo bisogno, era di realizzare in Fabric un task deploy che eseguisse in sequenza i seguenti task

  • source – clonare il repository Git remoto o scaricarne le differenze dei sorgenti
  • build – assemblare l'uberjar
  • stage – tenere un backup del precedente uberjar e preparare quello nuovo per l'installazione
  • install – installare il nuovo uberjar e raivviare l'applicazione

Innanzitutto, ho definito alcune funzioni ausiliarie: app_name per ottenere il nome dell'applicazione e git_url per ottenere l'url del repository Git remoto.

env.app_name = 'site4'
env.git_url = 'https://username:password@agitprovider.org/account/%s.git'

def app_name():
    return env.app_name

def git_url():
    return env.git_url % app_name()

Poi ho definito la funzione source che verifica se nella directory stage dell'utente esista il repository Git dell'app, Se esiste, vengono aggiornati i sorgenti altrimenti viene scaricato.

def source():
    with cd('stage'):
        if exists(app_name()):
            with cd(app_name()):
                # run('git pull')
                run('git pull %s master' % git_url())
        else:
            run('git clone %s' % git_url())

La funzione build è piuttosto semplice.

def build():
    with cd('stage/%s' % app_name()):
        run('lein uberjar')

La funzione stage prepara il tutto per l'installazione eseguendo una copia di sicurezza del precedente uberjar ed impostando la proprietà del file del nuovo uberjar.

def stage():
    with cd('stage'):
        # Backup in the 'stage' directory of the current application
        sudo('cp -a /opt/deploy/%s.jar %s-rollback.jar' % (app_name(), app_name()))
        # Copy in stage of the new application
        sudo('cp -a %s/target/uberjar/%s.jar .' % (app_name(), app_name()))
        sudo('chown deploy:deploy %s.jar' % app_name())

Tutto è pronto, ecco quindi la funzione install che copia l'uberjar nella directory deploy e riavvia l'applicazione.

def install():
    with cd('stage'):
        sudo('cp -a %s.jar /opt/deploy/%s.jar' % (app_name(), app_name()))
        sudo('systemctl restart %s' % app_name())

I quattro task descritti finora vengono eseguiti dalla funzione deploy.

def deploy():
    source()
    build()
    stage()
    install()

Qui c'è il sorgente completo del fabfile.py

Quando ho bisogno di installare la nuova versione dell'applicazione inviata al Git repository remoto, eseguo il seguente comando.

fab vps_server.domain.it deploy

Dato che ho configurato questo server nel file config di SSH, posso eseguire

fab -H vps_server deploy

Se necessario, posso eseguire i vari task uno ad uno, per esempio il primo

fab -H vps_server source

controllando così ogni fase della distribuzione.

Questo è tutto!
Spero questo articolo sia stato interessante.
Ciao!!!
Gianni Di Sanzo