容器环境下 pod 的 cpu 资源不设置request会是什么样的表现

我们分别在 java 程序以及使用 stress 压测工具来模拟高负载情况下pod的资源负载情况。

环境

  • Kubernetes 1.24
  • Containerd 1.6.16
  • CentOS 8 (node节点 12c32g)
  • Java-openjdk 11

一,在不设置 containers.resources.requests.cpu 资源限制的情况下,程序能使用到多少CPU资源?

Deployment 资源定义文件:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
     matchLabels:
       app: nginx
  replicas: 1
  template:
     metadata:
       labels:
         app: nginx
     spec:
       containers:
       - name: nginx
         image: nginx:1.14.2
         ports:
         - containerPort: 80
         resources:  {}

这里将 resources 字段设置为空。

stress是一个linux的压力测试工具,专门用于对设备的CPU、IO、内存、负载、磁盘等进行压测。 适用在监控告警类、主机资源跑高等测试上。

1. stress 压测

使用参数 -c 6 选项参数来模拟6个核心满载的情况,看看能否跑得上去:

$ apt update
$ apt install stress procps -y
$ stress -c 6

观察ps aux进程的CPU使用率情况:

# ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root        3677  0.0  0.0   7280   828 pts/1    S+   13:37   0:00 stress -c 6
root        3678 99.0  0.0   7280    84 pts/1    R+   13:37   3:26 stress -c 6
root        3679 99.1  0.0   7280    84 pts/1    R+   13:37   3:27 stress -c 6
root        3680 99.1  0.0   7280    84 pts/1    R+   13:37   3:27 stress -c 6
root        3681 99.1  0.0   7280    84 pts/1    R+   13:37   3:27 stress -c 6
root        3682 99.0  0.0   7280    84 pts/1    R+   13:37   3:26 stress -c 6
root        3683 99.1  0.0   7280    84 pts/1    R+   13:37   3:27 stress -c 6

其中PID 3677 为父进程,用于管理执行压测任务的worker进程。

我们可以看到,后续一共fork了6个子进程,且每个子进程CPU占用率都几乎满载。

我们再来看一下 prometheus 监控中的pod cpu占用情况:

可以看到,的确CPU是使用了6个核心,符合预期。

我们大胆一点,把压力给到 16 个任务,看看能否把 node 的12个核心都压满:

$ stress -c 16

观察后台 ps aux 进程任务情况:

# ps aux 
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root        3779  0.0  0.0   7280   880 pts/1    S+   14:17   0:00 stress -c 16
root        3780 71.7  0.0   7280    88 pts/1    R+   14:17  10:09 stress -c 16
root        3781 70.9  0.0   7280    88 pts/1    R+   14:17  10:03 stress -c 16
root        3782 70.5  0.0   7280    88 pts/1    R+   14:17   9:59 stress -c 16
root        3783 71.0  0.0   7280    88 pts/1    R+   14:17  10:04 stress -c 16
root        3784 68.9  0.0   7280    88 pts/1    R+   14:17   9:46 stress -c 16
root        3785 69.1  0.0   7280    88 pts/1    R+   14:17   9:47 stress -c 16
root        3786 73.9  0.0   7280    88 pts/1    R+   14:17  10:28 stress -c 16
root        3787 70.3  0.0   7280    88 pts/1    R+   14:17   9:58 stress -c 16
root        3788 71.4  0.0   7280    88 pts/1    R+   14:17  10:07 stress -c 16
root        3789 73.7  0.0   7280    88 pts/1    R+   14:17  10:27 stress -c 16
root        3790 73.9  0.0   7280    88 pts/1    R+   14:17  10:28 stress -c 16
root        3791 72.1  0.0   7280    88 pts/1    R+   14:17  10:12 stress -c 16
root        3792 71.5  0.0   7280    88 pts/1    R+   14:17  10:08 stress -c 16
root        3793 70.2  0.0   7280    88 pts/1    R+   14:17   9:57 stress -c 16
root        3794 72.3  0.0   7280    88 pts/1    R+   14:17  10:14 stress -c 16
root        3795 71.8  0.0   7280    88 pts/1    R+   14:17  10:10 stress -c 16


可见,抛开k8s集群原有的一些运行在该node节点上的服务消耗以外,该pod是能够吃满剩余所有CPU资源的。

2. Java代码来跑并行任务

拿一个Java简单的 Concurrent 并行演示的例子来模拟:

