settembre 11, 2022 · python cloud foundry sap Docker

HTTP API Python su SAP BTP Cloud Foundry - Docker version

Nel mio precedente post ho spiegato come creare un programma Python che realizzasse un servizio REST, e come pubblicarlo su SAP BTP Cloud Foundry. Tutto bello, tutto funziona, ma ci sono dei limiti.

Il principale, a mio avviso, è l'incertezza legata alle dipendenze, in particolar modo binarie. Come precedentemente visto con poppler, non è garantito che la dipendenza binaria di una specifica libreria Python sia installabile su un sistema con provisioning automatico. Se non avessi avuto poppler in conda-forge sarei stato bloccato, e costretto a cercare possibili alternative magari meno calzanti con la mia esigenza. E, ciò nonostante, il fatto stesso di essere stato costretto a usare conda-forge per questo unico requisito si porta dietro una installazione di centinaia di megabyte e che richiede 4 GB di RAM per essere portata a termine, per una applicazione che di RAM ne usa si e no 100 MB. Decisamente non ottimale, specie per un uso in produzione.

Non sarebbe meraviglioso se potessi caricare su Cloud Foudry una sorta di macchina virtuale con le mie librerie installate, e solo quelle? Non sarebbe meraviglioso se Cloud Foundry supportasse qualcosa tipo Docker? Beh, signore e signori, dopo aver picchiato la testa con violenza sulla tastiera per qualche ora leggendo documentazione SAP ho scoperto che CF supporta invero Docker!

Facciamo un passo indietro. Per chi non conoscesse Docker, possiamo immaginarlo come un meccanismo per creare ed eseguire macchine virtuali. Badate bene che le immagini Docker NON sono macchine virtuali, assolutamente; ma per qualcuno che non conosce Docker il concetto di macchina virtuale è quanto più si avvicina a quello che sto cercando di descrivere. Docker è un applicativo open source che può essere installato su macchine Linux o Windows. Attraverso Docker posso costruire delle "immagini", ovvero dei modelli di "macchine virtuali" su misura per le mie esigenze, partendo tipicamente da immagini standard e andando a modificarle.


Per prima cosa dobbiamo installare Docker sulla nostra macchina locale. Le installazioni di Docker sono reperibili sul sito ufficiale. Per Windows esiste la versione Desktop, che oltre al client da riga di comando si porta dietro anche una comoda GUI.

La descrizione dell'immagine è scritta in un "Dockerfile" (un file che per l'appunto si chiama "Dockerfile", con la D maiuscola e senza estensione), che si presenta più o meno così:

FROM python:3.10-slim
RUN mkdir -p /app
WORKDIR /app
ADD barcode.py /app
RUN apt-get update && apt-get -y install zbar-tools poppler-utils
RUN apt-get clean
RUN pip install flask pyzbar pdf2image
EXPOSE 3333
CMD [ "python", "./barcode.py"]

La direttiva FROM identifica l'immagine di partenza, che si può cercare su Docker Hub, il repository ufficiale di immagini Docker. In questo caso usiamo l'immagine "python:3.10-slim", una variante dell'immagine ufficiale Python 3 (basata su Debian Linux) con la versione 3.10 dell'interprete e "slim", quindi libera di componenti non strettamente necessari all'utilizzo di Python.

La direttiva WORKDIR identifica il path di lavoro all'interno dell'immagine.

La direttiva ADD consente di copiare file dal mio pc all'immagine. In questo caso sto copiando il mio script Python nella cartella di lavoro dell'immagine.

La direttiva RUN permette di eseguire comandi bash all'interno dell'immagine. Nell'esempio sopra è usato, tra l'altro, per chiamare apt-get (per installare package linux delle nostre librerie binarie) e pip (per installare pacchetti Python). Queste operazioni consentono di avere nella mia immagine TUTTO quello che serve per eseguire il mio script.

La direttiva EXPOSE è usata per segnalare al runtime Docker quali porte desidero esporre all'esterno della mia immagine. In questo caso ho impostato la porta 3333, stessa porta che andrò a impostare nel mio script Python.

Infine, la direttiva CMD descrive il comando da eseguire al termine della creazione dell'istanza. In questo caso è la semplice chiamata del mio script attraverso l'interprete Python.

A questo punto ho nella mia cartella di lavoro due file: il Dockerfile e il mio script barcode.py. Quest'ultimo è identico alla versione nel post precedente, salvo l'impostazione di una porta fissa.

A questo punto sono pronto a eseguire il Dockerfile e creare la mia immagine in locale. Prima di procedere bisogna creare un account gratuito su Docker Hub. Docker Hub sarà usato per rendere la mia immagine disponibile a CF, e il "tag" (nome) dell'immagine dovrà necessariamente riportare come prefisso il mio username (altrimenti non la potrò caricare con il mio utente). Il mio utente Docker Hub è "piccimario", e questo sarà il prefisso che userò in questi esempi.

Per creare l'immagine in locale usiamo questo comando. Il punto alla fine specifica di andare a cercare il Dockerfile nella cartella corrente.

docker build -t piccimario/barcode-cf .

Verifichiamo che la nostra immagine sia stata creata in locale:

docker image ls

Carichiamo la nostra immagine su Docker Hub (verrà chiesta la password del nostro account).

docker push piccimario/barcode-cf

A questo punto possiamo dire a CF di andare a prendersi questa immagine e con essa creare una applicazione (dopo aver effettuato il login come da precedente post "cf login"):

cf push barcode-docker --docker-image piccimario/barcode-cf --docker-username piccimario -k 512M -m 512M

Questo comando crea una applicazione "barcode-docker" usando la nostra immagine, prelevata con l'username "piccimario" (verrà richiesta la password). I comandi -k e -m impostano rispettivamente lo spazio di archiviazione e la RAM da riservare all'istanza, e già qui vediamo un deciso miglioramento rispetto alla situazione dello scorso post :-).

Se tutto va per il verso giusto, l'immagine viene recuperata dall'hub ed eseguita. Nello stesso spazio sulla BTP in cui in precedenza abbiamo visto la nostra applicazione Python ora vedremo la nostra nuova applicazione. Possiamo testarla con le stesse modalità viste in precedenza.

Con una richiesta di risorse così modesta possiamo tranquillamente attivare più istanze (per gestire più richieste in parallelo) con il pulsante "+ Instances".

Voilà!