Compilare una nuova immagine Docker: il Dockerfile

docker - Dockerfile
docker – Dockerfile

Se non hai ancora letto questo articolo, ti consiglio vivamente di farlo perché la sua comprensione precede lo studio del presente articolo.

Introduzione

Un Dockerfile è uno script con le istruzioni su come creare un’immagine Docker. Queste istruzioni sono, infatti, un gruppo di comandi eseguiti automaticamente in ambiente Docker per costruire un’immagine Docker specifica.

In questo tutorial, scopri come creare un’immagine Docker con un Dockerfile.

Salto la parte relativa all’installazione di Docker già trattata altrove nel blog.

Creare un Dockerfile

La prima cosa che devi fare è creare una directory in cui puoi archiviare tutte le immagini Docker che crei.

Ad esempio, creeremo una directory denominata ~/docker/images , e un file Dockerfile al suo interno con il comando:

$ mkdir -p ~/docker/images && cd ~/docker/images && touch Dockerfile

Ora occorre editare il Dockerfile e questo apre un grande capitolo sulla sintassi dei Dockerfile: i Dockerfile sono degli script che servono ad allestire l’immagine (che programmi usare? ad esempio Python, PostgreSQL… che librerie/dipendenze mi servono? ad ese GDAL, … che software devo fare girare? ad esempio quello che ho sviluppato io che dovrà trovarsi nella cartella /app/ del container). Un semplice esempio tratto dal blog di Sofija è questo che dà un primo esempio di template che delinea alcune funzionalità (mutatis mutandis):

FROM ubuntu  # <--- 1

MAINTAINER Marco Barbato <marco@betaingegneria.it>  # <--- 2

RUN apt-get update  # <--- 3

CMD ["echo", "Ciao, Mondo!"]  # <--- 4
  1. FROM: Definisce le fondamenta dell’immagine che stai creando. Puoi iniziare da un’immagine padre (come nell’esempio sopra) o da un’immagine base.
    Quando si utilizza un’immagine padre, si utilizza un’immagine esistente su cui ne basi una nuova.
    Usare un’immagine base significa invece che stai partendo da zero (che è esattamente come la definiresti: FROM scratch).
  2. MANTAINER: Specifica l’autore dell’immagine. Qui puoi digitare il tuo nome e/o cognome (o anche aggiungere un indirizzo email). Puoi anche utilizzare l’istruzione LABEL per aggiungere metadati a un’immagine.
  3. RUN: istruzioni per eseguire un comando mentre si costruisce un’immagine in un livello sopra di essa. In questo esempio, il sistema cerca gli aggiornamenti del repository una volta iniziata la creazione dell’immagine Docker. Puoi avere più di un’istruzione RUN in un Dockerfile.
  4. CMD: Può esserci solo un’istruzione CMD all’interno di un Dockerfile. Il suo scopo è fornire i valori predefiniti per un container in esecuzione. Con esso, imposti un comando predefinito. Il sistema lo eseguirà se si esegue un container senza specificare un comando.

Creare una immagine da un Dockerfile

La sintassi generale per costruire (build) una immagine a partire da un file Dockerfile è questa:

$ docker build [OPTIONS] PATH | URL | -

Pertanto fondamentalmente dovrai lanciare questo comando

$ docker build /path/to/dockerfile

Se sei già nella direcotry del Dockerfile, semplicemente scriverai:

$ docker build .

Siccome ogni immagine che viene creata viene battezzata con uno stringone esadecimale, che è poco mnemonico, si può associare un tag all’immagine per aiutarsi ad organizzare le immagini:

$ docker build -t my_image .
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM ubuntu
 ---> 27941809078c
Step 2/4 : MAINTAINER marco Barbato <marco@betaingegneria.it>
 ---> Running in 7cd4f42b5e84
Removing intermediate container 7cd4f42b5e84
 ---> d0377afa0d1b
Step 3/4 : RUN apt-get update
 ---> Running in b223682b3e0f
Get:1 http://security.ubuntu.com/ubuntu jammy-security InRelease [110 kB]
Get:2 http://archive.ubuntu.com/ubuntu jammy InRelease [270 kB]
Get:3 http://security.ubuntu.com/ubuntu jammy-security/restricted amd64 Packages [313 kB]
Get:4 http://security.ubuntu.com/ubuntu jammy-security/main amd64 Packages [332 kB]
Get:5 http://security.ubuntu.com/ubuntu jammy-security/multiverse amd64 Packages [4644 B]
Get:6 http://security.ubuntu.com/ubuntu jammy-security/universe amd64 Packages [131 kB]
Get:7 http://archive.ubuntu.com/ubuntu jammy-updates InRelease [114 kB]
Get:8 http://archive.ubuntu.com/ubuntu jammy-backports InRelease [99.8 kB]
Get:9 http://archive.ubuntu.com/ubuntu jammy/restricted amd64 Packages [164 kB]
Get:10 http://archive.ubuntu.com/ubuntu jammy/universe amd64 Packages [17.5 MB]
Get:11 http://archive.ubuntu.com/ubuntu jammy/multiverse amd64 Packages [266 kB]
Get:12 http://archive.ubuntu.com/ubuntu jammy/main amd64 Packages [1792 kB]
Get:13 http://archive.ubuntu.com/ubuntu jammy-updates/universe amd64 Packages [258 kB]
Get:14 http://archive.ubuntu.com/ubuntu jammy-updates/multiverse amd64 Packages [7791 B]
Get:15 http://archive.ubuntu.com/ubuntu jammy-updates/restricted amd64 Packages [354 kB]
Get:16 http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 Packages [631 kB]
Get:17 http://archive.ubuntu.com/ubuntu jammy-backports/universe amd64 Packages [5814 B]
Fetched 22.3 MB in 6s (4013 kB/s)
Reading package lists...
Removing intermediate container b223682b3e0f
 ---> 719d25fccd26
Step 4/4 : CMD ["echo", "Ciao, Mondo!"]
 ---> Running in aa4e9ccb74e0
Removing intermediate container aa4e9ccb74e0
 ---> ade2f7ecc592
Successfully built ade2f7ecc592
Successfully tagged my_image:latest

Ora si possono vedere tutte le immagini create con il comando docker image ls (oppure docker images):

$ docker image ls
REPOSITORY    TAG       IMAGE ID       CREATED      SIZE
<none>        <none>    e7a5d4ae5d10   4 days ago   1.44GB
sent_crunch   latest    6dcb0bbb67c7   4 days ago   1.27GB

Creare un container da una immagine

Il passo successivo è avviare un nuovo contenitore Docker in base all’immagine che hai creato nei passaggi precedenti. Chiameremo il contenitore “test” e lo creeremo con il comando:

$ sudo docker run --name test my_image
Ciao, Mondo!

