이 문제의 핵심은 rdkafka
젬이 librdkafka
라는 복잡한 C 라이브러리를 래핑하고, 이 라이브러리가 OpenSSL, Cyrus SASL, MIT Kerberos, 다양한 압축 라이브러리(zlib, zstd, lz4, snappy) 등 수많은 시스템 라이브러리에 의존한다는 점이었습니다. 각 Linux 배포판(Ubuntu, CentOS, Alpine)과 macOS 등 플랫폼마다 라이브러리 버전과 구현 방식이 상이하여 ‘호환성 지옥’이 발생했고, 동적 링크 방식으로는 범용적인 바이너리를 만드는 것이 불가능했습니다.
저자는 nokogiri
젬의 성공 사례와 Python의 wheels, Go의 정적 바이너리처럼 다른 생태계의 접근 방식을 연구했습니다. 그리고 모든 의존성을 소스 코드로부터 컴파일하여 최종 라이브러리에 정적으로 링크하는 방식을 채택했습니다. 이는 단순해 보였지만, 각 플랫폼(Linux x86_64 GNU, Alpine Linux의 musl libc, macOS ARM64)의 고유한 특성(다른 시스템 호출, 라이브러리 규칙, 컴파일러 동작) 때문에 수많은 난관에 부딪혔습니다.
이를 극복하기 위해 모든 의존성을 올바른 순서와 플래그로 컴파일하는 2,000줄 이상의 정교한 셸 스크립트를 개발했습니다. 또한, 사전 컴파일된 바이너리의 보안 문제를 해결하기 위해 모든 다운로드된 의존성에 대한 SHA256 검증, RubyGems Trusted Publishing을 통한 암호화 증명, 고정된 버전의 재현 가능한 빌드, 공급망 보호 등 포괄적인 보안 모델을 구현했습니다.
CI/CD 파이프라인 또한 복잡해졌습니다. Linux는 Docker 컨테이너, macOS는 실제 macOS 러너에서 빌드 및 테스트를 수행했으며, Ruby 3.1부터 3.5까지의 모든 버전을 지원하기 위해 각 플랫폼별로 10개의 CI 액션이 필요했습니다. 릴리스 과정 역시 단순한 gem push
를 넘어 여러 플랫폼에서 동시에 빌드, 테스트하고 플랫폼별 젬을 조율하며, 보안 강화를 위해 RubyGems Trusted Publishing을 적용하는 등 전면적인 재구축이 이루어졌습니다.