Spring Boot incontra Docker
Abbiamo già visto, nella serie di esercitazioni dedicate a Spring Boot, come realizzare delle applicazioni di un certo livello (supporto per i database, API, documentazione con Swagger ecc.) con un minimo sforzo di programmazione.
In sostanza si tratta di utilizzare le annotazioni giuste al posto giusto, scegliendo le componenti più adatte al nostro progetto (Spring Cloud, Spring JPA, Spring MVC e via dicendo).
Ora vedremo come creare delle immagini Docker di queste applicazioni. Non mi dilungherò cercando di spiegare i vantaggi dell'uso dei container: basti pensare ai termini scalabilità e architetture a microservizi (di cui parleremo nei prossimi appuntamenti).
Spring Boot si presta molto bene allo scopo perchè consente di creare i cosiddetti "fat jar" che includono un server, come Tomcat o Jetty, in grado di far girare le applicazioni autonomamente.
Cerchiamo di capire cosa ci serve.
Il primo requisito è un'installazione di Docker sulla nostra macchina. In questo post ho illustrato come fare su Linux, in particolare con la distribuzione Ubuntu 16.04. Naturalmente anche gli utenti di Windows e Mac potranno trovare ulteriori dettagli nella documentazione ufficiale.
Poi dobbiamo scegliere un'immagine di base da cui partire per creare quella definitiva. A tal proposito è doverosa qualche precisazione. Va da sé che avremo bisogno di Java, quindi dovremo installare nella nostra immagine il JRE o il JDK (quale dei due lo vedremo tra un attimo) oppure partire da qualcosa di già pronto all'uso.
Una regola d'oro nel mondo Docker è quella di minimizzare le dimensioni finali dell'immagine prodotta. A tale scopo è molto frequente ricorrere ad Alpine. Si tratta appunto di un'immagine ridotta all'osso di circa 5 MB ed equipaggiata con Busybox a cui è possibile aggiungere selettivamente tutto ciò di cui si ha effettivamente bisogno. Vi rimando alla pagina ufficiale su Docker Hub dove troverete maggiori dettagli ed esempi.
Nel nostro caso cerchiamo un'immagine basata su Alpine ma con Java preinstallato.
Sebbene la questione delle licenze Oracle Java sia relativa al mondo embedded (vedi Raspberry & C) è ormai abbastanza diffuso l'utilizzo di implementazioni alternative come OpenJDK o Azul (forse avrete già sentito parlare di Zulu Embedded). Quindi ci orienteremo su OpenJDK.
Dunque cosa scegliere: JRE o JDK? Dipende.
Se abbiamo già una versione precompilata della nostra applicazione (il famoso "fat jar") ci basta il solo ambiente di runtime. A titolo di esempio vi illustro il workflow che utilizziamo per il nostro progetto Freedomotic. Il codice è ospitato su GitHub e, ad ogni push sul branch master, il nostro sistema di CI basato su Jetbrains Teamcity genera un nuovo package che viene scaricato e configurato opportunamente all'interno dell'immagine Docker ospitata su Docker Hub. Il tutto in maniera automatica sfruttando il meccanismo dei trigger messo a disposizione dal repository.
Per ulteriori dettagli potete dare un'occhiata al Dockerfile.
Se invece vogliamo o siamo obbligati a compilare il codice sorgente ci servirà un JDK. Questa soluzione è molto utile quando vogliamo pubblicare una nuova immagine Docker ad ogni aggiornamento del codice sul repository. Sebbene si possa ricorrere ad uno specifico plugin per maven, in questo esempio vedremo come fare tutto a livello di Dockerfile.
Il codice completo, come sempre, si trova sul mio repository GitHub. Si tratta del classico Hello World in Spring Boot.
Analizziamo le poche righe necessarie per produrre l'immagine
FROM maven:3.5-jdk-8-alpine
COPY src /usr/src/hello-docker/src
COPY pom.xml /usr/src/hello-docker
RUN mvn -f /usr/src/hello-docker/pom.xml clean package
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/usr/src/hello-docker/target/hello-docker-0.0.1-SNAPSHOT.jar"]
Prima di tutto partiamo dall'immagine maven:3.5-jdk-8-alpine che evidentemente è basata su Alpine, contiene il JDK 8 nonchè maven che utilizzeremo per la compilazione.
Copiamo la cartella src e il file pom.xml dal repository GitHub al filesystem dell'immagine e lanciamo il comando maven per generare il package. Infine dobbiamo esporre la porta 8080 per consentire l'accesso all'applicazione dal browser (EXPOSE) e specificare il comando che verrà eseguito all'avvio del container (ENTRYPOINT).
Per creare l'immagine possiamo impostare una build su Docker Hub oppure lavorare in locale. In questo caso, dopo aver clonato il repository ed essersi posizionati nella cartella contenente il Dockerfile, basterà eseguire
sudo docker -t hello-docker .
In breve abbiamo specificato il nome da attribuire all'immagine, che avrà come tag "latest", e indicato la cartella corrente come sorgente.
Non ci resta che avviare un container con sudo docker run -d --name=hello-docker -p 8080:8080 hello-docker:latest
Quindi puntiamo il browser all'indirizzo http://localhost:8080/hello e il gioco è fatto.
Naturalmente ci sono da fare delle ottimizzazioni in quanto l'immagine pesa 136 MB per un semplice Hello World. Questo è dovuto al fatto che ci portiamo dietro il JDK anche dopo aver fatto la compilazione.
Ne parleremo in dettaglio la prossima volta.
Stay tuned!