Il messaggio Ciao, Mondo! dovrebbe apparire come output nella shell, come effetto dell’ultimo comando del Dockerfile:

CMD ["echo", "Ciao, Mondo!"]

Conclusione

L’utilizzo di Dockerfile è il modo più semplice e veloce per creare un’immagine Docker. Automatizza il processo passando attraverso lo script con tutti i comandi per assemblare un’immagine.

Quando crei un’immagine Docker, assicurati anche di mantenere le dimensioni dell’immagine Docker contenute. Evitare immagini di grandi dimensioni velocizza la creazione e la distribuzione dei container. Pertanto, è fondamentale ridurre al minimo le dimensioni dell’immagine.

Riferimenti

Docker: terminologia base

Partire con Docker non è difficile ma mi sono trovato subito a fare confusione tra questi tre termini: image, container, volume. Provo a spiegarmeli con l’aiuto di questi articoli veramente chiari di Sofija Simic che trovate linkati alla fine del post e che rimastico quà e là un po’ a modo mio.

Differenza tra image e container

Cos’è un’immagine Docker?

Un’immagine Docker (image) è un file immutabile (non modificabile) che contiene il codice sorgente, le librerie, le dipendenze, gli strumenti e altri file necessari per l’esecuzione di un’applicazione.

A causa della loro qualità di sola lettura, queste immagini sono talvolta denominate istantanee (snapshots). Rappresentano un’applicazione e il suo ambiente virtuale in un momento specifico. Questa coerenza è una delle grandi caratteristiche di Docker. Consente agli sviluppatori di testare e sperimentare software in condizioni stabili e uniformi.

Poiché le immagini sono, in un certo senso, solo modelli (templates), non è possibile avviarle o eseguirle. Quello che puoi fare è usare quel modello come base per costruire un contenitore.

Un contenitore è, in definitiva, solo un’immagine in esecuzione.

Una volta creato un contenitore, viene aggiunto un livello scrivibile sopra l’immagine immutabile, il che significa che ora puoi modificarlo.

L’immagine di base dalla quale crei un contenitore esiste separatamente e non può essere modificata. Quando si esegue un ambiente containerizzato, si crea essenzialmente una copia di lettura e scrittura di quel filesystem (immagine docker) all’interno del container. Ciò aggiunge un livello contenitore che consente le modifiche dell’intera copia dell’immagine.

Docker: container e uno stack di images
Docker: container e uno stack di images

Puoi creare un numero illimitato di immagini Docker da un’unica immagine di base. Ogni volta che modifichi lo stato iniziale di un’immagine e salvi lo stato esistente, crei un nuovo modello con un livello aggiuntivo sopra di esso, in una struttura a pila (stack).

Le immagini Docker possono, quindi, essere costituite da una serie di livelli, ciascuno diverso ma anche originato dal precedente. I livelli immagine rappresentano file di sola lettura a cui viene aggiunto un livello contenitore una volta utilizzati per avviare un ambiente virtuale.

Cos’è un container Docker?

Un container Docker è un ambiente di runtime virtualizzato in cui gli utenti possono isolare le applicazioni dal sistema sottostante. Questi contenitori sono unità compatte e portatili in cui è possibile avviare un’applicazione in modo rapido e semplice.

Una caratteristica preziosa è la standardizzazione dell’ambiente di calcolo in esecuzione all’interno del container. Non solo garantisce che la tua applicazione funzioni in circostanze identiche, ma semplifica anche la condivisione con altri collaboratori.

Poiché i container sono autonomi, essi forniscono un forte isolamento, assicurando che non interrompano altri container in esecuzione, nonché il server che li supporta. Docker afferma che queste unità “forniscono le più potenti capacità di isolamento del settore”. Pertanto, non dovrai preoccuparti di proteggere la tua macchina durante lo sviluppo di un’applicazione.

A differenza delle macchine virtuali (VM) in cui la virtualizzazione avviene a livello di hardware, i container vengono virtualizzati a livello di app. Possono utilizzare una macchina, condividere il suo kernel e virtualizzare il sistema operativo per eseguire processi isolati. Ciò rende i contenitori estremamente leggeri, consentendo di trattenere risorse preziose.

Differenza tra macchina virtuale (VM) e Container Docker
Differenza tra macchina virtuale (VM) e Container Docker

Contenitori contro Immagini

Quando si parla della differenza tra immagini e contenitori, un errore che capita di fare è quello di metterli in contrasto come entità antagoniste. Entrambi gli elementi sono invece strettamente correlati e fanno parte di un sistema definito dalla piattaforma Docker.

Se hai letto le due sezioni precedenti che definiscono le immagini e i contenitori Docker, potresti già avere una certa comprensione di come i due stabiliscono una relazione.

Le immagini possono esistere senza contenitori, mentre un contenitore deve eseguire un’immagine per esistere.

Pertanto, i contenitori dipendono dalle immagini e le utilizzano per creare un ambiente di runtime ed eseguire un’applicazione.

I due concetti esistono come componenti essenziali (o meglio come fasi) nel processo di esecuzione di un container Docker. Avere un contenitore in esecuzione è la “fase” finale di quel processo, a indicare che dipende dai passaggi e dai componenti precedenti. Ecco perché le immagini Docker essenzialmente governano e modellano i contenitori.

Dal Dockerfile all’image al container

Tutto inizia con uno script di istruzioni che definiscono come creare un’immagine Docker specifica. Questo script è chiamato Dockerfile. Eseguendo un Dockerfile, vengono eseguiti singolarmente i comandi elencati e viene creata un’immagine Docker.

Il comando per creare un’immagine da un Dockerfile è docker build.

L’immagine viene quindi utilizzata come modello (o base), che uno sviluppatore può copiare e utilizzarla per eseguire un’applicazione. L’applicazione necessita di un ambiente isolato in cui eseguire: un container.

Questo ambiente non è solo uno “spazio” virtuale. Si basa interamente sull’immagine che lo ha creato. Il codice sorgente, i file, le dipendenze e le librerie binarie, che si trovano tutti nell’immagine Docker, costituiscono un contenitore.

Per creare un contenitore da un’immagine, usa il comando docker create.

Infine, dopo aver avviato un contenitore da un’immagine esistente, ne avvii il servizio ed esegui l’applicazione.

Riutilizzo di un contenitore come immagine

Se apporti modifiche all’immagine iniziale e desideri conservarla per lavori futuri, puoi salvare l’immagine modificata facendo uno screenshot dello stato corrente del contenitore. In questo modo, alleghi un livello contenitore sopra l’immagine, costruendo infine una nuova immagine immutabile. Di conseguenza, ti ritroverai con due immagini Docker derivate dallo stesso filesystem.

Docker volumes: creazione e utilizzo

