Compreendendo o comportamento da memória Java no Docker

2

Nosso cluster atual do Kubernetes em execução no Google Container Engine (GKE) consiste em n1-standard-1 tipos de máquinas (1 CPU virtual e 3,75 GB de memória) com Debian GNU / Linux 7.9 (wheezy), mantido pelo Google. Devido ao aumento de carga e uso de memória de nossos serviços, precisamos atualizar nossos nós para um tipo de máquina maior. Enquanto tentamos isso em nosso cluster de teste, experimentamos algo que (para nós) parece bastante estranho.

A memória consumida pelo aplicativo JVM quando implantada em um nó do Google parece ser proporcional ao número de núcleos disponíveis no nó. Mesmo se definirmos a memória máxima da JVM (Xmx) para 128Mb, ela consome cerca de 250Mb em uma máquina de 1 núcleo (isso é compreensível, pois a JVM consome mais memória do que o limite máximo devido ao GC, a própria JVM etc.), mas consome cerca de 700Mb em uma máquina de 2 núcleos ( n1-standard-2 ) e cerca de 1,4Gb em uma máquina de 4 núcleos ( n1-standard-4 ). A única coisa diferente é o tipo de máquina, a mesma imagem e configurações do Docker são usadas.

Por exemplo, se eu usar o SSH em uma máquina usando um tipo de máquina n1-standard-4 e executar sudo docker stats <container_name> , obtenho isto:

CONTAINER CPU %               MEM USAGE / LIMIT    MEM %               NET I/O             BLOCK I/O
k8s.name  3.84%               1.11 GB / 1.611 GB   68.91%              0 B / 0 B 

Quando executo a imagem do same Docker com a configuração exata same (aplicativo) localmente (mac osx e docker-machine), vejo:

CONTAINER CPU %               MEM USAGE / LIMIT    MEM %               NET I/O               BLOCK I/O
name      1.49%               236.6 MB / 1.044 GB  22.66%              25.86 kB / 14.84 kB   0 B / 0 B         

O que está muito mais em sintonia com o que eu esperaria devido à configuração Xmx (para o registro eu tenho 8 núcleos e 16Gb de memória). A mesma coisa é confirmada quando executo top -p <pid> na instância do GKE, o que me dá uma alocação de memória RES / RSS de 1,1 a 1,4 Gb.

A imagem do Docker é definida assim:

FROM java:8u91-jre
EXPOSE 8080
EXPOSE 8081

ENV JAVA_TOOL_OPTIONS -Dfile.encoding=UTF-8

# Add jar
ADD uberjar.jar /data/uberjar.jar

CMD java -jar /data/uberjar.jar -Xmx128m -server

Eu também tentei adicionar:

ENV MALLOC_ARENA_MAX 4

que eu já vi recomendado em vários segmentos, como isso , mas não parece fazer diferença alguma. Eu também tentei mudar para outra imagem de base Java, bem como usando o linux alpino, mas isso não parece mudar as coisas também.

Minha versão local do Docker é 1.11.1 e a versão do Docker no Kubernetes / GKE é 1.9.1. A versão do Kubernetes (se for importante) é v1.2.4.

O que também é interessante é que, se implantarmos várias instâncias do pod / container na mesma máquina / nó, algumas instâncias alocarão muito menos memória. Por exemplo, os três primeiros podem alocar 1,1-1,4 Gb de memória, mas os 10 contêineres sucessivos alocam apenas cerca de 250 Mb cada, o que é aproximadamente o que eu esperaria que cada instância alocasse. O problema é que, se atingirmos a limitação de memória da máquina, as três primeiras instâncias (aquelas que alocam a memória de 1.1Gb) nunca parecem liberar sua memória alocada. Se eles fossem liberar memória quando a máquina estivesse sob pressão aumentada, eu não me preocuparia com isso, mas como eles mantêm a alocação de memória mesmo quando a máquina é carregada, isso se torna um problema (pois proíbe que outros recipientes sejam programados nesta máquina e assim, os recursos são desperdiçados).

Perguntas:

  1. O que poderia estar causando esse comportamento? É um problema da JVM? Problema do Docker? Problema de VM? Problema do Linux? Questão de configuração? Ou talvez uma combinação?
  2. O que eu posso tentar fazer para limitar a alocação de memória da JVM neste caso?
por Johan 30.06.2016 / 20:40

2 respostas

1

O problema foi com as configurações da JVM especificadas em Dockerfile . Nós começamos nossa JVM assim:

CMD java -jar /data/uberjar.jar -Xmx128m -server

mas quando mudamos para:

CMD java -Xmx128m -server -jar /data/uberjar.jar

as configurações foram levadas em conta. Eu ainda não entendi porque eu não vi isso localmente, mas estou feliz por termos resolvido isso.

    
por 01.07.2016 / 09:37
4

Quando você especifica

CMD java -jar /data/uberjar.jar -Xmx128m -server

, em seguida, os valores que você pretende que sejam argumentos da JVM ( -Xmx128m -server ) são passados como argumentos da linha de comandos para a classe Main no arquivo .jar. Eles estão disponíveis como os argumentos no seu método public static void main(String... args) .

Observe que isso também é verdadeiro se você estiver executando uma classe principal pelo nome, em vez de especificar um arquivo executável jar .

Para passá-los como argumentos da JVM, em vez de argumentos para o seu programa, você precisa especificá-los antes do -jar arg.

Veja link

    
por 14.06.2017 / 23:19