Linux 设置内存用量限制

实验室有一位大神,他写的程序每次都会吃掉几百 G 的内存然后把服务器搞崩。作为 SysAdmin,肯定不能让服务器动不动就崩溃,因此我决定采取一些方法限制内存用量。

使用 earlyoom

earlyoom 会在可用内存和 SWAP 均不足 10% 时关闭内存占用最大的进程。

安装

  • 从 APT 安装 earlyoom(版本可能较旧):

    sudo apt install earlyoom
    
  • 手动安装:

    git clone https://github.com/rfjakob/earlyoom.git
    cd earlyoom
    make
    sudo make install
    

配置

  1. 编辑配置文件:

    sudoedit /etc/default/earlyoom
    
    EARLYOOM_ARGS="-m 5 -r 60 --avoid '(^|/)(init|Xorg|ssh)$' --prefer '(^|/)(java|chromium)$'"
    
    • -m PERCENT[,KILL_PERCENT]:设置可用内存的百分比阈值。当可用内存低于 PERCENT(默认 10)时发送 SIGTERM 信号,低于 KILL_PERCENT(默认 PERCENT/2)时发送 SIGKILL 信号
    • -s PERCENT[,KILL_PERCENT]:设置空闲 SWAP 的 PERCENTKILL_PERCENT。可用内存和 SWAP 的阈值都达到之后 earlyoom 才会介入
    • -M SIZE[,KILL_SIZE]:设置可用内存的绝对值阈值(KiB)
    • -S SIZE[,KILL_SIZE]:设置 SWAP 的绝对值阈值(KiB)
    • -n:启用 d-bus 通知
    • -N /PATH/TO/SCRIPT:oom kill 后要执行的脚本
    • -g:杀死进程组内的所有进程
    • -r INTERVAL:内存报告间隔(sec)(默认 1)
    • --dryrun:dry run,不实际杀死进程
    • --ignore-root-user:不杀死 root 用户的进程
    • --prefer REGEX:优先杀死匹配正则表达式的进程
    • --avoid REGEX:避免杀死匹配正则表达式的进程
    • --ignore REGEX:忽略匹配正则表达式的进程
  2. 重启 earlyoom:

    sudo systemctl restart earlyoom
    
  3. 查看服务状态:

    sudo systemctl status earlyoom
    
  4. 查看服务日志:

    sudo journalctl -u earlyoom | grep sending
    

测试

  1. 模拟内存泄露:

    tail /dev/zero
    
  2. 启动 earlyoom

    earlyoom
    

使用 cgroup

对于基于 systemd 的发行版,建议使用 systemd slice 管理 cgroup:

  1. 编辑 .slice 文件:

    sudoedit /etc/systemd/system/user.slice                      # 对所有用户添加共享 cgroup 限制
    sudoedit /etc/systemd/system/user-.slice.d/50-defaults.conf  # 对所有用户添加独立 cgroup 限制
    sudoedit /etc/systemd/system/user-1000.slice                 # 对指定 UID 用户添加 cgroup 限制
    

    不要命名为 /etc/systemd/system/user-.slice.d/10-defaults.conf,会覆盖 /usr/lib/systemd/system/user-.slice.d/10-defaults.conf

    [Unit]
    Description=User Slice of UID %j
    Documentation=man:user@.service(5)
    StopWhenUnneeded=yes
    
    [Slice]
    # 限制 CPU 使用率
    CPUQuota=200%
    # 限制内存用量
    MemoryHigh=3G
    MemoryMax=4G
    # 限制任务数量
    TasksMax=100
    # IO 权重设置
    IOWeight=100
    

    CPU 使用率是以单核计算的。对于多核 CPU,最大使用率 = 核数 * 100%

  2. 重载 systemd 配置:

    sudo systemctl daemon-reload
    
  3. 查看 slice 状态

    systemctl list-units --type=slice  # 查看所有 slice
    systemctl status user.slice        # 查看特定 slice 状态
    systemctl show user.slice          # 查看 slice 的 cgroup 信息
    

模拟高 CPU、内存占用进程:

hog.go

package main

import (
  "context"
  "fmt"
  "os/signal"
  "runtime"
  "sync"
  "syscall"
  "time"
)

func main() {
  ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
  defer cancel()

  var wg sync.WaitGroup

  // Allocate all available CPUs
  numCPU := runtime.NumCPU()
  runtime.GOMAXPROCS(numCPU)

  // Start a busy goroutine for each CPU
  for i := 0; i < numCPU; i++ {
    go func() {
      wg.Add(1)
      defer wg.Done()

      fmt.Println("Started a CPU hog")
      for ctx.Err() == nil { /* Busy loop to keep CPU busy */ }
    }()
  }

  // Start allocating memory every second
  const memChunkMB = 10
  var chunks [][]byte
  go func() {
    wg.Add(1)
    defer wg.Done()

    for ctx.Err() == nil {
      chunk := make([]byte, memChunkMB*1024*1024)

      // Prevent the memory from being optimized away
      for i := 0; i < len(chunk); i++ {
        chunk[i] = byte(i % 256)
      }
      chunks = append(chunks, chunk)

      fmt.Printf("Allocated %d MB of memory\n", memChunkMB)
      time.Sleep(1 * time.Second)
    }
  }()

  <-ctx.Done()
  fmt.Println("Received termination signal. Initiating shutdown...")

  wg.Wait()
  fmt.Println("Shutdown complete.")
}

参考:Set a default resource limit for all users with systemd cgroups | Unix & Linux Stack Exchange

参见:

posted @ 2025-04-18 13:53  Undefined443  阅读(233)  评论(0)    收藏  举报