I volumi Docker sono strumenti ampiamente utilizzati e utili per garantire la persistenza dei dati mentre si lavora nei container. Sono un’alternativa migliore rispetto alla compilazione di livelli scrivibili aggiuntivi, che aumentano le dimensioni dell’immagine Docker.

In pratica si potrebbero scrivere i contenuti dentro ai container ma così facendo se distruggiamo il container perdiamo anche i dati.

Cosa sono i volumi Docker?

I volumi Docker sono file systems montati su container Docker per preservare i dati generati dal container in esecuzione.

È come se montassimo un hard disk USB nel nostro filesystem per utilizzare i dati anche a computer spento.

I volumi vengono archiviati nell’host, indipendentemente dal ciclo di vita del contenitore. Ciò consente agli utenti di eseguire facilmente il backup dei dati e condividere file system tra contenitori.

Primi passi con i volumi

Esistono diversi modi per montare un volume Docker durante l’avvio di un container. Si possono utilizzare in alternativa i flag -v e --mount, che vengono aggiunti al comando docker run.

Questo articolo mostra esempi di entrambi i flag in uso.

Funzioni di manutenzione dei volumi

Creare un volume Docker

Per creare un volume Docker utilizzare il comando:

$ docker volume create [volume_name]

In conseguenza a questo comando, Docker crea una directory per il volume sull’host nel percorso /var/lib/docker/volume/. È ora possibile montare questo volume su un container, garantendo la persistenza dei dati e la condivisione dei dati tra più container.

Ad esempio, per creare un volume con il nome data, eseguire il comando:

$ docker volume create data

Elenco dei volumi

Per verificare di aver creato correttamente un volume Docker, chiedi a Docker di elencare tutti i volumi disponibili con:

$ docker volume list

Nelle prove fatte finora ho creato un po’ di volumi e questo è il risultato

# docker volume ls
DRIVER    VOLUME NAME
local     0effd02df709acd840c431ad7ffa8c24f19107b0fc534cd86b47b2d7a2d38ded
local     83eac56a552efee4167050921b771395da0100f94df95b72bee7fdbfb95c9ff0
local     35409dbb5911bf3ad9454375a7d4c855f5fe46f7800acc3a0e8b204417b274f0
local     b6c20a389cc440e77208aa62c48792f3c3d381253ba200fbb7779048eb729d72

L’output visualizza un elenco di volumi, specificando la loro posizione (DRIVER) e il loro nome VOLUME NAME.

Si può vedere la stessa situazione dal file system dell’host, a parttire dalla posizione in cui Docker crea i volumi:

$ ls -l /var/lib/docker/volumes
totale 48
drwx-----x  6 root root  4096 ago  9 10:17 ./
drwx--x--- 13 root root  4096 ago  9 10:17 ../
drwx-----x  3 root root  4096 giu 29 17:25 0effd02df709acd840c431ad7ffa8c24f19107b0fc534cd86b47b2d7a2d38ded/
drwx-----x  3 root root  4096 giu 29 12:25 35409dbb5911bf3ad9454375a7d4c855f5fe46f7800acc3a0e8b204417b274f0/
drwx-----x  3 root root  4096 giu 29 12:12 83eac56a552efee4167050921b771395da0100f94df95b72bee7fdbfb95c9ff0/
drwx-----x  3 root root  4096 giu 29 17:08 b6c20a389cc440e77208aa62c48792f3c3d381253ba200fbb7779048eb729d72/
brw-------  1 root root  8, 1 ago  9 10:17 backingFsBlockDev
-rw-------  1 root root 32768 ago  9 10:17 metadata.db

Ispezionare i volumi

Per visualizzare ulteriori informazioni su un volume Docker, utilizzare il comando inspect:

# docker volume inspect b6c20a389cc440e77208aa62c48792f3c3d381253ba200fbb7779048eb729d72
[
    {
        "CreatedAt": "2022-06-29T17:13:42+02:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/b6c20a389cc440e77208aa62c48792f3c3d381253ba200fbb7779048eb729d72/_data",
        "Name": "b6c20a389cc440e77208aa62c48792f3c3d381253ba200fbb7779048eb729d72",
        "Options": null,
        "Scope": "local"
    }
]

Il comando elenca i dettagli di un volume, inclusa la sua posizione nel filesystem host (Mountpoint). Tutto ciò che è memorizzato all’interno del volume di dati può essere trovato anche nella directory elencata sotto il percorso del punto di montaggio.

Montare un volume in un container

Come quando si monta un disco USB collegandolo ad una porta del computer, per montare un volume di dati in un contenitore, aggiungi il flag --mount al comando docker run. Docker aggiunge il volume al contenitore specificato, dove archivia i dati prodotti all’interno dell’ambiente virtuale.

Per eseguire un container e montarvi un volume di dati, segui la sintassi di base:

$ docker run --mount source=[volume_name],destination=[path_in_container] [docker_image]

Basta sostituire [path_in_container] con il percorso in cui desideri posizionare il volume di dati nel contenitore (ad esempio /app/data). Tutto ciò che è memorizzato in quella directory viene automaticamente salvato anche sul volume di dati sull’host.

Ad esempio, per avviare un contenitore Ubuntu e montarvi il volume di dati, eseguire:

$ docker run -it --name=example1 --mount source=data,destination=/app/data myimage

Il comando indica a Docker di eseguire (run) un contenitore in modalità interattiva (-it, in conseguneza a questa opzione si aprirà una shell sul contenitore) dall’immagine myimage, con il nome example1, mentre monta i dati del volume nella directory /app/data all’interno del contenitore.

Quindi, controlla per verificare che il volume sia stato montato correttamente elencando il contenuto del contenitore con il comando ls dalla console del contenitore.

Riferimenti

Network pills: traceroute

network traceroute
network traceroute

traceroute (per gli utenti Windows il programma si chiama tracert) è un programma di utilità per il network che troviamo nei sistemi operativi Linux e Mac che permette di seguire il percorso di un pacchetto di dati dal nostro computer ad un qualsiasi altro host per evidenziare criticità inm questa trasmissione.

Il percorso di un un pacchetto in Internet (ma anche in una rete chiusa, come ad esempio un’azienda) è un percorso fatto di tanti punti discreti in cui il primo punto (origine) è il PC che invia il pacchetto e l’ultimo punto (destinazione) è l’host che vogliamo raggiungere. Per andare dal’origine alla destinazione, il pacchetto attraversa (ogni device attraversato è un “hop” ovvero un salto) un certo numero di dispositivi che hanno il compito di fare avanzare il pacchetto: sono i router. Ogni router, quando riceve il pacchetto dal suo predecessore, guarda nell’intestazione TCP/IP qual è l’indirizzo di destinazione e decide in base ad un algoritmo di percorso minimo (shortest path) a quale router inviarlo per farlo progredire nel suo cammino.

