JavaとDocker:誰もがこれを知っているべき

多くの開発者は、Linuxコンテナー内で実行されるJavaプロセス(特にdockerrktrunClxcfsなど)が期待どおりに動作しないことを知っているか、知っておく必要があります。 これは、ヒープのサイズを制御するために、JVMエルゴノミクスメカニズムがガベージコレクターとコンパイラーのパラメーターを個別に設定できる場合に発生します。 パラメータを設定する必要があることを示すキーなしでJavaアプリケーションを起動すると、たとえばjava -jar myapplication-fat.jar



と、JVMはアプリケーションのパフォーマンスを最適化するjava -jar myapplication-fat.jar



にいくつかのパラメータを個別に設定します。



この記事では、開発者がJavaアプリケーションをLinuxコンテナーにパックする前に知っておくべきことについて説明します。



仮想プロセッサの形式とメモリの量を設定できる仮想マシンの形のコンテナを検討します。 コンテナは、特定のプロセスに割り当てられたリソース(プロセッサ、メモリ、ファイルシステム、ネットワークなど)が他のプロセスから分離される分離メカニズムに似ています。 この分離は、Linux cgroupsカーネルメカニズムのおかげで可能です。



作業時にランタイムから取得したデータに依存する一部のアプリケーションは、cgroupが現れる前に作成されたことに注意する必要があります。 top



free



ps



、さらにはJVMのようなユーティリティは、コンテナ内での実行に最適化されていません。実際、非常に限られたLinuxプロセスです。 プログラムがコンテナ内の作業の機能を考慮しない場合に何が起こるかを見て、エラーを回避する方法を見つけましょう。



問題文



デモンストレーションのために、次のコマンドを使用して、1 GBのRAMを持つ仮想マシンにdockerデーモンを作成しました。



 docker-machine create -d virtualbox –virtualbox-memory '1024' docker1024
      
      





次に、 -m



および--memory-swap



指定された100 MBの制限を使用して、コンテナーで実行されている3つの異なるLinuxディストリビューションでfree -h



コマンドを実行しました。 その結果、それらはすべて995 MBの合計メモリ容量を示しました。









無料の-hコマンドの結果



Kubernetes / OpenShiftクラスターでも同様の結果が得られます。 次のコマンドを使用して、メモリが制限されたKubernetesコンテナーグループを実行しました。



 kubectl run mycentos –image=centos -it –limits='memory=512Mi'
      
      





この場合、クラスターには15 GBのメモリが割り当てられました。 その結果、システムによって報告されるメモリの総量は14 GBでした。









15 GBのメモリを備えたクラスターを調べる



何が起こっているのかの理由を理解するために、Linuxコンテナでメモリを操作する機能に関するこの資料を読むことをお勧めします。



Dockerキー( -m,



--memory



および--memory-swap



)、およびKubernetes(-- --limits)



キーは、指定された制限を超えようとするとLinuxカーネルにプロセス停止するように指示することを理解する必要があります。 ただし、JVMはこれについて何も認識しておらず、このような制限を超えると、良いことは期待できません。



指定されたメモリ制限を超えた後、システムがプロセスを停止する状況を再現するために、次のコマンドを使用して、メモリ制限が50 MBのコンテナでWildFly Application Serverを起動できます



 docker run -it –name mywildfly -m=50m jboss/wildfly
      
      





これで、コンテナの実行中に、制限を確認するためにdocker stats



実行できます。









コンテナ情報



数秒後、WildFlyコンテナの実行が中断され、メッセージが表示されます。



 *** JBossAS process (55) received KILL signal ***
      
      





次のコマンドを実行します。



 docker inspect mywildfly -f '{{json .State}}
      
      





彼女は、コンテナがOOM(メモリ不足、メモリ不足)状況のために停止したことを報告します。 コンテナの状態はOOMKilled=true



であることに注意してください。









コンテナを停止する理由の分析



Javaアプリケーションでの不適切なメモリ処理の影響



