CPU ISA level is lower than required 원인, x86-64-v3였다
댓글 0
포스트 내용에 대한 의견이나 질문을 남길 수 있습니다. 답글과 좋아요는 로그인한 사용자 기준으로 기록됩니다.
이전 글과 다음 글
현재 글을 기준으로 앞뒤 글을 바로 이동해서 볼 수 있습니다.
Loading Services...
포스트 내용에 대한 의견이나 질문을 남길 수 있습니다. 답글과 좋아요는 로그인한 사용자 기준으로 기록됩니다.
현재 글을 기준으로 앞뒤 글을 바로 이동해서 볼 수 있습니다.
nginx 1.30.6 보안 업데이트를 하려고 Ubuntu 26.04(resolute)용 패키지를 받아 압축을 풀고 바이너리를 실행했다. 그런데 버전 출력 대신 이 한 줄이 나오고 끝났다.
$ ./extract/usr/sbin/nginx -V
CPU ISA level is lower than required같은 1.30.6인데 24.04(noble)용 빌드는 멀쩡히 돌았다. 버전 번호도 같고 소스도 같을 텐데 왜 하나만 안 뜨는 건지, 그게 이번에 파고든 내용이다.
메시지만 보고 든 첫 생각은 다운로드가 잘못됐나였다. 그래서 SHA-256부터 저장소 메타데이터와 맞춰 봤는데 일치했다. 파일은 멀쩡했다.
두 번째로 의심한 건 라이브러리 버전 차이였다. resolute 빌드니까 더 새 glibc나 OpenSSL을 요구하는 거 아닌가 했다. 그런데 이 서버 OS가 이미 26.04라 라이브러리도 resolute 기준이다. 오히려 맞아야 하는 쪽이 안 맞는 상황이라 이 가설도 금방 접었다.
솔직히 여기서 메시지를 제대로 안 읽은 게 삽질의 원인이었다. CPU ISA level이라고 대놓고 써 있는데 소프트웨어 쪽만 의심하고 있었다.
nginx 소스에 저런 문구가 있을 리 없어서 어디서 나오는지부터 찾아봤다.
strings /lib64/ld-linux-x86-64.so.2 | grep -i 'ISA level'CPU ISA level is lower than required
%s: CPU ISA level is lower than requiredglibc의 동적 로더(ld.so)가 찍는 메시지였다. nginx의 main은 시작도 못 했다는 뜻이다. 로더가 바이너리를 메모리에 올리기 전에 이 CPU로는 못 돌린다고 판단하고 거부한 거다.
로더가 그걸 어떻게 아냐면, ELF 바이너리 안에 이 프로그램은 최소 어느 수준의 CPU가 필요하다는 노트가 박혀 있다. readelf -n으로 보면 나온다. 두 빌드를 나란히 놓고 봤다.
readelf -n noble/usr/sbin/nginx | grep 'ISA needed'
readelf -n resolute/usr/sbin/nginx | grep 'ISA needed'noble: x86 ISA needed: x86-64-baseline
resolute: x86 ISA needed: x86-64-baseline, x86-64-v2, x86-64-v3차이가 딱 여기 있었다. resolute 빌드는 x86-64-v3를 요구한다고 표시돼 있었다.
x86-64라고 다 같은 x86-64가 아니다. 2003년쯤 처음 나온 64비트 명령어 세트를 기준선(baseline, v1)으로 잡고, 이후 CPU에 추가된 명령어들을 묶어서 단계를 나눈 게 마이크로아키텍처 레벨이다. 대략 이렇게 나뉜다.
레벨 | 추가되는 대표 명령어 | 대략 어느 세대부터 |
|---|---|---|
v1 (baseline) | SSE, SSE2 | 초기 64비트 CPU 전부 |
v2 | SSE3, SSSE3, SSE4.1/4.2, POPCNT, CMPXCHG16B | 2009년 전후 Nehalem급 |
v3 | AVX, AVX2, BMI1/2, FMA, F16C, LZCNT, MOVBE | 2013년 Haswell급 |
v4 | AVX-512 계열 | 일부 서버/데스크톱만 |
v3 바이너리면 컴파일러가 코드 아무 데나 AVX2나 FMA 명령을 넣어도 된다는 뜻이다. 반복문 벡터화 같은 데서 성능 이득이 있어서 요즘 배포판이나 빌드 환경이 기준선을 올리는 추세다. RHEL 10은 아예 x86-64-v3를 최소 요구사항으로 잡았다.
서버 CPU를 확인해 봤다.
grep -m1 'model name' /proc/cpuinfo
/lib64/ld-linux-x86-64.so.2 --help | tail -4model name : Intel(R) Core(TM) i7-3770K CPU @ 3.50GHz
Subdirectories of glibc-hwcaps directories, in priority order:
x86-64-v4
x86-64-v3
x86-64-v2 (supported, searched)i7-3770K는 2012년 Ivy Bridge다. 로더가 직접 v2까지만 supported라고 말해준다. v3에 필요한 명령어를 하나씩 찍어보니 더 명확했다.
for f in avx avx2 bmi1 bmi2 fma movbe f16c lzcnt; do
grep -qw $f /proc/cpuinfo && echo "$f yes" || echo "$f NO"
doneavx yes
avx2 NO
bmi1 NO
bmi2 NO
fma NO
movbe NO
f16c yes
lzcnt NOAVX랑 F16C는 있는데 AVX2, BMI, FMA, MOVBE, LZCNT가 없다. 이 명령어들이 들어간 게 바로 다음 세대인 Haswell이었다. 딱 한 세대 차이로 v3 문턱을 못 넘는 거였다.
여기는 솔직히 끝까지 못 밝혔다. 바이너리에 남아 있는 빌드 옵션을 꺼내 봤는데 -march=x86-64-v3 같은 명시적인 옵션은 두 빌드 모두 없었다.
strings resolute/usr/sbin/nginx | grep -o -- "--with-cc-opt='[^']*'"noble 쪽과 비교하면 -Werror=implicit-function-declaration 하나 더 붙은 정도였다. 그러니 v3 표시는 nginx 빌드 스크립트가 아니라 resolute 빌드 환경 어딘가, 그러니까 컴파일러 기본값이나 빌드 호스트의 플래그 쪽에서 들어온 것 같다. 근데 이건 추정이다. 확인은 못 했다.
헷갈렸던 건 OS 자체는 26.04인데 잘 돈다는 점이었다. 시스템 바이너리를 찍어 보니 이유가 보였다.
/usr/bin/ls: x86 ISA needed: x86-64-baseline
libc.so.6: x86 ISA needed: x86-64-baselineUbuntu 자체 패키지는 여전히 baseline으로 빌드돼 있다. 문제는 OS가 아니라 서드파티 저장소에서 받은 resolute용 nginx 빌드 하나였다. 이 차이를 몰랐으면 OS 업그레이드 자체를 의심하면서 엉뚱한 데를 한참 팠을 거다.
이 메시지가 무섭게 보여도 실은 꽤 친절한 실패다. 저 ISA 노트가 없었으면 nginx는 일단 떴을 거다. 그러다 어느 요청에서 AVX2 명령어가 처음 실행되는 순간 Illegal instruction(SIGILL)으로 워커가 죽었겠지. 그건 재현도 어렵고 원인 찾기는 더 어렵다.
로더가 시작 단계에서 딱 잘라 거부했고, 나는 설치 전에 압축만 풀어서 돌려봤다. 덕분에 운영에는 아무 영향이 없었다. 게이트웨이 서버에서 그냥 apt upgrade 했으면 패키지 교체 직후 nginx가 기동을 못 해서 앞단이 통째로 내려갔을 거다.
OS는 26.04, nginx는 noble 빌드로 일부러 엇갈리게 쓰고 있다.
Types: deb
URIs: https://nginx.org/packages/ubuntu
Suites: noble
Components: nginx이 Suites 한 줄이 왜 OS랑 다른지 모르는 사람이 resolute로 맞춰놓으면 똑같은 사고가 난다. 그래서 운영 문서에 이유를 같이 적어 두었다.
이게 nginx만의 문제도 아니다. 앞으로 서드파티 패키지든 도커 이미지든, 새 빌드가 v3로 넘어가면 이 서버에서는 똑같이 안 뜬다. 그래서 이제는 새 바이너리를 받으면 실행해 보기 전에 readelf -n으로 ISA needed 줄부터 본다. 한 줄이면 확인되는데, 이걸 몰라서 메시지 앞에서 한참 엉뚱한 의심만 하고 있었다.
장기적으로는 하드웨어를 바꾸는 게 답이긴 하다. 다만 언제 바꿀지는 아직 모르겠다. 그때까지는 noble 빌드가 계속 나와주길 바라는 수밖에 없다.
이번 수치는 전부 3770k에서 읽기 전용 명령으로 직접 확인했습니다. CPU 플래그, 로더가 지원하는 레벨, 두 빌드의 readelf ISA 표시, 시스템 바이너리 표시까지 포함입니다. resolute 빌드에 v3 표시가 왜 붙었는지는 빌드 옵션에서 찾지 못해서 본문에도 추정이라고 적었습니다. RHEL 10이 v3를 요구한다는 것과 표의 세대 구분은 일반 지식이고, 이번에 따로 확인하지는 않았습니다.