Ogni hop comporta un tempo che va a sommarsi al tempo totale che il pacchetto impiegherà.

Inoltre, per evitare che un pacchetto giri all’infinito senza raggiungere la destinazione, il protocollo IP prevede di dargli un numero massimo di chance prima di essere scartato: è il “tempo di vita” (TTL Time To Live) che è un numero intero che viene diminuito di 1 ad ogni hop. Ogni device che prende in carico il pacchetto, prima di inoltrarlo al prossimo device prende il valore di TTL nell’intestazione TCP e lo diminuisce di 1:

TTL = TTL -1

Traceroute ci aiuta a diagnosticare eventuali problemi di rete nel raggiungere un certo host analizzando il percorso di un singolo pacchetto ICMP.

Un altro elemento chiave da comprendere è il “tempo di andata e ritorno” (RTT, Round Trip Time). Traceroute assicura che ogni hop sulla strada per un dispositivo di destinazione rilasci un pacchetto e restituisca un messaggio di errore ICMP. Ciò significa che traceroute può misurare la durata del tempo tra l’invio dei dati e la ricezione del messaggio ICMP per ogni hop, fornendo il valore RTT per ogni hop.

Ma come fa traceroute a ritornare un risultato ad ogni hop?

Prima cosa: traceroute fa affidamento sulla capacità di routing di ogni singolo nodo che separa l’origine dalla destinazione e, in realtà, lui non invia un solo pacchetto ma ne invia molti, almeno quanto è il numero massimo di hop previsto. Il funzionamento è questo

  1. traceroute invia un pacchetto ICMP verso la destinazione finale con TTL = 1.
  2. Ciò significa che appena il pacchetto ha raggiunto il primo router sarà già morto e il router informa di questo traceroute che mette via l’indirizzo IP del router raggiunto assieme al RTT e lo stampa.
  3. traceroute invia quindi un nuovo pacchetto ICMP verso la destinazione e gli assegna un TTL=2; il pacchetto raggiungerà il router 1 (che potrebbe anche essere diverso da quello di prima, ma non importa); questi a sua volta lo consegna al router 2 e qui il pacchetto muore. Quindi router 2 invia indietro un pacchetto ICMP all’origine con l’informazione dell’esaurimento del TTL e quindi traceroute pusha nella coda l’indirizzo IP del router2 insieme al RTT.

E così via. Quindi traceroute invia fino a 30 pacchetti, ogni singolo invio è detto probe, (a meno che non gli indichiamo noi il numero di pacchetti massimo con l’opzione -m max_ttl o --max-hops=max_ttl) e per ognuno registra l’indirizzo raggiunto e il tempo di andata e ritorno.

Leggere l’output di traceroute

Un esempio è il seguente

root@jsbach:/home/marcob# traceroute -m 30 www.google.com
traceroute to www.google.com (142.250.180.164), 30 hops max, 60 byte packets
 1  _gateway (192.168.83.125)  2.392 ms  2.375 ms  2.335 ms
 2  10.8.0.1 (10.8.0.1)  45.510 ms  38.503 ms  38.480 ms
 3  192.168.251.30 (192.168.251.30)  53.828 ms  54.004 ms  53.981 ms
 4  192.168.255.21 (192.168.255.21)  41.459 ms  53.936 ms  42.337 ms
 5  194.149.188.16 (194.149.188.16)  53.890 ms  53.868 ms  53.845 ms
 6  * 194.149.187.14 (194.149.187.14)  51.268 ms  51.217 ms
 7  * * partner-network-device.nflxvideo.net (66.197.165.233)  44.639 ms
 8  72.14.220.82 (72.14.220.82)  58.851 ms  55.300 ms  61.741 ms
 9  * * *
10  142.251.50.134 (142.251.50.134)  77.915 ms 142.251.50.138 (142.251.50.138)  76.934 ms 108.170.232.168 (108.170.232.168)  77.465 ms
11  142.250.211.23 (142.250.211.23)  77.647 ms mil04s44-in-f4.1e100.net (142.250.180.164)  77.586 ms  77.523 ms
root@jsbach:/home/marcob# 

Il comando stampa una intestazione con scritto il nome host (con il suo indirizzo IP che si è fatto dare dal DNS locale o dal file /etc/hosts), il numero massimo di probe che invierà e la dimensione del pacchetto (60 bytes).

Segue la descrizione di ogni probe (ogni probe si avanza di un hop con il meccanismo di aumento di 1 del TTL per ogni probe). Ogni riga ha questa struttura:

#hop | nome host (se esiste, altrimenti l'indirizzo IP) | (indirizzo IP) | RTT1 | RTT2 | RTT3 |

Hop 1: _gateway è il device che mi connette ad internet, in questo momento il mio cellulare utilizzato come hotspot; il dhcp mi ha dato come indirizzo 192.168.83.118, _gateway ha l’indirizzo 192.168.83.125 (sono chiaramete sulla stessa sottorete 192.168.83). Seguono poi tre misure temporali (2.392 ms 2.375 ms 2.335 ms) che sono rispettivamente tre round tript time per tre distinti pacchetti (quindi per ogni probe in realtà traceroute invia 3 pacchetti e non uno soltanto): questo per mostrare la coerenza, o una sua mancanza, nel percorso.

Hop 2: 10.8.0.1 è il primo router incontrato dal pacchetto

e così via.

Cosa sono gli asterischi?

In realtà, se leggiamo bene, questi asterischi sono scritti nella colonna sbagliata, dovrebbero essere scritti così:

 1  _gateway (192.168.83.125)  2.392 ms  2.375 ms  2.335 ms
 2  10.8.0.1 (10.8.0.1)  45.510 ms  38.503 ms  38.480 ms
 3  192.168.251.30 (192.168.251.30)  53.828 ms  54.004 ms  53.981 ms
 4  192.168.255.21 (192.168.255.21)  41.459 ms  53.936 ms  42.337 ms
 5  194.149.188.16 (194.149.188.16)  53.890 ms  53.868 ms  53.845 ms
 6  194.149.187.14 (194.149.187.14)  *          51.268 ms  51.217 ms
 7  partner-network-device.nflxvideo.net (66.197.165.233)  * * 44.639 ms
 8  72.14.220.82 (72.14.220.82)  58.851 ms  55.300 ms  61.741 ms
 9  * * *
10  142.251.50.134 (142.251.50.134)  77.915 ms 
    142.251.50.138 (142.251.50.138)  76.934 ms 
    108.170.232.168 (108.170.232.168)  77.465 ms
11  142.250.211.23 (142.250.211.23)  77.647 ms 
    mil04s44-in-f4.1e100.net (142.250.180.164)  77.586 ms  77.523 ms

