SSD Nodes Learn Hosting plans →
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-08-21

Linux 바이너리 실행 오류: glibc와 musl 버전 호환성 문제

GLIBC_2.34 not found 오류는 빌드 환경의 버전 제약 때문입니다. 바이너리가 요구하는 라이브러리 버전을 확인하고, 정적 링크나 컨테이너 활용 등 이식 가능한 바이너리를 배포하는 4가지 구체적인 해결 방법을 제시합니다.

Linux 바이너리가 구형 서버에서 실행되지 않는 이유

glibc(GNU C 라이브러리)는 단방향 호환성만 보장하므로 이식 가능한 Linux 바이너리를 만드는 것은 어렵습니다. 구형 바이너리는 새로운 glibc 환경에서도 계속 실행되지만, 새로운 바이너리는 구형 glibc 환경에서 실행되지 않습니다. 따라서 바이너리를 컴파일하는 시스템이 배포 대상이 되는 모든 서버의 최소 요구 사양이 됩니다.

오류 메시지는 프로그램이 요구하는 정확한 버전을 명시합니다.

./mytool: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.38' not found (required by ./mytool)

파일이 손상되었거나 설정이 잘못된 것이 아닙니다. 동적 로더(dynamic loader)가 바이너리 내부에 기록된 버전 태그를 읽고 시스템의 libc에서 해당 태그를 찾았으나, 이를 발견하지 못해 프로그램 실행을 거부한 것입니다. 파일을 다시 다운로드하거나 chmod +x을 실행해도 요구 사항 자체가 파일 내부에 기록되어 있으므로 아무런 변화가 없습니다. 바이너리 빌드 방식을 변경하거나, 배포하는 파일 자체를 변경해야 합니다.

glibc 심볼 버전 관리가 실제로 수행하는 작업

glibc가 내보내는 모든 함수에는 버전 태그가 포함됩니다. printf 내부의 libc.so.6는 실제로는 printf@@GLIBC_2.2.5입니다. glibc가 함수의 동작이나 ABI(애플리케이션 바이너리 인터페이스)를 변경할 때, 기존 함수를 대체하지 않습니다. 이전 코드를 기존 태그 아래에 유지하고 새로운 코드를 새 태그 아래에 추가하므로, 하나의 libc.so.6은 동일한 심볼의 여러 버전을 동시에 보유하게 됩니다. 2009년에 빌드된 바이너리는 여전히 그곳에 존재하는 2009년 태그를 찾아내며, 이것이 바로 하위 호환성이 잘 작동하는 이유입니다.

링크 단계에서는 사용된 심볼을 기록합니다. 바이너리에는 "나는 libc.so.6에서 GLIBC_2.38가 필요하다"는 내용을 담은 .gnu.version_r 섹션이 생성됩니다. 이전 버전의 libc에는 해당 태그가 없으므로, main이 실행되기 전에 로더가 중단됩니다. 대체 동작(fallback)은 존재하지 않는데, 이는 컴파일 시점에 의도한 함수와 다른 함수를 프로그램에 조용히 전달하는 결과를 초래하기 때문입니다.

2021년 이후 가장 흔하게 발생하는 원인은 glibc 2.34입니다. 해당 릴리스는 libpthreadlibdllibc로 통합하고, 모든 C 프로그램을 시작하는 함수인 __libc_start_mainGLIBC_2.34 태그로 이동시켰습니다. 따라서 glibc 2.34 이상이 설치된 배포판에서 컴파일된 hello-world 프로그램은 소스 코드에서 최신 기능을 전혀 호출하지 않더라도 GLIBC_2.34을 요구하게 됩니다. 이것이 바로 수년간 코드를 변경하지 않은 사용자들에게 갑자기 문제가 발생한 이유입니다.

내 배포판은 어떤 glibc 버전을 제공합니까?

