Microservizi

Tutti i microservizi che compongono la piattaforma devo rispettare i seguenti standard e regole generali

Tutti i servizi devono rispettare i 12factor, descritti in modo anche più dettagliato nell'articolo An illustrated guide to 12 Factor Apps

Packaging

Quello che ci si aspetta è che si possa usare il docker-compose.yml scaricandolo direttamente sul proprio pc, possibilmente senza necessità di clonare tutto il repository.

  • Dockerfile e docker-compose.yml devono essere presenti nella root del repo (.dockerignore se serve) per facilitare l'avvio in locale dell'applicativo e farne saggiare l'utilizzo; Perché sia pienamente usabile è necessario che le immagini facciano riferimento al registry pubblico (nostro o meno).

  • usare un docker-compose.dev.yml per aggiungere tool utili per lavorare in locale durante lo sviluppo.

Per esempio la chiave "build" per le immagini create deve essere presente nel file ".dev" e non nel file compose principale.

In questo modo scaricando il repository e rinominando il file .dev in docker-compose.override.yml ci si mette nelle condizioni ideali per sviluppare il servizio e un docker-compose up comporterà automaticamente l'uso in cascata dei file docker-compose.yml e docker-compose.override.yml.Per ulteriori dettagli consultare il manuale di Docker: Merge compose files.

Terminazione pulita

  • il servizio deve gestire correttamente i segnali inviati da docker SIGTERM e SIGKILL per fare uno shutdown pulito e senza interrompere l'esecuzione in fasi critiche per la consistenza dei dati.

Configurazione

  • la configurazione *deve avvenire mediante variabili d'ambiente

  • la configurazione può essere fatta anche tramite un file di environment (.env) o parametri da cli

Healthcheck

  • se http, deve essere presente un /status entrypoint che risponde 200 se va tutto bene, uno status code diverso in caso contrario

  • se non http, l'healthcheck può essere un controllo su un file o su un processo

  • l'health check, se disponibile, deve essere inserito nel Dockerfile

Logging

Vanno tenute sempre in considerazione le raccomandazioni implementative di Agid, espresse nell'Allegato 4 alle Linee Guida di Interoperabilità.

  • Il log deve supportare almeno tre livelli: debug, info e error.

    • debug: deve essere comprensibile il flusso logico dell'applicativo

    • info:ci si aspetta di leggere 1 riga per ogni evento o chiamata ricevuta dall'applicativo, che non ha presentato errori. Se una transazione o un evento gestito presenta errori NON si deve produrre la riga di INFO e la riga di ERROR, ma solo la seconda.

    • error: non si deve vedere nulla se non ci sono errori. A ERROR, il rate dei log che si alza dovrebbe essere indice che ci sono problemi per i quali è importante attrarre l'attenzione degli amministratori. Attenzione a non confondere errori che si verificano su sistemi esterni e che non sono nostri errori. Date sempre per scontato che una riga di errore prodotta dovrebbe sempre essere letta da qualcuno, altrimenti meglio esporre un livello di warning o non loggare proprio.

  • Non mi schiare log http e log multiriga (stacktrace o similari): se si è costretti, per esempio inviare lo stacktrace a stderr e riservare stdout ai log a singola riga

  • Se possibile implementare log in formato json, ma solo se si può produrre solo del JSON, mischiare testo semplice e JSON non è di grande utilità.

  • Sul contenuto:

    • DEVE esserci l'istante della comunicazione in formato UTC (RFC 3339) e con separatori Z e T in maiuscolo.

    • DEVE contenere la URI o l'identificazione dell'operazione che si sta eseguendo

    • Dovrebbe contenere la tipologia della chiamata, ad esempio il verbo HTTP o se si tratta di una chiamata fatta da CLI o in un azione batch.

    • DEVE contenere l'esito della chiamata

    • Se è una chiamata fatta da un client DEVE essere inserito l'IP sorgente della chiamata (attenzione a rispettare eventuali header X-FORWARDED-XXX)

    • Se è una chiamata fatta per conto di un essere umano è possibile inserire un riferimento all'utente, ma NON DEVE contenere dati personali in chiaro (il codice fiscale ad esempio deve essere troncato al mssimo alle prime 7 lettere). Per avere un riferimento certo inserire lo userid della piattaforma.

    • Se disponibile DEVE inserire l'identificatore unico della chiamata o dell'evento (event_id).

    • DEVE contenere l'environment in cui sta girando il microservizio: boat-qa, boat-prod, local, etc...

