単一のLinuxプロセスのメモリ使用量を制限する


152

pdftoppmユーザー提供のPDFを300DPI画像に変換するために実行しています。これは、ユーザーが非常に大きなページサイズのPDFを提供する場合を除いて、うまく機能します。 pdftoppmそのサイズの300DPIイメージをメモリに保持するのに十分なメモリを割り当てます。100インチの正方形ページの場合、100 * 300 * 100 * 300 *ピクセルあたり4バイト= 3.5GBです。悪意のあるユーザーが、ばかげた大きなPDFを渡して、あらゆる種類の問題を引き起こす可能性があります。

だから、私がやりたいことは、私が実行しようとしている子プロセスのメモリ使用量に何らかのハード制限を設定することです。たとえば、500MB以上のメモリを割り当てようとすると、プロセスが死ぬだけです。それは可能ですか?

これにulimitを使用できるとは思いませんが、1プロセスに相当するものはありますか?


たぶんdocker
スリダールサルノバト

回答:


58

ulimitにはいくつかの問題があります。Linuxのプログラムの時間とメモリ消費を制限するトピックに関する有用な記事を次に示します。これにより、時間またはメモリ消費によってプロセス(およびそのフォーク)をケージすることができるタイムアウトツールになります。

タイムアウトツールには、Perl 5以降と/procファイルシステムがマウントされている必要があります。その後、ツールを次の/usr/local/binようにコピーします。

curl https://raw.githubusercontent.com/pshved/timeout/master/timeout | \
  sudo tee /usr/local/bin/timeout && sudo chmod 755 /usr/local/bin/timeout

その後、あなたの質問のように、メモリ消費によってプロセスを「ケージ」することができます:

timeout -m 500 pdftoppm Sample.pdf

または、とを使用-t <seconds>-x <hertz>て、時間またはCPUの制約によってプロセスをそれぞれ制限できます。

このツールが機能する方法は、生成されたプロセスが設定された境界をオーバーサブスクライブしていない場合、1秒間に複数回チェックすることです。これは、タイムアウトが通知されてプロセスが強制終了される前に、プロセスが潜在的にオーバーサブスクライブする可能性のある小さなウィンドウがあることを意味します。

したがって、より正確なアプローチにはcgroupが関係する可能性がありますが、DockerまたはrunCを使用する場合でも、セットアップにはより多くの労力がかかります。


私のために今も働いているようです(再び?)が、ここにGoogleキャッシュバージョンがあります:webcache.googleusercontent.com/…–
kvz

タスクセットとともにタイムアウトを使用できますか(メモリとコアの両方を制限する必要があります)?
ランチ

7
この答えはcoreutils、同じ名前のLinux標準ユーティリティに関するものではないことに注意してください!したがって、システムのどこかで、一部のパッケージtimeoutにLinux標準coreutilsパッケージを想定したスクリプトがある場合、答えは潜在的に危険です!このツールがdebianなどのディストリビューション用にパッケージ化されていることは知りません。
user1404316

ない-t <seconds>制約は、その何秒後にそのプロセスを殺しますか?
xxx374562

116

これを制限する別の方法は、Linuxのコントロールグループを使用することです。これは、プロセス(またはプロセスのグループ)の物理メモリの割り当てを仮想メモリとは明確に制限したい場合に特に役立ちます。例えば:

cgcreate -g memory:myGroup
echo 500M > /sys/fs/cgroup/memory/myGroup/memory.limit_in_bytes
echo 5G > /sys/fs/cgroup/memory/myGroup/memory.memsw.limit_in_bytes

という名前のコントロールグループを作成しますmyGroup。myGroupの下で実行される一連のプロセスを最大500 MBの物理メモリと最大5000 MBのスワップに制限します。制御グループの下でプロセスを実行するには:

cgexec -g memory:myGroup pdftoppm

最新のUbuntuディストリビューションでは、この例を変更するにはcgroup-binパッケージをインストールして編集/etc/default/grubする必要がありますGRUB_CMDLINE_LINUX_DEFAULT