Chartglibc version in each distribution's own repositories
The data behind this chart
[
  {
    "distro": "CentOS 7",
    "glibc": 2.17
  },
  {
    "distro": "RHEL 8",
    "glibc": 2.28
  },
  {
    "distro": "Ubuntu 20.04",
    "glibc": 2.31
  },
  {
    "distro": "Debian 11",
    "glibc": 2.31
  },
  {
    "distro": "RHEL 9",
    "glibc": 2.34
  },
  {
    "distro": "Ubuntu 22.04",
    "glibc": 2.35
  },
  {
    "distro": "Debian 12",
    "glibc": 2.36
  },
  {
    "distro": "Ubuntu 24.04",
    "glibc": 2.39
  },
  {
    "distro": "Debian 13",
    "glibc": 2.41
  }
]

각 배포판이 자체 저장소에서 제공하는 버전은 2026년 8월 기준으로 배포판별 발표 내용과 같습니다. CentOS 7은 이미 수명이 종료되었으나, 여전히 운영 중인 서버가 남아 있어 목록에 포함했습니다. 보유한 서버에서 ldd --version 명령을 실행하면 첫 줄에 glibc 릴리스 정보가 출력되며, getconf GNU_LIBC_VERSION 명령으로도 확인할 수 있습니다.

9개의 행을 사다리처럼 읽으십시오. Debian 13의 glibc 2.41 버전에서 빌드하면, 그 결과물은 Debian 13보다 낮은 버전에서는 실행되지 않습니다. Ubuntu 20.04의 glibc 2.31 버전에서 빌드하면, 해당 행보다 위에 있는 모든 배포판(Debian 13 포함)에서 동일한 소스 코드가 실행됩니다. 지원해야 할 가장 오래된 서버가 빌드 호스트로서 유일하게 중요한 기준이 되므로, 표준으로 삼는 배포판이 컴파일하는 모든 결과물의 ABI 하한선을 결정합니다. 이는 서버 인프라를 구축하기 전에 VPS 운영 체제 선택 시 고려해야 할 다른 요소들과 함께 결정하는 것이 좋습니다.

바이너리가 요구하는 최소 glibc 버전을 확인하는 방법

파일에서 직접 읽어 확인합니다. 이 방법은 빌드 정보가 없는 벤더 제공 바이너리를 포함하여 모든 파일에 적용할 수 있습니다.

objdump -T ./mytool | grep -o 'GLIBC_[0-9.]*' | sort -Vu | tail -5

마지막 줄이 최소 요구 버전입니다. objdump 명령을 사용할 수 없다면 binutils 패키지를 설치하십시오. 해당 시스템에 패키지를 설치할 수 없는 상황이라면 strings -a ./mytool | grep -o 'GLIBC_[0-9.]*' | sort -Vu | tail -5 명령으로도 근사치를 얻을 수 있습니다. 태그가 파일 내부에 일반 문자열로 저장되어 있기 때문입니다.

readelf -V ./mytool 명령은 동일한 요구 사항을 구조화하여 보여줍니다. Version needs section '.gnu.version_r'로 시작하는 블록을 찾으십시오. 이 블록은 라이브러리가 제공해야 하는 태그를 기준으로 각 Name: GLIBC_x.y 줄을 나열합니다.

어떤 함수가 최소 버전을 결정하는지 확인하려면 grep으로 해당 태그를 검색하십시오: objdump -T ./mytool | grep GLIBC_2.38. 보통 단일 심볼인 경우가 많으며, 때로는 피할 수 있는 함수일 수도 있습니다. 만약 objdump -T 명령이 아무것도 출력하지 않는다면, 해당 바이너리는 정적 링크(statically linked)된 것입니다. 따라서 동적 심볼 테이블이 존재하지 않으며 출력할 버전 요구 사항도 없습니다.

누군가 전달한 바이너리에 대해 filereadelf -d가 알려주는 정보

file은 아키텍처와 링크 유형을 한 줄로 보여줍니다.

file ./mytool

일반적인 glibc 빌드는 다음과 같이 표시됩니다.

ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=..., for GNU/Linux 3.2.0, not stripped

정적 빌드는 statically linked이라고 표시되며 인터프리터를 명시하지 않습니다. musl 빌드는 다른 인터프리터를 명시합니다.

ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-x86_64.so.1, ...

