이 문제의 해결은 ‘호환성 지옥’이라 불리는 rdkafka
의 복잡한 의존성 구조 때문에 쉽지 않았습니다. rdkafka
는 OpenSSL, Cyrus SASL, MIT Kerberos, 다양한 압축 라이브러리(zlib, zstd, lz4, snappy) 등 수많은 C 라이브러리에 의존하며, 이들 라이브러리의 버전은 각 Linux 배포판(Ubuntu, CentOS, Alpine Linux)과 macOS에서 상이하여 단일 바이너리를 만드는 것이 불가능해 보였습니다. 기존 시도들은 시스템 라이브러리에 동적으로 링크하여 배포 시스템의 라이브러리 버전이 다르면 문제가 발생하는 한계를 보였습니다.
필자는 nokogiri
젬이 사전 컴파일된 바이너리를 성공적으로 제공하여 XML 처리의 고질적인 컴파일 문제를 해결한 사례에서 영감을 얻어, ‘정적 링크’가 해답임을 깨달았습니다. 이는 모든 의존성을 소스 코드에서 컴파일하여 최종 라이브러리에 정적으로 포함함으로써, 시스템 라이브러리에 대한 의존성을 없애는 방식이었습니다. 첫 번째 성공은 Linux x86_64 GNU 시스템에서 나타났으며, 설치 시간은 60초 이상에서 5초 미만으로 극적으로 단축되었습니다. 그러나 Alpine Linux(musl libc 사용), macOS ARM64(Apple Silicon 아키텍처) 등 플랫폼별로 고유한 문제점들이 발생했습니다. 특히 Alpine Linux에서는 Cyrus SASL 라이브러리가 glibc 관련 함수를 계속 사용하려 하여 어려움을 겪었습니다.
보안 문제 또한 중요한 고려사항이었습니다. 사전 컴파일된 바이너리는 소스 코드보다 신뢰도가 낮다는 점을 인지하고, 모든 다운로드된 의존성에 대한 SHA256 검증, RubyGems Trusted Publishing을 통한 암호화 증명, 고정된 버전으로 재현 가능한 빌드, 악성 의존성에 대한 공급망 보호 등 포괄적인 보안 모델을 구현했습니다. 단일 네이티브 확장을 빌드하는 과정은 각 라이브러리가 올바른 순서와 플래그, 타겟 아키텍처로 컴파일되어야 하는 복잡한 ‘컴파일 발레’와 같았습니다. 2,000줄이 넘는 정교한 셸 코드로 구성된 빌드 스크립트를 개발했으며, 각 플랫폼의 미묘한 차이를 처리해야 했습니다.
CI/CD 파이프라인은 여러 플랫폼과 Ruby 버전(3.1, 3.2, 3.3, 3.4, 3.5)에 걸쳐 네이티브 확장을 테스트하는 ‘CI/CD 악몽’이었습니다. 이는 빌드 및 테스트 단계를 분리한 다단계 파이프라인으로 확장되었고, 각 플랫폼에 맞는 러너(Ubuntu Docker, macOS 러너)가 필요했습니다. 전체 시스템에 걸쳐 단 하나의 실패라도 발생하면 전체 빌드에 영향을 미쳤습니다. 각 플랫폼은 컴파일부터 Ruby 버전별 테스트까지 10개의 개별 CI 액션을 요구하며, 이는 총 30개의 복잡한 액션으로 구성된 파이프라인을 형성했습니다.
릴리스 프로세스 역시 혁신을 거쳤습니다. 이제 각 릴리스는 여러 플랫폼에서 동시에 빌드하고, 각 바이너리를 Ruby 버전별로 테스트하며, 여러 플랫폼별 젬의 릴리스를 조율해야 합니다. API 키 대신 암호화 토큰을 사용하는 RubyGems Trusted Publishing을 구현하여 보안을 강화하고 감사 추적 기능을 개선했습니다.