mirror of
https://github.com/vdsm/virtual-dsm.git
synced 2026-08-29 04:07:02 +00:00
fix: Restrict minimal configured RAM amount to 1 GB (#1351)
This commit is contained in:
+43
-28
@@ -4,10 +4,8 @@ set -Eeuo pipefail
|
||||
msg="Checking memory..."
|
||||
enabled "$DEBUG" && echo "$msg"
|
||||
|
||||
app() {
|
||||
echo "Virtual DSM"
|
||||
return 0
|
||||
}
|
||||
RAM_WARNING=""
|
||||
RAM_ALLOCATION=""
|
||||
|
||||
checkConfiguredMemory() {
|
||||
|
||||
@@ -15,70 +13,85 @@ checkConfiguredMemory() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
local wanted msg avail_mem
|
||||
local wanted avail_mem
|
||||
wanted=$(numfmt --from=iec "$RAM_SIZE")
|
||||
avail_mem=$(formatBytes "$RAM_AVAIL")
|
||||
|
||||
if (( (wanted + RAM_SPARE) > RAM_AVAIL )); then
|
||||
msg="Your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is too high for the $avail_mem of free memory available,"
|
||||
# ZFS ARC can release memory under pressure, so current free-memory checks
|
||||
# are advisory rather than a reason to reduce the requested guest RAM.
|
||||
|
||||
local msg="Your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is too high for the $avail_mem of free memory available,"
|
||||
|
||||
# ZFS ARC can release cached memory under pressure, so this free-memory
|
||||
# heuristic remains informational instead of rewriting RAM_SIZE.
|
||||
if [[ "${FS,,}" == "zfs" ]]; then
|
||||
|
||||
info "$msg but since ZFS is active this will be ignored."
|
||||
|
||||
else
|
||||
|
||||
RAM_SIZE="max"
|
||||
warn "$msg it will automatically be adjusted to a lower amount."
|
||||
RAM_WARNING="$msg it will automatically be adjusted to a lower amount."
|
||||
|
||||
fi
|
||||
|
||||
else
|
||||
|
||||
if (( (wanted + (RAM_SPARE * 3)) > RAM_AVAIL )); then
|
||||
msg="your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is very close to the $avail_mem of free memory available,"
|
||||
|
||||
local msg="your configured RAM_SIZE of ${RAM_SIZE/G/ GB} is very close to the $avail_mem of free memory available,"
|
||||
|
||||
if [[ "${FS,,}" == "zfs" ]]; then
|
||||
info "$msg but since ZFS is active this will be ignored."
|
||||
else
|
||||
warn "$msg please consider a lower amount."
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
configureHalfMemory() {
|
||||
local wanted
|
||||
|
||||
if [[ "${RAM_SIZE,,}" != "half" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# half uses half of currently available memory only when that still leaves
|
||||
# the host reserve; otherwise it falls through to the max calculation.
|
||||
if (( (RAM_AVAIL / 2) > RAM_SPARE )); then
|
||||
wanted=$(( (RAM_AVAIL / 2) / 1048577 ))
|
||||
RAM_SIZE="${wanted}M"
|
||||
info "Allocated $wanted MB of RAM for $(app)."
|
||||
|
||||
local wanted=$(( RAM_AVAIL / 2 ))
|
||||
|
||||
# Divide by one byte more than a MiB to round down
|
||||
local target=$(( wanted / 1048577 ))
|
||||
RAM_SIZE="${target}M"
|
||||
RAM_ALLOCATION="$wanted"
|
||||
|
||||
else
|
||||
|
||||
RAM_SIZE="max"
|
||||
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
configureMaxMemory() {
|
||||
local wanted
|
||||
|
||||
if [[ "${RAM_SIZE,,}" != "max" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# max normally leaves multiple reserve units for the container and host,
|
||||
# but scales that margin down on memory-constrained systems.
|
||||
# max keeps a host reserve when possible, but on very small systems falls back
|
||||
# to half the available memory to avoid starving the container.
|
||||
if (( RAM_AVAIL < (RAM_SPARE * 2) )); then
|
||||
|
||||
wanted=$(( RAM_AVAIL / 2 ))
|
||||
local wanted=$(( RAM_AVAIL / 2 ))
|
||||
|
||||
else
|
||||
|
||||
wanted=$(( RAM_AVAIL - (RAM_SPARE * 3) ))
|
||||
local wanted=$(( RAM_AVAIL - (RAM_SPARE * 3) ))
|
||||
|
||||
if (( wanted < (RAM_SPARE * 6) )); then
|
||||
wanted=$(( RAM_AVAIL - RAM_SPARE ))
|
||||
@@ -86,22 +99,21 @@ configureMaxMemory() {
|
||||
|
||||
fi
|
||||
|
||||
wanted=$(( wanted / 1048577 ))
|
||||
RAM_SIZE="${wanted}M"
|
||||
|
||||
info "Allocated $wanted MB of RAM for $(app)."
|
||||
# Divide by one byte more than a MiB to round down
|
||||
local target=$(( wanted / 1048577 ))
|
||||
RAM_SIZE="${target}M"
|
||||
RAM_ALLOCATION="$wanted"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
checkMinimumMemory() {
|
||||
local wanted
|
||||
|
||||
local wanted
|
||||
wanted=$(numfmt --from=iec "$RAM_SIZE")
|
||||
|
||||
if [ "$wanted" -lt "$RAM_MINIMUM" ]; then
|
||||
wanted=$(( wanted / 1048577 ))
|
||||
error "Not enough memory available, there is only $wanted MB left!"
|
||||
error "$(app) requires at least $(formatBytes "$RAM_MINIMUM") of RAM, but only $(formatBytes "$wanted") can be allocated."
|
||||
exit 16
|
||||
fi
|
||||
|
||||
@@ -115,4 +127,7 @@ configureHalfMemory
|
||||
configureMaxMemory
|
||||
checkMinimumMemory
|
||||
|
||||
[ -n "$RAM_WARNING" ] && warn "$RAM_WARNING"
|
||||
[ -n "$RAM_ALLOCATION" ] && info "Allocated $(formatBytes "$RAM_ALLOCATION") of RAM for $(app)."
|
||||
|
||||
return 0
|
||||
|
||||
+3
-4
@@ -571,7 +571,7 @@ getReservedPorts() {
|
||||
# allowing WEB_PORT (normally 5000) to be handed over to DSM.
|
||||
|
||||
normalizePorts "$list" "$mode"
|
||||
return $?
|
||||
return $?
|
||||
}
|
||||
|
||||
getCustomHostPorts() {
|
||||
@@ -589,7 +589,7 @@ getCustomHostPorts() {
|
||||
done
|
||||
|
||||
normalizePorts "$ports" "$mode"
|
||||
return 0
|
||||
return $?
|
||||
}
|
||||
|
||||
getHostPorts() {
|
||||
@@ -602,8 +602,7 @@ getHostPorts() {
|
||||
reserved=$(getReservedPorts "all")
|
||||
custom=$(getCustomHostPorts "all")
|
||||
normalizePorts "$reserved,$custom" "$mode"
|
||||
|
||||
return 0
|
||||
return $?
|
||||
}
|
||||
|
||||
getUserPorts() {
|
||||
|
||||
+52
-80
@@ -79,15 +79,15 @@ checkPrivileged() {
|
||||
return 0
|
||||
}
|
||||
|
||||
normalizeCpuCores() {
|
||||
checkCores() {
|
||||
|
||||
CPU_CORES=$(strip "$CPU_CORES")
|
||||
[ -z "$CPU_CORES" ] && CPU_CORES=2
|
||||
[[ "${CPU_CORES,,}" == "max" ]] && CPU_CORES="$CORES"
|
||||
[[ "${CPU_CORES,,}" == "half" ]] && CPU_CORES=$(( CORES / 2 ))
|
||||
[ -z "${CPU_CORES##*[!0-9]*}" ] && error "Invalid amount of CPU_CORES: $CPU_CORES" && exit 15
|
||||
|
||||
[ "$CPU_CORES" -lt "1" ] && CPU_CORES=1
|
||||
|
||||
if [ "$CPU_CORES" -gt "$CORES" ]; then
|
||||
warn "The amount for CPU_CORES (${CPU_CORES}) exceeds the amount of logical cores available (${CORES}) and will be limited."
|
||||
CPU_CORES="$CORES"
|
||||
@@ -96,17 +96,31 @@ normalizeCpuCores() {
|
||||
return 0
|
||||
}
|
||||
|
||||
checkSockets() {
|
||||
|
||||
local lscpu_out
|
||||
lscpu_out=$(lscpu 2>/dev/null || true)
|
||||
|
||||
if grep -qi "socket(s)" <<< "$lscpu_out"; then
|
||||
SOCKETS=$(grep -m 1 -i 'socket(s)' <<< "$lscpu_out" | awk '{print $2}')
|
||||
[ -z "${SOCKETS##*[!0-9]*}" ] && SOCKETS=1
|
||||
[ "$SOCKETS" -lt "1" ] && SOCKETS=1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
checkStorage() {
|
||||
|
||||
# Check system
|
||||
|
||||
# Runtime sockets, pidfiles, and status files live in shared memory so they
|
||||
# are fast, ephemeral, and visible to helper processes.
|
||||
QEMU_DIR="/run/shm"
|
||||
|
||||
if [ ! -d "/dev/shm" ]; then
|
||||
error "Directory /dev/shm not found!" && exit 14
|
||||
else
|
||||
# Keep runtime sockets and PID files on shared memory even on images where
|
||||
# /run/shm is absent but /dev/shm is available.
|
||||
[ ! -d "$QEMU_DIR" ] && ln -s /dev/shm "$QEMU_DIR"
|
||||
fi
|
||||
|
||||
@@ -115,7 +129,9 @@ checkStorage() {
|
||||
# Check folder
|
||||
|
||||
if [[ "${STORAGE,,}" != "/storage" ]]; then
|
||||
mkdir -p "$STORAGE"
|
||||
if ! mkdir -p -- "$STORAGE"; then
|
||||
error "Cannot create storage folder ($STORAGE)!" && exit 13
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -d "$STORAGE" ]; then
|
||||
@@ -131,11 +147,9 @@ checkStorage() {
|
||||
return 0
|
||||
}
|
||||
|
||||
checkFilesystem() {
|
||||
checkHost() {
|
||||
|
||||
# Check filesystem
|
||||
FS=$(stat -f -c %T "$STORAGE")
|
||||
|
||||
if [[ "${FS,,}" == "ecryptfs" || "${FS,,}" == "tmpfs" ]]; then
|
||||
DISK_IO="threads"
|
||||
DISK_CACHE="writeback"
|
||||
@@ -144,69 +158,22 @@ checkFilesystem() {
|
||||
return 0
|
||||
}
|
||||
|
||||
finiteMemoryLimit() {
|
||||
|
||||
local limit="$1"
|
||||
# cgroup v1 commonly reports an enormous sentinel instead of an unlimited
|
||||
# marker; compare as decimal text to avoid shell integer overflow.
|
||||
local sentinel="4611686018427387904"
|
||||
local i
|
||||
|
||||
[[ "$limit" =~ ^[0-9]+$ ]] || return 1
|
||||
|
||||
(( ${#limit} < ${#sentinel} )) && return 0
|
||||
(( ${#limit} > ${#sentinel} )) && return 1
|
||||
|
||||
for (( i=0; i<${#sentinel}; i++ )); do
|
||||
local left="${limit:i:1}"
|
||||
local right="${sentinel:i:1}"
|
||||
|
||||
(( left < right )) && return 0
|
||||
(( left > right )) && return 1
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
getMemoryInfo() {
|
||||
|
||||
local limit="" current=""
|
||||
local host_total host_avail
|
||||
|
||||
host_total=$(free -b | awk '/^Mem:/ {print $2; exit}')
|
||||
host_avail=$(free -b | awk '/^Mem:/ {print $7; exit}')
|
||||
|
||||
RAM_TOTAL="$host_total"
|
||||
RAM_AVAIL="$host_avail"
|
||||
|
||||
if [ -r /sys/fs/cgroup/memory.max ] && [ -r /sys/fs/cgroup/memory.current ]; then
|
||||
limit=$(< /sys/fs/cgroup/memory.max)
|
||||
current=$(< /sys/fs/cgroup/memory.current)
|
||||
elif [ -r /sys/fs/cgroup/memory/memory.limit_in_bytes ] && [ -r /sys/fs/cgroup/memory/memory.usage_in_bytes ]; then
|
||||
limit=$(< /sys/fs/cgroup/memory/memory.limit_in_bytes)
|
||||
current=$(< /sys/fs/cgroup/memory/memory.usage_in_bytes)
|
||||
fi
|
||||
|
||||
# Use the tighter of host availability and the container's remaining cgroup
|
||||
# allowance so RAM sizing works in both limited and unlimited containers.
|
||||
if finiteMemoryLimit "$limit" && [[ "$current" =~ ^[0-9]+$ ]]; then
|
||||
(( limit < RAM_TOTAL )) && RAM_TOTAL="$limit"
|
||||
|
||||
local available=$(( limit - current ))
|
||||
(( available < 0 )) && available=0
|
||||
(( available < RAM_AVAIL )) && RAM_AVAIL="$available"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
normalizeRamSize() {
|
||||
checkRam() {
|
||||
|
||||
# Read host and container memory limits.
|
||||
getMemoryInfo
|
||||
|
||||
RAM_SPARE=500000000
|
||||
RAM_MINIMUM=136314880
|
||||
RAM_MINIMUM="${RAM_MINIMUM:-1073741824}"
|
||||
|
||||
RAM_MINIMUM=$(strip "$RAM_MINIMUM")
|
||||
RAM_MINIMUM="${RAM_MINIMUM// /}"
|
||||
RAM_MINIMUM=$(echo "${RAM_MINIMUM^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
|
||||
numfmt --from=iec "$RAM_MINIMUM" &>/dev/null || {
|
||||
error "Invalid RAM_MINIMUM: $RAM_MINIMUM"
|
||||
exit 16
|
||||
}
|
||||
RAM_MINIMUM=$(numfmt --from=iec "$RAM_MINIMUM")
|
||||
|
||||
RAM_SIZE=$(strip "$RAM_SIZE")
|
||||
RAM_SIZE="${RAM_SIZE// /}"
|
||||
@@ -214,16 +181,24 @@ normalizeRamSize() {
|
||||
|
||||
if [[ "${RAM_SIZE,,}" != "max" && "${RAM_SIZE,,}" != "half" ]]; then
|
||||
|
||||
# Preserve the historical shorthand: small bare numbers mean GiB, while
|
||||
# values of 130 or more are interpreted as MiB.
|
||||
# Bare values below 130 are interpreted as GiB for convenience; larger bare
|
||||
# values are treated as MiB to preserve historical configurations.
|
||||
if [ -z "${RAM_SIZE//[0-9. ]}" ]; then
|
||||
[ "${RAM_SIZE%%.*}" -lt "130" ] && RAM_SIZE="${RAM_SIZE}G" || RAM_SIZE="${RAM_SIZE}M"
|
||||
fi
|
||||
|
||||
RAM_SIZE=$(echo "${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g')
|
||||
numfmt --from=iec "$RAM_SIZE" &>/dev/null || { error "Invalid RAM_SIZE: $RAM_SIZE" && exit 16; }
|
||||
numfmt --from=iec "$RAM_SIZE" &>/dev/null || {
|
||||
error "Invalid RAM_SIZE: $RAM_SIZE"
|
||||
exit 16
|
||||
}
|
||||
|
||||
wanted=$(numfmt --from=iec "$RAM_SIZE")
|
||||
[ "$wanted" -lt "$RAM_MINIMUM" ] && error "RAM_SIZE is too low: $RAM_SIZE" && exit 16
|
||||
|
||||
if [ "$wanted" -lt "$RAM_MINIMUM" ]; then
|
||||
error "$(app) requires at least $(formatBytes "$RAM_MINIMUM") of RAM, but RAM_SIZE is set to $(formatBytes "$wanted")."
|
||||
exit 16
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
@@ -302,6 +277,7 @@ checkKvm() {
|
||||
TZ=$(strip "$TZ")
|
||||
STORAGE=$(strip "$STORAGE")
|
||||
COUNTRY=$(strip "$COUNTRY")
|
||||
MACHINE=$(strip "${MACHINE,,}")
|
||||
DISK_SIZE=$(strip "$DISK_SIZE")
|
||||
|
||||
# Helper variables
|
||||
@@ -329,22 +305,17 @@ SOCKETS=1
|
||||
CPU=$(cpu)
|
||||
SYS=$(uname -r)
|
||||
ARCH=$(dpkg --print-architecture)
|
||||
IFS=. read -r KERNEL MINOR _ <<< "$SYS"
|
||||
CORES=$(grep -c '^processor' /proc/cpuinfo)
|
||||
IFS=. read -r KERNEL MINOR _ <<< "$SYS"
|
||||
|
||||
if grep -qi "socket(s)" <<< "$(lscpu)"; then
|
||||
SOCKETS=$(lscpu | grep -m 1 -i 'socket(s)' | awk '{print $2}')
|
||||
[ -z "${SOCKETS##*[!0-9]*}" ] && SOCKETS=1
|
||||
[ "$SOCKETS" -lt "1" ] && SOCKETS=1
|
||||
fi
|
||||
|
||||
normalizeCpuCores
|
||||
checkSockets
|
||||
checkCores
|
||||
checkStorage
|
||||
checkFilesystem
|
||||
normalizeRamSize
|
||||
checkRam
|
||||
|
||||
# Print system info
|
||||
SYS="${SYS/-generic/}"
|
||||
FS=$(stat -f -c %T "$STORAGE")
|
||||
FS="${FS/UNKNOWN //}"
|
||||
FS="${FS/ext2\/ext3/ext4}"
|
||||
FS=$(echo "$FS" | sed 's/[)(]//g')
|
||||
@@ -356,6 +327,7 @@ TOTAL_MEM=$(formatBytes "$RAM_TOTAL" "up")
|
||||
echo "❯ CPU: ${CPU} | RAM: ${AVAIL_MEM/ GB/}/$TOTAL_MEM | DISK: $SPACE_GB (${FS}) | KERNEL: ${SYS}"
|
||||
echo
|
||||
|
||||
checkHost
|
||||
checkKvm
|
||||
|
||||
# Runtime state is intentionally discarded at each container start; persistent
|
||||
|
||||
@@ -290,6 +290,70 @@ makeDir() {
|
||||
return 0
|
||||
}
|
||||
|
||||
app() {
|
||||
|
||||
echo "Virtual DSM"
|
||||
return 0
|
||||
}
|
||||
|
||||
finiteMemoryLimit() {
|
||||
|
||||
local limit="$1"
|
||||
# cgroup v1 commonly reports this enormous sentinel for an unlimited memory
|
||||
# limit; compare as decimal strings to avoid shell integer overflow.
|
||||
local sentinel="4611686018427387904"
|
||||
local i
|
||||
|
||||
[[ "$limit" =~ ^[0-9]+$ ]] || return 1
|
||||
|
||||
(( ${#limit} < ${#sentinel} )) && return 0
|
||||
(( ${#limit} > ${#sentinel} )) && return 1
|
||||
|
||||
for (( i=0; i<${#sentinel}; i++ )); do
|
||||
local left="${limit:i:1}"
|
||||
local right="${sentinel:i:1}"
|
||||
|
||||
(( left < right )) && return 0
|
||||
(( left > right )) && return 1
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
getMemoryInfo() {
|
||||
|
||||
local host_total
|
||||
local host_avail
|
||||
local limit=""
|
||||
local current=""
|
||||
|
||||
host_total=$(free -b | awk '/^Mem:/ {print $2; exit}')
|
||||
host_avail=$(free -b | awk '/^Mem:/ {print $7; exit}')
|
||||
|
||||
RAM_TOTAL="$host_total"
|
||||
RAM_AVAIL="$host_avail"
|
||||
|
||||
if [ -r /sys/fs/cgroup/memory.max ] && [ -r /sys/fs/cgroup/memory.current ]; then
|
||||
limit=$(< /sys/fs/cgroup/memory.max)
|
||||
current=$(< /sys/fs/cgroup/memory.current)
|
||||
elif [ -r /sys/fs/cgroup/memory/memory.limit_in_bytes ] && [ -r /sys/fs/cgroup/memory/memory.usage_in_bytes ]; then
|
||||
limit=$(< /sys/fs/cgroup/memory/memory.limit_in_bytes)
|
||||
current=$(< /sys/fs/cgroup/memory/memory.usage_in_bytes)
|
||||
fi
|
||||
|
||||
# Use the tighter of host availability and the container's remaining cgroup
|
||||
# allowance so RAM sizing cannot exceed either boundary.
|
||||
if finiteMemoryLimit "$limit" && [[ "$current" =~ ^[0-9]+$ ]]; then
|
||||
(( limit < RAM_TOTAL )) && RAM_TOTAL="$limit"
|
||||
|
||||
local available=$(( limit - current ))
|
||||
(( available < 0 )) && available=0
|
||||
(( available < RAM_AVAIL )) && RAM_AVAIL="$available"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
stateFile() {
|
||||
|
||||
local name="$1"
|
||||
|
||||
Reference in New Issue
Block a user