CLOVER🍀

That was when it all began.

JavaがDockerコンテナ内でどのようにCPU数、メモリサむズを取埗しおいるのかを調べおみる

これは、なにをしたくお曞いたもの

珟圚のJavaは、コンテナ環境䞋ではホスト偎ではなくコンテナにリ゜ヌス制限がかけられおいればそちらの倀を芋るように
なっおいたす。

これはどこの倀を芋おいるのかなずいうのを確認しおみたくなりたしお。

なお、自分にはcgroupに関する知識はほがありたせん。あくたで、Javaがどこの情報を芋おいるかずいう芳点で
远っおいたす。

JDK-8146115

Javaも以前はホスト偎のCPU数やメモリサむズを参照しおいたのですが、JDK-8146115およびそのバックポヌトが
入っおからはコンテナに割り圓おられたCPU数やメモリサむズを芋るようになりたした。

https://bugs.openjdk.java.net/browse/JDK-8146115

Java 10以降、Java 8に぀いおは8u191以降で察応しおいたす。

デフォルトでこの機胜は有効になっおいお、明瀺的に無効にしたい堎合は-XX:-UseContainerSupportを指定すれば
OKです。
たた、CPU数に぀いおは-XX:ActiveProcessorCount=[CPU数]でオヌバヌラむドするこずもできたす。

今回は、JDK-8146115等の察応で、Javaがどのような情報を参照しおCPU数やメモリサむズを取埗しおいるのかを
調べおみたした。

環境

確認環境は、こちらです。

$ docker version
Client: Docker Engine - Community
 Version:           20.10.12
 API version:       1.41
 Go version:        go1.16.12
 Git commit:        e91ed57
 Built:             Mon Dec 13 11:45:33 2021
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.12
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.16.12
  Git commit:       459d0df
  Built:            Mon Dec 13 11:43:42 2021
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.4.12
  GitCommit:        7b11cfaabd73bb80907dd23182b9347b4245eb5d
 runc:
  Version:          1.0.2
  GitCommit:        v1.0.2-0-g52b36a2
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

ホスト偎はUbuntu Linux 20.04 LTS。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.3 LTS
Release:        20.04
Codename:       focal


$ uname -srvmpio
Linux 5.4.0-91-generic #102-Ubuntu SMP Fri Nov 5 16:31:28 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

CPU数は8個、メモリは16Gです。

$ grep ^processor /proc/cpuinfo
processor       : 0
processor       : 1
processor       : 2
processor       : 3
processor       : 4
processor       : 5
processor       : 6
processor       : 7


$ head -n 1 /proc/meminfo
MemTotal:       16306452 kB

確認には、Eclipse TemurinのDockerむメヌゞを䜿うこずにしたす。

DockerHub / eclipse-temurin

今回利甚するバヌゞョンは、こちら。

$ docker container run -it --rm --name java eclipse-temurin:17-jdk-focal bash
# java --version
openjdk 17.0.1 2021-10-19
OpenJDK Runtime Environment Temurin-17.0.1+12 (build 17.0.1+12)
OpenJDK 64-Bit Server VM Temurin-17.0.1+12 (build 17.0.1+12, mixed mode, sharing)

Dockerコンテナのリ゜ヌス制限に぀いお

たず、Dockerコンテナでリ゜ヌス制限する方法を芋おいきたしょう。

Runtime options with Memory, CPUs, and GPUs | Docker Documentation

メモリに関しおは、--memoryでコンテナが䜿えるメモリを制限できたす。

Runtime options with Memory, CPUs, and GPUs / Memory

CPUに関しおは、--cpu-periodで指定した時間あたりのCPU䜿甚時間の䞊限を、--cpu-quotaで指定したす。
この倀ずホスト偎のCPU数から、コンテナ偎で利甚できるCPU数が決たりたす。 なのですが、ドキュメントでも勧められおいるように--cpusでCPU数で指定するのがわかりやすいです。
--cpusは、--cpu-periodず--cpu-quotaをDocker偎に蚈算させるような意味になりたす。

--cpu-sharesでは、優先床をコントロヌルしたす。

Runtime options with Memory, CPUs, and GPUs / CPU

たた、コンテナに割り圓おるCPUを具䜓的に指定するには、--cpuset-cpusを䜿甚したす。

そもそも、コンテナ内はどうなっおいるのか

そもそもコンテナ内ではどうなっおいるのか、ちょっず確認しおみたしょう。

特になにも制限せずにコンテナを起動しおみたす。

