Come costruire una pipeline di distribuzione iniziale efficace
Pubblicato: 2022-03-11Amo costruire cose, quale sviluppatore non lo fa? Mi piace trovare soluzioni a problemi interessanti, scrivere implementazioni e creare codice bellissimo. Tuttavia, quello che non mi piace sono le operazioni . Le operazioni sono tutto ciò che non è coinvolto nella creazione di un ottimo software, dalla configurazione dei server alla consegna del codice alla produzione.
Questo è interessante, perché come sviluppatore freelance di Ruby on Rails, devo spesso creare nuove applicazioni web e ripetere il processo per capire il lato DevOps delle cose. Fortunatamente, dopo aver creato dozzine di applicazioni, ho finalmente optato per una pipeline di distribuzione iniziale perfetta. Sfortunatamente, non tutti hanno capito come me—alla fine, questa conoscenza mi ha portato a fare il grande passo e documentare il mio processo.
In questo articolo, ti guiderò attraverso la mia pipeline perfetta da utilizzare all'inizio del tuo progetto. Con la mia pipeline, ogni push viene testato, il ramo master viene distribuito allo staging con un nuovo dump del database dalla produzione e i tag con versione vengono distribuiti alla produzione con backup e migrazioni che avvengono automaticamente.
Nota, poiché è la mia pipeline, è anche supponente e adatto alle mie esigenze; tuttavia, puoi sentirti libero di scambiare tutto ciò che non ti piace e sostituirlo con ciò che ti colpisce. Per la mia pipeline, useremo:
- GitLab per ospitare il codice.
- Perché: i miei clienti preferiscono che il loro codice rimanga segreto e il livello gratuito di GitLab è meraviglioso. Inoltre, la CI gratuita integrata è fantastica. Grazie GitLab!
- Alternative: GitHub, BitBucket, AWS CodeCommit e molti altri.
- GitLab CI per creare, testare e distribuire il nostro codice.
- Perché: si integra con GitLab ed è gratuito!
- Alternative: TravisCI, Codeship, CircleCI, DIY con Fabric8 e molti altri.
- Heroku per ospitare la nostra app.
- Perché: funziona immediatamente ed è la piattaforma perfetta per iniziare. Puoi modificarlo in futuro, ma non tutte le nuove app devono essere eseguite su un cluster Kubernetes appositamente creato. Anche Coinbase ha iniziato su Heroku.
- Alternative: AWS, DigitalOcean, Vultr, DIY con Kubernetes e molti altri.
Vecchia scuola: crea un'app di base e distribuiscila su Heroku
Innanzitutto, ricreiamo un'applicazione tipica per qualcuno che non utilizza alcuna pipeline CI/CD di fantasia e desidera semplicemente distribuire la propria applicazione.
Non importa che tipo di app stai creando, ma avrai bisogno di Yarn o npm. Per il mio esempio, sto creando un'applicazione Ruby on Rails perché viene fornita con migrazioni e una CLI e ho già scritto la configurazione per essa. Puoi usare qualsiasi framework o linguaggio che preferisci, ma avrai bisogno di Yarn per eseguire il controllo delle versioni che farò in seguito. Sto creando una semplice app CRUD utilizzando solo pochi comandi e nessuna autenticazione.
E testiamo se la nostra app funziona come previsto. Sono andato avanti e ho creato alcuni post, solo per essere sicuro.
E distribuiamolo su Heroku spingendo il nostro codice ed eseguendo le migrazioni
$ heroku create toptal-pipeline Creating ⬢ toptal-pipeline... done https://toptal-pipeline.herokuapp.com/ | https://git.heroku.com/toptal-pipeline.git $ git push heroku master Counting objects: 132, done. ... To https://git.heroku.com/toptal-pipeline.git * [new branch] master -> master $ heroku run rails db:migrate Running rails db:migrate on ⬢ toptal-pipeline... up, run.9653 (Free) ...
Infine testiamolo in produzione
E questo è tutto! In genere, è qui che la maggior parte degli sviluppatori lascia le proprie operazioni. In futuro, se apporti modifiche, dovrai ripetere i passaggi di distribuzione e migrazione precedenti. Potresti anche eseguire dei test se non sei in ritardo a cena. Questo è ottimo come punto di partenza, ma pensiamo un po' di più a questo metodo.
Professionisti
- Veloce da configurare.
- Le distribuzioni sono facili.
contro
- Non SECCO: richiede la ripetizione degli stessi passaggi ad ogni modifica.
- Senza versione: "Sto ripristinando la distribuzione di ieri a quella della scorsa settimana" non è molto specifico tra tre settimane.
- Non male a prova di codice: sai che dovresti eseguire i test, ma nessuno sta guardando, quindi potresti spingerlo nonostante l'occasionale test interrotto.
- Non a prova di cattivo attore: cosa succede se uno sviluppatore scontento decide di rompere la tua app spingendo il codice con un messaggio su come non ordini abbastanza pizze per il tuo team?
- Non è scalabile: consentire a ogni sviluppatore di eseguire la distribuzione darebbe loro l'accesso all'app a livello di produzione, violando il principio del privilegio minimo.
- Nessun ambiente di gestione temporanea: gli errori specifici dell'ambiente di produzione non verranno visualizzati fino alla produzione.
La pipeline di distribuzione iniziale perfetta
Oggi proverò qualcosa di diverso: facciamo un'ipotetica conversazione. Darò voce a "te" e parleremo di come possiamo migliorare questo flusso di corrente. Vai avanti, dì qualcosa.
Che cosa? Aspetta, posso parlare?
Sì, è questo che intendevo dire a darti una voce. Come stai?
Sono buono. Sembra strano
Capisco, ma continua a seguirlo. Ora, parliamo della nostra pipeline. Qual è la parte più fastidiosa dell'esecuzione delle distribuzioni?
Oh, è facile. La quantità di tempo che perdo. Hai mai provato a spingere su Heroku?
Sì, guardare il download delle tue dipendenze e la creazione dell'applicazione come parte di git push
è orribile!
Infatti, NO? È da pazzi. Vorrei non doverlo fare. C'è anche il fatto che devo eseguire le migrazioni *dopo* la distribuzione, quindi devo guardare lo spettacolo e controllare per assicurarmi che la mia distribuzione venga eseguita
Ok, potresti effettivamente risolvere quest'ultimo problema concatenando i due comandi con &&
, come git push heroku master && heroku run rails db:migrate
, o semplicemente creando uno script bash e inserendolo nel tuo codice, ma comunque ottima risposta, il il tempo e la ripetizione sono un vero dolore.
Sì, fa davvero schifo
E se ti dicessi che potresti correggere quel bit immediatamente con una pipeline CI/CD?
E adesso? Cos'è quello?
CI/CD sta per integrazione continua (CI) e distribuzione/distribuzione continua (CD). È stato abbastanza difficile per me capire esattamente cosa fosse quando stavo iniziando perché tutti usavano termini vaghi come "amalgama di sviluppo e operazioni", ma in parole povere:
- Integrazione continua: assicurati che tutto il tuo codice sia unito in un unico posto. Fai in modo che il tuo team utilizzi Git e utilizzerai CI.
- Consegna continua: assicurati che il tuo codice sia sempre pronto per essere spedito. Significa produrre rapidamente una versione da leggere per distribuire il tuo prodotto.
- Distribuzione continua: prelevare senza problemi il prodotto dalla consegna continua e distribuirlo semplicemente ai server.
Oh, ho capito ora. Si tratta di distribuire magicamente la mia app nel mondo!
Il mio articolo preferito che spiega CI/CD è di Atlassian qui. Questo dovrebbe chiarire tutte le domande che hai. Comunque, torniamo al problema.
Sì, torniamo a quello. Come posso evitare le distribuzioni manuali?
Configurazione di una pipeline CI/CD da distribuire su Push to master
E se ti dicessi che puoi correggere quel bit immediatamente con un CI/CD? Puoi eseguire il push sul tuo telecomando GitLab ( origin
) e verrà generato un computer per inviare semplicemente quel tuo codice a Heroku.
Non c'è modo!
Sì modo! Torniamo di nuovo al codice.
Crea un .gitlab-ci.yml
con i seguenti contenuti, sostituendo toptal-pipeline
con il nome della tua app Heroku:
image: ruby:2.4 before_script: - > : "${HEROKU_EMAIL:?Please set HEROKU_EMAIL in your CI/CD config vars}" - > : "${HEROKU_AUTH_TOKEN:?Please set HEROKU_AUTH_TOKEN in your CI/CD config vars}" - curl https://cli-assets.heroku.com/install-standalone.sh | sh - | cat >~/.netrc <<EOF machine api.heroku.com login $HEROKU_EMAIL password $HEROKU_AUTH_TOKEN machine git.heroku.com login $HEROKU_EMAIL password $HEROKU_AUTH_TOKEN EOF - chmod 600 ~/.netrc - git config --global user.email "[email protected]" - git config --global user.name "CI/CD" variables: APPNAME_PRODUCTION: toptal-pipeline deploy_to_production: stage: deploy environment: name: production url: https://$APPNAME_PRODUCTION.herokuapp.com/ script: - git remote add heroku https://git.heroku.com/$APPNAME_PRODUCTION.git - git push heroku master - heroku pg:backups:capture --app $APPNAME_PRODUCTION - heroku run rails db:migrate --app $APPNAME_PRODUCTION only: - master
Spingilo in alto e guardalo fallire nella pagina Pipelines del tuo progetto. Questo perché mancano le chiavi di autenticazione per il tuo account Heroku. Ripararlo è abbastanza semplice, però. Per prima cosa avrai bisogno della tua chiave API Heroku. Scaricalo dalla pagina Gestisci account, quindi aggiungi le seguenti variabili segrete nelle impostazioni CI/CD del repository GitLab:
-
HEROKU_EMAIL
: L'indirizzo email che usi per accedere a Heroku -
HEROKU_AUTH_KEY
: La chiave che hai ricevuto da Heroku
Ciò dovrebbe comportare la distribuzione di un GitLab su Heroku funzionante ad ogni spinta. Quanto a cosa sta succedendo:
- Dopo aver spinto per padroneggiare
- La CLI di Heroku è installata e autenticata in un container.
- Il tuo codice viene inviato a Heroku.
- Un backup del tuo database viene acquisito in Heroku.
- Le migrazioni vengono eseguite.
Puoi già vedere che non solo stai risparmiando tempo automatizzando tutto in un git push
, ma stai anche creando un backup del tuo database su ogni distribuzione! Se qualcosa va storto, avrai una copia del tuo database a cui tornare.
Creazione di un ambiente di staging
Ma aspetta, domanda veloce, cosa succede ai tuoi problemi specifici di produzione? Cosa succede se ti imbatti in uno strano bug perché il tuo ambiente di sviluppo è troppo diverso dalla produzione? Una volta mi sono imbattuto in alcuni strani problemi con SQLite 3 e PostgreSQL quando ho eseguito una migrazione. I dettagli mi sfuggono, ma è del tutto possibile.