1 GBのメモリ(以前はdocker-machine create -d virtualbox –virtualbox-memory '1024' docker1024



によって作成された)を搭載したマシンで実行されるDockerデーモンでは、コンテナメモリは150 MBに制限され、 Springアプリケーションには十分と思われるブートすると 、JavaアプリケーションがパラメーターXX:+PrintFlagsFinal



および-XX:+PrintGCDetails



Dockerfileで指定された-XX:+PrintGCDetails



されます。 これにより、JVMエルゴノミクスメカニズムの初期パラメーターを読み取り、ガベージコレクションの開始(GC、ガベージコレクション)の詳細を確認できます。



これを試してみましょう:



 $ docker run -it --rm --name mycontainer150 -p 8080:8080 -m 150M rafabene/java-container:openjdk
      
      





/api/memory/



にエンドポイントを準備しました。これは、文字列オブジェクトをJVMメモリにロードして、大量のメモリを消費する操作をシミュレートします。 この呼び出しを行いましょう:



 $ curl http://`docker-machine ip docker1024`:8080/api/memory
      
      





エンドポイントはおよそ次のように応答します。



 Allocated more than 80% (219.8 MiB) of the max allowed JVM memory size (241.7 MiB)
      
      





これはすべて、少なくとも2つの質問につながる可能性があります。





これに対処するには、最初にJVMエルゴノミクスのドキュメントで最大ヒープサイズについて述べられていることを覚えておく必要があります 。 最大ヒープサイズは物理メモリのサイズの1/4です。 JVMはコンテナで何が実行されているかを知らないため、最大ヒープサイズは260 MBに近くなります。 コンテナの初期化時に-XX:+PrintFlagsFinal



を追加した場合、この値を確認できます。



 $ docker logs mycontainer150|grep -i MaxHeapSize uintx MaxHeapSize := 262144000 {product}
      
      





ここで、Dockerコマンドラインでm 150M



パラメーターを使用すると、Dockerデーモンがメモリとスワップファイルのサイズを150メガバイトに制限することを理解する必要があります。 その結果、プロセスは300メガバイトを割り当てることができます。これは、プロセスがLinuxカーネルからKILLシグナルを受信しなかった理由を説明しています。



Dockerコマンドライン 、メモリ制限パラメーター( --memory



)とスワップファイル( --swap



)のさまざまな組み合わせの機能については、 こちらをご覧ください



問題に対する誤った解決策の例としてのメモリ容量の増加



何が起きているのか本質を理解していない開発者は、上記の問題は環境がJVMを実行するのに十分なメモリを提供しないことであると信じがちです。 その結果、この問題に対する頻繁な解決策は、使用可能なメモリの量を増やすことですが、実際には、このアプローチ状況を悪化させるだけです。



デーモンに1 GBのメモリを提供するのではなく、8 GBを提供したとします。 作成するには、次のコマンドが適しています。



 docker-machine create -d virtualbox –virtualbox-memory '8192' docker8192
      
      





同じ考えに従って、コンテナの制限を弱め、150ではなく800 MBのメモリを提供します。



 $ docker run -it --name mycontainer -p 8080:8080 -m 800M rafabene/java-container:openjdk
      
      





curl http://`docker-machine ip docker8192`:8080/api/memory



コマンドcurl http://`docker-machine ip docker8192`:8080/api/memory



は、8 GBのメモリーを備えた環境でJVMの計算されたMaxHeapSize



パラメーターが2092957696バイトになるため、このような条件下でも実行できないことに注意してください。 (約2 GB)。 次のコマンドでこれを確認できます。



 docker logs mycontainer|grep -i MaxHeapSize
      
      











MaxHeapSizeの確認



アプリケーションは、コンテナの制限(800 MBのRAMとスワップファイル内の同じ量)を超える1.6 GBを超えるメモリを割り当てようとします。その結果、プロセスは停止します。