// ForkJoinTest.java
import java.util.concurrent.*;
import java.util.function.*;
/**
 * This program demonstrates the fork-join framework.
 *
 * @author Cay Horstmann
 * @version 1.01 2015-06-21
 */
public class ForkJoinTest {
    public static void main(String[] args) {
        final int SIZE = 10000000;
        var numbers = new double[SIZE];    
        for (int i = 0; i < SIZE; i++) numbers[i] = Math.random();  
        var counter = new Counter(numbers, 0, numbers.length, x -> x > 0.5);
        var pool = new ForkJoinPool();     
        pool.invoke(counter);   
        System.out.println("ForkJoinPool size: " + pool.getPoolSize());    // poolsize 为计算机核心数
        System.out.println(counter.join());
    }
}

class Counter extends RecursiveTask<Integer> {  
    public static final int THRESHOLD = 1000;  
    private double[] values;
    private int from;
    private int to;
    private DoublePredicate filter;

    public Counter(double[] values, int from, int to, DoublePredicate filter) { 
        this.values = values;
        this.from = from;
        this.to = to;
        this.filter = filter;
    }

    @Override
    protected Integer compute() {      
        if (to - from < THRESHOLD) {      
            System.out.println("join task: from " + from + " to " + to);
            int count = 0;
            for (int i = from; i < to; i++) {
                if (filter.test(values[i])) count++;
            }
            return count;   // 返回当前子任务的结果合并值
        } else {
            System.out.println("fork task: from " + from + " to " + to);
            int mid = (from + to) / 2;      // 计算两者的平均值,取出中间值
            var first = new Counter(values, from, mid, filter);    
            var second = new Counter(values, mid, to, filter);    
            // 分割子任务
//            first.fork();   // 方式一
//            second.fork();  // 方式一
            invokeAll(first, second);   
            return first.join() + second.join();      
        }
    }
}

安装 oenjdk-11 :

$ apt update && apt install wget unzip 
$ wget https://github.com/ojdkbuild/contrib_jdk11u-ci/releases/download/jdk-11.0.15%2B10/jdk-11.0.15-ojdkbuild-linux-x64.zip
$ unzip jdk-11.0.15-ojdkbuild-linux-x64.zip
$ export PATH=$PATH:/jdk-11.0.15-ojdkbuild-linux-x64/bin
$ java --version
openjdk 11.0.15 2022-04-19 LTS
OpenJDK Runtime Environment 18.9 (build 11.0.15+10-LTS)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.15+10-LTS, mixed mode)

使用 javac 编译并运行java这段java代码:

$ javac ForkJoinTest.java
$ java ForkjoinTest
........
fork task: from 9998779 to 10000000
join task: from 9998779 to 9999389
join task: from 9999389 to 10000000
ForkJoinPool size: 1
5001986

这里的 pool.getPoolSize() 返回jdk获取到的容器CPU核心数,这里拿到值仅为 1 。

我们来使用参数获取一下 jdk 的 Flags 信息:

$ java -XX:+PrintFlagsFinal --version | grep -E "ActiveProcessorCount|ParallelGCThreads"
      int ActiveProcessorCount                     = -1                                        {product} {default}
     uint ParallelGCThreads                        = 0                                         {product} {default}

可见 ActiveProcessorCount 值为 -1。
ParallelGCThreads 会影响到JVM GC 性能。

我们再使用 jshell 获取一下使用的 cpu 核心数量:

jshell> Runtime.getRuntime().availableProcessors()
$1 ==> 1

jvm 默认使用 1 核心。


二,在正确配置 containers.resources.requests.cpu 资源限制的情况下,程序能使用到多少CPU资源?

Deployment 资源定义文件:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment2
spec:
  selector:
     matchLabels:
       app: nginx2
  replicas: 1
  template:
     metadata:
       labels:
         app: nginx2
     spec:
       containers:
       - name: nginx2
         image: nginx:1.14.2
         ports:
         - containerPort: 80
         resources:  
           requests:
             cpu: "100m"
           limits:
             cpu: 8

这里将配置 resources 字段中的 CPU 资源大小,requests保证分配给到100mlimits最大分配给到8个核心。

stress是一个linux的压力测试工具,专门用于对设备的CPU、IO、内存、负载、磁盘等进行压测。 适用在监控告警类、主机资源跑高等测试上。

1. stress 压测

使用参数 -c 10 选项参数来模拟10个核心满载的情况,看看能否跑得上去:

