Häufige Missverständnisse über die Begrenzung der Java-Anwendungsressourcen bei Verwendung von Kubernetes

Im ersten Artikel dieser Reihe werden einige der häufigsten Missverständnisse zur Begrenzung der Java-Anwendungsressourcen bei der Verwendung von ** Kubernetes ** behandelt.

In dieser Artikelserie werden wir einige der häufigsten Probleme untersuchen, auf die Unternehmenskunden bei der Verwendung von [Kubernetes] stoßen (https://www.alibabacloud.com/en/product/kubernetes).

Da die Containertechnologie (https://www.alibabacloud.com/de/product/container-service) immer ausgefeilter wird, wählen immer mehr Unternehmenskunden Docker und Kubernetes als Grundlage für ihre Anwendungsplattform. Diese Kunden haben jedoch tatsächlich viele Probleme. In dieser Artikelserie werden wir einige Erkenntnisse und Best Practices austauschen, die aus den Erfahrungen des Alibaba Cloud Container Services-Teams stammen, das unseren Kunden bei der Steuerung dieses Prozesses geholfen hat.

Für containerisierte Bereitstellungen von Java-Anwendungen gibt es Berichte, dass der aktive Java-Anwendungscontainer von OOM Killer auf mysteriöse Weise getötet wird, obwohl Sie die Containerressourcen begrenzt haben.

Dieses Problem ist das Ergebnis eines sehr häufigen Fehlers, bei dem das Containerressourcenlimit und die entsprechende JVM-Heap-Größe nicht korrekt festgelegt wurden.

Hier wird die Tomcat-Anwendung als Beispiel genommen. Der Instanzcode und die Kubernetes-Bereitstellungsdatei sind unter [GitHub] verfügbar (https://github.com/denverdino/system-info?spm=a2c65.11461447.0.0.19b432f1QUkwgh).

git clone https://github.com/denverdino/system-info
cd system-info`

Es wird die folgende Kubernetes-Pod-Definition verwendet.

  1. Die App im Pod ist der Initialisierungscontainer und für das Kopieren einer JSP-Anwendung in das Verzeichnis "webapps" des Tomcat-Containers verantwortlich. Hinweis: In der Abbildung wird die JSP-Anwendung index.jsp verwendet, um die JVM- und Systemressourceninformationen anzuzeigen.
  2. Der Tomcat-Container bleibt aktiv und begrenzt die maximale Speichernutzung auf 256 MB.
apiVersion: v1
kind: Pod
metadata:
  name: test
spec:
  initContainers:
  - image: registry.cn-hangzhou.aliyuncs.com/denverdino/system-info
    name: app
    imagePullPolicy: IfNotPresent
    command:
      - "cp"
      - "-r"
      - "/system-info"
      - "/app"
    volumeMounts:
    - mountPath: /app
      name: app-volume
  containers:
  - image: tomcat:9-jre8
    name: tomcat
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - mountPath: /usr/local/tomcat/webapps
      name: app-volume
    ports:
    - containerPort: 8080
    resources:
      requests:
        memory: "256Mi"
        cpu: "500m"
      limits:
        memory: "256Mi"
        cpu: "500m"
  volumes:
  - name: app-volume
    emptyDir: {}

Führen Sie den folgenden Befehl aus, um Ihre Anwendung bereitzustellen und zu testen.

$ kubectl create -f test.yaml
pod "test" created
$ kubectl get pods test
NAME      READY     STATUS    RESTARTS   AGE
test      1/1       Running   0          28s
$ kubectl exec test curl http://localhost:8080/system-info/
...

Informationen wie System-CPU und Speicher werden jetzt im HTML-Format angezeigt. Mit dem Befehl html2text können Sie die Informationen in das Textformat konvertieren.

Hinweis: Wir testen die Anwendung auf einem 2C 4G-Knoten. Tests in verschiedenen Umgebungen können leicht unterschiedliche Ergebnisse liefern.

$ kubectl exec test curl http://localhost:8080/system-info/ | html2text

Java version     Oracle Corporation 1.8.0_162
Operating system Linux 4.9.64
Server           Apache Tomcat/9.0.6
Memory           Used 29 of 57 MB, Max 878 MB
Physica Memory   3951 MB
CPU Cores        2
                                          **** Memory MXBean ****
Heap Memory Usage     init = 65011712(63488K) used = 19873704(19407K) committed
                      = 65536000(64000K) max = 921174016(899584K)
Non-Heap Memory Usage init = 2555904(2496K) used = 32944912(32172K) committed =
                      33882112(33088K) max = -1(-1K)

Wie Sie sehen können, beträgt der Systemspeicher im Container 3.951 MB, die maximale Heap-Größe der JVM jedoch 878 MB. Warum passiert das? Haben Sie die Ressourcenkapazität des Containers nicht auf 256 MB festgelegt? In dieser Situation beträgt die Speichernutzung der Anwendung mehr als 256 MB, die JVM implementiert jedoch keine Garbage Collection (GC). Vielmehr wird der JVM-Prozess direkt vom OOM-Killer des Systems beendet.

Grundursache des Problems:

  1. Wenn Sie die JVM-Heap-Größe nicht festlegen, wird die maximale Heap-Größe standardmäßig basierend auf der Speichergröße der Host-Umgebung festgelegt.
  2. Der Docker-Container verwendet cgroups, um die vom Prozess verwendeten Ressourcen zu begrenzen. Wenn die JVM im Container weiterhin die Standardeinstellungen verwendet, die auf den Speicher- und CPU-Kernen der Hostumgebung basieren, führt dies zu einer falschen Berechnung des JVM-Heapspeichers.

In ähnlicher Weise wird die Standardanzahl der JVM GC- und JIT-Compiler-Threads durch die Anzahl der Host-CPU-Kerne bestimmt. Wenn Sie mehrere Java-Anwendungen auf einem einzelnen Knoten ausführen, kann der GC-Thread den Wechsel zwischen Anwendungen verhindern, selbst wenn Sie ein CPU-Limit festlegen, was sich auf die Anwendungsleistung auswirken kann.

Jetzt, da wir die Grundursache für dieses Problem kennen, ist es leicht zu lösen.

Lösung

Aktivieren Sie das Ressourcenbewusstsein für Gruppe c

Die Java-Community ist sich dieses Problems ebenfalls bewusst und unterstützt jetzt die automatische Erkennung von Containerressourcenlimits in Java SE 8u131 + und JDK 9: [https://blogs.oracle.com/java-platform-group/java] -se-support-for-docker-cpu-and-memory-limit](https://blogs.oracle.com/java-platform-group/java-se-support-for-docker-cpu-and-memory- Grenzwerte? spm = a2c65.11461447.0.0.19b432f1RHoeWq)

Fügen Sie die folgenden Parameter hinzu, um diese Methode zu verwenden:

java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap ⋯

Fügen Sie gemäß dem vorherigen Beispiel für den Tomcat-Container die Umgebungsvariable "JAVA_OPTS" hinzu.

apiVersion: v1
kind: Pod
metadata:
  name: cgrouptest
spec:
  initContainers:
  - image: registry.cn-hangzhou.aliyuncs.com/denverdino/system-info
    name: app
    imagePullPolicy: IfNotPresent
    command:
      - "cp"
      - "-r"
      - "/system-info"
      - "/app"
    volumeMounts:
    - mountPath: /app
      name: app-volume
  containers:
  - image: tomcat:9-jre8
    name: tomcat
    imagePullPolicy: IfNotPresent
    env:
    - name: JAVA_OPTS
      value: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap"
    volumeMounts:
    - mountPath: /usr/local/tomcat/webapps
      name: app-volume
    ports:
    - containerPort: 8080
    resources:
      requests:
        memory: "256Mi"
        cpu: "500m"
      limits:
        memory: "256Mi"
        cpu: "500m"
  volumes:
  - name: app-volume
    emptyDir: {}

Stellen Sie nun den neuen Pod bereit und wiederholen Sie den Test.

$ kubectl create -f cgroup_test.yaml
pod "cgrouptest" created

$ kubectl exec cgrouptest curl http://localhost:8080/system-info/ | html2txt
Java version     Oracle Corporation 1.8.0_162
Operating system Linux 4.9.64
Server           Apache Tomcat/9.0.6
Memory           Used 23 of 44 MB, Max 112 MB
Physica Memory   3951 MB
CPU Cores        2
                                          **** Memory MXBean ****
Heap Memory Usage     init = 8388608(8192K) used = 25280928(24688K) committed =
                      46661632(45568K) max = 117440512(114688K)
Non-Heap Memory Usage init = 2555904(2496K) used = 31970840(31221K) committed =
                      32768000(32000K) max = -1(-1K)

Wie Sie sehen können, wurde die maximale Größe des JVM-Heapspeichers auf 112 MB geändert, und die Anwendung wird vom OOM-Killer nicht beendet. Dies wirft jedoch ein anderes Problem auf. Warum sollte der maximale JVM-Heapspeicher auf nur 112 MB festgelegt werden, wenn ich die maximale Speicherkapazität für Container auf 256 MB festgelegt habe?

Die Antwort hat mit den Details der JVM-Speicherverwaltung zu tun. Der Speicherverbrauch in der JVM umfasst sowohl den Heap-Speicher als auch den Nicht-Heap-Speicher. Der für Klassenmetadaten, JIT-kompatiblen Code, Thread-Stacks, GC usw. erforderliche Speicher wird aus einem Nicht-Heap-Speicher abgerufen. Basierend auf den Ressourcenlimits der cgroup reserviert die JVM daher einen Teil ihres Speichers für das Nicht-Heaping, um die Systemstabilität sicherzustellen. (Im vorherigen Beispiel sehen Sie nach dem Starten von Tomcat, dass der Nicht-Heap-Speicher fast 32 MB belegt.)

Die neueste Version von JDK 10 wurde für JVM-Vorgänge in Containern weiter optimiert und verbessert.

Kenntnis der Ressourcengrenzen von Gruppen in Containern

Wenn Sie die neuen Funktionen in JDK 8 und 9 nicht nutzen können (z. B. wenn Sie noch eine ältere JDK 6-Anwendung verwenden), verwenden Sie ein Skript im Container, um die Ressourcenlimits für die cgroup des Containers abzurufen und damit die Ressourcenlimits der JVM abzurufen. Sie können die Heap-Größe festlegen.

Ab Docker 1.7 werden die cgroup-Informationen des Containers im Container bereitgestellt, und Anwendungen können Einstellungen wie Speicher und CPU aus Dateien wie "/ sys / fs / cgroup / memory / memory.limit_in_bytes" abrufen. Daher enthält der Befehl zum Starten der Anwendung im Container die richtigen Ressourceneinstellungen basierend auf den cgroup-Einstellungen, z. B. -Xmx, -XX: ParallelGCThreads.

Fazit

In diesem Artikel werden einige häufig auftretende Probleme bei der Heap-Konfiguration behandelt, die beim Ausführen von Java-Anwendungen in Containern auftreten. Im Gegensatz zu virtuellen Maschinen sind in Containern Ressourcenbeschränkungen mithilfe von C-Gruppen implementiert. Darüber hinaus können Speicher- und CPU-Zuweisungen Ressourcenkonflikte und -probleme verursachen, wenn der interne Containerprozess die cgroup-Grenzwerte nicht kennt.

Dieses Problem kann sehr einfach behoben werden, indem neue JVM-Funktionen und benutzerdefinierte Skripts verwendet werden, um Ressourcenlimits korrekt festzulegen. Diese Lösungen lösen die meisten Probleme mit der Ressourcenbeschränkung.

Bei diesen Lösungen bleibt jedoch ein Problem mit der Ressourcenbeschränkung ungelöst, das sich auf Containeranwendungen auswirkt. Einige ältere Überwachungstools und Systembefehle wie "free" und "top" erhalten weiterhin die Host-CPU- und Speichereinstellungen, wenn sie im Container ausgeführt werden. Dies bedeutet, dass der Ressourcenverbrauch nicht genau berechnet werden kann, wenn bestimmte Überwachungstools im Container ausgeführt werden. Eine gängige Lösung für dieses von der Community vorgeschlagene Problem ist LXCFS, Um die Konsistenz zwischen dem Ressourcenvisualisierungsverhalten des Containers und der virtuellen Maschine zu gewährleisten. spm = a2c65.11461447.0.0.19b432f1eicljw) ist zu verwenden. In den folgenden Artikeln wird die Verwendung dieser Methode in Kubernetes erläutert.

Alibaba Cloud Kubernetes Service ist der erste Service, der für die Integrität von Kubernetes zertifiziert wurde. Vereinfacht das Lebenszyklusmanagement von Kubernetes-Clustern und bietet eine eingebettete Integration in Alibaba Cloud-Produkte. Darüber hinaus optimiert der Service die Entwicklererfahrung von Kubernetes weiter und gibt den Benutzern die Möglichkeit, sich auf den Wert von Cloud-Anwendungen und weitere Innovationen zu konzentrieren.

Recommended Posts

Häufige Missverständnisse über die Begrenzung der Java-Anwendungsressourcen bei Verwendung von Kubernetes
Unerwartete Ausnahme bei Verwendung von DateTimeFormatter in Java
Versuchen Sie es mit dem Java Framework Nablarch [Web Application]
Spring Dependency Injection mit Java, Kotlin
ERRORCODE = -4471 tritt in einer Java-Anwendung auf, die Db2 verwendet.