cioè sono dati che vanno scritti nelle colonne dei RTT: sono pacchetti che si sono persi; a volte si perdono per un singolo invio (hop 6), a volte in due (hop 7) a volte in tutti e tre i probe (come si vede all’hop 9).

Questo a significare che statisticamente non tutti i router riusciranno a mandarci indietro il pacchetto e perderemo il tempo di transito di quell’hop.

Analisi dei risultati

Prima di addentrarci nell’analisi occorre rscrivere l’outut in un modo più ordinato:

root@jsbach:/home/marcob# traceroute -m 30 www.google.com
traceroute to www.google.com (142.250.180.164), 30 hops max, 60 byte packets
 1  _gateway (192.168.83.125)  				   2.392 ms   2.375 ms   2.335 ms
 2  10.8.0.1 (10.8.0.1)  45.510 ms  		  38.503 ms  38.480 ms
 3  192.168.251.30 (192.168.251.30)  		  53.828 ms  54.004 ms  53.981 ms
 4  192.168.255.21 (192.168.255.21)  		  41.459 ms  53.936 ms  42.337 ms
 5  194.149.188.16 (194.149.188.16)  		  53.890 ms  53.868 ms  53.845 ms
 6  194.149.187.14 (194.149.187.14)               *          51.268 ms  51.217 ms
 7  partner-network-de... (66.197.165.233)        *          *          44.639 ms
 8  72.14.220.82 (72.14.220.82)                   58.851 ms  55.300 ms  61.741 ms
 9  						  * 	     *          *
10  142.251.50.134 (142.251.50.134)  		  77.915 ms 
    142.251.50.138 (142.251.50.138)  		  76.934 ms 
    108.170.232.168 (108.170.232.168)  	          77.465 ms
11  142.250.211.23 (142.250.211.23)  		  77.647 ms 
    mil04s44-in-f4.1e10  (142.250.180.164)        77.586 ms  77.523 ms
root@jsbach:/home/marcob# 

In generale i tempi di latenza (i delta più grandi) si possono localizzare all’inizio, in mezzo o alla fine del percorso. Possono esserci problemi soltanto nei casi in cui la latenza riguarda l’inzio del percorso (problemi del gateway) oppure all’ultimo passaggio, che può essere sintomo di diversi problemi, per esempio uno può essere quello della presenza di un firewall in entrata (della destinazione) che rende difficile la consegna dei pacchetti all’ultimo step.

Riferimenti

Git, istruzioni di soccorso

Vi segnalo (e ricordo a me stesso) questo utilissimo sito web che descrive scenari molto comuni, generatori di ansia quando si lavora con Git.

Un guida scritta in stile what if? che potrà aiutarmi (-vi) di scuro in diverse occasioni. Il nome è molto esplicativo: Oh Shit, Git??

https://ohshitgit.com/

Cambiare skin all’applicazione con Bootstrap Italia

Devo vedere come si fa a cambiare rapidamente uno schema di colori e mi avventuro in questo howto di Bootstrap Italia.

Installazione di yarn (package manager)

Per installare il package di playground di Boostrap Italia (un esempio semplice funzionante out-of-the-box) seguire [1].

C’è bisogno di utilizzare il package manager Yarn, ma non mi funziona quanto detto in [2] per cui trovo in rete questo howto che invece funziona:

marcob@jsbach:~/Scaricati$ mkdir yarn
marcob@jsbach:~/Scaricati$ cd yarn/
marcob@jsbach:~/Scaricati/yarn$ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
[sudo] password di marcob: 
OK
marcob@jsbach:~/Scaricati/yarn$ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
deb https://dl.yarnpkg.com/debian/ stable main
marcob@jsbach:~/Scaricati/yarn$ sudo apt-get update
marcob@jsbach:~/Scaricati/yarn$ sudo apt-get install yarn -y

Compilazione di Bootstrap Italia Playground

Mi muovo quindi all’interno della directory bootstrap-italia-playground (che ho scaricato da [1]):

marcob@jsbach:~/IdeaProjects/grails$ cd bootstrap-italia-playground/

e lancio l’installazione dell’applicazione con yarn

marcob@jsbach:~/IdeaProjects/grails/bootstrap-italia-playground$ yarn install
yarn install v1.22.19
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
$ mkdir -p ./deps && cp -R ./node_modules/bootstrap-italia/dist/ ./deps/bootstrap-italia && cp -R ./node_modules/jquery/dist/ ./deps/jquery && cp -R ./node_modules/popper.js/dist/ ./deps/popper.js
Done in 4.82s.

Quindi avvio il server

marcob@jsbach:~/IdeaProjects/grails/bootstrap-italia-playground$ yarn start
yarn run v1.22.19
$ yarn prepare && http-server .
$ mkdir -p ./deps && cp -R ./node_modules/bootstrap-italia/dist/ ./deps/bootstrap-italia && cp -R ./node_modules/jquery/dist/ ./deps/jquery && cp -R ./node_modules/popper.js/dist/ ./deps/popper.js
Starting up http-server, serving .

http-server version: 14.1.0

http-server settings: 
CORS: disabled
Cache: 3600 seconds
Connection Timeout: 120 seconds
Directory Listings: visible
AutoIndex: visible
Serve GZIP Files: false
Serve Brotli Files: false
Default File Extension: none

Available on:
  http://127.0.0.1:8080
  http://10.1.23.54:8080
  http://10.251.0.32:8080
Hit CTRL-C to stop the server
....
[seguono il log di accesso a localhost:8080]

Ora punto il browser a http://127.0.0.1:8080/ e vedo questo:

bootstrap-italia playground
bootstrap-italia playground

Personalizzazione

Adesso, come dice l’how-to, posso continuare a leggere [1]. Non manca molto in realtà!

Ah nota bene: avventurarsi a modificare a mano i CSS è come entrare in un roveto ardente: Bootstrap usa una forte parametrizzazione, quindi i valori RGB vengono calcolati (dal colore primario a tutte le sue declinazioni): per cui è più conveniente guardare come funziona.

Per personalizzare i colori di Bootstrap Italia, esamino il file scss/bootstrap-italia-custom.scss, dove il colore $primary è descritto nelle sue componenti HSB (Hue, Saturation, Brightness = Tonalità, saturazione, Luminosità. vedi [5]).

Occorre quindi modificare questi tre parametri a piacimento (questo è il cuore del funzionamento).

Per esempio devo mutare lo schema da blu a verde. Vado quindi, per esempio, in questo sito [4] (ma va bene una qualunque ruota dei colori che si trova in rete) dove scelgo il verde che mi piace, ad esempio quello RBG = #67E098 corrispondente a (H,S,B)=(144,54,88)