인터프리터는 중요한 필드입니다. 이는 커널이 프로그램 실행 전에 구동하는 동적 로더이며, 해당 경로가 대상 머신에 반드시 존재해야 합니다. /lib/ld-musl-x86_64.so.1는 musl을 별도로 설치하지 않은 Debian이나 Ubuntu 서버에는 존재하지 않습니다.

readelf -d ./mytool은 바이너리가 요구하는 공유 라이브러리를 이름별로 나열합니다.

Dynamic section at offset 0x2d58 contains 27 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)   Shared library: [libssl.so.3]
 0x0000000000000001 (NEEDED)   Shared library: [libc.so.6]
 0x000000000000001d (RUNPATH)  Library runpath: [$ORIGIN/../lib]

모든 NEEDED 항목은 soname으로 일치시켜야 하는 필수 요구 사항입니다. libssl.so.3는 OpenSSL 3이므로, libssl.so.1.1만 설치된 서버에서는 해당 바이너리가 로드되지 않습니다. soname이 다르다는 것은 의도적으로 호환되지 않는 ABI를 의미하기 때문입니다. RUNPATH은 로더가 가장 먼저 검색할 위치를 지정하며, $ORIGIN은 바이너리가 포함된 디렉터리로 확장됩니다. 이를 통해 독립형 번들이 자신의 라이브러리를 찾을 수 있습니다.

신뢰할 수 없는 바이너리에 ldd를 실행하지 마십시오. glibc 환경에서 ldd은 로더 하위에서 프로그램을 실행하여 의존성을 해결할 수 있으므로, 악의적인 파일이 코드를 실행할 위험이 있습니다. readelfobjdump는 바이트만 읽습니다. 이 모든 과정 이전에 다운로드한 파일이 프로젝트에서 실제로 배포한 파일인지 확인하십시오. 게시된 체크섬으로 다운로드 파일 검증하기만이 현재 보유한 바이트가 누구의 것인지 알려주는 유일한 단계입니다.

실제로 마주하게 될 오류와 각 오류의 의미

로더가 찾을 수 없는 GLIBC 버전을 명시합니다. 바이너리가 대상 시스템보다 더 최신 버전의 glibc를 사용하여 컴파일되었습니다. 대상 시스템에 설치된 그 어떤 것도 이 문제를 안전하게 해결할 수 없습니다. 아래 네 가지 옵션 중 하나를 선택하십시오.

쉘이 분명히 존재하는 파일에 대해 No such file or directory 오류를 보고합니다. 누락된 것은 바이너리가 아니라 인터프리터입니다. ELF 헤더에 로더 경로가 없으면 execveENOENT를 반환하며, 쉘은 자신이 가진 유일한 메시지를 출력합니다. file을 실행하여 인터프리터 경로를 확인하십시오. glibc 전용 서버에서 musl로 링크된 바이너리를 실행하면 정확히 이 증상이 나타납니다.

cannot execute binary file: Exec format error은 아키텍처가 잘못되었음을 의미합니다. arm64 서버에서 x86-64 바이너리를 실행하거나 그 반대의 경우입니다. file 출력의 첫 번째 줄을 보면 현재 어떤 아키텍처의 바이너리를 가지고 있는지 알 수 있습니다.

error while loading shared libraries: libssl.so.3: cannot open shared object fileNEEDED 라이브러리가 없거나 다른 soname으로 존재함을 의미합니다. 일치하는 배포판 패키지를 설치하십시오. 패키지 이름은 배포판 계열마다 다르므로 README에서 apt 명령어를 복사하기 전에 변환해야 합니다. dnf 및 apt 명령어 대응표에서 해당 매핑 정보를 확인할 수 있습니다.

glibc 환경에서 잘 작동하던 musl 빌드에서 발생하는 단순 Segmentation fault 오류입니다. 일반적으로 옵션 2에서 다루는 스레드 스택 크기 문제일 가능성이 높습니다.

이식 가능한 Linux 바이너리를 배포하는 4가지 방법

각 방식에는 실질적인 비용이 따릅니다. 어떤 방식이 가장 깔끔해 보이는지가 아니라, 프로그램이 런타임에 무엇을 수행하는지에 따라 선택하십시오.

옵션 1: 지원하는 가장 오래된 배포판에서 빌드하기

