RHEL 8 계열(glibc 2.28)이나 그보다 오래된 서버에 최신 바이너리를 올려 실행하면 곧바로 죽는다.
./node_exporter: /lib64/libc.so.6: version `GLIBC_2.32' not found (required by ./node_exporter)
Go 로 만든 프로그램이니 정적 링크일 것이라 생각하기 쉽지만, 빌드 시점에 cgo 가 켜져 있으면 빌드 환경의 glibc 심볼을 참조한다. glibc 는 하위 호환은 되지만 상위 호환은 되지 않으므로, 더 새 버전에서 빌드한 바이너리를 오래된 시스템에서 돌릴 수 없다.
"몇 버전부터 안 된다" 는 목록은 릴리스마다, 그리고 배포판 패키지냐 공식 바이너리냐에 따라 달라진다. 받아 둔 바이너리를 직접 검사하는 것이 가장 확실하고 빠르다.
# 바이너리가 요구하는 glibc 심볼 버전
objdump -T node_exporter | grep -o 'GLIBC_[0-9.]*' | sort -Vu | tail -5
# 동적 링크 여부
file node_exporter
ldd node_exporter
ldd 가 not a dynamic executable 이라고 하면 정적 링크이므로 glibc 와 무관하게 실행된다. 출력된 심볼 가운데 가장 높은 버전이 시스템의 glibc 보다 높으면 실행되지 않는다.
# 시스템 glibc 버전
ldd --version | head -1
rpm -q glibc
배포판 패키지(Fedora · EPEL 등)로 만들어진 것은 그 배포판의 빌드 환경 glibc 를 따라가므로 대체로 요구 버전이 높다. 프로젝트가 GitHub 릴리스로 내놓는 공식 바이너리와 배포판 패키지를 구분해서 받는다.
cgo 를 끄면 순수 Go 로 정적 링크되어 glibc 의존이 사라진다. exporter 계열 대부분이 이 방식으로 빌드된다.
git clone https://github.com/prometheus/node_exporter.git
cd node_exporter
CGO_ENABLED=0 go build -o node_exporter .
file node_exporter # statically linked 로 나와야 한다
빌드는 glibc 가 새 장비에서 해도 된다. 결과물만 옮기면 된다. cgo 를 끄면 일부 수집기가 빠질 수 있으므로 필요한 지표가 나오는지 확인한다 (확인 필요).
같은 프로젝트의 이전 릴리스 바이너리를 위 방법으로 검사해 요구 버전이 낮은 것을 고른다. 다만 보안 수정이 빠진 버전을 운영에 두는 것이므로 임시 조치로만 본다.
호스트 glibc 와 무관하게 이미지 안의 라이브러리를 쓴다. node_exporter 처럼 호스트 지표를 읽는 것은 호스트 경로를 마운트하고 네임스페이스를 공유해야 한다.
docker run -d --name node_exporter \
--net=host --pid=host \
-v /:/host:ro,rslave \
quay.io/prometheus/node-exporter:latest \
--path.rootfs=/host
근본 대응이다. glibc 만 따로 올리는 것은 시스템 전체가 그 위에 얹혀 있으므로 하지 않는다.