Sincronizzazione e Deploy di un progetto web su un server proprio via ssh e git hooks

un momento di ispirazione ci viene in mente un progetto web rivoluzionario, nell'ambiente di sviluppo quasi tutto funziona, quindi vogliano iniziare a condividere su un server pubblico il suo output e il suo sviluppo con persone fidate, che fare?

myproject-local (_running-branch_) ⇆ ☁ myproject.git → myproject (master)

Copiare i file via ftp/ssh/sftp ? nah.. ogni volta vengono sovrascritti molti file uguali, si rischia il timeout e poi non è detto che le configurazioni usate in locale siano le stesse che si usano in remoto.
Rsync ? Già meglio, va bene per le sincronizzazioni o backup, ma non ha le versioni dei file e a volte è più il tempo che perde a generare l'indice dei file cambiati che non tanto il loro trasferimento.
Git ? Siiii! Con qualche accortezza lo si può usare per sincronizzare un progetto tra più persone e mantenere il versioning dei file, vediamo come.

Workflow git + ssh + git hooks

Git è un VCS decentralizzato, che permette di sviluppare e fare i commit senza la connessione ad internet. Il vantaggio principale è che si può avere una cartella sotto versioning senza disporre di un server remoto, ma se vogliamo condividere lo sviluppo del nostro lavoro con altri necessariamente ci vorrà un account github o un altro server con git e possibilmente ssh.

Locale: ambiente di sviluppo (development)

Partiamo con una cartella di esempio chiamata myproject-local, che è stata inizializzata per git e contiene diversi file.
Immaginate contenga il vostro progetto web che a cadenze più o meno regolari deve essere pubblicato sul web (deploy), in questo caso nella cartella remota myproject.

Remoto: Repository git e web server

Le la cartella del progetto di sviluppo non comunica direttamente con l'istanza della cartella pubblicata, ma in mezzo c'é un repository git che è contenuto nella cartella myproject.git
Quindi arriviamo ai comandi necessari per configurare l'ambiente

[remoto] git init --bare myproject.git

Crea il repository "ufficiale" (vuoto) che conterrà, tutti i branch e le modifiche pubblicate da uno o più sviluppatori.

[locale] git remote add _running-branch_ ssh://user@server.com/~/repo/myproject.git
[locale] git remote show _running-branch_

Nella cartella myproject-locale che è già stata inizializzata aggiungiamo il branch remoto "_running-branch_" che sarà sincronizzato con quello attivo pubblicato. Il secondo comando serve solo a controllare che il branch sia stato effettivamente aggiunto.

[locale] git push _running-branch_ master

Dopo qualche commit possiamo condividere le modifiche locali col server remoto, "master" è il nome del branch remoto che corrisponderà alla versione "online".

[remoto] git clone /path/locale/myproject.git
[remoto] git pull

Inizializziamo "myproject" che è la copia "online" del progetto oppure aggiorniamo il suo contenuto. Questa cartella è quella che sarà puntata dal web server.
Un nuovo sviluppatore darà gli stessi comandi per avere la propria copia, l'unica differenza è che il path non sarà locale ma del tipo ssh://

Normale workflow

Una volta fatto il setup, l'ideale è non effettuare il login con ssh, ma fare tutto da locale. L'utilizzo di una chiave ssh è vivamente consigliato.

[locale] git pull

Sincronizza la copia di sviluppo con eventuali modifiche fatte da altri sviluppatori remoti

<Modifiche e commit vari>

[locale] git push _running-branch_ master
[locale] ssh user@server.com 'cd repo/myproject && git pull'

Infine condividiamo le modifiche e facoltativamente le rendiamo attive nella copia online o di produzione eseguendo "git pull" in remoto.

Quest'ultimo passaggio può essere ulteriormente automatizzato utilizzando i git hooks, cioé degli script fatti da noi che verranno attivati all'occorrenza di certi eventi.
In questo caso vogliamo che ad ogni push sul branch remoto venga aggiornata la copia attiva online, cioé "myproject".
Nel mondo ruby progetti grossi usano un tool chiamato Capistrano (esempio) per fare il deploy, perché magari devono gestire diversi server ridondati e hanno necessità maggiori, ma non è questo il caso.

