История изменений
Исправление manul91, (текущая версия) :
Максимальную память бэкап процесса/скрипта проще всего организовать если запускать его сервисом средствами systemd (по таймеру), см. MemoryMax, MemoryHigh параметры юнитов https://serverfault.com/questions/1108857/what-is-the-maximum-value-for-system-d-memorymax
Если запускать ручно прямо с консоли, от не-рута, можно примерно так:
systemd-run --user --scope -p MemoryMax=5G -p MemorySwapMax=0 executable-to-run-memorylimited
ionice -c 3 дополнительно еще ограничит приоритет IO процесса до idle (только когда другие процессы не пользуют IO):
systemd-run --user --scope -p MemoryMax=5G -p MemorySwapMax=0 ionice -c 3 executable-to-run-memorylimited-and-iolimited
Если systemd не пользовать, можно прямо заюзать файловую систему cgroups v2 скриптом.
Вот кустарный скрипт, который запускает любой другой скрипт ограниченным в памяти (недостаток что нужно запускать только в контексте рута)
# cat run-memory-limited
#!/bin/sh
executableFileExistOrDie() {
[ -x "${1}" ] || { echo "Could not find command ${1}! Exit."; exit 1; }
}
showhelp() {
echo ${1}
echo
echo "Usage:"
echo "${0} [mem-limit] [command args]"
echo
echo "Example:"
echo "${0} 500M cp file1 dest1/"
echo
}
# veryfy args and executables
ARGNUM=$#
[ ${ARGNUM} -lt 2 ] && { showhelp "Need at least 2 arguments, currently ${ARGNUM}!!"; exit 1; }
[ $(id -u) = 0 ] || { showhelp "This script must be run as root !!!"; exit 1; }
MKTEMP="/usr/bin/mktemp" && executableFileExistOrDie "${MKTEMP}"
MKDIR="/usr/bin/mkdir" && executableFileExistOrDie "${MKDIR}"
RMDIR="/usr/bin/rmdir" && executableFileExistOrDie "${RMDIR}"
MOUNT="/usr/bin/mount" && executableFileExistOrDie "${MOUNT}"
UMOUNT="/usr/bin/umount" && executableFileExistOrDie "${UMOUNT}"
CAT="/usr/bin/cat" && executableFileExistOrDie "${CAT}"
WC="/usr/bin/wc" && executableFileExistOrDie "${WC}"
TR="/usr/bin/tr" && executableFileExistOrDie "${TR}"
ECHO="/usr/bin/echo" && executableFileExistOrDie "${ECHO}"
BASENAME="/usr/bin/basename" && executableFileExistOrDie "${BASENAME}"
CGROUPDIR="/tmp/memgroup"
CGROUPSUBDIR="memory-limited-temp"
SCRIPTNAME=`${BASENAME} "${0}"`
CEXITCODE=0
# process args
MEM="$1"
shift
CMND="$@"
### CREATE SPECIAL MEM-LIMITED CGROUP AND RUN INSIDE; THIS IS TO ENSURE THE PROCESS WILL NOT CONSUME TOO MUCH BUFFER MEM AND WILL NOT SWAP-OUT
THISPROCESS=$$
${MKDIR} -p ${CGROUPDIR}
${MOUNT} -t cgroup2 none ${CGROUPDIR}
MEMORY_LIMITED_DIR="$(${MKTEMP} -d ${CGROUPDIR}/${CGROUPSUBDIR}.XXXXXXXXXXXXX)"
#ensure memory is enabled for subtrees
echo "+memory" > ${CGROUPDIR}/cgroup.subtree_control
#enable mem max
echo ${MEM} > ${MEMORY_LIMITED_DIR}/memory.max
echo ${THISPROCESS} > ${MEMORY_LIMITED_DIR}/cgroup.procs
### END OF CGROUP SELF-ASSIGNMENT
${ECHO} -e "Jail cgroup dir:\t ${MEMORY_LIMITED_DIR}"
${ECHO} -e "Memory jailed to:\t ${MEM}"
${ECHO} -e "Wrapper proc id:\t ${THISPROCESS}"
${ECHO} -e "Wrapper proc name:\t ${SCRIPTNAME}"
${ECHO} -e "Command to execute:\t ${CMND}"
### EXECUTE COMMAND CGROUP MEMORY-JAILED
echo
echo "Executing command ""'""${CMND}""'"" ...."
${CMND}
CEXITCODE=$?
[ ${CEXITCODE} -eq 0 ] || { echo "Command ""'""${CMND}""'"" failed with exit code ${CEXITCODE} !"; }
[ ${CEXITCODE} -ne 0 ] || { echo "Command ""'""${CMND}""'"" executed and exited with code 0."; }
### WAIT TO FINISH AND CLEANUP CGROUP MEMORYJAIL
echo
echo "Waiting for all possibly async raised child jobs/processes to finish..."
wait
# move back in the main process pool, therefore leaving ${MEMORY_LIMITED_DIR}/cgroup.procs eventually empty, except perhaps the child async tasks/processes
echo ${THISPROCESS} > ${CGROUPDIR}/cgroup.procs
WAITCTR=0
while :
do
PROCNUMLEFT=`${CAT} ${MEMORY_LIMITED_DIR}/cgroup.procs | ${WC} -l`
if [ ${PROCNUMLEFT} -gt 0 ]
then
# is there still something in tasks? Ideally, we may need to wait until this becomes empty - or explicitly move all of existing stuff in the main task pool?
${ECHO} -n "${SCRIPTNAME} : `date`: ${PROCNUMLEFT} procs ids left in ${MEMORY_LIMITED_DIR}/cgroup.procs "
${CAT} ${MEMORY_LIMITED_DIR}/cgroup.procs | ${TR} '\n' ','
${ECHO} ". Waited ${WAITCTR} sec total..."
sleep 10
WAITCTR=$((${WAITCTR} + 10))
else
break;
fi
done
echo "All possibly async raised child jobs/processes finished."
${RMDIR} ${MEMORY_LIMITED_DIR}
${UMOUNT} ${CGROUPDIR}
${RMDIR} ${CGROUPDIR}
echo "Syncing..."
sync
echo "Done."
exit $CEXITCODE
Пользоваться так:
# run-memory-limited 5G executable-to-run-memorylimited
Чтобы не только память не засорялась (макс. 5G), а также и IO бэкапа тоже не мешало/замедляло виртуалок, можно запускать примерно так через ionice (хотя если vm-backup-script.sh рождает асинхронные процессы не уверен что ionice idle распространится и на них, лучше проверить, можно понадобится вызывать их всегда с ionice -c 3 експлицитно) :
# run-memory-limited 5G ionice -c 3 vm-backup-script.sh
Исправление manul91, :
Максимальную память бэкап процесса/скрипта проще всего организовать если запускать его сервисом средствами systemd (по таймеру), см. MemoryMax, MemoryHigh параметры юнитов https://serverfault.com/questions/1108857/what-is-the-maximum-value-for-system-d-memorymax
Если запускать ручно прямо с консоли, от не-рута, можно примерно так:
systemd-run --user --scope -p MemoryMax=5G -p MemorySwapMax=0 executable-to-run-memorylimited
ionice -c 3 дополнительно еще ограничит приоритет IO процесса до idle (только когда другие процессы не пользуют IO):
systemd-run --user --scope -p MemoryMax=5G -p MemorySwapMax=0 ionice -c 3 executable-to-run-memorylimited-and-iolimited
Если systemd не пользовать, можно прямо заюзать файловую систему cgroups v2 скриптом.
Вот кустарный скрипт, который запускает любой другой скрипт ограниченным в памяти (недостаток что нужно запускать только в контексте рута)
# cat run-memory-limited
#!/bin/sh
executableFileExistOrDie() {
[ -x "${1}" ] || { echo "Could not find command ${1}! Exit."; exit 1; }
}
showhelp() {
echo ${1}
echo
echo "Usage:"
echo "${0} [mem-limit] [command args]"
echo
echo "Example:"
echo "${0} 500M cp file1 dest1/"
echo
}
# veryfy args and executables
ARGNUM=$#
[ ${ARGNUM} -lt 2 ] && { showhelp "Need at least 2 arguments, currently ${ARGNUM}!!"; exit 1; }
[ $(id -u) = 0 ] || { showhelp "This script must be run as root !!!"; exit 1; }
MKTEMP="/usr/bin/mktemp" && executableFileExistOrDie "${MKTEMP}"
MKDIR="/usr/bin/mkdir" && executableFileExistOrDie "${MKDIR}"
RMDIR="/usr/bin/rmdir" && executableFileExistOrDie "${RMDIR}"
MOUNT="/usr/bin/mount" && executableFileExistOrDie "${MOUNT}"
UMOUNT="/usr/bin/umount" && executableFileExistOrDie "${UMOUNT}"
CAT="/usr/bin/cat" && executableFileExistOrDie "${CAT}"
WC="/usr/bin/wc" && executableFileExistOrDie "${WC}"
TR="/usr/bin/tr" && executableFileExistOrDie "${TR}"
ECHO="/usr/bin/echo" && executableFileExistOrDie "${ECHO}"
BASENAME="/usr/bin/basename" && executableFileExistOrDie "${BASENAME}"
CGROUPDIR="/tmp/memgroup"
CGROUPSUBDIR="memory-limited-temp"
SCRIPTNAME=`${BASENAME} "${0}"`
CEXITCODE=0
# process args
MEM="$1"
shift
CMND="$@"
### CREATE SPECIAL MEM-LIMITED CGROUP AND RUN INSIDE; THIS IS TO ENSURE THE PROCESS WILL NOT CONSUME TOO MUCH BUFFER MEM AND WILL NOT SWAP-OUT
THISPROCESS=$$
${MKDIR} -p ${CGROUPDIR}
${MOUNT} -t cgroup2 none ${CGROUPDIR}
MEMORY_LIMITED_DIR="$(${MKTEMP} -d ${CGROUPDIR}/${CGROUPSUBDIR}.XXXXXXXXXXXXX)"
#ensure memory is enabled for subtrees
echo "+memory" > ${CGROUPDIR}/cgroup.subtree_control
#enable mem max
echo ${MEM} > ${MEMORY_LIMITED_DIR}/memory.max
echo ${THISPROCESS} > ${MEMORY_LIMITED_DIR}/cgroup.procs
### END OF CGROUP SELF-ASSIGNMENT
${ECHO} -e "Jail cgroup dir:\t ${MEMORY_LIMITED_DIR}"
${ECHO} -e "Memory jailed to:\t ${MEM}"
${ECHO} -e "Wrapper proc id:\t ${THISPROCESS}"
${ECHO} -e "Wrapper proc name:\t ${SCRIPTNAME}"
${ECHO} -e "Command to execute:\t ${CMND}"
### EXECUTE COMMAND CGROUP MEMORY-JAILED
echo
echo "Executing command ""'""${CMND}""'"" ...."
${CMND}
CEXITCODE=$?
[ ${CEXITCODE} -eq 0 ] || { echo "Command ""'""${CMND}""'"" failed with exit code ${CEXITCODE} !"; }
[ ${CEXITCODE} -ne 0 ] || { echo "Command ""'""${CMND}""'"" executed and exited with code 0."; }
### WAIT TO FINISH AND CLEANUP CGROUP MEMORYJAIL
echo
echo "Waiting for all possibly async raised child jobs/processes to finish..."
wait
# move back in the main process pool, therefore leaving ${MEMORY_LIMITED_DIR}/cgroup.procs eventually empty, except perhaps the child async tasks/processes
echo ${THISPROCESS} > ${CGROUPDIR}/cgroup.procs
WAITCTR=0
while :
do
PROCNUMLEFT=`${CAT} ${MEMORY_LIMITED_DIR}/cgroup.procs | ${WC} -l`
if [ ${PROCNUMLEFT} -gt 0 ]
then
# is there still something in tasks? Ideally, we may need to wait until this becomes empty - or explicitly move all of existing stuff in the main task pool?
${ECHO} -n "${SCRIPTNAME} : `date`: ${PROCNUMLEFT} procs ids left in ${MEMORY_LIMITED_DIR}/cgroup.procs "
${CAT} ${MEMORY_LIMITED_DIR}/cgroup.procs | ${TR} '\n' ','
${ECHO} ". Waited ${WAITCTR} sec total..."
sleep 10
WAITCTR=$((${WAITCTR} + 10))
else
break;
fi
done
echo "All possibly async raised child jobs/processes finished."
${RMDIR} ${MEMORY_LIMITED_DIR}
${UMOUNT} ${CGROUPDIR}
${RMDIR} ${CGROUPDIR}
echo "Syncing..."
sync
echo "Done."
exit $CEXITCODE
Пользоваться так:
# run-memory-limited 5G executable-to-run-memorylimited
Чтобы не только память не засорялась (макс. 5G), а также и IO бэкапа тоже не мешало/замедляло виртуалок, можно запускать примерно так через ionice (хотя если vm-backup-script.sh рождает асинхронные процессы не уверен что ionice idle распространится и на них, лучше проверить, можно понадобится вызывать их всегда с ionice -c 3 експлицитно) :
# run-memory-limited 5G ionice -3 vm-backup-script.sh
Исходная версия manul91, :
Максимальную память бэкап процесса/скрипта проще всего организовать если запускать его сервисом средствами systemd (по таймеру), см. MemoryMax, MemoryHigh параметры юнитов https://serverfault.com/questions/1108857/what-is-the-maximum-value-for-system-d-memorymax
Если запускать ручно прямо с консоли, от не-рута, можно примерно так:
systemd-run --user --scope -p MemoryMax=5G -p MemorySwapMax=0 executable-to-run-memorylimited
ionice -c 3 дополнительно еще ограничит приоритет IO процесса до idle (только когда другие процессы не пользуют IO):
systemd-run --user --scope -p MemoryMax=5G -p MemorySwapMax=0 ionice -c 3 executable-to-run-memorylimited-and-iolimited
Если systemd не пользовать, можно прямо заюзать файловую систему cgroups v2 скриптом.
Вот кустарный скрипт, который запускает любой другой скрипт ограниченным в памяти (недостаток что нужно запускать только в контексте рута)
# cat run-memory-limited
#!/bin/sh
executableFileExistOrDie() {
[ -x "${1}" ] || { echo "Could not find command ${1}! Exit."; exit 1; }
}
showhelp() {
echo ${1}
echo
echo "Usage:"
echo "${0} [mem-limit] [command args]"
echo
echo "Example:"
echo "${0} 500M cp file1 dest1/"
echo
}
# veryfy args and executables
ARGNUM=$#
[ ${ARGNUM} -lt 2 ] && { showhelp "Need at least 2 arguments, currently ${ARGNUM}!!"; exit 1; }
[ $(id -u) = 0 ] || { showhelp "This script must be run as root !!!"; exit 1; }
MKTEMP="/usr/bin/mktemp" && executableFileExistOrDie "${MKTEMP}"
MKDIR="/usr/bin/mkdir" && executableFileExistOrDie "${MKDIR}"
RMDIR="/usr/bin/rmdir" && executableFileExistOrDie "${RMDIR}"
MOUNT="/usr/bin/mount" && executableFileExistOrDie "${MOUNT}"
UMOUNT="/usr/bin/umount" && executableFileExistOrDie "${UMOUNT}"
CAT="/usr/bin/cat" && executableFileExistOrDie "${CAT}"
WC="/usr/bin/wc" && executableFileExistOrDie "${WC}"
TR="/usr/bin/tr" && executableFileExistOrDie "${TR}"
ECHO="/usr/bin/echo" && executableFileExistOrDie "${ECHO}"
BASENAME="/usr/bin/basename" && executableFileExistOrDie "${BASENAME}"
CGROUPDIR="/tmp/memgroup"
CGROUPSUBDIR="memory-limited-temp"
SCRIPTNAME=`${BASENAME} "${0}"`
CEXITCODE=0
# process args
MEM="$1"
shift
CMND="$@"
### CREATE SPECIAL MEM-LIMITED CGROUP AND RUN INSIDE; THIS IS TO ENSURE THE PROCESS WILL NOT CONSUME TOO MUCH BUFFER MEM AND WILL NOT SWAP-OUT
THISPROCESS=$$
${MKDIR} -p ${CGROUPDIR}
${MOUNT} -t cgroup2 none ${CGROUPDIR}
MEMORY_LIMITED_DIR="$(${MKTEMP} -d ${CGROUPDIR}/${CGROUPSUBDIR}.XXXXXXXXXXXXX)"
#ensure memory is enabled for subtrees
echo "+memory" > ${CGROUPDIR}/cgroup.subtree_control
#enable mem max
echo ${MEM} > ${MEMORY_LIMITED_DIR}/memory.max
echo ${THISPROCESS} > ${MEMORY_LIMITED_DIR}/cgroup.procs
### END OF CGROUP SELF-ASSIGNMENT
${ECHO} -e "Jail cgroup dir:\t ${MEMORY_LIMITED_DIR}"
${ECHO} -e "Memory jailed to:\t ${MEM}"
${ECHO} -e "Wrapper proc id:\t ${THISPROCESS}"
${ECHO} -e "Wrapper proc name:\t ${SCRIPTNAME}"
${ECHO} -e "Command to execute:\t ${CMND}"
### EXECUTE COMMAND CGROUP MEMORY-JAILED
echo
echo "Executing command ""'""${CMND}""'"" ...."
${CMND}
CEXITCODE=$?
[ ${CEXITCODE} -eq 0 ] || { echo "Command ""'""${CMND}""'"" failed with exit code ${CEXITCODE} !"; }
[ ${CEXITCODE} -ne 0 ] || { echo "Command ""'""${CMND}""'"" executed and exited with code 0."; }
### WAIT TO FINISH AND CLEANUP CGROUP MEMORYJAIL
echo
echo "Waiting for all possibly async raised child jobs/processes to finish..."
wait
# move back in the main process pool, therefore leaving ${MEMORY_LIMITED_DIR}/cgroup.procs eventually empty, except perhaps the child async tasks/processes
echo ${THISPROCESS} > ${CGROUPDIR}/cgroup.procs
WAITCTR=0
while :
do
PROCNUMLEFT=`${CAT} ${MEMORY_LIMITED_DIR}/cgroup.procs | ${WC} -l`
if [ ${PROCNUMLEFT} -gt 0 ]
then
# is there still something in tasks? Ideally, we may need to wait until this becomes empty - or explicitly move all of existing stuff in the main task pool?
${ECHO} -n "${SCRIPTNAME} : `date`: ${PROCNUMLEFT} procs ids left in ${MEMORY_LIMITED_DIR}/cgroup.procs "
${CAT} ${MEMORY_LIMITED_DIR}/cgroup.procs | ${TR} '\n' ','
${ECHO} ". Waited ${WAITCTR} sec total..."
sleep 10
WAITCTR=$((${WAITCTR} + 10))
else
break;
fi
done
echo "All possibly async raised child jobs/processes finished."
${RMDIR} ${MEMORY_LIMITED_DIR}
${UMOUNT} ${CGROUPDIR}
${RMDIR} ${CGROUPDIR}
echo "Syncing..."
sync
echo "Done."
exit $CEXITCODE
Пользоваться так:
# run-memory-limited 5G executable-to-run-memorylimited