$ docker container run -it --rm --name java eclipse-temurin:17-jdk-focal bash

コンテナ内で、CPUやメモリの情報を芋おみたす。

# grep ^processor /proc/cpuinfo
processor       : 0
processor       : 1
processor       : 2
processor       : 3
processor       : 4
processor       : 5
processor       : 6
processor       : 7


#  head -n 1 /proc/meminfo
MemTotal:       16306452 kB

ホスト偎ず差がないですね。

Javaで芋おも同様です。

# jshell
Jan 04, 2022 5:06:09 PM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.
|  Welcome to JShell -- Version 17.0.1
|  For an introduction type: /help intro

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

jshell> ((com.sun.management.OperatingSystemMXBean)java.lang.management.ManagementFactory.getOperatingSystemMXBean()).getAvailableProcessors()
$2 ==> 8

jshell> ((com.sun.management.OperatingSystemMXBean)java.lang.management.ManagementFactory.getOperatingSystemMXBean()).getTotalMemorySize()
$3 ==> 16697806848

docker container inspectで芋おも、特に倀は指定されおいたせん。

$ docker container inspect java | jq '.[].HostConfig' | grep -iE 'cpu|memory' | grep -v Kernel
  "CpuShares": 0,
  "Memory": 0,
  "NanoCpus": 0,
  "CpuPeriod": 0,
  "CpuQuota": 0,
  "CpuRealtimePeriod": 0,
  "CpuRealtimeRuntime": 0,
  "CpusetCpus": "",
  "CpusetMems": "",
  "MemoryReservation": 0,
  "MemorySwap": 0,
  "MemorySwappiness": null,
  "CpuCount": 0,
  "CpuPercent": 0,

次に、CPU 2぀、メモリ2Gに制限しおコンテナを起動しおみたす。

$ docker container run -it --rm --name java --cpus 2 --memory 2G eclipse-temurin:17-jdk-focal bash

/procで芋える情報には、倉化がありたせん。ホスト偎の情報が芋えたたたです。

# grep ^processor /proc/cpuinfo
processor       : 0
processor       : 1
processor       : 2
processor       : 3
processor       : 4
processor       : 5
processor       : 6
processor       : 7


# head -n 1 /proc/meminfo
MemTotal:       16306452 kB

Java偎では、この制限を認識できおいたす。

# jshell
Jan 04, 2022 5:05:11 PM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.
|  Welcome to JShell -- Version 17.0.1
|  For an introduction type: /help intro

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

jshell> ((com.sun.management.OperatingSystemMXBean)java.lang.management.ManagementFactory.getOperatingSystemMXBean()).getAvailableProcessors()
$2 ==> 2

jshell> ((com.sun.management.OperatingSystemMXBean)java.lang.management.ManagementFactory.getOperatingSystemMXBean()).getTotalMemorySize()
$3 ==> 2147483648

docker container inspectするず、制限が入っおいるこずは確認できたす。

$ docker container inspect java | jq '.[].HostConfig' | grep -iE 'cpu|memory' | grep -v Kernel
  "CpuShares": 0,
  "Memory": 2147483648,
  "NanoCpus": 2000000000,
  "CpuPeriod": 0,
  "CpuQuota": 0,
  "CpuRealtimePeriod": 0,
  "CpuRealtimeRuntime": 0,
  "CpusetCpus": "",
  "CpusetMems": "",
  "MemoryReservation": 0,
  "MemorySwap": -1,
  "MemorySwappiness": null,
  "CpuCount": 0,
  "CpuPercent": 0,

ずいうわけで、特に考慮しないたただずDockerコンテナでリ゜ヌス制限をしおも、ホスト偎のリ゜ヌスの情報を
芋おしたうこずがわかりたす。

JDK-8146115での察応内容を確認する

ここで、JDK-8146115ではどのようにしおCPU数やメモリサむズを算出するのか芋おいきたいず思いたす。

https://bugs.openjdk.java.net/browse/JDK-8146115

答えは、issue内に曞いおありたす。

CPU数は、以䞋の芁領で算出したす。

  • cpu_quota / cpu_period
    • 補足 cpu_quotaが蚭定されおいる-1でない堎合に有効
  • コンテナにcpu_sharesが指定されおいる堎合は、cpu_shares / 1024

Number of CPUs