Nella cartella "myproject.git/hooks" potete automatizzare l'evento di quando il repository remoto riceve un push, cioé post-receive che corrisponde a un file eseguibile con all'interno i comandi per fare il deploy, quindi aggiornare la copia live con la versione successiva.

#!/bin/sh # via: stackoverflow
unset $(git rev-parse --local-env-vars)
cd ~/repo/myproject
git pull 

In questo esempio ci limitiamo ad aggiornare i dati contenuti nella cartella live, ma in realtà molte altre azioni potrebbero essere fatte:

  • riavviare un web server
  • aggiornare lo schema del db
  • scrivere un log di quando sono stati fatti i deploy
  • eseguire dei test automatici sul codice
  • inviare un'email agli interessati al progetto

Ma la cosa più importante è ridurre il "Time to Market" e per farlo ci vanno delle soluzioni che accorpino il più possibile i comandi ripetitivi o che potrebbero causare errori e rendano il deploy semplice e scalabile.

commenti

Articolo molto

Articolo molto interessante.

Alcune domande:
- a cosa serve il comando "unset $(git rev-parse --local-env-vars)" ?
- in questo modo gli utenti del sito web hanno accesso alla cartella nascosta ".git", o sbaglio?

Io uso un meccanismo simile, ma un po più complicato.
Infatti un problema comune è che alcuni file di configurazione devono essere
diversi tra la versione locale di sviluppo e quella di deploy: per esempio
i parametri di configurazione del database.

Come gestite questo problema?

Ciao Luigi, felice che ti

Ciao Luigi, felice che ti sia piaciuto l'articolo, ti rispondo:

- a cosa serve il comando "unset $(git rev-parse --local-env-vars)" ?
Questo comando disattiva alcune variabili d'ambiente che git potrebbe impostare associate all'utente. Non è questo il caso, ma normalmente "git", "webserver" e "deverloperN" sono utenti remoti differenti con privilegi diversi.

- in questo modo gli utenti del sito web hanno accesso alla cartella nascosta ".git", o sbaglio?
Dipende, se la root del tuo progetto è anche la root dove punta il webserver, sì.

L'ultima cosa che chiedi, riguardo i diversi tipi di configurazione a seconda dell'environment, dipende dal framework che usi o da come il webserver gestisce la cartella del progetto web. Per esempio con Ruby on Rails è già tutto predisposto nella fase di inizializzazione e vengono chiamate configurazioni o connessioni diverse a seconda di una variabile d'ambiente che viene impostata a livello di web server. Questa variabile normalmente ha lo stesso nome di una della 4 fasi di sviluppo: development, test, staging, production ( http://dltj.org/article/software-development-practice/ ).
Ciao

Grazie per la risposta. Io

Grazie per la risposta.
Io uso php senza nessun framework e quindi devo ancora trovare
un modo furbo per gestire le configurazioni.

Visto che sei un utente esperto di Git, ti propongo il seguente problema,
al quale, discutendo con diversi colleghi, non ho trovato una soluzione elegante.

Immaginiamo di avere un progetto php chiamato sito1.
Ad un certo punto voglio fare un altro progetto chiamato sito2.
Nel fare sito2 voglio partire da sito1, personalizzando solo i file che mi interessano.
Fin qui tutto semplice.
Problema: ogni volta che faccio un bug-fixing su sito1 vorrei che si propagasse a sito2.
Cioè vorrei che i file di sito2 che non sono stati personalizzati ( e quindi identici alla versione iniziale di sito1)
rimanessero sincronizzati con sito1.

Hai avuto questo tipo di problema? Ti viene in mente una soluzione elegante?

Grazie in anticipo

Dipende da cosa intendi con

Dipende da cosa intendi con "altro progetto chiamato sito2".
Bisogna valutare da progetto a progetto, in generale si può fare un modulo core generico e altri moduli specifici che "decorano" ogni sito simile.
Altrimenti se le somiglianze a livello di codice sono davvero tante puoi ignorare dei file che non vuoi fare entrare nel versioning oppure mantenere un branch "generico" e dei branch "sito1" e "sito2" dove importi le modifiche solo le modifiche che ti serve condividere.