Uso rigorosamente PostgreSQL in fase di sviluppo, non abbino mai motori di database del genere e monitoro diligentemente il mio stack per potenziali incompatibilità.
Bene, è un lavoro noioso e applaudo alla tua disciplina. Personalmente, sono troppo pigro per farlo. Tuttavia, puoi garantire quel livello di diligenza per tutti i potenziali futuri sviluppatori, collaboratori o contributori?
Errr— Sì, no. Mi hai portato lì. Altre persone lo rovineranno. Qual è il tuo punto, però?
Il punto è che hai bisogno di un ambiente di allestimento. È come la produzione ma non lo è. Un ambiente di staging è il luogo in cui provi la distribuzione alla produzione e intercetta tutti i tuoi errori in anticipo. I miei ambienti di gestione temporanea di solito rispecchiano la produzione e scarico una copia del database di produzione durante la distribuzione temporanea per assicurarmi che nessun caso d'angolo fastidioso rovini le mie migrazioni. Con un ambiente di staging, puoi smettere di trattare i tuoi utenti come cavie.
Questo ha senso! Quindi come faccio questo?
Ecco dove diventa interessante. Mi piace distribuire il master
direttamente nello staging.
Aspetta, non è lì che stiamo implementando la produzione in questo momento?
Sì, lo è, ma ora ci sposteremo invece allo staging.
Ma se il master
viene distribuito allo staging, come lo distribuiamo alla produzione?
Usando qualcosa che avresti dovuto fare anni fa: eseguire il controllo delle versioni del nostro codice e spingere i tag Git.
Git tag? Chi usa i tag Git?! Questo sta cominciando a suonare come un sacco di lavoro.
Sicuramente lo era, ma per fortuna ho già fatto tutto quel lavoro e puoi semplicemente scaricare il mio codice e funzionerà.
Innanzitutto, aggiungi un blocco sulla distribuzione di staging al tuo file .gitlab-ci.yml
, ho creato una nuova app Heroku chiamata toptal-pipeline-staging
:
… variables: APPNAME_PRODUCTION: toptal-pipeline APPNAME_STAGING: toptal-pipeline-staging deploy_to_staging: stage: deploy environment: name: staging url: https://$APPNAME_STAGING.herokuapp.com/ script: - git remote add heroku https://git.heroku.com/$APPNAME_STAGING.git - git push heroku master - heroku pg:backups:capture --app $APPNAME_PRODUCTION - heroku pg:backups:restore `heroku pg:backups:url --app $APPNAME_PRODUCTION` --app $APPNAME_STAGING --confirm $APPNAME_STAGING - heroku run rails db:migrate --app $APPNAME_STAGING only: - master - tags ...
Quindi modifica l'ultima riga del blocco di produzione in modo che venga eseguita su tag Git con versione semantica anziché sul ramo principale:
deploy_to_production: ... only: - /^v(?'MAJOR'(?:0|(?:[1-9]\d*)))\.(?'MINOR'(?:0|(?:[1-9]\d*)))\.(?'PATCH'(?:0|(?:[1-9]\d*)))(?:-(?'prerelease'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?(?:\+(?'build'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$/ # semver pattern above is adapted from https://github.com/semver/semver.org/issues/59#issuecomment-57884619
L'esecuzione di questo in questo momento fallirà perché GitLab è abbastanza intelligente da consentire solo ai rami "protetti" l'accesso alle nostre variabili segrete. Per aggiungere tag di versione, vai alla pagina delle impostazioni del repository del tuo progetto GitLab e aggiungi v*
ai tag protetti.
Ricapitoliamo cosa sta succedendo ora:
- Al momento del push al master o del push di un commit con tag
- La CLI di Heroku è installata e autenticata in un container.
- Il tuo codice viene inviato a Heroku.
- Un backup della produzione del tuo database viene acquisito in Heroku.
- Il backup viene scaricato nell'ambiente di gestione temporanea.
- Le migrazioni vengono eseguite sul database di staging.
- Dopo aver eseguito il push di una versione con tag semantico di commit
- La CLI di Heroku è installata e autenticata in un container.
- Il tuo codice viene inviato a Heroku.
- Un backup della produzione del tuo database viene acquisito in Heroku.
- Le migrazioni vengono eseguite sul database di produzione.
Ti senti potente ora? Mi sento potente. Ricordo che la prima volta che sono arrivato così lontano, ho chiamato mia moglie e ho spiegato l'intera pipeline con dettagli strazianti. E non è nemmeno tecnica. Sono rimasto molto colpito da me stesso e dovresti esserlo anche tu! Ottimo lavoro arrivando fin qui!
Testare ogni spinta
Ma c'è di più, dal momento che un computer fa comunque cose per te, potrebbe anche eseguire tutte le cose che sei troppo pigro per fare: test, errori di filatura, praticamente qualsiasi cosa tu voglia fare, e se qualcuno di questi fallisce, hanno vinto non passare alla distribuzione.
Adoro averlo nella mia pipeline, rende divertenti le mie revisioni del codice. Se una richiesta di unione supera tutti i miei controlli del codice, merita di essere rivista.
Aggiungi un blocco di test
:
test: stage: test variables: POSTGRES_USER: test POSTGRES_PASSSWORD: test-password POSTGRES_DB: test DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSSWORD}@postgres/${POSTGRES_DB} RAILS_ENV: test services: - postgres:alpine before_script: - curl -sL https://deb.nodesource.com/setup_8.x | bash - apt-get update -qq && apt-get install -yqq nodejs libpq-dev - curl -o- -L https://yarnpkg.com/install.sh | bash - source ~/.bashrc - yarn - gem install bundler --no-ri --no-rdoc - bundle install -j $(nproc) --path vendor - bundle exec rake db:setup RAILS_ENV=test script: - bundle exec rake spec - bundle exec rubocop
Ricapitoliamo cosa sta succedendo ora:
- Ad ogni push o richiesta di unione
- Ruby e Node sono impostati in un contenitore.
- Le dipendenze sono installate.
- L'app è testata.
- Dopo aver eseguito il push su master o su un commit con tag e solo se tutti i test sono stati superati
- La CLI di Heroku è installata e autenticata in un container.
- Il tuo codice viene inviato a Heroku.
- Un backup della produzione del tuo database viene acquisito in Heroku.
- Il backup viene scaricato nell'ambiente di gestione temporanea.
- Le migrazioni vengono eseguite sul database di staging.
- Al momento del push di un commit con tag semantico della versione e solo se tutti i test vengono superati
- La CLI di Heroku è installata e autenticata in un container.
- Il tuo codice viene inviato a Heroku.
- Un backup della produzione del tuo database viene acquisito in Heroku.
- Le migrazioni vengono eseguite sul database di produzione.
Fai un passo indietro e ammira il livello di automazione che hai raggiunto. D'ora in poi, tutto ciò che devi fare è scrivere codice e premere. Prova la tua app manualmente durante lo staging se ne hai voglia e, quando ti senti abbastanza sicuro da pubblicarla, taggala con il versionamento semantico!
Versione semantica automatica
Sì, è perfetto, ma manca qualcosa. Non mi piace cercare l'ultima versione dell'app e contrassegnarla esplicitamente. Ciò richiede più comandi e mi distrae per alcuni secondi.
Ok, amico, fermati! È abbastanza. Lo stai solo progettando eccessivamente ora. Funziona, è geniale, non rovinare una buona cosa andando sopra le righe.
Ok, ho una buona ragione per fare quello che sto per fare.
Prega, illuminami.
Ero come te. Ero contento di questa configurazione, ma poi ho sbagliato. git tag
elenca i tag in ordine alfabetico, v0.0.11
è superiore v0.0.2
. Una volta ho accidentalmente etichettato una versione e ho continuato a farlo per circa una mezza dozzina di versioni finché non ho visto il mio errore. In quel momento ho deciso di automatizzare anche questo.
Ci risiamo
Ok, quindi, per fortuna, abbiamo la potenza di npm a nostra disposizione, quindi ho trovato un pacchetto adatto: esegui yarn add --dev standard-version
e aggiungi quanto segue al tuo file package.json
:
"scripts": { "release": "standard-version", "major": "yarn release --release-as major", "minor": "yarn release --release-as minor", "patch": "yarn release --release-as patch" },
Ora devi fare un'ultima cosa, configurare Git per inviare i tag per impostazione predefinita. Al momento, è necessario eseguire git push --tags
per caricare un tag, ma farlo automaticamente su git push
regolare è semplice come eseguire git config --global push.followTags true
.
Per utilizzare la tua nuova pipeline, ogni volta che desideri creare un'esecuzione di rilascio:
-
yarn patch
per i rilasci di patch -
yarn minor
per rilasci minori -
yarn major
per le versioni principali
Se non sei sicuro del significato delle parole "maggiore", "minore" e "patch", leggi di più su questo sul sito di versionamento semantico.
Ora che hai finalmente completato la tua pipeline, ricapitoliamo come usarla!
- Scrivi il codice.
- Impegnati e spingilo per testarlo e distribuirlo allo staging.
- Usa la
yarn patch
per taggare una versione della toppa. -
git push
per portarlo in produzione.
Riepilogo e ulteriori passaggi
Ho appena scalfito la superficie di ciò che è possibile fare con le pipeline CI/CD. Questo è un esempio abbastanza semplicistico. Puoi fare molto di più sostituendo Heroku con Kubernetes. Se decidi di utilizzare GitLab CI, leggi i documenti yaml perché c'è molto di più che puoi fare memorizzando nella cache i file tra le distribuzioni o salvando artefatti!
Un'altra enorme modifica che potresti apportare a questa pipeline è l'introduzione di trigger esterni per eseguire il controllo delle versioni semantico e il rilascio. Attualmente, ChatOps fa parte del loro piano a pagamento e spero che lo rilascino ai piani gratuiti. Ma immagina di essere in grado di attivare l'immagine successiva tramite un singolo comando Slack!
Alla fine, poiché l'applicazione inizia a diventare complessa e richiede dipendenze a livello di sistema, potrebbe essere necessario utilizzare un contenitore. Quando ciò accade, dai un'occhiata alla nostra guida: Guida introduttiva a Docker: Simplifying Devops .
Questa app di esempio è davvero attiva e puoi trovare il codice sorgente qui.