Gestione della memoria immagini DOCKER con JAVA



INTRODUZIONE

JAVA

Tutti oramai conoscono java sia perchè quando è nato, alla fine degli anni 90, fu una rivoluzione e sia perchè è stato il precursore dei moderni telefoni cellulari (spero che pochi di hanno avuto il dispiacere di scrivere sofware in J2ME!). 

La rivoluzione principale è stata inserire una Virtual Machine dove far girare i software astraendosi dalla reale struttura hardware della macchina.

CONTAINER DOCKER

Il concetto di "container" nasce già intorno al 1979 con il "chroot UNIX", un sistema in grado di fornire ai processi degli spazi isolati all'interno della macchina in uso. 

Dal 2013 Docker inizia a prendere piede ed essere il sistema più utilizzato in campo IT. 

Nato principalmente per simulare ambienti controllati e asettici in ambito di sviluppo software fin da subito l'attenzione dell'intera comunità si sposta negli ambienti di produzione dato che viene intuita la potenza di avere un sistema isolato che funziona in qualsiasi architettura supportata da docker stesso.

Il principio è molto vicino ad un film dentro un DVD... il film può essere riprodotto in tutti i dispositivi compatibili senza sforzo alcuno!

JAVA + DOCKER

L'unione di queste due tecnologie che a prima vista possono sembrare simili a volte può essere pericoloso perchè appunto i processi java eseguiti all'interno della JVM inseriti in un container docker non si comportano come in un ambiente sterile (solo con la JVM per intenderci).

Il problema deriva innanzitutto dal famoso comando

java -jar my-super-application.jar

Quando eseguiamo questo comando lasciamo che la JVM faccia un tuning automantico impostando i valori di GC, Heap Memory, etc etc

L'errore sta appunto nel pensare che un container sia assimilabile ad una macchina virtuale dove è possibile definire il numero di CPU da utilizzare e la quantità di RAM da assegnare.
Non è così infatti perchè i container hanno meccanismi di isolamento delle risorse assegnate ad un processo.
Ricordiamoci però che la JVM è stata scritta tanti anni prima dei sistema che controlla l'isolamento delle risorse che usa docker (cgroups)

Occorre quindi controllare bene come veicolare e gestire le risorse!!


GESTIONE DELLA MEMORIA

Uno dei problemi comuni è la gestione della memoria, infatti docker permette di limitare l'utilizzo della memoria uccidendo un processo se questo supera i valori preimpostati. Inserire lo stesso parametro su una JVM potrebbe avere dei risvolti imprevisti perchè il più delle volte la JVM non controlla direttamente la quantità di memoria utilizzata facendo appunto creare situazioni paradossali.

Un tipico esempio è dato da un applicazione java inserita in un container che alloca sempre più spazio (concatenare le stringhe ogni secondo è un'idea sufficiente).

Questa applicazione poi viene impacchettata in un'immagine docker e avviata usando il comando famoso comando java -jar my-super-application.java. Infine l'immagine viene avviata con il comando apposito con il limite della memoria impostato (vedi url)

docker run -m 250m fabryprog/example001

Se utilizziamo uno dei tanti tool oppure eseguiamo il comando descritto precedentemente con gli appositi parametri per stampare le informazioni relative alla memoria scopriremo che la JVM sta allocando molto più dei 250m di memoria impostata.

Questo deriva dal fatto che la JVM imposta automaticamente il maximum heap size pari al 1/4 della memoria fisica (oppure al più 1 GB) (vedi url)

Inoltre usando il comando -m 250m docker ha imposto al processo 250 MB di RAM con in aggiunta altri 500 MB di SWAP (totali 750 MB)

Seguendo però quello detto precedentente, docker dovrebbe killare l'applicazione che sfora i parametri di memoria impostati!

Questo è vero se non fosse che a volte la matematica può prenderci in contro piede. 

Per esempio, se abbiamo una macchina fisica con una memoria pari a 1 GB ed eseguiamo il seguente comando:

docker run -m 150m fabryprog/example001

La JVM prenderà 1/4 della nostra memoria (250 MB) e il processo non viene killato perchè rientra nei limiti imposti (150 MB RAM + 300 MB di SWAP)

Qualcuno di voi però potrebbe dire "Siamo nel 2018, chi possiede una macchina con 1 GB di RAM?"

Facciamo quindi l'esempio inverso. Ammettiamo di avere una macchina fisica con una memoria pari a 16 GB e assegniamo al nostro programma ben 800 MB

docker run -m 800m fabryprog/example001

A questo punto sappiamo che la JVM prenderà per se 1/4 della memoria (4GB) superando il limite imposto dal docker (800 MB + 1.600 MB = 2.4 GB). A questo punto verrà scatenato il kill dell'applicazione!!!

E' chiaro quindi che è sempre buona norma assegnare direttamente i giusti parametri di memoria alle nostre applicazioni JAVA all'interno di un container docker.

NOTE FINALI

Con la nuova versione di java 10 è stato inserito un flag UnlockExperimentalVMOptions che indica alla VM di leggere le informazioni da CGgroups invece del sistema fisico risolvendo definitivamente questi problemi

Alla prossima!

Commenti

Post popolari in questo blog

Hadoop, how to create a single node cluster using docker

How to install IOTA node with docker

Apache Spark - Try it using docker!