$ apt update
$ apt install stress procps -y
$ stress -c 10

这里给到的压力是 10 个核心,已经超过了 limits 的最大值,看看实际的表现情况。

观察ps aux进程的CPU使用率情况:

# ps aux 
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root        3779  0.0  0.0   7280   880 pts/1    S+   14:17   0:00 stress -c 16
root        3780 71.7  0.0   7280    88 pts/1    R+   14:17  10:09 stress -c 16
root        3781 70.9  0.0   7280    88 pts/1    R+   14:17  10:03 stress -c 16
root        3782 70.5  0.0   7280    88 pts/1    R+   14:17   9:59 stress -c 16
root        3783 71.0  0.0   7280    88 pts/1    R+   14:17  10:04 stress -c 16
root        3784 68.9  0.0   7280    88 pts/1    R+   14:17   9:46 stress -c 16
root        3785 69.1  0.0   7280    88 pts/1    R+   14:17   9:47 stress -c 16
root        3786 73.9  0.0   7280    88 pts/1    R+   14:17  10:28 stress -c 16
root        3787 70.3  0.0   7280    88 pts/1    R+   14:17   9:58 stress -c 16
root        3788 71.4  0.0   7280    88 pts/1    R+   14:17  10:07 stress -c 16
root        3789 73.7  0.0   7280    88 pts/1    R+   14:17  10:27 stress -c 16
root        3790 73.9  0.0   7280    88 pts/1    R+   14:17  10:28 stress -c 16
root        3791 72.1  0.0   7280    88 pts/1    R+   14:17  10:12 stress -c 16
root        3792 71.5  0.0   7280    88 pts/1    R+   14:17  10:08 stress -c 16
root        3793 70.2  0.0   7280    88 pts/1    R+   14:17   9:57 stress -c 16
root        3794 72.3  0.0   7280    88 pts/1    R+   14:17  10:14 stress -c 16
root        3795 71.8  0.0   7280    88 pts/1    R+   14:17  10:10 stress -c 16

其中 PID 3606 为剩下worker子进程的父进程,本身不会给到负载。
可见,我们给了超出总可用核心数的参数,实际每个子进程都不能满载,但是总的子进程CPU占用率加起来恰好等于 800%,即符合8核心满载的情况。


在监控面板中,也获取到了pod的 requestslimits 大小,其中container nginx 的CPU使用率跑满8核心,和limits持平,符合预期。

2. Java代码来跑并行任务

代码见上文。

使用 javac 编译并运行java这段java代码:

$ javac ForkJoinTest.java
$ java ForkjoinTest
........
fork task: from 8377685 to 8378906
join task: from 8375853 to 8376464
join task: from 8378295 to 8378906
ForkJoinPool size: 8
4999188

这里的 pool.getPoolSize() 返回jdk获取到的容器CPU核心数,这里拿到值为 8,说明jdk正确获取到了容器的CPU资源上限。

我们来使用参数获取一下 jdk 的 Flags 信息:

$ java -XX:+PrintFlagsFinal --version | grep -E "ActiveProcessorCount|ParallelGCThreads"
      int ActiveProcessorCount                     = -1                                        {product} {default}
     uint ParallelGCThreads                        = 8                                         {product} {default}

可见 ActiveProcessorCount 值为 -1。
ParallelGCThreads 会影响到JVM GC 性能。

我们再使用 jshell 获取一下使用的 cpu 核心数量:

jshell> Runtime.getRuntime().availableProcessors()
$1 ==> 8

jvm 正确识别 8 个核心。


三,总结

在没有设置 pod 的cpu资源大小时,默认程序可以使用的资源为宿主机总资源数。
但是得益于在特定服务环境的情况下,比如这里的 java 服务,jdk 需要判断是否为传统vm类型还是容器运行时,会事先获取当前运行环境的可用资源大小,如果获取失败,则会使用默认值 1 核心来设置 jvm gc 任务线程配置。

在设置有 pod 的 cpu 资源大小时,jdk >= 1.8_191 版本后,会正确识别 cgroup 资源限制。
Java服务和传统服务的资源使用上限,也将等于 limits 允许的cpu资源大小。

所以推荐在 Java 环境下正确配置 containers.resources.requestscontaienrs.resources.limits 资源大小,避免因jvm获取cpu失败,选用默认的1核心运行任务。

posted @ 2023-04-15 23:05  Professor哥  阅读(301)  评论(0编辑  收藏  举报