代码改变世界

在华为欧拉操作系统上部署dify, 遇到“OpenBLAS blas_thread_init: pthread_create failed for thread 1 of 16: Operation not permitted OpenBLAS blas_thread_init: RLIMIT_NPROC -1 current, -1 max”报错。

2025-12-29 11:35  千里马365  阅读(22)  评论(0)    收藏  举报

问题分析

  • 报错来自 OpenBLAS 在初始化线程时调用 pthread_create 失败,错误是 Operation not permitted。日志中的 RLIMIT_NPROC 为 -1,通常表示“无限”,因此更可能是安全策略或内核系统调用被拦截,而不是进程数上限不足。
  • 在 openEuler(华为欧拉)内核上,glibc 可能优先使用新 syscall clone3 创建线程;如果容器的 seccomp 配置未允许 clone3,就会得到 EPERM(Operation not permitted)。Ubuntu 上默认 seccomp 通常已放开 clone3,所以不会报错。

通过docker部署

  • 你通过 Docker 部署,在无授权或受限 seccomp 的容器里,OpenBLAS多线程会触发大量 pthread_create,最容易踩到这个限制。

快速规避

  • 把 BLAS/OMP 设为单线程,避免创建额外线程:
    • 环境变量:
      • OPENBLAS_NUM_THREADS=1
      • OMP_NUM_THREADS=1
      • GOTO_NUM_THREADS=1(可选)
    • Python 中在 import numpy/scipy 之前设置:
      import os
      os.environ["OPENBLAS_NUM_THREADS"] = "1"
      os.environ["OMP_NUM_THREADS"] = "1"
      # 接着再 import numpy/scipy
      
    • 如果使用 PyTorch:
      import torch
      torch.set_num_threads(1)
      
    • Docker 运行时添加环境变量:
      docker run -e OPENBLAS_NUM_THREADS=1 -e OMP_NUM_THREADS=1 your/image
      

容器级修复

  • 放宽容器 seccomp(验证用或作为临时解决方案):
    • 运行时禁用 seccomp:
      docker run --security-opt seccomp=unconfined your/image
      
    • 或使用自定义 seccomp profile,允许 clone/clone3:
      1. 创建 seccomp.json(示例,仅放开必要 syscall)
        {
          "defaultAction": "SCMP_ACT_ERRNO",
          "architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_AARCH64"],
          "syscalls": [
            { "names": ["clone", "clone3", "fork", "vfork"], "action": "SCMP_ACT_ALLOW" }
          ]
        }
        
      2. 运行容器时加载该配置:
        docker run --security-opt seccomp=/path/to/seccomp.json your/image
        
    • 如果以 rootless Docker 或更严格策略运行,先用 --privileged 做一次对比测试:
      docker run --privileged your/image
      
      如果问题消失,基本可以确认是 seccomp/能力限制导致。

资源与系统检查

  • 在容器内确认进程/线程限制是否异常:
    ulimit -u                      # 用户进程数限制
    cat /proc/sys/kernel/threads-max
    cat /proc/sys/kernel/pid_max
    
  • 检查 Docker/内核对 seccomp 的支持和默认策略:
    docker info | grep -i seccomp
    getconf GNU_LIBC_VERSION
    uname -a
    
  • 如果 ulimit -u 返回很小的数值,可在运行时提高:
    docker run --ulimit nproc=8192:8192 your/image
    

长期建议

  • 升级 Docker/Moby 到包含允许 clone3 的默认 seccomp 版本,或升级 openEuler 的容器引擎到默认放开 clone3 的版本。
  • 如果业务允许,考虑使用“串行版” BLAS 库(如 Debian/Ubuntu 的 libopenblas0-serial)或改用 BLIS/MKL,并将线程数控制在 1。
  • 在镜像或入口脚本中统一设置线程环境变量,避免因不同运行环境导致不一致。