가장 지루하지만 보통은 가장 올바른 방법입니다. 지원을 약속한 가장 오래된 배포판의 컨테이너 이미지 안에서 컴파일하십시오. 링커는 해당 구버전 glibc가 실제로 가지고 있는 태그만 기록할 수 있으므로, 바이너리는 glibc의 모든 동작을 그대로 유지하면서도 최소 요구 사항은 해당 릴리스로 낮아집니다.

docker run --rm -v "$PWD:/src" -w /src ubuntu:20.04 sh -c 'apt-get update && apt-get install -y build-essential && make'

출력물은 glibc 2.31 이상 버전에서 실행됩니다. 추측하지 말고 검증하십시오. 앞서 언급한 objdump -T 한 줄 명령어를 결과물에 실행하여 가장 높은 태그가 예상한 것과 일치하는지 확인하십시오.

비용은 툴체인에서 발생합니다. 오래된 베이스 이미지는 오래된 컴파일러를 포함하고 있으며, 이는 코드에서 최신 C++ 표준이 필요할 때 문제가 됩니다. Go와 Rust는 툴체인이 배포판 패키지와 독립적으로 컨테이너에 설치되므로 이 문제를 대부분 피할 수 있습니다. C 및 C++의 경우 배포판 자체의 툴체인 채널에서 더 새로운 컴파일러를 추가하거나, 고대 glibc와 최신 GCC를 결합하기 위해 존재하는 manylinux 이미지를 사용할 수 있습니다. Zig의 번들 C 컴파일러 또한 zig cc -target x86_64-linux-gnu.2.28와 같이 특정 glibc 버전을 직접 타겟팅할 수 있으며, 이 방식을 사용하면 오래된 이미지를 유지하지 않고도 동일한 최소 요구 사항을 달성할 수 있습니다.

옵션 2: musl에 정적 링크하기

musl은 정적 링크를 염두에 두고 작성된 소형 C 라이브러리입니다. 정적 musl 바이너리는 자체 libc를 포함하며 인터프리터를 지정하지 않으므로, 적절한 아키텍처의 모든 Linux 커널에서 실행됩니다. 대부분의 단일 파일 도구 다운로드가 이런 방식으로 빌드됩니다.

sudo apt install -y musl-tools
musl-gcc -static -O2 hello.c -o hello
file ./hello

이제 file는 인터프리터 필드 없이 statically linked을 출력해야 합니다. Rust의 경우, 타겟을 추가하고 해당 타겟으로 빌드하십시오:

rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl

Go는 이러한 과정이 필요 없습니다. CGO_ENABLED=0 go build을 사용하면 결과물은 이미 정적이며 어떤 libc도 링크하지 않습니다.

NSS 조회가 변경됩니다. glibc는 프로그램 실행 중에 dlopen를 사용하여 libnss_* 모듈을 로드함으로써 사용자 및 호스트 이름을 확인합니다(NSS, name service switch). 정적으로 링크된 glibc는 이를 수행할 수 없으며, 링커는 다음과 같은 경고를 표시합니다:

warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking

musl은 NSS를 전혀 구현하지 않음으로써 이 경고를 우회합니다. musl은 자체 리졸버를 가지고 있으며 /etc/resolv.conf/etc/hosts을 직접 읽습니다. 일반적인 VPS에서는 이것으로 충분하며 더 간단합니다. 계정이나 이름이 LDAP 또는 SSSD에서 제공되는 호스트에서는, 시스템의 다른 모든 프로그램은 이를 인식하더라도 귀하의 바이너리는 인식하지 못할 것입니다. 또한 musl의 리졸버는 glibc보다 최신 기술이 아닙니다. 512 바이트보다 큰 DNS 응답에 대한 TCP 폴백 기능은 2023년 musl 1.2.4 버전에서 도입되었으므로, 이전 버전의 musl로 빌드하면 큰 응답이 잘릴 수 있습니다.

dlopen는 작동하지 않습니다. 정적 musl 바이너리에서 dlopen은 항상 실패하는 스텁(stub)입니다. 런타임에 코드를 로드하는 모든 기능은 사용할 수 없습니다. 플러그인 시스템, PAM 모듈, GPU 드라이버, glibc 자체의 iconv 문자셋 모듈 등이 이에 해당합니다. 프로그램이 dlopen를 필요로 한다면 정적 링크는 불가능하며, 다른 세 가지 방법 중 하나를 선택해야 합니다.

