빌드 exit 137 — 디스크가 아니라 turbo 동시 빌드 메모리
Jenkins 빌드가 Node.js 빌드 단계에서 exit 137 로 죽었습니다.
@<repo>/apps-algo:build: Killed
@<repo>/apps-algo:build: npm error code 137
ERROR @<repo>/apps-algo#build: command (...) npm run build exited (137)
Tasks: 6 successful, 24 total
Failed: @<repo>/apps-algo#build
ERROR: script returned exit code 137
모노레포 패키지를 turborepo 로 한 번에 여덟 개씩(--concurrency=8) 빌드하는 잡입니다. turborepo 는
패키지 의존 순서를 보고 빌드를 병렬로 돌려 주는 도구이고, 레거시 빌드 대신 이 경로를 alpha 에서
먼저 시험하던 중이었습니다.
빌드를 요청한 쪽에서는 디스크가 찬 것 같다고 했습니다.
디스크는 아니었음 — 실패한 빌드의 로그가 반증
이 잡은 빌드가 끝나면 성공·실패와 상관없이 디스크 사용률을 찍고 이미지 정리 여부를 정하는 단계가 있어서 디스크를 따로 들여다볼 필요 없이 실패한 그 빌드의 로그에 답이 있었습니다.
df -h / /dev/root 97G 29G 69G 30% /
docker system df Images 4.563GB, Build Cache 0B
DISK_USAGE=30, DISK_THRESHOLD=85 → "Disk usage is 30%, skipping aggressive image prune"
사용률은 30% 로, 디스크가 찼다면 이 줄이 90% 넘게 찍혀 있었을 겁니다.
137 을 읽는 법 — 힙 OOM 이 아니라 커널 OOM
exit 137 은 128 에 시그널 번호 9(SIGKILL)를 더한 값입니다. Node.js 의 V8 힙이 모자라 죽었다면
JavaScript heap out of memory 라는 FATAL 메시지가 찍히고 종료 코드도 다른데, 이번엔 그 메시지 없이
프로세스가 Killed 로 사라졌습니다. 힙 상한에 걸린 게 아니라 시스템 메모리가 바닥나 커널 OOM
킬러가 프로세스를 죽인 겁니다.
같은 로그에는 빌드 도중 에이전트가 열한 번이나 끊겼다 붙은 기록도 남아 있었습니다.
i-0xxxxxxxxxxxxxxxx seems to be removed or offline (java.lang.InterruptedException);
will wait for 5 min 0 sec for it to come back online
i-0xxxxxxxxxxxxxxxx is back online
스왑이 없는 에이전트가 메모리 압박으로 한동안 응답을 못 하면서 Jenkins 컨트롤러와의 연결이 끊겼다 붙었다 한 것입니다.
에이전트가 작아서도 아니었음
그렇다면 에이전트 인스턴스가 작은 spot 이라 메모리가 모자랐다고 의심했습니다. 그런데 이 에이전트는
빌드할 때만 오토스케일링 그룹이 띄우고 끝나면 회수하는 구조라, 확인하려고 했을 때 인스턴스가 이미
없었습니다. 대신 CloudTrail 의 RunInstances 기록에서 그때 뜬 인스턴스 타입을 찾았습니다.
c5.4xlarge, 16 vCPU 에 메모리 32GiB 였습니다. 같은 그룹에 섞여 뜨는 다른 타입도 전부 16 vCPU 에 32GiB 안팎입니다.
32GiB 를 주고도 터졌다면 문제는 에이전트 크기가 아니라 빌드가 한꺼번에 쓰는 양입니다. 동시성 8 에 webpack 빌드가 무거운 앱 여러 개가 같은 시점에 겹치면서 메모리 피크가 32GiB 를 넘은 것이었습니다.
조치 — 동시성을 8 에서 4 로
동시성 값은 Jenkinsfile 의 environment 블록에 상수로 박혀 있어서 Jenkins UI 에서 빌드할 때 바꿀 수가
없고 파일의 기본값을 바꿔야 합니다.
environment {
TURBO_BUILD_CONCURRENCY = 4 // 8 에서 내림
}
고치기 전에 이 잡이 실제로 어느 Jenkinsfile 과 브랜치를 쓰는지 Jenkins 의 잡 설정(config.xml)에서
scriptPath 와 브랜치를 확인했습니다. 저장소에는 이름이 비슷한 Jenkinsfile 이 여럿이라, 파일만 보고
고치면 엉뚱한 파일을 바꿀 수 있습니다.
바꾼 뒤 다시 돌린 빌드는 Node.js 빌드 단계를 통과했고 이미지 업로드와 배포까지 이어져 alpha 서버가 떴습니다.
같은 137 을 다시 만나면
빌드가 137 에 Killed 로 끝나면 디스크부터 보지 말고 같은 로그 안의 df 출력과 에이전트가
offline/online 을 오간 줄부터 찾습니다. 디스크가 정말 찼다면 사용률이 90% 넘게 찍혀 있고, 에이전트가
반복해서 끊겼다 붙었다면 메모리 쪽입니다. 에이전트가 이미 넉넉한데도 그렇다면 인스턴스를 키우기 전에
동시성부터 내려 봅니다. 4 로도 피크에 닿으면 그다음은 패키지별 NODE_OPTIONS=--max-old-space-size
로 상한을 걸거나, 메모리 비중이 큰 인스턴스 계열로 바꾸는 순서입니다.
turbo 경로는 레거시 빌드보다 동시에 쓰는 메모리가 커서 다른 환경으로 넓히기 전에 에이전트 메모리에 맞는 동시성인지부터 확인해 두는 게 맞습니다.
댓글 0