GRUB_CMDLINE_LINUX_DEFAULT="cgroup_enable=memory swapaccount=1"

次にsudo update-grub、新しいカーネルブートパラメータで起動するために実行および再起動します。


3
また、このfirejailプログラムを使用すると、メモリ制限のあるプロセスを開始できます(cgroupと名前空間を使用して、メモリ以上のものを制限します)。私のシステムでは、これが機能するためにカーネルコマンドラインを変更する必要はありませんでした!
Ned64

1
GRUB_CMDLINE_LINUX_DEFAULT設定を永続化するために変更が必要ですか?ここで永続化する別の方法を見つけまし
-stason


この回答では、一部のディストリビューション(Ubuntuなど)では、cgcreateにsudoが必要であり、現在のユーザーに許可が与えられない限り、後のコマンドにも注意してください。これにより、読者は他の場所でこの情報を見つける必要がなくなります(例:askubuntu.com/questions/345055)。この効果の編集を提案しましたが、拒否されました。
stewbasic

77

プロセスが、最もメモリを消費する子を生成しない場合、setrlimit関数を使用できます。そのためのより一般的なユーザーインターフェイスulimitは、シェルのコマンドを使用しています。

$ ulimit -Sv 500000     # Set ~500 mb limit
$ pdftoppm ...

これにより、プロセスの「仮想」メモリが制限されます。これは、呼び出されるプロセスが他のプロセスと共有するメモリと、マップされているが予約されていないメモリ(Javaの大きなヒープなど)を考慮して制限します。それでも、仮想メモリは、非常に大きくなるプロセスに最も近い近似値であるため、上記のエラーはわずかです。

プログラムが子を生成し、それがメモリを割り当てるのがそれらである場合、それはより複雑になり、制御下でプロセスを実行するための補助スクリプトを作成する必要があります。私は自分のブログに、その理由方法を書いた。


2
なぜsetrlimitより多くの子供にとってより複雑なのですか?man setrlimit「fork(2)を介して作成された子プロセスは親のリソース制限を継承します。リソース制限はexecve(2)全体で保持されます」
-akira

6
カーネルはすべての子プロセスのvmサイズを合計しないためです。それが行われた場合、とにかく間違った答えを得るでしょう。制限はプロセスごとであり、メモリ使用量ではなく仮想アドレス空間です。メモリ使用量を測定することは困難です。
MarkR

1
私が質問を正しく理解している場合、OPはサブプロセス(子)ごとの制限を..合計ではなく
アキラ

とにかく、@ MarkRは、特に仮想マシン(Javaなど)によって制御されていないプログラムを実行する場合、使用されるメモリの適切な近似です。少なくとも、これ以上のメトリックはわかりません。

2
ただ、感謝を言いたかった-このulimitアプローチはで私を助けたfirefoxのが「バグ622816 - Firefoxの『フリーズ』、またはシステムがクラッシュすることができ、大きな画像をロードします。USBブート(RAMから)ではOSがフリーズする傾向があり、ハード再起動が必要です。今では少なくともfirefoxクラッシュし、OSが生き残っています...乾杯!
sdaau

8

以下のスクリプトを使用していますが、これは非常に効果的です。cgroupsを使用しcgmanagerます。 更新:からのコマンドを使用するようになりましたcgroup-toolsこのスクリプトに名前limitmemを付けて$ PATHに入れると、次のように使用できますlimitmem 100M bash。これにより、メモリとスワップの両方の使用が制限されます。メモリだけを制限するには、で行を削除しmemory.memsw.limit_in_bytesます。

編集:デフォルトのLinuxインストールでは、これはメモリ使用量のみを制限し、スワップ使用量は制限しません。スワップ使用制限を有効にするには、Linuxシステムでスワップアカウンティングを有効にする必要があります。それを設定/追加swapaccount=1/etc/default/grubて、次のようにします