보안 업데이트는 귀하의 책임입니다. 동적 링크된 바이너리는 서버가 패키지 업그레이드를 수행하는 즉시 libc 수정 사항을 반영합니다. 정적 바이너리는 그렇지 않습니다. libc나 함께 번들로 포함한 정적 OpenSSL에 CVE(공통 취약점 및 노출)가 발견되면, 귀하는 다시 빌드하여 배포해야 합니다. 누군가 파일을 교체하기 전까지 이미 배포된 모든 복사본은 취약한 상태로 남습니다. 귀하가 무엇을 링크했는지 기록을 유지하십시오. 대상 시스템의 관리자는 귀하의 단일 파일 바이너리에 2년 전 라이브러리가 포함되어 있는지 확인할 방법이 없기 때문입니다.

라이선스가 변경됩니다. glibc는 LGPL이며, 정적 링크는 LGPL의 재링크 의무를 유발합니다. 즉, 수신자에게 귀하의 프로그램을 다른 glibc에 재링크하는 데 필요한 것을 제공해야 합니다. musl은 MIT 라이선스이며 이러한 조건이 없습니다. 이것이 단일 파일 바이너리를 배포하는 프로젝트들이 정적 glibc 대신 musl을 선택하는 주된 이유입니다.

두 런타임의 차이로 인해 혼란스러운 충돌이 발생할 수 있습니다. musl의 기본 스레드 스택은 128 KiB인 반면 glibc는 8 MiB입니다. 따라서 스레드 스택에 큰 버퍼를 할당하는 코드는 메시지나 로그 없이 세그멘테이션 오류(segfault)를 일으킵니다. pthread_attr_setstacksize을 사용하여 크기를 명시적으로 설정하거나 버퍼를 힙으로 옮기십시오. 또한 musl의 할당자는 다수의 스레드가 동시에 할당하는 상황보다는 작은 크기와 예측 가능한 동작을 위해 작성되었습니다. 따라서 할당이 빈번한 스레드 프로그램은 눈에 띄게 느려질 수 있습니다. 특정 라이브러리의 평판을 맹신하기보다 귀하의 워크로드에 맞춰 벤치마크를 수행하십시오.

옵션 3: 로더와 라이브러리 번들링

바이너리 옆에 라이브러리와 일치하는 로더를 함께 배치한 뒤, 해당 로더를 통해 프로그램을 실행합니다.

./ld-linux-x86-64.so.2 --library-path ./lib ./mytool

이 설정을 영구적으로 적용하려면 patchelf을 사용하여 파일에 경로를 기록하십시오.

patchelf --set-interpreter "$PWD/lib/ld-linux-x86-64.so.2" --set-rpath '$ORIGIN/lib' ./mytool

이 방식의 작동 여부를 결정하는 규칙은 단 하나입니다. 로더와 libc.so.6은 반드시 동일한 glibc 빌드에서 나온 것이어야 합니다. 이 둘은 한 쌍으로 묶여 있으며, 호스트의 로더와 번들된 libc를 혼용하면 시작 시점에 읽기 쉬운 오류 메시지 대신 프로그램이 즉시 충돌합니다. 둘 다 번들하거나, 아니면 둘 다 하지 마십시오.

AppImage는 이 패턴을 패키징한 것으로, 페이로드를 squashfs 이미지에 담고 이를 마운트하는 작은 런타임을 포함합니다. 사람들이 간과하는 AppImage의 특성이 하나 있는데, AppImage는 glibc를 번들하지 않는다는 점입니다. 따라서 최신 배포판에서 빌드한 AppImage는 구형 서버에서 여전히 동일한 버전 오류로 실패합니다. AppImage의 자체 가이드는 지원하려는 가장 오래된 베이스 환경에서 빌드하라는 것이며, 이는 이 방식이 옵션 1을 대체하는 것이 아니라 그 위에 얹혀진 배포 형식임을 의미합니다.

