비교적 새로운 베이스 이미지(Ubuntu 22.04 이상, Debian 12 등)로 만든 컨테이너가 기동 직후 죽는다. 스레드를 만드는 지점에서 Operation not permitted 가 반복된다.
OpenBLAS blas_thread_init: pthread_create failed for thread 1 of 24: Operation not permitted
OpenBLAS blas_thread_init: RLIMIT_NPROC 767586 current, 767586 max
메시지가 RLIMIT_NPROC 을 같이 찍기 때문에 프로세스 수 제한 문제로 오해하기 쉽다. 값이 이미 충분히 큰데도 실패한다면 제한 문제가 아니다.
glibc 2.34 이상은 스레드를 만들 때 clone 대신 clone3 시스템 콜을 먼저 시도한다. 오래된 Docker 의 기본 seccomp 프로파일에는 clone3 가 허용 목록에 없고, 차단 방식이 ENOSYS(그런 시스템 콜 없음)가 아니라 EPERM(권한 없음)이었다. glibc 는 ENOSYS 를 받으면 옛 clone 으로 되돌아가지만 EPERM 을 받으면 그대로 실패한다.
즉 호스트의 Docker · containerd · libseccomp 가 낡았고 이미지 안의 glibc 가 새로우면 이 조합이 만들어진다. Python 의 numpy · OpenBLAS 처럼 기동 시 스레드를 여럿 만드는 라이브러리에서 먼저 드러난다.
docker version --format '{{.Server.Version}}'
docker info | grep -i seccomp
rpm -q libseccomp || dpkg -l libseccomp2
docker run --rm <이미지> ldd --version | head -1
seccomp 를 끄고 한 번 실행해 보면 원인이 바로 확인된다. 이 상태로 운영하라는 뜻은 아니다.
docker run --rm --security-opt seccomp=unconfined <이미지> <명령>
근본 해결은 호스트 쪽을 올리는 것이다. Docker 20.10.10 이상, 또는 libseccomp 2.5.x 이상으로 올리면 기본 프로파일이 clone3 를 처리한다.
dnf update -y docker-ce docker-ce-cli containerd.io libseccomp
systemctl restart docker
당장 올릴 수 없다면 해당 컨테이너에만 프로파일을 완화한다. 전면 해제보다는 clone3 만 허용한 프로파일을 만들어 쓰는 편이 낫다.
curl -sSL https://raw.githubusercontent.com/moby/moby/master/profiles/seccomp/default.json -o /etc/docker/seccomp-clone3.json
받은 프로파일의 syscalls 배열 첫 항목 names 에 clone3 를 추가한 뒤 실행 시 지정한다.
docker run --rm --security-opt seccomp=/etc/docker/seccomp-clone3.json <이미지>
compose 에서는 다음과 같이 쓴다.
services:
app:
image: myapp:1.0
security_opt:
- seccomp:/etc/docker/seccomp-clone3.json
Kubernetes 에서 같은 현상을 만나면 파드의 securityContext.seccompProfile 을 확인한다. 노드의 컨테이너 런타임을 올리는 것이 정답이며, Unconfined 로 바꾸는 것은 임시 조치다.
OPENBLAS_NUM_THREADS=1 · OMP_NUM_THREADS=1 을 주면 스레드 수가 줄어 증상이 완화되는 경우가 있지만, 스레드를 하나도 만들 수 없는 상태라면 효과가 없다. --privileged 는 seccomp 를 포함한 대부분의 격리를 해제하므로 쓰지 않는다.