Monitoring errors

  • Il microservizio DEVE integrare Sentry per il monitoraggio centralizzato degli errori.

  • Una volta operativa l'integrazione è necessario:

    • abilitare l'integrazione con sentry sul repo gitlab

    • inserire regole di allerta per quel microservizio, se le regole esistenti non sono sufficienti

Monitoring metrics

  • se http, deve esistere l'entrypoint /metrics che espone metriche in formato prometheus.

  • un servizio può mostrare metriche sulle chiamate (status code, timing), anche se solitamente le possiamo prendere altrove (traefik), - un servizio deve mostrare metriche specifiche dell'app, in particolare delle condizioni di errore (counters) e dei tempi di risposta di eventuali servizi esterni richiamati da questo (histograms).

  • il servizio NON deve mostrare metriche che derivano ad esempio da errori 404, perché i bot che scansionano tutte le URL provocano infinite varianti che comportano la creazione di infinite metriche di errore

Cron

  • sui sistemi di deploy e orchestrazione che usiamo ci sono sempre dei modi di eseguire task stile cron, inutile reimplementare quella logica dentro la propria App.

  • è indispensabile invece rendere il proprio software idempotente: deve essere rieseguibile più volte sugli stessi dati senza fare danno.

  • il modo più semplice per implementare una chiamata a tempo è implementare un comando da cli che possa essere eseguito con la stessa immagine con cui gira l'applicativo.Questo applicativo viene poi schedulato nel nostro ambiente di produzione istruendo il nostro clustered cron. Per istruzioni sulla configurazione si rimanda alla documentazione di crazymax-cronjob.

Parametri e file di configurazione

  • in docker c'e' sempre un mapping tra esterno e interno, inutile parametrizzare questi aspetti in modo particolarmente flessibile: il controllo sul filesystem interno è sempre in mano allo sviluppatore del microservizio, quindi si può sempre usare un path interno del tipo /data senza preoccuparsi tanto del fatto che all'esterno quel path diventerà qualcosa come /srv/data/qualcosa/altro/data.

  • Per le configurazioni e' importante usare sempre lo strumento configs di docker swarm, senza ricorrere al volume mapping.

  • Analogamente per le credenziali o le parti segrete DEVE essere usato lo strumento dei secrets.

Continuous Integration

  • Ogni progetto può inserire nella CI attività di linting del codice e enforcemente dei coding styles

  • Ogni progetto DEVE includere la CI condivisa, nel seguente modo:

    stages:
     - build
     - test
     - deploy  # dummy stage to follow the template guidelines
     - review
     - dast
     - staging
     - canary
     - production
     - incremental rollout 10%
     - incremental rollout 25%
     - incremental rollout 50%
     - incremental rollout 100%
     - performance
     - cleanup
    
    include:
      - project: 'opencity-labs/product'
        ref: main
        file: '.gitlab/ci/devops.yml'`
  • Eventuali step di test devono essere inclusi dopo questo snippet di codice e devono sfruttare la fase di build già eseguite, per esempio se viene fatta la build di un binario la si può sfruttare negli step successivi mediante un artifact.

  • La durata della CI non DEVE mai superare i 15 minuti e non dovrebbe mai superare i 5: questo perché soprattutto in condizioni di emergenza, fare un hotfix non può richiedere troppo tempo, è necessario che ogni step sia ben ottimizzato per ottenere questo scopo, per esempio sfruttando le build multi-stage di docker o la cache di Gitlab CI.

  • in caso di pubblicazione su Developers Italia, la correttezza del public-code.yml deve essere testato con:

    publiccode:
      stage: test
      allow_failure: false
      image:
        name: italia/publiccode-parser-go
        entrypoint: [""]
      script:
        - publiccode-parser /dev/stdin < publiccode.yml
      only:
        - master

Last updated