GRUB_CMDLINE_LINUX="swapaccount=1"

次に、実行sudo update-grubして再起動します。

免責事項:cgroup-tools将来的に壊れても驚かないでしょう。正しい解決策は、cgroup管理にsystemd apiを使用することですが、そのatm用のコマンドラインツールはありません

#!/bin/sh

# This script uses commands from the cgroup-tools package. The cgroup-tools commands access the cgroup filesystem directly which is against the (new-ish) kernel's requirement that cgroups are managed by a single entity (which usually will be systemd). Additionally there is a v2 cgroup api in development which will probably replace the existing api at some point. So expect this script to break in the future. The correct way forward would be to use systemd's apis to create the cgroups, but afaik systemd currently (feb 2018) only exposes dbus apis for which there are no command line tools yet, and I didn't feel like writing those.

# strict mode: error if commands fail or if unset variables are used
set -eu

if [ "$#" -lt 2 ]
then
    echo Usage: `basename $0` "<limit> <command>..."
    echo or: `basename $0` "<memlimit> -s <swaplimit> <command>..."
    exit 1
fi

cgname="limitmem_$$"

# parse command line args and find limits

limit="$1"
swaplimit="$limit"
shift

if [ "$1" = "-s" ]
then
    shift
    swaplimit="$1"
    shift
fi

if [ "$1" = -- ]
then
    shift
fi

if [ "$limit" = "$swaplimit" ]
then
    memsw=0
    echo "limiting memory to $limit (cgroup $cgname) for command $@" >&2
else
    memsw=1
    echo "limiting memory to $limit and total virtual memory to $swaplimit (cgroup $cgname) for command $@" >&2
fi

# create cgroup
sudo cgcreate -g "memory:$cgname"
sudo cgset -r memory.limit_in_bytes="$limit" "$cgname"
bytes_limit=`cgget -g "memory:$cgname" | grep memory.limit_in_bytes | cut -d\  -f2`

# try also limiting swap usage, but this fails if the system has no swap
if sudo cgset -r memory.memsw.limit_in_bytes="$swaplimit" "$cgname"
then
    bytes_swap_limit=`cgget -g "memory:$cgname" | grep memory.memsw.limit_in_bytes | cut -d\  -f2`
else
    echo "failed to limit swap"
    memsw=0
fi

# create a waiting sudo'd process that will delete the cgroup once we're done. This prevents the user needing to enter their password to sudo again after the main command exists, which may take longer than sudo's timeout.
tmpdir=${XDG_RUNTIME_DIR:-$TMPDIR}
tmpdir=${tmpdir:-/tmp}
fifo="$tmpdir/limitmem_$$_cgroup_closer"
mkfifo --mode=u=rw,go= "$fifo"
sudo -b sh -c "head -c1 '$fifo' >/dev/null ; cgdelete -g 'memory:$cgname'"

# spawn subshell to run in the cgroup. If the command fails we still want to remove the cgroup so unset '-e'.
set +e
(
set -e
# move subshell into cgroup
sudo cgclassify -g "memory:$cgname" --sticky `sh -c 'echo $PPID'`  # $$ returns the main shell's pid, not this subshell's.
exec "$@"
)

# grab exit code 
exitcode=$?

set -e

# show memory usage summary

peak_mem=`cgget -g "memory:$cgname" | grep memory.max_usage_in_bytes | cut -d\  -f2`
failcount=`cgget -g "memory:$cgname" | grep memory.failcnt | cut -d\  -f2`
percent=`expr "$peak_mem" / \( "$bytes_limit" / 100 \)`

echo "peak memory used: $peak_mem ($percent%); exceeded limit $failcount times" >&2