Use a combination of number_of_cpus() and cpu_sets() in order to determine how many processors are available to the process and adjust the JVMs os::active_processor_count appropriately. The number_of_cpus() will be calculated based on the cpu_quota() and cpu_period() using this formula: number_of_cpus() = cpu_quota() / cpu_period(). If cpu_shares has been setup for the container, the number_of_cpus() will be calculated based on cpu_shares()/1024. 1024 is the default and standard unit for calculating relative cpu usage in cloud based container management software.

Also add a new VM flag (-XX:ActiveProcessorCount=xx) that allows the number of CPUs to be overridden. This flag will be honored even if UseContainerSupport is not enabled.

メモリの総量に぀いおは、cgroupファむルシステムのmemory_limitを䜿甚しお取埗したす。

Total available memory

Use the memory_limit() value from the cgroup file system to initialize the os::physical_memory() value in the VM. This value will propagate to all other parts of the Java runtime.

䜿甚しおいるメモリに぀いおは、OSの利甚可胜なメモリos::available_memoryからmemory_usage_in_bytesを
匕いお算出したす。

Memory usage

Use memory_usage_in_bytes() for providing os::available_memory() by subtracting the usage from the total available memory allocated to the container.

cgroupが出おきたしたね。

関連するカヌネルのドキュメントずOpenJDKの゜ヌスコヌド

このあたりの話題で、関連するカヌネルのドキュメントはこちらです。

cgroupはv1ずv2がありたす。

OpenJDKのcgroup v1、v2にそれぞれ察応しおいそうですが、cgroupSubsystem_linux.cppでその振り分けを行っお
いたす。

https://github.com/openjdk/jdk17u/blob/jdk-17.0.1%2B12/src/hotspot/os/linux/cgroupSubsystem_linux.cpp

cgroup v1に぀いおはこちら。

https://github.com/openjdk/jdk17u/blob/jdk-17.0.1%2B12/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp https://github.com/openjdk/jdk17u/blob/jdk-17.0.1%2B12/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp

cgroup v2に぀いおはこちら。

https://github.com/openjdk/jdk17u/blob/jdk-17.0.1%2B12/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp https://github.com/openjdk/jdk17u/blob/jdk-17.0.1%2B12/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp

コンテナ内のどこの情報を芋おいるのかは、cgroup v1であればcgroupV1Subsystem_linux.cppを、
cgroup v2であればcgroupV2Subsystem_linux.cppを芋ればわかるようになっおいたす。

たずえば、cgroup v1のcpu_quotaずcpu_periodは、cpu.cfs_quota_usずcpu.cfs_period_usです。

/* cpu_quota
 *
 * Return the number of microseconds per period
 * process is guaranteed to run.
 *
 * return:
 *    quota time in microseconds
 *    -1 for no quota
 *    OSCONTAINER_ERROR for not supported
 */
int CgroupV1Subsystem::cpu_quota() {
  GET_CONTAINER_INFO(int, _cpu->controller(), "/cpu.cfs_quota_us",
                     "CPU Quota is: %d", "%d", quota);
  return quota;
}

int CgroupV1Subsystem::cpu_period() {
  GET_CONTAINER_INFO(int, _cpu->controller(), "/cpu.cfs_period_us",
                     "CPU Period is: %d", "%d", period);
  return period;
}

https://github.com/openjdk/jdk17u/blob/jdk-17.0.1%2B12/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp#L204-L224

controllerずいう郚分がわかりたせんね。これはたた埌で。

ちなみに、利甚可胜なCPU数の算出方法はcgroupSubsystem_linux.cppにコメントずしおも曞かれおいたす。

https://github.com/openjdk/jdk17u/blob/jdk-17.0.1%2B12/src/hotspot/os/linux/cgroupSubsystem_linux.cpp#L406-L441

たた、DockerのCPU制限のずころで、CFSスケゞュヌラヌずいう蚀葉が出おくるのですが、

Specify the CPU CFS scheduler period, which is used alongside --cpu-quota.

Runtime options with Memory, CPUs, and GPUs / CPU

これはこちらのこずですね。

CFS Scheduler — The Linux Kernel documentation

自分の環境を確認する

ここで、自分の手元の環境がcgroup v1なのかcgroup v2なのかを確認したいず思いたす。

mountで芋るずよいみたいなのですが、cgroup v1ずcgroup v2の䞡方が入っおいたす。

$ mount | grep cgroup
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
cgroup2 on /sys/fs/cgroup/unified type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)

これは、cgroup v1がデフォルトではマりントされ、そうならなかったものがcgroup v2でマりントされおいるようなのですが。

A cgroup v2 controller is available only if it is not currently in use via a mount against a cgroup v1 hierarchy. Or, to put things another way, it is not possible to employ the same controller against both a v1 hierarchy and the unified v2 hierarchy.

