LINUX.ORG.RU

История изменений

Исправление 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