어떻게 하면 쿠버네티스에서 자바 애플리케이션에 할당된 힙 공간의 양을 우아하고 안전하게 최대화할 수 있을까요?
어떻게 하면 쿠버네티스에서 자바 애플리케이션에 할당된 힙 공간의 양을 우아하고 안전하게 최대화할 수 있을까요?
나는 이미지를 기반으로 자바 애플리케이션을 배포하는 Kubernetes 배포를 가지고 있다. 컨테이너에서 실행 중인 Java 애플리케이션 및 컨테이너 오버헤드에 대해 예상되는 다른 것은 없습니다.
나는 자바 프로세스가 도커 컨테이너 안에서 사용할 수 있는 메모리의 양을 극대화하고 예약되지만 절대 사용되지 않는 램의 양을 최소화하고 싶다.
예를 들어 다음이 있습니다:
- 각각 8기가 램이 있고 스왑이 없는 두 개의 Kubernetes 노드
- 최적으로 작동하기 위해 최대 1GB의 힙을 소비하는 Java 프로세스를 실행하는 Kubernetes 배포
메모리 제한으로 인해 Kubernetes가 POD를 종료하지 않으면서 두 노드에서 실행되는 포드의 양을 안전하게 최대화하려면 어떻게 해야 합니까?
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
spec:
replicas: 1
template:
metadata:
labels:
app: my-deployment
spec:
containers:
- name: my-deployment
image: myreg:5000/my-deployment:0.0.1-SNAPSHOT
ports:
- containerPort: 8080
name: http
resources:
requests:
memory: 1024Mi
limits:
memory: 1024Mi
Java 8 업데이트 131+에는 -XX: 플래그가 있습니다:+ CGroup Memory LimitForHeap을 사용하여 Kubernetes 배포에서 발생하는 Docker 제한을 사용합니다.
내 도커 실험은 쿠버네티스에서 무슨 일이 일어나고 있는지 보여준다
도커에서 다음을 실행하면:
docker run -m 1024m anapsix/alpine-java:8_server-jre_unlimited java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XshowSettings:vm -version
이해:
VM settings:
Max. Heap Size (Estimated): 228.00M
이 낮은 값은 Java가 기본적으로 -XX:MaxRAMFraction을 4로 설정하고 RAM의 약 1/4이 할당되기 때문입니다...
도커에서 -XX:MaxRAMFraction=2와 동일한 명령을 실행하는 경우:
docker run -m 1024m anapsix/alpine-java:8_server-jre_unlimited java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XshowSettings:vm -XX:MaxRAMFraction=2 -version
이해:
VM settings:
Max. Heap Size (Estimated): 455.50M
마지막으로 MaxRAMFraction=1을 빠르게 설정하면 Kubernetes가 내 컨테이너를 죽입니다.
docker run -m 1024m anapsix/alpine-java:8_server-jre_unlimited java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XshowSettings:vm -XX:MaxRAMFraction=1 -version
이해:
VM settings:
Max. Heap Size (Estimated): 910.50M
우리가 이 경우에 하는 일은 쿠버네츠에서 높은 메모리 제한으로 시작하고, 부하 상태에서 시간 경과에 따라 관찰하며, 우리가 도달하고자 하는 수준으로 메모리 사용을 조정하거나, 메모리 제한(및 요청)을 실제 메모리 소비에 맞게 조정하는 것이다. 사실, 우리는 보통 두 가지 접근법을 혼합하여 사용한다. 이 방법의 핵심은 클러스터에서 적절한 모니터링을 사용하도록 설정하는 것입니다(이 경우 Prometheus). 높은 수준의 미세 조정을 원하는 경우 설정을 조정할 때 메트릭에 대한 자세한 정보를 얻기 위해 JMX Prometheus 내보내기와 같은 기능을 추가할 수도 있습니다.
여기서 문제는 kubernetes 메모리 제한이 컨테이너에 대한 것이고 MaxRAMFraction이 jvm에 대한 것이라는 것입니다. 따라서 jvm 힙이 kubernetes 제한과 동일한 경우 컨테이너 자체에 충분한 메모리가 남아 있지 않습니다.
네가 시도할 수 있는 한 가지는 증가하는 것이다
limits:
memory: 2048Mi
제한을 그대로 유지합니다. 요청과 제한의 근본적인 차이점은 노드 수준에서 사용 가능한 메모리가 있는 경우 요청을 통해 제한을 초과할 수 있다는 것입니다. 이것은 이상적인 솔루션이 아닐 수도 있고 jvm 위에 포드가 얼마나 많은 메모리를 소비하는지 파악해야 하지만 빠른 해결책으로 증가하는 것이 효과적일 것이다.
쿠버네티스가 당신의 포드를 죽이는 이유는. 컨테이너 오버헤드와 메모리 사용 사양에서 십진 접두사와 이진 접두사 간의 일반적인 불일치 때문에 계산하기가 어렵습니다. 제 해결책은 제한을 완전히 포기하고 요구 사항(예약된 경우 어떤 경우에도 포드가 사용할 수 있는 것)만 유지하는 것입니다. 정적 규격을 통해 힙을 제한하고 리소스 요구 사항을 통해 단일 노드에 예약된 포드 수를 Kubernetes가 관리할 수 있도록 JVM을 사용합니다.
먼저 원하는 힙 크기로 실행할 때 컨테이너의 실제 메모리 사용량을 결정해야 합니다. 예약된 호스트 도커 데몬을 사용하여 포드를 실행하고 연결합니다. 실행하여 포드를 찾고 현재 메모리 사용량(JVM 힙, 직접 메모리와 같은 기타 정적 JVM 사용량 및 컨테이너 오버헤드(glibc를 사용한 알파인)을 확인합니다. 이 값은 JVM 외부에서 처리되는 일부 네트워크 사용 때문에 키비바이트 내에서만 변동해야 합니다. 이 값을 포드 템플릿에 메모리 요구 사항으로 추가합니다.
노드의 다른 구성 요소가 제대로 작동하기 위해 필요한 메모리 양을 계산하거나 추정합니다. 적어도 쿠버네티스 쿠벨렛, 리눅스 커널, 사용자랜드, 아마도 SSH 데몬, 그리고 당신의 경우에는 도커 데몬이 그것들에서 실행될 것이다. 추가로 몇 바이트를 남겨둘 수 있다면 쿠벨렛을 제외한 1 Gibyte와 같은 넉넉한 기본값을 선택할 수 있다. 및 큐벳 플래그를 지정하고 다시 시작합니다. 이렇게 하면 노드에서 실행할 수 있는 포드 수를 결정할 때 예약된 리소스가 Kubernetes 스케줄러 계산에 추가됩니다. 자세한 내용은 을 참조하십시오.
이렇게 하면 위에서 선택하고 측정한 값에 따라 8기가바이트 RAM이 있는 노드에서 5~7개의 포드가 예약될 수 있습니다. 메모리 요구 사항에 지정된 RAM이 보장되며 종료되지 않습니다. 를 통해 메모리 사용량을 확인합니다. 레거시/유연성의 경우 애플리케이션에서 사용 가능한 RAM을 늘리려면 메모리 요구 사항과 JVM 힙 크기를 조정하면 됩니다.
이 접근 방식은 포드 메모리 사용량이 폭발하지 않을 경우에만 작동하며, JVM에 의해 제한되지 않을 경우 루즈 포드가 제거될 수 있습니다( 참조).
- 메모리 요청은 주로 (쿠버네티스) 포드 스케줄링 중에 사용됩니다.
- 메모리 제한은 해당 cgroup에 대한 메모리 제한을 정의합니다.
이 기사에 따르면 JVM을 구성하는 가장 좋은 방법은 다음과 같은 JVM 인수를 사용하는 것입니다:
-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0
이와 함께 메모리가 부족할 경우 항상 사용해야 합니다. 상태가 정상이라고 생각하는 상태 끝점보다 더 나쁜 것은 없지만 JVM의 메모리가 부족합니다!
-XX:+CrashOnOutOfMemoryError
참고로 75가 아닌 75.0을 지정해야 하는 버그가 있습니다
리눅스 컨테이너에 제한이 있는 Kubernetes에서 발생하는 현상을 시뮬레이션하려면 다음을 실행하십시오:
docker run --memory="300m" eclipse-temurin:17-jdk java -XX:+UseContainerSupport -XX:MinRAMPercentage=50.0 -XX:MaxRAMPercentage=75.0 -XX:+CrashOnOutOfMemoryError -XshowSettings:vm -version
결과:
VM settings:
Max. Heap Size (Estimated): 218.50M
Using VM: OpenJDK 64-Bit Server VM
오래된 자바 8에서도 작동한다:
docker run --memory="300m" eclipse-temurin:8-jdk java -XX:+UseContainerSupport -XX:MinRAMPercentage=50.0 -XX:MaxRAMPercentage=75.0 -XX:+CrashOnOutOfMemoryError -XshowSettings:vm -version
이렇게 하면 컨테이너가 cgroups(cgroups v1 또는 cgroups v2)의 요청을 읽습니다. 제한을 두는 것은 퇴거와 시끄러운 이웃들을 예방하기 위해 매우 중요하다. 나는 개인적으로 요청보다 10% 한도를 설정했다.
Java 8과 같은 이전 버전의 Java는 cgroups v2를 읽지 않으며 Docker 데스크톱은 cgroups v2를 사용합니다