헤드리스 서버에서 AppImage는 페이로드를 마운트하기 위해 FUSE(filesystem in userspace)가 필요하며, 최소 사양의 VPS 이미지에는 이 기능이 포함되지 않은 경우가 많습니다. 이때 발생하는 오류는 libfuse.so.2입니다. ./App.AppImage --appimage-extract-and-run을 사용하면 마운트 과정을 완전히 건너뛸 수 있으며, 이 경우 임시 디렉터리에 압축을 푼 뒤 해당 위치에서 실행됩니다.

옵션 4: 컨테이너 이미지 배포

프로그램과 함께 사용자 공간(userland) 전체를 이동합니다. 이미지가 자체 libc를 포함하므로 호스트의 glibc는 중요하지 않게 되며, 커널과 아키텍처만 일치하면 됩니다. 이는 가장 단순한 방식이며, 이로 인해 발생하는 문제 유형 전체를 제거합니다. 오늘날 많은 서버 소프트웨어가 이 방식으로 배포되는 이유입니다. VPS에서 Docker 실행하기가 이를 사용하는 일반적인 방법입니다.

호스트 커널은 여전히 제한을 설정합니다. 최신 glibc는 최신 시스템 호출을 사용하며, 오래된 컨테이너 호스트는 이를 차단할 수 있습니다. glibc 2.34 이상은 clone3을 사용하는데, 이전의 기본 seccomp 프로필은 이를 거부합니다. 이 경우 glibc에 대한 언급 없이 Operation not permitted 오류와 함께 즉시 실패하는 증상이 나타납니다. 차단은 커널이 아닌 런타임의 시스템 호출 필터에 존재하므로, 호스트의 컨테이너 런타임을 업그레이드하면 해결됩니다.

비용은 일반적인 수준입니다. 대상 서버에는 컨테이너 런타임과 이를 사용할 권한이 필요합니다. 다운로드 크기는 단일 파일에서 수십 또는 수백 메가바이트로 증가합니다. 이제 베이스 이미지와 그 패치 일정을 직접 관리해야 하므로, 옵션 2에서 피했던 보안 작업이 이미지 재빌드라는 형태로 돌아옵니다. 장기 실행 서비스의 경우 이는 합리적인 교환입니다. 하지만 누군가 한 번만 실행하는 명령줄 도구라면 그렇지 않습니다.

어떤 옵션을 선택해야 합니까?

  • 직접 관리하며 동일한 배포판을 사용하는 서버들을 위한 내부 도구: 해당 배포판에서 동적으로 빌드하고 거기서 마칩니다.
  • 외부 사용자가 다운로드하여 실행할 단일 파일: 프로그램이 dlopen을 필요로 하지 않고 NSS 기반 조회를 사용하지 않는다면 static musl을 선택합니다.
  • 플러그인이나 GPU 접근이 필요한 프로그램: 동적 빌드를 유지하고, 구형 베이스에서 빌드하며, 배포 시 옵션 3을 사용합니다.
  • 이미 런타임이 설치된 머신에서 장기간 실행되는 서비스: 이미지를 배포합니다.

무엇을 선택하든 이를 기록하고 확인하십시오. 빌드 호스트의 glibc는 이제 릴리스 프로세스의 일부가 되었습니다. LTS 릴리스를 업그레이드한 빌드 머신은 아무런 경고 없이 최소 요구 사항을 높여, 지난달까지 정상 작동하던 사용자들의 환경을 망가뜨릴 수 있습니다. 빌드 이미지의 태그를 고정하고, 빌드 단계에서 출력물의 가장 높은 GLIBC_ 태그를 확인하도록 설정하십시오. 이렇게 하면 문제가 다른 사용자의 터미널이 아닌 귀하의 파이프라인에서 먼저 발견되어 실패하게 됩니다.

FAQ

서버에서 "version GLIBC_2.38 not found" 오류가 발생하는 이유는 무엇입니까?