cgroups(7) - Linux manual page

実質、自分の環境ではcgroup v1ですね。

ちなみに、コンテナ内で確認するず完党にcgroup v1になっおいたす。

$ docker container run -it --rm --name java eclipse-temurin:17-jdk-focal bash -c 'mount | grep cgroup'
tmpfs on /sys/fs/cgroup type tmpfs (rw,nosuid,nodev,noexec,relatime,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (ro,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/rdma type cgroup (ro,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/blkio type cgroup (ro,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (ro,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (ro,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/perf_event type cgroup (ro,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/freezer type cgroup (ro,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/devices type cgroup (ro,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/memory type cgroup (ro,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/cpuset type cgroup (ro,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (ro,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/pids type cgroup (ro,nosuid,nodev,noexec,relatime,pids)

cgroup v1でコンテナに割り圓おられたリ゜ヌスを確認する

ここから先は、手元の環境cgroup v1で確認しおいきたいず思いたす。

再床、CPUを2぀、メモリを2Gに制限したコンテナに入っおみたす。

$ docker container run -it --rm --name java --cpus 2 --memory 2G eclipse-temurin:17-jdk-focal bash

自身のcgroupの情報は、/proc/self/cgroupで確認できたす。

# cat /proc/self/cgroup
12:pids:/docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba
11:net_cls,net_prio:/docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba
10:cpuset:/docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba
9:memory:/docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba
8:devices:/docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba
7:freezer:/docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba
6:perf_event:/docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba
5:cpu,cpuacct:/docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba
4:hugetlb:/docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba
3:blkio:/docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba
2:rdma:/
1:name=systemd:/docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba
0::/system.slice/containerd.service

2列目にあるmemoryやcpu,cpuacctずいうのは、サブシステムを衚しおいたす。

そしお、/proc/self/mountinfoを芋るず、各サブシステムがどこにあるのかがわかりたす。

# grep docker /proc/self/mountinfo
1646 1569 0:135 / / rw,relatime master:726 - overlay overlay rw,lowerdir=/var/lib/docker/overlay2/l/GJRR3TKMSGCOQ6S7DE34V67ZDU:/var/lib/docker/overlay2/l/VSPKK6AJ62IU6LXW7CUURU5NBL:/var/lib/docker/overlay2/l/NM6ZKZ76GIHXKGJFFADB3UAEB7:/var/lib/docker/overlay2/l/L4PQYHUEGPQIFE5VDUMPZK3ZJ2:/var/lib/docker/overlay2/l/RXIPGFSFBRXJUBRP7FGTUIN5Y2,upperdir=/var/lib/docker/overlay2/dbf0492bd7fdc22e4755c6364b451cdc69acdb306cbda5f7c7a6897ed0262180/diff,workdir=/var/lib/docker/overlay2/dbf0492bd7fdc22e4755c6364b451cdc69acdb306cbda5f7c7a6897ed0262180/work,xino=off
1658 1657 0:30 /docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba /sys/fs/cgroup/systemd ro,nosuid,nodev,noexec,relatime master:11 - cgroup cgroup rw,xattr,name=systemd
1660 1657 0:35 /docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba /sys/fs/cgroup/blkio ro,nosuid,nodev,noexec,relatime master:17 - cgroup cgroup rw,blkio
1661 1657 0:36 /docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba /sys/fs/cgroup/hugetlb ro,nosuid,nodev,noexec,relatime master:18 - cgroup cgroup rw,hugetlb
1662 1657 0:37 /docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba /sys/fs/cgroup/cpu,cpuacct ro,nosuid,nodev,noexec,relatime master:19 - cgroup cgroup rw,cpu,cpuacct
1663 1657 0:38 /docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba /sys/fs/cgroup/perf_event ro,nosuid,nodev,noexec,relatime master:20 - cgroup cgroup rw,perf_event
1664 1657 0:39 /docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba /sys/fs/cgroup/freezer ro,nosuid,nodev,noexec,relatime master:21 - cgroup cgroup rw,freezer
1665 1657 0:40 /docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba /sys/fs/cgroup/devices ro,nosuid,nodev,noexec,relatime master:22 - cgroup cgroup rw,devices
1666 1657 0:41 /docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba /sys/fs/cgroup/memory ro,nosuid,nodev,noexec,relatime master:23 - cgroup cgroup rw,memory
1667 1657 0:42 /docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba /sys/fs/cgroup/cpuset ro,nosuid,nodev,noexec,relatime master:24 - cgroup cgroup rw,cpuset
1668 1657 0:43 /docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba /sys/fs/cgroup/net_cls,net_prio ro,nosuid,nodev,noexec,relatime master:25 - cgroup cgroup rw,net_cls,net_prio
1669 1657 0:44 /docker/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba /sys/fs/cgroup/pids ro,nosuid,nodev,noexec,relatime master:26 - cgroup cgroup rw,pids
1672 1646 8:8 /var/lib/docker/containers/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/sda8 rw,errors=remount-ro
1673 1646 8:8 /var/lib/docker/containers/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba/hostname /etc/hostname rw,relatime - ext4 /dev/sda8 rw,errors=remount-ro
1674 1646 8:8 /var/lib/docker/containers/435086c1708f59eaae929124b129e64b2a93adea24c6cb570ed99323064d4dba/hosts /etc/hosts rw,relatime - ext4 /dev/sda8 rw,errors=remount-ro

たずえば、memoryサブシステムであれば/sys/fs/cgroup/memory、cpu,cpuacctサブシステムであれば
/sys/fs/cgroup/cpu,cpuacctを参照すればよいこずがわかりたす。

実際、OpenJDKでもこれらの情報は芋おいるようです。

https://github.com/openjdk/jdk17u/blob/jdk-17.0.1%2B12/src/hotspot/os/linux/cgroupSubsystem_linux.cpp#L44-L46

ここで、CPU数はcpu_quotaをcpu_periodで割れば算出できるずいうこずでした。そしお、cgroup v1のcpu_quotaず
cpu_periodは、cpu.cfs_quota_usずcpu.cfs_period_usでした。

ずいうわけで、コンテナ内でそれぞれの倀を確認したす。

# cat /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us
200000


# cat /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us
100000

200000 / 100000なので、2ですね。コンテナに割り圓おたCPU数ず䞀臎したした。

コンテナ自䜓が認識しおいるCPUは、ホスト偎の8぀党郚のようですが、これをスケゞュヌリングしお実質2個に
しおいる感じでしょうか。

# cat /sys/fs/cgroup/cpuset/cpuset.cpus
0-7

メモリに぀いおは、/sys/fs/cgroup/memory/memory.limit_in_bytesを芋たす。

# cat /sys/fs/cgroup/memory/memory.limit_in_bytes
2147483648

https://github.com/openjdk/jdk17u/blob/jdk-17.0.1%2B12/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp#L104-L127

これは、Javaで芋た結果ずも、docker container inspectで芋た結果ずも䞀臎したすね。

jshell> ((com.sun.management.OperatingSystemMXBean)java.lang.management.ManagementFactory.getOperatingSystemMXBean()).getTotalMemorySize()
$3 ==> 2147483648


$ docker container inspect java | jq '.[].HostConfig' | grep -iE 'cpu|memory' | grep -v Kernel
  "CpuShares": 0,
  "Memory": 2147483648,

〜省略〜

珟圚のメモリ䜿甚量なら、/sys/fs/cgroup/memory/memory.usage_in_bytesみたいです。

# cat /sys/fs/cgroup/memory/memory.usage_in_bytes
4104192

ちなみに、cpu_sharesに぀いおは/sys/fs/cgroup/cpu,cpuacct/cpu.sharesを参照したす。今回はここは調敎したせんが。

# cat /sys/fs/cgroup/cpu,cpuacct/cpu.shares
1024

cpuset-cpusを指定するず

--cpuset-cpusを䜿い、CPUを2個固定で割り圓おおみたす。

$ docker container run -it --rm --name java --cpuset-cpus 0,1 eclipse-temurin:17-jdk-focal bash

cpu.cfs_quota_usが-1なので、こちらは無効のようです。

# cat /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us
-1


# cat /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us
100000

それでもJava偎は認識できおいたす。

# jshell
Jan 04, 2022 6:05:44 PM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.
|  Welcome to JShell -- Version 17.0.1
|  For an introduction type: /help intro

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

jshell> ((com.sun.management.OperatingSystemMXBean)java.lang.management.ManagementFactory.getOperatingSystemMXBean()).getAvailableProcessors()
$2 ==> 2

/sys/fs/cgroup/cpuset/cpuset.cpusを芋るず、確かに2個割り圓おられおいたす。

# cat /sys/fs/cgroup/cpuset/cpuset.cpus
0-1

これはどうやっおいるかずいうず、sched_getaffinityの結果を芋おいたす。

sched_getaffinity(2) - Linux man page

https://github.com/openjdk/jdk17u/blob/jdk-17.0.1%2B12/src/hotspot/os/linux/os_linux.cpp#L4678-L4685

--cpuset-cpusの堎合は、コンテナ内のプロセスに察しおCPUアフィニティで実珟しおいるようです。

確認甚のコヌド。
※sysconfおよび_SC_NPROCESSORS_CONFが登堎する理由は、埌で出おきたす

print_processors_count.c

#define _GNU_SOURCE 
#include <stdio.h>
#include <unistd.h>
#include <sched.h>
#include <stdlib.h>

int main()
{
  printf("available processors = %lu\n", sysconf(_SC_NPROCESSORS_CONF));

  cpu_set_t cpu_set;
  CPU_ZERO(&cpu_set);

  sched_getaffinity(0, sizeof(cpu_set), &cpu_set);
  printf("number of cpus: %d\n", CPU_COUNT(&cpu_set));
}

これをビルドしおコンテナ内に送り蟌み、

$ gcc print_processors_count.c -o print_processors_count
$ docker container cp print_processors_count java:/

コンテナ内で実行するず以䞋の結果になりたす。

# /print_processors_count
available processors = 8
number of cpus: 2

認識しおいるCPU数は8ですが、sched_getaffinityで埗られた結果は2になっおいたす。

docker container inspectでは、こうなりたした。

$ docker container inspect java | jq '.[].HostConfig' | grep -iE 'cpu|memory' | grep -v Kernel
  "CpuShares": 0,
  "Memory": 0,
  "NanoCpus": 0,
  "CpuPeriod": 0,
  "CpuQuota": 0,
  "CpuRealtimePeriod": 0,
  "CpuRealtimeRuntime": 0,
  "CpusetCpus": "0,1",
  "CpusetMems": "",
  "MemoryReservation": 0,
  "MemorySwap": 0,
  "MemorySwappiness": null,
  "CpuCount": 0,
  "CpuPercent": 0,

同じ情報をホスト偎で参照するず

これらの情報を、ホスト偎で参照するずどうなっおいるんでしょう

$ cat /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us
-1


$ cat /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us
100000


$ cat /sys/fs/cgroup/memory/memory.limit_in_bytes
9223372036854771712


$ cat /sys/fs/cgroup/cpu,cpuacct/cpu.shares
1024

/sys/fs/cgroup/memory/memory.limit_in_bytesの倀がすごいこずになっおいたす 。

ちなみに、コンテナにリ゜ヌス制限を入れずにコンテナ内で同じ情報を確認するず、同様の結果になりたした。

コンテナ察応を無効にした堎合はそもそも、元はどこを参照しおいた

cgroupの情報を参照する凊理は、-XX:-UseContainerSupportを指定するず行わずに飛ばしおしたいたす。

https://github.com/openjdk/jdk17u/blob/jdk-17.0.1%2B12/src/hotspot/os/linux/osContainer_linux.cpp#L53-L56

ずいうか、そもそも元はどこの情報を芋おいたんでしょうね

sysconfみたいです。CPU数は_SC_NPROCESSORS_CONFから、メモリは_SC_PHYS_PAGESず_SC_PAGESIZEから。

https://github.com/openjdk/jdk17u/blob/jdk-17.0.1%2B12/src/hotspot/os/linux/os_linux.cpp#L364-L379

sysconf(3) - Linux manual page

たずめ

Javaがどうやっおコンテナ偎のCPU数やメモリサむズを取埗しおいるのか、興味があったので調べおみたした。

䞀応、情報はだいたい揃った気がしたすが、cgroupに察する理解が党然ないので、本圓に情報が䞊んでいるだけです。

もうちょっず远いたい気もするのですが、いったんここたでで 。

でも、いろいろ勉匷になりたした。

コンテナに割り圓おられたリ゜ヌスを参照したり、その情報に合わせお動䜜するようなプログラムは、このあたりの
情報を意識しないずいけないずいうこずになるんですね。

参考

第3回 Linuxカーネルのコンテナ機能[2] ─cgroupとは?(その1) | gihyo.jp

第4回 Linuxカーネルのコンテナ機能[3] ─cgroupとは?(その2) | gihyo.jp

第5回 Linuxカーネルのコンテナ機能[4] ─cgroupとは?(その3) | gihyo.jp