メモリ量を増やし、JVMが独自のパラメータを設定できるようにすることは、コンテナでアプリケーションを実行するときに常に正しいとはほど遠いことは明らかです。 Javaアプリケーションをコンテナで実行する場合、アプリケーションのニーズとコンテナの制限に基づいて、(- --Xmx



パラメータを使用して)自分で最大ヒープサイズを設定する必要があります。



問題の正しい解決策



Dockerfileの小さな変更により、JVMの追加パラメーターを定義する環境変数を設定できます。 次の行を見てください。



 CMD java -XX:+PrintFlagsFinal -XX:+PrintGCDetails $JAVA_OPTIONS -jar java-container.jar
      
      





これで、 JAVA_OPTIONS



環境変数を使用して、システムにJVMヒープのサイズを伝えることができます。 このアプリケーションには十分な300 MBがあるようです。 後で、ログを見て、そこに314572800バイト(300 MiB )の値を見つけることができます。



-e



スイッチを使用してDockerの環境変数を設定できます。



 $ docker run -d --name mycontainer8g -p 8080:8080 -m 800M -e JAVA_OPTIONS='-Xmx300m' rafabene/java-container:openjdk-env $ docker logs mycontainer8g|grep -i MaxHeapSize uintx    MaxHeapSize := 314572800       {product}
      
      





Kubernetesでは、環境変数は–env=[key=value]



スイッチを使用して設定できます。



 $ kubectl run mycontainer --image=rafabene/java-container:openjdk-env --limits='memory=800Mi' --env="JAVA_OPTIONS='-Xmx300m'" $ kubectl get pods NAME                          READY  STATUS    RESTARTS AGE mycontainer-2141389741-b1u0o  1/1    Running   0        6s $ kubectl logs mycontainer-2141389741-b1u0o|grep MaxHeapSize uintx     MaxHeapSize := 314572800     {product}
      
      





問題の正しい解決策を改善する



コンテナの制限に基づいてヒープサイズを自動的に計算できるとしたらどうでしょうか?



これは、 Fabric8コミュニティによって準備された基本的なDockerイメージを使用する場合に実現可能です。 fabric8 / java-jboss-openjdk8-jdkイメージは、コンテナの制限を検出し、利用可能なメモリの50%を上限として使用するスクリプトを使用します 。 50%の代わりに、別の値を使用できることに注意してください。 さらに、このイメージを使用すると、デバッグ、診断などを有効または無効にできます。 SpringブートアプリケーションのDockerfileがどのように見えるかを見てください。



 FROM fabric8/java-jboss-openjdk8-jdk:1.2.3 ENV JAVA_APP_JAR java-container.jar ENV AB_OFF true EXPOSE 8080 ADD target/$JAVA_APP_JAR /deployments/
      
      





これですべてが正常に機能するようになります。 コンテナのメモリ制限に関係なく、Javaアプリケーションは、デーモンのパラメータに基づいてではなく、コンテナのパラメータに応じて常にヒープサイズを調整します。

























Fabric8開発の使用



まとめ



JVMには、コンテナ化された環境で実行することを決定し、メモリやプロセッサなどの一部のリソースの制限を考慮する手段がまだありません。 したがって、JVMエルゴノミクスメカニズムに最大ヒープサイズを単独で設定させることはできません。



この問題を解決する1つの方法は、Fabric8 Baseイメージを使用することです。これにより、システムはコンテナーのパラメーターに基づいてヒープサイズを自動的に調整できます。 このパラメーターは個別に設定できますが、自動化されたアプローチの方が便利です。



JDK9には、コンテナ(たとえばDocker)のcgroupsメモリ制限に対する実験的なJVMサポートが含まれています。 ここで詳細を見つけることができます。



ここで、JVMとメモリ使用の機能について説明したことに注意してください。 プロセッサは別のトピックであるため、さらに詳しく説明します。



親愛なる読者! LinuxコンテナでJavaアプリケーションを操作するときに問題が発生しましたか? 出会ったら、どう対処したか教えてください。



All Articles