排查路径

  • 先用 --security-opt seccomp=unconfined 验证是否是 seccomp 限制。(这一步可能是最有效的
  • 若验证通过,再选择:
    • 保持单线程(最低风险的快速修复),或
    • 配置自定义 seccomp profile,仅放开 clone/clone3。
  • 同步将 OPENBLAS/OMP 线程数设置为 1,以减少资源占用和避免类似问题在其他环境复现。

在docker-compose部署

解决方案概览

  • 根因是容器在 openEuler 上初始化线程时碰到安全策略(seccomp)限制,导致 OpenBLAS 的 pthread 创建失败(常见于 clone/clone3 被拦截)。
  • 两条修复路径:
    • 最稳妥:将 BLAS/OMP 线程数控制为 1,避免大量线程创建
    • 放宽容器 seccomp(临时或按需):允许 clone/clone3,或直接使用 unconfined

docker-compose 修改(推荐最小改动)

  • 在需要使用 NumPy/Scipy/PyTorch 的服务中加入环境变量,限制线程为 1
  • 可选提高进程数限制(nproc)

示例(将以下内容合并到你的服务配置中):

# 片段,用于你的 docker-compose.yaml 某个服务(例如 api)
services:
  api:
    environment:
      OPENBLAS_NUM_THREADS: "1"
      OMP_NUM_THREADS: "1"
      GOTO_NUM_THREADS: "1"
      NUMEXPR_MAX_THREADS: "1"  # 如使用 numexpr
      MKL_NUM_THREADS: "1"      # 如使用 MKL
    ulimits:
      nproc: 8192
      nofile:
        soft: 65536
        hard: 65536
    # 若需快速验证 seccomp 是否导致问题,可临时放开:
    # security_opt:
    #   - seccomp:unconfined
    # 注意:生产环境建议使用自定义 seccomp profile,而不是 unconfined

更精细的 seccomp 方案(按需启用)

  • 创建一个自定义 seccomp profile,仅放开线程相关 syscall(clone/clone3/fork/vfork)
  • 在 docker-compose 中引用该 profile

示例 seccomp 文件(请根据实际路径保存):

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_AARCH64"],
  "syscalls": [
    { "names": ["clone", "clone3", "fork", "vfork"], "action": "SCMP_ACT_ALLOW" },
    { "names": ["rt_sigreturn", "rt_sigprocmask", "rt_sigaction"], "action": "SCMP_ACT_ALLOW" },
    { "names": ["futex"], "action": "SCMP_ACT_ALLOW" },
    { "names": ["mmap", "munmap", "mremap", "brk"], "action": "SCMP_ACT_ALLOW" },
    { "names": ["close", "openat", "read", "write", "lseek"], "action": "SCMP_ACT_ALLOW" },
    { "names": ["sched_yield", "sched_getaffinity", "sched_setaffinity"], "action": "SCMP_ACT_ALLOW" }
  ]
}

在 docker-compose 中引用:

services:
  api:
    security_opt:
      - seccomp:/opt/dify/seccomp-openblas.json
  • 说明:上述 profile 为演示用途。实际生产建议基于 Docker 默认 seccomp 配置做增量放开(clone/clone3),而不是用“全拒+少量放开”的策略,以免遗漏其他必要的 syscall。

验证建议

  • 优先尝试仅环境变量线程数=1 的方案;多数场景足以避免错误,且风险最低
  • 若仍报错,再临时加入 security_opt: - seccomp:unconfined 验证是否为 seccomp 所致;验证通过后改用自定义 profile
  • 同时检查容器内资源限制:
    • nproc 是否正常(已在 compose 提高)
    • openEuler 的 Docker/内核版本是否较旧(旧版默认 seccomp 可能不含 clone3 允许项)

补充说明

  • 如果你的应用入口在 Python 中能提前设置环境变量(import numpy/scipy 之前),也可以在代码层面加固:
    import os
    os.environ["OPENBLAS_NUM_THREADS"] = "1"
    os.environ["OMP_NUM_THREADS"] = "1"
    os.environ["GOTO_NUM_THREADS"] = "1"
    os.environ["NUMEXPR_MAX_THREADS"] = "1"