Per ottenere una versione personalizzata della libreria quindi:

  • Compilare la libreria Bootstrap Italia personalizzata con: yarn build
  • La compilazione crea dei file nella cartella css/compiled che vanno referenziati nel file index.html (posso mantenere il vecchio file bootstrap-italia.min.css rinominandolo con .old – oppure mi affido a Git).

Il risultato è immediato:

bootstrap-italia-verde
bootstrap-italia-verde

Bootstrap Italia

Bootstrap Italia è un progetto di Developers Italia, un’iniziativa del Ministro Per l’Innovazione Tecnologica – Dipartimento per la Transizione Digitale + AgID [3]

Risorse

  1. Bootstrap Italia
  2. Yarn
  3. MITD
  4. Adobe Color Wheel
  5. Coordinate HSB dello spazio colore (Wikipedia)

Docker application container

docker
docker

Docker è un gestore di container, un sistema che consente di gestire degli ambienti di lavoro isolati per sviluppare applicazioni.

Ciò che mi ha spinto ad utilizzare Docker è l’urgenza di confinare un’applicazione che sto scrivendo in un ambiente stabile immune dagli avanzamenti di versione a cui devo sottoporre regolarmente il sistema operativo (Ubuntu Linux) della mia macchina (HP ProBook 440G).

Ho bisogno di una sandbox che rimanga stabile per tutto lo sviluppo dell’applicazione.

Questa prassi avrà poi un altro risvolto positivo, quello di poter trasferire (“deployare”) la sandbox così com’è nell’ambiente di collaudo o di produzione, e rendere così le operazioni CI/CD molto rapide e indolori.

Istruzioni per installare Docker su una macchina Ubuntu.

Passo 1: installazione

Seguo la guida del sito [1]; non funziona il comando com’è scritto, ma il suggerimento che danno poco prima, quello di studiarsi prima lo script, è illuminante: lo eseguo da root una riga alla volta:

# Executing docker install script, commit: b2e29ef7a9a89840d2333637f7d1900a83e7153f
apt-get update -qq >/dev/null
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq apt-transport-https ca-certificates curl >/dev/null
mkdir -p /etc/apt/keyrings && chmod -R 0755 /etc/apt/keyrings
curl -fsSL "https://download.docker.com/linux/ubuntu/gpg" | gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu jammy stable" > /etc/apt/sources.list.d/docker.list
apt-get update -qq >/dev/null
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends docker-ce docker-ce-cli containerd.io docker-compose-plugin docker-scan-plugin >/dev/null
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq docker-ce-rootless-extras >/dev/null

e riesco a terminare.

Passo 2: post-installazione: usare Docker come non root user

Eseguire questi comandi se si vuole agire da utente normale (vedi i drawbacks in [2])

$ sudo groupadd docker
$ sudo usermod -aG docker $USER
$ cat /etc/group | grep docker
docker:x:999:

Passo 3: test installazione

Digitare

$ sudo docker run hello-world

Output

[sudo] password di marcob: 
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete 
Digest: sha256:13e367d31ae85359f42d637adf6da428f76d75dc9afeb3c21faea0d976f5c651
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:

Utilizzando il package Manager di Ubuntu

Provo anche a installare con il gestore di pacchetti APT.

$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

e verifico:

$ sudo docker run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

Un po’ più complicato: una installazione Ubuntu intera

Ho bisognno di un container con una versione di Ubuntu precedeente a quella della mia macchina.

L’esempio seguente fa al caso mio. Ovviamente dovrò approfondire, ma mi pare di essere sulla buona strada.

$ sudo docker run -it ubuntu bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
405f018f9d1d: Pull complete 
Digest: sha256:b6b83d3c331794420340093eb706a6f152d9c1fa51b262d9bf34594887c2c7ac
Status: Downloaded newer image for ubuntu:latest
root@9d0779c49ba7:/# whoami
root
root@9d0779c49ba7:/# hostname
9d0779c49ba7
root@9d0779c49ba7:/# pwd
/
root@9d0779c49ba7:/# cd home
root@9d0779c49ba7:/home# ll
total 8
drwxr-xr-x 2 root root 4096 Apr 18 10:28 ./
drwxr-xr-x 1 root root 4096 Jun 17 13:16 ../

La prossima sfida è installare una release di Ubuntu leggermente più vecchia (la 21.04 Hirsute Hippo), Python, GDAL, QGIS e PostgrSQL.

Problema di conflitto degli indirizzi di rete

Per impostazione predefinita, il sistema di virtualizzazione Docker utilizza le reti 172.17.0.0/12 per il suo funzionamento. Se queste impostazioni creano conflitti nelle reti a cui dobbiamo connetterci, occorre cambiare questo indirizzo e per fare questo si possono modificare le impostazioni.

Per vedere quale rete sta usando il tuo demone, lista la confgurazione delle interfacce di rete:

