======== Docker ======== Queste poche righe vogliono essere una sintesi pragmatica dei comandi da usare per operare correttamente nello sviluppo di applicazioni django/quasar di cui facciamo il deploy con Docker. Non ha alcuna ambizione di essere esaustiva e probabilmente è poco utile fuori da Thux in quanto fa riferimento ad alcune utility interni. Le premesse sono intese a rendere plausibili e facili da memorizzare i comandi, una presentazione più rigorosa del setup sarà curata in un webinar. Deploy ====== .. image:: ../images/schema-heptapod.png L'ambiente in cui usiamo docker è molto semplice: usiamo ``docker-compose`` per descrivere la relazione fra i docker e per avviarli. Questo significa che, al momento, non stiamo usando né swarm né kubernetes per l'orchestrazione. Al momento abbiamo le seguenti macchine docker: :docker1.thux.dev: per clienti senza macchina propria :docker-c.thux.dev: per tutti i servizi: hepapod, runner, jitsy-meet, npm, pypi... Heptapod ha anche il servizio ``registry.thux.dev``, ovvero uno storage di immagini :acomea-dk: una macchina per acomea (per ora il nuovo cms) :onplay.it: una macchina per adrenalinik Ogni macchina ha traefik, un web proxy che gira pure in docker e che gestisce i certificati https. Setup --------------- Usiamo un container per l'applicazione di backend ed un db postgres per il frontend ed un db ospitato da un sistema operativo tradizionale (non dockerizzato, ma pur sempre virtualizzato). Nel caso di frontend separato il docker che ospita il frontend ha al suo interno un nginx a cui arriva tutto il traffico e fa eventualmente da proxy verso django. Il container django serve i file statici con uwsgi. I file di configurazione ``uwsgi.py`` e ``nginx.conf`` stanno nella radice dei rispettivi progetti assieme al file che contiene la ricetta per creare il docker ``Dockerfile``. Un estratto dei file del backend Django:: django/ ├── apps ├── Dockerfile ├── setup.py ├── uwsgi └── web Un estratto dei file del frontend Vuejs/Quasar:: quasar/ ├── Dockerfile ├── nginx.conf ├── quasar.conf.js └── src immagini ---------- Le immagini che produciamo (direttamente al commit di mercurial tramite il meccanismo delle pipeline di gitlab) hanno dentro il codice python/js/css oltre all'interprete Python (uwsgi) ma non hanno dati e configurazioni "delicate" (password). docker-compose.yml ------------------- Ogni applicazione è composta da una o più immagini che devono essere accessibili fra di loro. Ad esempio un cms è tipicamente solo una applicazione Django quindi un solo container, jeetsi-meet è composto da 4 servizi. Con zoooom potremmo decidere di avere 4 docker differenti per backend, vettori, supplier, customer (risulta più facile gestire gli aggiornamenti) o produrre una sola immagine per i 3 frontend. In ogni caso la scelta viene scritta in un file ``docker-compose.yml`` che mostra in modo molto chiaro il layout deciso. In questo file vengono presentati i ``servizi`` (django, quasar, jeetsy, heptapod, registry....) che verranno eseguiti ed eventualmente il numero di container che devono eseguire quel servizio (caratteristica che avrà un senso solo per traffico molto elevato). Questo file sta in un repository suo, indipendente dagli altri ma strettamente legato a questi:: opencapital-2020/ ├── docker-compose.yml ├── local.py ├── logs │   ├── django │   │   ├── opencapital-api_exceptions.log │   │   ├── opencapital-exceptions.log │   │   └── opencapital.log │   ├── nginx │   └── uwsgi │   ├── access.log │   └── errors.log ├── media └── README.rst Alla fine, nel server di produzione il codice sta *dentro* il container in modo molto meno visibile ed accessibile di prima. Soprattutto non facilmente modificabile. volumes ------- Oltre all'immagine nel momento in cui avviamo il docker dobbiamo anche fornire: * configurazione (es.: ``local.py``) * dati (es.: media) * spazio per i log Queste cose vengono collegate con un ``bind`` in modo tale che siano visibili e modificabili sia dal docker che da fuori:: services: django: image: registry.thux.dev/customers/open-capital/office2020.opencapital.it volumes: - ./media:/code/media - ./logs/django:/var/log/django - ./local.py:/code/web/settings/local.py networks: - django Nell'estratto qui sopra si notano 3 volumi dove il ``:`` divide a sinistra la cartella nel server ospitante a destra il nome che avrà nel container. Quindi concretamente: se vogliamo cambiare i valori del file ``local.py`` dovremo cambiarli nel file che sta nella cartella dove c'è ``docker-compose.yml`` e non nella solita posizione. CI/CD ====== La produzione dell'immagine che eseguiamo in produzione viene fatta direttamente nel server heptapod da un "runner" (che è una container a parte) che segue le istruzioni scritte nel file ``.gitlab-ci.yml``. Questa è chiamata una "pipeline" ed è ben visibile in heptapod. Tendenzialmente non ci sarà necessità di creare file diversi e questo file sarà presente già nel cookiecutter. Se il codice contiene errori è probabile che la pipeline fallisca ed è probabilmente poco opportuno fare merge di un branch che sia fallito. Di sicuro è impossibile fare il deploy di un branch che sia fallito. .. note:: tag delle immagini Abbiamo scelto di mettere un tag alle immagini corrispondente al branch di cui fa parte. Nel caso di un topic il nome sarà ad esempio: ``topic-default-ci``. Operatività ============= Dal momento che c'è l'immagine pronta, il passaggio in produzione è semplicemente (suppongo di avere già fatto io la prima configurazione con il db):: docker-compose up -d Quando si deve aggiornare l'immagine che però ha lo stesso nome (in quanto sullo stesso branch) si può lanciare:: docker-compose pull Ho modificato il comando ``dj`` in modo che si renda conto che siamo in un contesto docker e faccia le cose correttamente, ovvero esegua il comando all'interno del docker anche se lo lanciate dal server ospitante. Ad esempio all'inizio:: dj migrate dj createsuperuser Se modificate il file di setting o volete spegnere il solo django:: dj reload dj stop dj start Ogni altro comando funziona come vi aspettate... Se per qualche motivo volete spegnere il docker:: docker-compose down topics ------ Se occasionalmente vi interessa installare un topic o un branch in modo semplice, sostituendo via variabili d'ambiente il nome dell'immagine django/quasar. Nel file docker-compose.yml trovate una direttiva simile a questa:: django: image: registry.thux.dev/customers/acomea/office2020.acomea.it:$TAG_DJ Dove ``TAG_DJ`` è la variabile che va sostituita per avere il nome completo. 1. Editate il file ``.ENV`` in modo che ci sia ad esempio: TAG_DJ=topic-default-my-topic-name o ad esempio: TAG_DJ=branch-default A questo punto potete procedere con i comandi sopra ``docker-compose pull && docker-compose up -d`` django multipli ---------------- Progetti come ``adk`` hanno varie istanze di django: ``office2020``, ``gfp2020``, ``www2020``. In questi casi tutte vengono fatte partire dal medesimo docker-compose ed è possibile arrivare ad interagire direttamente con quelle istanze uando l'opt ``-t`` (target) di ``dj``. Per conoscere l'esatto nome di target da usare dovere analizzare il fiel ``docker-compose.yml`` e guardare il noe del servizio (django, gfp, www2020 nel nostro caso) e quindi:: dj -t gfp shell dj -t www2020 shell Andrà direttamente ad aprire la shell corretta. dj sh ------ ``dj`` offre anche la possibilità di arrivare direttamente alla shell del docker senza usare la sintassi più lunga (``docker-compose exec django``) :: dj sh dj -t gfp sh Requirements ============= L'installazione avviene in automatico all'interno della pipeline, quindi è vitale che il file ``requirements.txt`` sia correttamente compilato con tutte le dipendenze con versione. È altresì importante che siano messe le più recenti compatibilmente con il sistema usato. In questo modo, soprattutto per le installazioni con file binari, si riesce ad evitare la compilazione mettendo le wheel. Per ottenere un ``requirements.txt`` recente potete ad esempio eliminare ogni vincolo non necessario (es.: 'Django=>2.2,<3' lasciatelo...) e reinstallare tutto. Ovviamente qeesto va bene in sviluppo, occorre essere molto più prudenti in produzione, particolarmente se non ci sono test. Attenzioni =========== Rispetto a quanto succedeva prima, tenete presente che: * deve esistere ``requirements.txt`` * deve esistere ``manage.py`` * `settings/__init__.py` + deve permettere creazione link da variabile ENV + deve permettere creazioen dinaminca di SECRET_KEY * ``dj compilemessages`` non è necessario in quanto è già fatto all'interno del docker * ``dj collectstatic`` non è necessario * Il database sta di fatto su una macchina diversa, non viene raggiunto come ``localhost`` * se avete necessità di provare una immagine di un topic lanciate il comando così:: TAG=topic-default-my-topic docker-compose up -d Per dockerizzare un nostro progetto =================================== È suff: * aggiungere ``Dockerfile`` (quasi sempre lo stesso usato ad esempio in opencapital o cookiecutter) * ``uwsgi.ini`` uguale per tutti i progetti * aggiungere ``settings/__init__.py`` (opencapital o cookiecutter) * ``.gitlab-ci.yml`` (opencapital o cookiecutter) * Zoooom è più complesso in quanto aveva bisogno di librerie aggiuntive, non escludo di semplificarlo.