if [ "$memsw" = 1 ]
then
    peak_swap=`cgget -g "memory:$cgname" | grep memory.memsw.max_usage_in_bytes | cut -d\  -f2`
    swap_failcount=`cgget -g "memory:$cgname" |grep memory.memsw.failcnt | cut -d\  -f2`
    swap_percent=`expr "$peak_swap" / \( "$bytes_swap_limit" / 100 \)`

    echo "peak virtual memory used: $peak_swap ($swap_percent%); exceeded limit $swap_failcount times" >&2
fi

# remove cgroup by sending a byte through the pipe
echo 1 > "$fifo"
rm "$fifo"

exit $exitcode

1
call to cgmanager_create_sync failed: invalid requestで実行しようとするプロセスごとにlimitmem 100M processname。Xubuntu 16.04 LTSを使用していますが、そのパッケージがインストールされています。
アーロンフランケ

UPS、このエラーメッセージが表示されます$ limitmem 400M rstudio limiting memory to 400M (cgroup limitmem_24575) for command rstudio Error org.freedesktop.DBus.Error.InvalidArgs: invalid request
Rキセリョフ博士

@RKiselev cgmanagerは非推奨になり、Ubuntu 17.10でも使用できなくなりました。使用するsystemd apiはある時点で変更されたため、おそらくそれが理由です。cgroup-toolsコマンドを使用するようにスクリプトを更新しました。
-JanKanis

計算percent結果がゼロの場合、exprステータスコードは1で、このスクリプトは途中で終了します。行を次のように変更することをお勧めします:(percent=$(( "$peak_mem" / $(( "$bytes_limit" / 100 )) ))参照:unix.stackexchange.com/questions/63166/…
Willi Ballenthin

制限を超えた場合、cgroupを設定してプロセスを強制終了するにはどうすればよいですか?
d9ngle

7

ツールに加えてdaemontools、マーク・ジョンソンによって提案され、あなたも考慮することができますchpstで発見されましたrunit。Runit自体はにバンドルされているbusyboxため、既にインストールされている可能性があります。

manページにchpstオプション表示されます。

-mバイトはメモリを制限します。データセグメント、スタックセグメント、ロックされた物理ページ、およびプロセスごとのすべてのセグメントの合計をそれぞれバイトバイトに制限します。


3

私はUbuntu 18.04.2 LTSを実行していますが、JanKanisスクリプトは彼が示唆するように私にはまったく機能しません。実行limitmem 100M scriptすると、無制限のスワップで100MBのRAMが制限されます。

という名前のパラメーターがないためlimitmem 100M -s 100M script、実行はサイレントに失敗しcgget -g "memory:$cgname"ますmemory.memsw.limit_in_bytes

だから私はスワップを無効にしました:

# create cgroup
sudo cgcreate -g "memory:$cgname"
sudo cgset -r memory.limit_in_bytes="$limit" "$cgname"
sudo cgset -r memory.swappiness=0 "$cgname"
bytes_limit=`cgget -g "memory:$cgname" | grep memory.limit_in_bytes | cut -d\  -f2`

@sourcejediが追加しました:)
d9ngle

2
そう、答えを編集しました。スワップ制限を有効にするには、システムでスワップアカウンティングを有効にする必要があります。これには実行時のオーバーヘッドがわずかにあるため、Ubuntuではデフォルトで有効になっていません。編集をご覧ください。
JanKanis

3

systemdベースのディストリビューションでは、systemd-runを介して間接的にcgroupsを使用することもできます。たとえばpdftoppm、RAMを500Mに制限する場合、次を使用します。

systemd-run --scope -p MemoryLimit=500M pdftoppm

注:これはパスワードを要求しますが、アプリはユーザーとして起動されます。これがコマンドを必要とすることを思い込ませることを許可sudoしないでください。

パスワードを入力しない場合(結局、メモリを所有するユーザーとして、なぜそれを制限するためにパスワードが必要なのか)--userオプションを使用できますが、これを機能させるにはcgroupsv2サポートを有効にする必要があります今でブートするために必要なsystemd.unified_cgroup_hierarchyカーネルパラメータ


ありがとう、私の一日
Geradlus_RU
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.