这里发生了很多事情。让我们一个一个来。
您似乎在每个 pod 使用一个容器(尽管每个 pod 可以有 许多 个容器)。 requests.memory 和 limits.memory 特定于 容器,Kubernetes 计算每个 pod 的 limits 和 requests 作为所有容器限制的总和。 p>
所以想一想 - 你是说 pod 显示 904.38Mi,但你显示 requests.memory 和 limits.memory,这是每个 容器。这就是为什么我假设每个 pod 有一个容器。这是一个一般性的介绍,无法回答您的问题 - 但我们会到达那里。
然后是pod 由docker 启动,它以kubectl 开头,读取requires.memory 和limits.memory。为了简化一点:您在limits.memory 中设置的内容将作为docker -m 传递。因此,在您的情况下,用于 docker 进程的总内存为1.5GC。请记住,这是整个进程限制,而不仅仅是堆。 java 进程远不止堆,你用-Xms512m -Xmx1024m 指定。所以回答你的问题:
kubernetes 是如何达到 904.38Mi 使用率的?
这是整个进程当前正在使用的,而不仅仅是堆。从您发布的非常短的日志文件来看,您的应用程序很好。
编辑
实际上我的环境中没有 Kubernetes 仪表板来专门测试这个,所以必须安装它才能真正了解发生了什么。我对大多数事情都有暗示,但为了确保这一点,我做了一些测试。
第一件事:仪表板中的数字是什么意思?花了一段时间才找到/理解,但那是the actual resident memory of the process,这实际上是一件非常好的事情。
任何理智的OS 都知道,当有人向它请求内存时,它很少需要/利用它,因此,它以一种懒惰的方式给它内存。这在k8s 中很容易证明。假设我有一个jdk-13 JVM 并以:
kubectl run jdk-13
--image=jdk-13
--image-pull-policy=Never
--limits "memory=100Mi"
--requests "memory=10Mi"
--command -- /bin/sh -c "while true; do sleep 5; done".
通知requests.memory=10Mi 和limits.memory=100Mi。从头开始阅读答案,您已经知道特定的pod将以docker -m 100m...开头,因为limits.memory=100Mi。这很容易证明,只需将sh 转换为pod:
kubectl exec -it jdk-13-b8d656977-rpzrg -- /bin/sh
看看cgroup 说了什么:
# cat /sys/fs/cgroup/memory/memory.limit_in_bytes
104857600 // 100MB
完美!所以 pod 的内存限制是100 MB max,但是 current 内存利用率是多少,也就是占用的常驻内存是多少?
kubectl top pod
NAME CPU(cores) MEMORY(bytes)
jdk-13-b8d656977-rpzrg 1m 4Mi
好的,所以当前的内存利用率只有4MB。
如果这样做,您可以“确保”这确实是准确的:
kubectl exec -it jdk-13-b8d656977-rpzrg -- /bin/sh
在那个 pod 问题中:
top -o %MEM
并注意RES 内存与通过仪表板或kubectl top pod 报告的内存相当。
现在让我们做一个测试。假设我在那个 pod 中有这个非常简单的代码:
// run this with: java "-Xlog:gc*=debug" -Xmx100m -Xms20m HeapTest
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class HeapTest {
public static void main(String[] args) throws Exception {
// allocate 1 MB every 3 seconds
for (int i = 0; i < 40; ++i) {
byte[] b = new byte[1024 * 1024 * 1];
b[i] = 1;
System.out.println(Arrays.hashCode(b));
LockSupport.parkNanos(TimeUnit.of(ChronoUnit.SECONDS).toNanos(3));
}
}
}
我每 3 秒分配一次1MB,持续大约 2 分钟。当我在仪表板中查看此过程时,我确实看到在某个时间点,内存会增长。程序结束后,仪表板会报告内存下降。好的!这意味着内存被归还并且 RSS 内存下降。这是仪表板中的样子:
现在让我们稍微修改一下这段代码。让我们在其中添加一些 GC,让我们永远不要完成这个过程(就像典型的 spring-boot 应用程序一样):
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class HeapTest {
public static void main(String[] args) throws Exception {
// allocate 1 MB every 3 seconds
for (int i = 0; i < 40; ++i) {
byte[] b = new byte[1024 * 1024 * 1];
b[i] = 1;
System.out.println(Arrays.hashCode(b));
LockSupport.parkNanos(TimeUnit.of(ChronoUnit.SECONDS).toNanos(3));
}
for (int i = 0; i < 10; i++) {
Thread.sleep(500);
System.gc();
}
while (true) {
try {
Thread.sleep(TimeUnit.of(ChronoUnit.SECONDS).toMillis(5));
Thread.onSpinWait();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
我运行这个:
java "-Xlog:heap*=debug"
"-Xlog:gc*=debug"
"-Xlog:ergo*=debug"
-Xmx100m
-Xms20m
HeapTest
在检查日志时(就像在您的示例中一样),我确实看到堆收集得很好。但是当我查看仪表板时,内存并没有下降(与前面的示例不同)。
一旦G1GC 占用了内存,它就不太急于将它还给操作系统。在极少数情况下它可以做到这一点,这里是 one example 或 you can instruct it to do so。
这两种方式都相当痛苦,取而代之的是GC 算法更智能(并且通常很多更好)。我个人的爱去Shenandoah,让我们看看它做了什么。如果我稍微修改一下代码(这样我可以更好地证明我的观点):
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class HeapTest {
public static void main(String[] args) throws Exception {
// allocate 1/4 MB every 100 ms
for (int i = 0; i < 6000; ++i) {
byte[] b = new byte[1024 * 256];
b[i] = 1;
System.out.println(Arrays.hashCode(b));
LockSupport.parkNanos(TimeUnit.of(ChronoUnit.MILLIS).toNanos(100));
}
while (true) {
try {
Thread.sleep(TimeUnit.of(ChronoUnit.SECONDS).toMillis(5));
Thread.onSpinWait();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
然后运行它:
java "-Xlog:gc*=debug"
"-Xlog:ergo*=debug"
"-Xlog:heap*=debug"
-XX:+UnlockExperimentalVMOptions
-XX:+UseShenandoahGC
-XX:+ShenandoahUncommit
-XX:ShenandoahGCHeuristics=compact
-Xmx1g
-Xms1m
HeapTest
您将看到以下内容:
还有你should, for a fact care about this:
这种行为在资源按使用付费的容器环境中尤其不利。即使在虚拟机由于不活动而仅使用其分配的内存资源的一小部分的阶段,G1 也会保留所有 Java 堆。这导致客户一直为所有资源付费,而云提供商无法充分利用他们的硬件。
附:我还要补充一点,其他 pod 也在遭受痛苦,因为一个 pod 决定在特定的峰值处尽可能多地占用内存,并且永远不会归还它。