$ ifconfig 
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 fe80::42:13ff:fe52:d90  prefixlen 64  scopeid 0x20<link>
        ether 02:42:13:52:0d:90  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 3  bytes 306 (306.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
...

Io sto lavorando in una rete aziendale che ha una sottorete esattamente all’indirizzo 172.17.0. Non riesco più a raggiungere le macchine di questa sottorete…

Quindi devo poter cambiare la configurazione di docker.

Step 1 creare un file di configurazione

Alla posizione

/etc/docker

creare il file daemon.js

$ touch daemon.js 

con questo contenuto:

{
	"live-restore": true,
	"bip": "10.10.0.1/16",
	"default-address-pools": [{
		"base": "10.0.0.0/8",
		"size": 16
	}]
}

Sostitutire gli indirizzi IP a seconda del bisogno (in questa rete mi vanno bene questi indirizzi),

Riavviare il demone

$ sudo service docker restart

Ripetendo il comando:

$ ifconfig 
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 10.10.0.1  netmask 255.255.0.0  broadcast 10.10.255.255
        inet6 fe80::42:13ff:fe52:d90  prefixlen 64  scopeid 0x20<link>
        ether 02:42:13:52:0d:90  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 3  bytes 306 (306.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

e riesco ad accedere alla sottorete nella quale devo lavorare.

Risorse

  1. Installazione di Docker
  2. Post-installazione
  3. Networking
  4. Dockerizing Python apps

Interrogare una tabella Oracle con like su campo LONG

oracle sqldeveloper
oracle sqldeveloper

Dovevo cercare una determinata stringa in una colonna TESTO di una tabella Oracle definita come LONG (contiene informazioni di testo, XML nel mio caso). Un’operazione semplice, pensavo.

Ho utilizzato l’operatore LIKE:

SELECT ID, TESTO
FROM TEMPLATE
WHERE UPPER(TESTO) LIKE '%LOREM%'

ma con scarso successo, infatti l’errore che si è verificato è il seguente:

ORA-00932: tipi di dati incoerenti: previsto CHAR, ottenuto LONG
00932. 00000 -  "inconsistent datatypes: expected %s got %s"
*Cause:    
*Action:
Errore alla riga: 2, colonna: 13

Infatti non posso utilizzare l’operatore LIKE su campi LONG (in genere lo uso con i campi VARCHAR2).

Da [1] apprendo che addirittura i campi LONG non possono nemmeno comparire nelle clausole di filtro (WHERE) delle query.

Non solo.

Le colonne di tipo LONG non possono comparire in vincoli di integrità (fatta eccezione per NULL/NOT NULL).

Le colonne di tipo LONG non possono comparire in una espressione regolare.

Ogni tabella può avere al più una colonna di tipo LONG.

Eccetera.

Insomma una serie di condizioni molto vincolanti. Ma soprattutto non posso cercare il testo che voglio.

Il tipo LONG è tuttavia un tipo di dato deprecato in favore del tipo CLOB, atto a contenere testo che eccede i 4000 byte

Sempre in [1] trovo la soluzione: occorre trasformare il dato in un Large Object (LOB). Creo quindi una nuova tabella così:

CREATE TABLE ZZZ_TEMPLATE AS
SELECT ID, TO_LOB(TESTO) AS TESTO
FROM TEMPLATE;

-- Creato table ZZZ_TEMPLATE.

Dunque posso eseguire la query:

SELECT * FROM zzz_template
WHERE UPPER(TESTO) LIKE '%LOREM%';

--
137	"<?xml version="1.0" encoding="ISO-8859-1"?>..."
312	"<?xml version="1.0" encoding="ISO-8859-1"?>..."
311	"<?xml version="1.0" encoding="ISO-8859-1"?>..."
310	"<?xml version="1.0" encoding="ISO-8859-1"?>..."

Risorse web

Spock test con Intellij IDEA per applicazioni Groovy

Spock Framework
Spock Framework
Groovy logo
Groovy logo

Questo post verte soltanto su un aspetto se volete marginale – ma molto utile – della IDE che uso per progetti PHP / Python / Groovy: IntelliJ IDEA di JetBrains: come la IDE ci aiuta a non commettere errori del tipo “leggere valori di variabili private”.

Scrivere un test (Specification) con Spock

Prendo l’esempio dal bel video tutorial di Spock di Trisha Gee che vi linko qui sotto:

Il codice è suo e qui lo analizzo soltanto (faccio solo piccole, innocue personalizzazioni). Precisamente: descrivo un po’ più in dettaglio un comportamento della IDE che mi aiuta a scrivere meglio il software.

Sto scrivendo (più precisamente: analizzando il funzionamento di) una classe di test con Spock che riguarda una classe Groovy Polygon (posto gli screenshot perché risulta evidente il comportamento della IDE di cui voglio parlare): la classe ha una sola proprietà che si chiama numberOfSides, il numero di lati del poligono, che è un attributo privato:

Spock for Groovy – private variable

Ora scriviamo un test (nel gergo di Spock i test si chiamano Specifications) che controlla se l’attribuzione del numero di lati del poligono funziona come ci aspettiamo; utilizziamo il costrutto given – when – then (che è l’oggetto di questa parte del tutorial di Trisha):

Spock: scrivere un test di tipo given-when-then
Spock: scrivere un test di tipo given-when-then

Notiamo che la proprietà numberOfSides dell’oggetto è evidenziata; passando con il mouse sopra la parte messa in risalto, la IDE ci informa sul perché di questa evidenziazione (Access to ‘numberOfSides’ exceedes its access rights):

Spock: tentativo di assegnare direttamente il valore di una variabile privata

Stiamo infatti tentando di accedere ad un valore di una variabile privata da un’istanza della classe e non dall’interno della classe stessa.

Potremmo correggere questa cosa in due modi: o attribuendo il qualificatore public alla variabile oppure definendo un getter. Percorro questa seconda via; aggiungo quindi il metodo getNumberOfSides() alla classe – è una costruzione standard: get + nome della variabile (con la regola CamelCase):

    int getNumberOfSides() {
        return numberOfSides
    }

Ecco che la IDE non mette più in evidenza la proprietà numberOfSides:

Spock: l'uso della variabile privata non è più segnalato come scorretto.
Spock: l’uso della variabile privata non è più segnalato come scorretto.

Si noti che la IDE riconosce la presenza di un getter secondo la convenzione espressa sopra – se non seguo la convenzione la IDE tornerà a segnalare il problema. Nota che non ho cambiato la scrittura (non ho utilizzato esplicitamente il getter nell’assegnazione, ma l’ho lasciata così com’è) ma Groovy lo farà durante il build, sostituendo all’assegnazione diretta l’invocazione del getter come risulta dal tooltip che viene visualizzato quando passiamo con il mouse sopra l’istruzione:

Spock: assegnare di una variabile privata con getter
Spock: assegnare di una variabile privata con getter

Non ci resta che lanciare il test cliccando sulla freccetta verde accanto alla definizione del metodo (notare come sia possibile utilizzare stringhe custom per definire il nome dei metodi, una caratteristica molto utile per documentare ciò che il test vuole fare):

Spock: lancio del test
Spock: lancio del test

Risorse web

Protetto: Problema Postgresql su Ubuntu 22.04 Jamming Jellyfish

Questo contenuto è protetto da password. Per visualizzarlo inserisci la password qui sotto.

Gestire varie versioni di Java/JDK sulla stessa macchina

Come partire da una situazione incasinata con molte versioni di Java e diversi JDK e fare ordine con SDKMAN.

Prendo spunto dall’articolo di Gunter Rosaert di DZone.

Situazione di partenza

Ecco quello che ho a bordo della mia macchina Linux:

$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 21.10
Release:	21.10
Codename:	impish

$ update-alternatives --list java
/usr/lib/jvm/java-11-openjd-amd64/bin/java
/usr/lib/jvm/java-8-openjd-amd64/jre/bin/java
/usr/lib/jvm/jd1.8.0_202/bin/java
/usr/local/java/jre1.8.0_73/bin/java

Premetto che mi servono tutte queste versioni per fare girare diversi client di epoca diversa come:

  • Mirth Connect, versioni 2.8, 3.2, 3.5
  • Oracle SqlDeveloper

Poi ho la IDE, Intellij IDEA, che si porta appresso un’altra versione

$ ls .jdks/
openjdk-17

SDK

Ho già una versione di sdk a bordo ma deve essere aggiornata:

$ sdk version
==== BROADCAST =================================================================
* 2022-04-12: quarkus 2.8.0.Final available on SDKMAN! https://github.com/quarkusio/quarkus/releases/tag/2.8.0.Final
* 2022-04-11: neo4jmigrations 1.5.4 available on SDKMAN! https://github.com/michael-simons/neo4j-migrations/releases/tag/1.5.4
* 2022-04-10: jreleaser 1.0.0 available on SDKMAN! https://github.com/jreleaser/jreleaser/releases/tag/v1.0.0
================================================================================

SDKMAN 5.14.3


ATTENTION: A new version of SDKMAN is available...

The current version is 5.15.0, but you have 5.14.3.

Would you like to upgrade now? (Y/n): 

Scelgo di fare l’update, che risulta velocissimo:

Updating SDKMAN...
######################################################################## 100,0%
Install scripts...
sdkman_auto_answer=false
......

Successfully upgraded SDKMAN!

Open a new terminal to start using SDKMAN 5.15.0.

To join our BETA channel, simply follow the instructions on:

   http://sdkman.io/install

Enjoy!!!

Lista dei JDK

Una lista completa di tutte versioni di Java esistenti si ottiene con il comando sdk list. In particolare si possono vedere tutti i tool installabili, da Ant a Tomcat a Grails e a Groovy; l’output è rediretto a less così da poter navigare e accedere anche al menu (stampato in alto):

$ sdk list
================================================================================
Available Candidates
================================================================================
q-quit                                  /-search down
j-down                                  ?-search up
k-up                                    h-help

--------------------------------------------------------------------------------
Apache ActiveMQ (Classic) (5.16.2)                  https://activemq.apache.org/

Apache ActiveMQ® is a popular open source, multi-protocol, Java-based message
.....

                                                          $ sdk install activemq
--------------------------------------------------------------------------------
Ant (1.10.12)                                            https://ant.apache.org/

Apache Ant is a Java library and command-line tool whose mission is to drive
...

                                                               $ sdk install ant
--------------------------------------------------------------------------------

eccetera. Vengono listati tutti i tool con descrizione e relativo comando di installazione. Aggiungendo il parametro java invece vengono listate tutte le versioni di Java installabili (da Java.net, a Oracle, a Amazon, a versioni Unclassified) e vengono rilevate quelle installate nel sistema:

================================================================================
Available Java Versions for Linux 64bit
================================================================================
 Vendor        | Use | Version      | Dist    | Status     | Identifier
--------------------------------------------------------------------------------
 Corretto      |     | 18           | amzn    |            | 18-amzn             
               |     | 17.0.2.8.1   | amzn    |            | 17.0.2.8.1-amzn
................................................................................   
 Temurin       |     | 18           | tem     |            | 18-tem              
               |     | 17.0.2       | tem     |            | 17.0.2-tem          
               | >>> | 11.0.14      | tem     | installed  | 11.0.14-tem         
 Unclassified  |     | openjdk11    | none    | local only | openjdk11           
               |     | jdk1.8.0_202 | none    | local only | jdk1.8.0_202        
================================================================================

Ho limitato la lunga lista alle prime righe e alle versioni che ho effettivamente installato.

Se si vuole installare una versione si digita sdk install java [TAB] e compare la lista

sdk install java options
sdk install java options

Insomma… pare sufficiente come scelta, no? 🙂

Scegliere la JDK di default

In questo momento io ho attiva questa versione

$ java --version
openjdk 11.0.14 2022-01-18
OpenJDK Runtime Environment Temurin-11.0.14+9 (build 11.0.14+9)
OpenJDK 64-Bit Server VM Temurin-11.0.14+9 (build 11.0.14+9, mixed mode)

Per uniformità con gli ambienti di sviluppo degli altri miei colleghi ho intallato la Temurin 11.0.14.

Posso fare switch tra le varie versioni utilizzando il comando sdk default:

$ sdk default java 11.0.14-tem 

Default java version set to 11.0.14-tem

Si può anche cambiare il default per la sola sessione di terminale in cui sono:

$ sdk use java jdk1.8.0_202

Using java version jdk1.8.0_202 in this shell.

Se cito una versione che non è a bordo, mi dice di installarla prima:

$ sdk default java 17.0.2-tem 

Stop! Candidate version is not installed.

Tip: Run the following to install this version

$ sdk install java 17.0.2-tem

Posso controllare la diversa versione in uso nelle due diverse sessioni di terminale:

[Terminale 1]
$ echo $JAVA_HOME 
/home/marcob/.sdkman/candidates/java/current

[Terminale 2]
$ echo $JAVA_HOME
/home/marcob/.sdkman/candidates/java/jdk1.8.0_202

Ora si potrebbe fare pulizia delle versioni non in uso. Però, attenzione, ho altro software da far funzionare.

Ad esempio mi collego ad un server Mirth di un cliente con il client MirthConnect 3.2.0 che usa Java WebStart 11.202.2, il quale insiste su JRE 1.8.0_73-b02 Java HotSpot(TM) 64-Bit Server VM, che è l’ultima indicata nelle alternatives.

Anche il client MirthConnect Administrator 3.5.2 utilizza lo stesso JRE.

Configurare il JDK da usare per il build in Intellij

Ora il passaggio fondamentale è questo: configurare il kit da usare con l’IDE (per me Intellij IDEA). Può essere fatto a livello globale (per tutti i progetti) o per progetto (si può derogare dallo standard e prenderne una specifica per un progetto). Il build non presenta particolari problemi. Il problema bloccante si manifesta invece all’avvio dell’applicazione (run app).

Ottengo infatti questo un errore bloccante:

/home/marcob/.jdks/openjdk-17/bin/java
...
Failed to compile intellij-command-proxy.groovy: BUG! exception in phase 'semantic analysis' in source unit 'intellij-command-proxy.groovy' Unsupported class file major version 61 (Use --stacktrace to see the full trace)

Ma come si vede in realtà il kit utilizzato per esegue l’applicazione non è quello che abbiamo installato.

Si può definire un kit di default per tutti i progetti Java creati cona IDE raggiungendo questa voce dal menu: File > Project Structure e selezionare la voce SDKs sotto “Platform Settings” nel menu di sinistra:

Selezionare la JDK di default per i nuovi progetti Grails
Selezionare il SDK di default per i nuovi progetti Grails

Quindi devo recarmi nelle impostazioni del progetto IntelliJ e compiere queste due oprazioni: File > Project Structure e selezionare la voce Project sotto “Project Settings” nel menu di sinistra:

Project JDK come da impostazione SDK
SDK di progetto

Nella selezione SDK seleziono temurin-11.

La SDK di solito va impostata quando si crea il progetto:

Impostazione generale della JDK per l'intera IDE
Impostazione generale della SDK per l’intera IDE

Se si vuole usare la Temurin per tutti i progetti, bisogna impostare il SDK Globale con la voce Platform Settings > SDKs nel menu di sinistra (nota: se vedete differenze in queste schermate rispetto alla vostra IDE, la mia versione è IntelliJ IDEA 2021.3.3 (Ultimate Edition)):

Risorse web