해당 바이너리는 서버의 glibc보다 더 최신 버전의 glibc가 설치된 환경에서 컴파일되었습니다. glibc의 심볼 버전 관리(symbol versioning)는 하위 호환성만 보장합니다. 즉, 구형 바이너리는 최신 glibc에서 실행될 수 있지만, 최신 바이너리는 구형 glibc에서 실행되지 않습니다. 로더는 파일에 기록된 정확한 버전 태그를 요구하는데, 구형 libc에는 해당 태그가 존재하지 않기 때문입니다. 서버에 패키지를 설치하여 이 문제를 안전하게 해결할 방법은 없습니다. 더 낮은 버전의 베이스 환경에서 다시 빌드하거나, 정적 musl 빌드를 배포하거나, 라이브러리와 호환되는 로더를 함께 묶거나, 컨테이너 이미지를 배포하십시오.

바이너리가 요구하는 glibc 버전을 확인하려면 어떻게 해야 합니까?

objdump -T ./mytool | grep -o 'GLIBC_[0-9.]*' | sort -Vu | tail -5를 사용하여 파일에서 버전 태그를 읽어오십시오. 마지막 줄에 표시되는 버전이 해당 바이너리를 로드할 수 있는 최소 glibc 버전입니다. readelf -V ./mytool.gnu.version_r 섹션에서 동일한 요구 사항을 라이브러리별로 분류하여 보여줍니다. objdump -T이 아무것도 출력하지 않는다면, 해당 바이너리는 정적(static) 바이너리이며 glibc 요구 사항이 전혀 없는 것입니다. 이 결과를 ldd --version를 통해 확인한 서버 자체의 glibc 버전과 비교하십시오.

musl 바이너리가 glibc 바이너리보다 느립니까?

워크로드에 따라 다르며, 정확한 확인을 위해서는 직접 측정해야 합니다. 정적 musl 바이너리는 실행 시 로더를 구동하거나 재배치(relocation) 작업을 수행할 필요가 없으므로 더 빠르게 시작됩니다. 반면, musl의 할당자(allocator)는 다수의 스레드가 동시에 메모리를 할당하는 상황보다는 작은 크기와 예측 가능한 동작에 최적화되어 있습니다. 또한 musl은 glibc보다 수작업으로 최적화된 문자열 및 메모리 루틴이 적으므로, 메모리 할당이나 문자열 처리가 많은 스레드 기반 프로그램은 눈에 띄게 느려질 수 있습니다. 어느 쪽의 주장을 받아들이기 전에 자신의 서버에서 직접 벤치마크를 수행하십시오.

존재하는 파일인데도 bash에서 "No such file or directory"라고 출력하는 이유는 무엇입니까?

존재하지 않는 파일은 바이너리가 아니라 동적 로더입니다. 커널은 ELF 헤더에서 인터프리터 경로를 읽는데, 해당 경로가 없으면 ENOENT을 반환하며 셸은 이를 해당 오류 메시지로 보고합니다. file ./mytool을 실행하여 interpreter 필드를 확인하십시오. 만약 Debian이나 Ubuntu 서버에서 /lib/ld-musl-x86_64.so.1이라고 표시된다면, glibc 시스템에서 musl로 링크된 바이너리를 실행하려는 상황입니다. 이 경우 musl 호환 다운로드 버전을 사용하거나 정적 빌드를 사용해야 합니다.

이 문제를 해결하기 위해 최신 서버의 libc.so.6을 복사해와도 됩니까?

아니요. libc.so.6ld-linux-x86-64.so.2는 특정 glibc 빌드에서 서로 짝을 이루는 파일이며, 시스템의 모든 프로세스가 이를 사용합니다. 따라서 시스템의 파일을 덮어쓰면 서버의 모든 도구가 실행 불가능해질 수 있으며, 복구에 필요한 도구조차 사용할 수 없게 됩니다. 구형 호스트에서 최신 바이너리를 반드시 실행해야 한다면, 최신 glibc를 별도의 디렉터리에 압축 해제한 뒤 --library-path을 사용하여 해당 프로그램 전용 로더로 실행하십시오. 이는 해당 프로세스에만 영향을 미칩니다. 6개월 뒤에도 문제가 발생하지 않도록 하려면 구형 glibc 환경에 맞춰 다시 빌드하는 것이 가장 좋은 해결책입니다.

#glibc#musl#static-linking#portability#binaries