Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.

pkg로 바이너리를 컴파일할 때 Native 애드온을 같이 사용할 때의 오류

며칠 전에 pkgNode.js를 하나의 파일로 패키징하는 방법에 관한 글을 썼는데 pkg를 사용할 때 주의할 점이 있다.

Node.js는 크게 JavaScirpt로 작성한 모듈과 C++ 애드온으로 작성한 네이티브 모듈이 있다. JavaScript로만 작성된 경우 V8 위에서 실행되므로 어디서나 같게 동작하지만 네이티브 모듈 같은 경우는 macOS나 Windows, Linux 등 플랫폼에 맞게 컴파일을 해야 제대로 동작한다. 각 플랫폼에서 일관된 인터페이스로 빌드 해주는 도구가 gyp이다.

각 플랫폼에서 컴파일해야 한다는 말이 담고 있는 의미대로 pkg로 애플리케이션을 여러 플랫폼에 맞게 하나의 바이너리로 만들 때 이 네이티브 애드온을 사용하고 있으면 문제가 된다. 이를 확인해 보자.

var bcrypt = require('bcrypt');

const saltRounds = 10;
const myPlaintextPassword = 's0/\/\P4$$w0rD';
const someOtherPlaintextPassword = 'not_bacon';

bcrypt.genSalt(saltRounds, function(err, salt) {
  bcrypt.hash(myPlaintextPassword, salt, function(err, hash) {
    console.log(hash);
  });
});

위 코드는 네이티브 애드온인 bcrypt를 사용해서 암호화한 비밀번호를 출력하는 간단한 코드다. pkg가 설치되어 있을 때 pkg . --out-path=dist로 컴파일하면 dist 디렉터리 아래 플랫폼별로 컴파일된 바이너리가 생성된다.

dist/
├── pkg-test-linux
├── pkg-test-macos
└── pkg-test-win.exe

이 컴파일된 파일을 실행하면(테스트 환경은 macOS이다.) 다음과 같이 정상적으로 실행된다.

$ ./dist/pkg-test-macos
$2b$10$vffib/49AN2ISuuTAxavuuAS7W/IEtJEzQc0emLbcQHQZdSn17Yte

이렇게 보면 아무런 문제가 없는 것 같지만, 이 파일을 프로젝트 디렉터리가 아닌 다른 곳으로 이동한 뒤에 실행하면 다음과 같은 오류가 발생한다.

$ ./pkg-test-macos
pkg/prelude/bootstrap.js:1172
      throw error;
      ^

Error: Cannot find module '/snapshot/pkg-test/node_modules/bcrypt/lib/binding/bcrypt_lib.node'
1) If you want to compile the package/file into executable, please pay attention to compilation warnings and specify a literal in 'require' call. 2) If you don't want to compile the package/file into executable and want to 'require' it from filesystem (likely plugin), specify an absolute path in 'require' call using process.cwd() or process.execPath.
    at Function.Module._resolveFilename (module.js:534:15)
    at Function.Module._resolveFilename (pkg/prelude/bootstrap.js:1269:46)
    at Function.Module._load (module.js:464:25)
    at Module.require (module.js:577:17)
    at Module.require (pkg/prelude/bootstrap.js:1153:31)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (/snapshot/pkg-test/node_modules/bcrypt/bcrypt.js:6:16)
    at Module._compile (pkg/prelude/bootstrap.js:1243:22)
    at Object.Module._extensions..js (module.js:644:10)
    at Module.load (module.js:552:32)

이는 Linux에서 실행해도 마찬가지다. 다음과 같은 Dockerfile을 만들어 보자.

FROM debian:jessie

ADD ./dist/pkg-test-linux .

CMD ./pkg-test-linux

이를 실행하면 앞과 같은 오류를 볼 수 있다.

$ docker build -t pkg-test .
Sending build context to Docker daemon  117.9MB
Step 1/3 : FROM debian:jessie
 ---> 4eb8376dc2a3
Step 2/3 : ADD ./dist/pkg-test-linux .
 ---> Using cache
 ---> 081da6150858
Step 3/3 : CMD ./pkg-test-linux
 ---> Using cache
 ---> ec3f44640e61
Successfully built ec3f44640e61
Successfully tagged pkg-test:latest

$ docker run --rm -it pkg-test:latest
pkg/prelude/bootstrap.js:1172
      throw error;
      ^

Error: Cannot find module '/snapshot/pkg-test/node_modules/bcrypt/lib/binding/bcrypt_lib.node'
1) If you want to compile the package/file into executable, please pay attention to compilation warnings and specify a literal in 'require' call. 2) If you don't want to compile the package/file into executable and want to 'require' it from filesystem (likely plugin), specify an absolute path in 'require' call using process.cwd() or process.execPath.
    at Function.Module._resolveFilename (module.js:534:15)
    at Function.Module._resolveFilename (pkg/prelude/bootstrap.js:1269:46)
    at Function.Module._load (module.js:464:25)
    at Module.require (module.js:577:17)
    at Module.require (pkg/prelude/bootstrap.js:1153:31)
    at require (internal/module.js:11:18)
    at Object.<anonymous> (/snapshot/pkg-test/node_modules/bcrypt/bcrypt.js:6:16)
    at Module._compile (pkg/prelude/bootstrap.js:1243:22)
    at Object.Module._extensions..js (module.js:644:10)
    at Module.load (module.js:552:32)

결국 /snapshot/pkg-test/node_modules/bcrypt/lib/binding/bcrypt_lib.node 파일을 찾지 못한다는 오류이다. 네이티브 애드온은 플랫폼에 맞게 컴파일되고 나면 .node 파일로 생성된다. 이 글을 쓰는 환경은 macOS인데 npm 모듈이 설치된 node_modules 아래를 보면 bcrypt_lib.node 파일이 생성된 것을 볼 수 있다.

$ ls -lh node_modules/bcrypt/lib/binding/bcrypt_lib.node
-rwxr-xr-x  1 outsider  staff    48K  4 21 02:22 node_modules/bcrypt/lib/binding/bcrypt_lib.node

Native addons (.node files) use is supported, but packaging .node files inside the executable is not resolved yet. You have to deploy native addons used by your project to the same directory as the executable.

pkg의 문서를 보면 위처럼 네이티브 애드온의 .node 파일을 패키징된 파일과 같은 디렉터리 안에 넣어야 한다고 나와 있다. 아직 네이티브 애드온까지 하나의 바이너리로 만드는 기능은 지원하지 않는다. 현재 pkg의 버전은 4.3.1이다.

위 파일을 프로젝트 디렉터리 외부로 pkg-test-macos 파일을 복사한 위치에 복사해 보자. 다시 pkg-test-macos를 실행하면 정상적으로 실행되는 것을 볼 수 있다.

$ ./pkg-test-macos
$2b$10$ZiRPqivssFsuMVrBgHSNauN9M9buvv40SXELNB1nYtkB.nd73U.PG

네이티브 애드온을 사용할 때의 문제는 파악했지만, 이는 현실적으로 제대로 활용하기는 어려워 보인다. macOS, Linux, Windows를 타겟으로 바이너리를 컴파일했다고 했을 때 지금 환경이 macOS라면 이 .node 파일은 macOS 환경에 맞춰서 빌드된 파일일 뿐이다. 그래서 위 3개 플랫폼에 맞게 배포하려면 각 플랫폼에서 컴파일한 .node 파일이 필요하고 이를 모두 배포한 뒤 사용자가 바이너리와 .node 파일을 같은 디렉터리 안에 넣고 사용해야 한다.

자동화를 한다면 플랫폼별 파일을 못 만들어 낼 것은 아니지만 회사나 조직 등 통제된 환경에서 배포하는 게 아니라 일반 사용자를 대상으로 배포한다면 위의 설명처럼 같은 디렉터리 내에 파일을 두고 사용하라고 안내하기는 쉽지 않은 일이라고 생각한다.

그래서 pkg를 사용하려고 할 때 네이티브 애드온을 사용한다면 이런 부분을 고려해봐야 한다. 배포 안내를 잘 하거나 네이티브 애드온을 최대한 자제하거나 하는 등의 선택이 필요해 보인다.

2018/06/03 04:08 2018/06/03 04:08

기술 뉴스 #103 : 18-06-01

웹개발 관련

  • A cartoon intro to DNS over HTTPS : 브라우저가 사이트에 접속하려고 할 때 DNS를 조회하는 과정을 설명하고 HTTPS를 이용한다고 하더라도 DNS를 조회할 때 DNS 리졸버가 그 중간 과정에 있는 서버들이 어떤 사이트에 접속하려고 하는지 정보를 볼 수 있는 등 이 과정에서 데이터가 유출될 수 있는 부분을 설명한다. 이어서 Trusted Recursive Resolver(TRR)와 DNS over HTTPS(DoH)를 이용하면 이 정보를 어떻게 막을 수 있는지 설명하고 TRR과 DoH를 적용하더라도 감출 수 없는 정보까지 설명하고 있다. 최근 정부에서 SNI를 이용해서 사이트를 감청하려는 시도로 문제가 되었는데 시기적절하게 DNS over HTTPS를 이해하기 좋은 글이다.(영어)
  • AST for JavaScript developers : Babel 등에서 JavaScript 트랜스파일이나 코드 분석에 사용하는 AST(Abstract Syntax Tree)에 관해설명한 글이다. AST가 어떻게 동작하고 Babel에서는 어떻게 이용하는지 설명한 후 개발자들이 많이 사용하는 Jscodeshift, Prettier같은 도구에서 AST를 이용하는 방법을 설명하고 있다. AST를 만들고 이용하는 방법을 이해하기에 좋다.(영어)
  • Progressive Web Apps on the Desktop : Chrome 67에 베타로 추가된 데스크톱 PWA에 대한 소개 글이다. 홈 스크린에 추가되는 방식과 디자인 고려사항 등을 간략히 설명해 주고 있다. 현재는 Mac과 Windows를 지원하는데 데스크톱 앱을 만들어야 할 때 앞으로 고려해 볼 수 있을 것 같다. (영어)
  • The Front-End Tooling Survey 2018 - Results : 프론트엔드 도구 관련 설문에 5,000여 명이 응답한 결과를 정리한 글이다. CSS 전처리기, Lint 도구, JS 라이브러리, 테스트 도구 등 사용하거나 익숙도 등을 묻는 말이 대부분이다. 이전 설문과 비교해주어서 도구의 트랜드를 어느 정도 파악해 볼 수 있고 많이 사용하는데 모르는 도구를 한번 찾아보기에 좋다.(영어)
  • Top 10 JavaScript errors from 1000+ projects (and how to avoid them) : JavaScript 오류 로깅 서비스인 Rollbar에서 1,000여 개의 프로젝트에서 수집한 오류를 기준으로 최상이 10개의 오류를 정리하고 각 오류가 어떤 브라우저의 어떤 상황에서 발생하고 이를 피하려면 어떻게 해야 하는지 나와있다. 오류는 흔히 볼 수 있는 부분이지만 브라우저마다 다른 오류나 처리방식에 대해서 참고해 볼만하다.(영어)


그 밖의 프로그래밍 관련

  • Using AWS Application Load Balancer and Network Load Balancer with EC2 Container Service : AWS에서 제공하는 Application Load Balancer(ALB)와 Network Load Balancer(NLB)가 OSI의 Level 4와 Level 7에서 어떻게 동작하는지 설명하는 글이다. 이 동작의 차이 때문에 두 로드 밸런서에서 어떤 기능 차이가 나는지 이해하기 쉽게 설명하고 이 둘이 지원하는 동적 포트를 ECS와 어떻게 연동할 수 있는지를 보여주고 있다.(영어)
  • 340만 동접자 이후 서비스 다운 관련 사후 분석 내용 : 게임 포트나이트가 340만 동시 접속을 이루면서 겪은 장애 내용을 정리한 글이다. 포트나이트의 설계내용과 관계없이 각 부분의 처리용량과 장애 상황에 대한 파악과 장애 내용 기록, 사후 대책에 대한 고민까지 깔끔하게 정리된 것이 인상적이다.(한국어)
  • Mocking is a Code Smell : 테스트 코드를 사용할 때 Mocking이 필요하다면 코드에 개선할 부분이 있다는 신호로 봐야 한다는 논조의 글이다. TDD와 유닛 테스트, 테스트 커버리지에 대해서 정의하고 Mocking이 필요하다면 코드에 깊은 커플링이 있다는 의미이고 커플링이 왜 발생하고 함수형 프로그래밍에서는 이러한 커플링을 어떻게 해결할 수 있는지 설명하고 있다. 마지막에는 JavaScript로 코드의 의존성을 해결할 수 있는 예시를 보여주고 있는데 아주 긴 글이지만 테스트에 관심이 있다면 충분히 도움이 될만한 글이다.(영어)
  • How to Build an Electron Desktop App in JavaScript: Multithreading, SQLite, Native Modules, and other Common Pain Points : Electron으로 데스크톱 애플리케이션을 만들 때 스레드 관리와 패키징 관련 팁을 정리한 글이다. CPU 중심 작업은 렌더러 프로세스를 느리게 할 수 있으므로 별도로 분리해야 하는데 웹 워커를 쓰거나 새로운 프로세스를 포크 하거나 렌더러 프로세스를 워커로 사용해서 해결하는 방법을 소개하고 각 장법의 장단점을 설명하고 있다. 그리고 패키지해서 배포할 때 필요한 도구와 주의해야 할 점도 정리되어 있다.(영어)
  • iOS-factor : 웹 서비스를 만드는 방법론인 The Twelve-Factor app에 영감을 받아서 iOS 개발 프로세스에 적용할 수 있는 원리를 정리한 내용이다. Dependencies, Config, Dev/prod parity, Deployment, Prefer local over remote, Backwards compatible APIs, App versioning, Persistence of data 8가지 항목으로 정리되어 있다.(영어)
  • Learning Go’s Concurrency Through Illustrations : Go routines의 사용방법과 동작 방식을 설명한 글이다. 간단한 예제를 통해서 실제로 Go routines이 어떻게 동작하는지 쉽게 설명하고 있다.(영어)
  • Introducing Git protocol version 2 : Git 프로토콜의 버전 2가 나왔다. 현재는 원격 저장소의 모든 레퍼런스(브랜치 등)를 가져와야 하지만 이젠 서버사이드 필터링이 추가되어 원하는 브랜치 등만을 가져오는 것이 가능해져서 50만 개 이상의 레퍼런스가 있는 저장소에서 3배 정도의 속도 향상을 보였다고 한다. 기존 프로토콜에서 버전 2 정보를 추가로 보내서 동작하기 때문에 구버전의 저장소와 호환해서 사용할 수 있고 Git 2.18에 새 프로토콜이 추가될 것으로 예상한다고 한다.(영어)
  • 행복을 찾기 위한 우리의 여정, 쿠팡의 MSA — Part 1, Part 2 : 쿠팡에서 모로리틱 아키텍터에서 마이크로서비스 아키텍처로 전환한 과정을 설명하다. 기존 아키텍처에서 겪은 문제를 설명하고 Vitamin 프레임워크, 프로바이더 헬퍼, 메시지 큐를 사용하는 전략을 통해서 MSA로 전환을 한 과정이 나와 있다. 2부에서는 MSA를 유지하기 위해 구성 관리 데이터베이스, 배포 시스템, A/B 테스트 등을 적용한 경험을 공유하고 있다. 쿠팡 정도의 큰 시스템에서 MSA로 전환한 경험이라 유용한 글이다.(한국어)
  • Kubernetes Containerd Integration Goes GA : Kubernetes의 Containerd 통합이 베타 단계를 넘어서 GA가 되었다. 이로써 Docker뿐 아니라 다른 컨테이너도 사용할 수 있게 되었고 Containerd로 인해 내부 구조가 더 간단해졌으면 성능도 상당히 높아졌다. 그리고 추가로 트러블슈팅에 사용할 수 있는 crictl에 대해서도 설명하고 있다.(영어)
  • Introducing play with Kubernetes : 웹 브라우저에서 Docker를 연습해 볼 수 있는 Play with Docker에 이어 Play with Kubernetes가 런치되어 아무것도 설치하지 않고 Kubernetes를 연습할 수 있게 되었다. Play with Kubernetes Classroom를 교재로 같이 연습해 볼 수도 있다.(영어)
  • Open Sourcing Zuul 2 : Netflix에서 클라우드 게이트웨이 Zuul 2를 오픈소스로 공개했다. 내부에서 사용하는 Zuul 2를 오픈소스로 공개한 것으로 이번 오픈소스 버전에 포함된 기능에 관한 설명과 아직 공개되지 않았지만, 내부에서 개발 중인 기능에 관한 설명이 포함되어 있다.(영어)
  • Automatically Generating InSpec Controls from Terraform : 보안, 정책 등을 테스트하는 도구 InSpec을 Terraform과 연동해서 .tfstate에서 테스트 파일을 만드는 방법을 설명한다. 얼마나 실용적일지는 테스트해봐야겠지만 흥미로운 접근 방법이다.(영어)

볼만한 링크

  • Full Cycle Developers at Netflix — Operate What You Build : Netflix에서 디자인, 개발, 테스트, 배포, 운영 등의 단계에 각 전문가를 배치했지만 서로 간의 커뮤니케이션 비용이 커지고 개선이 안 되는 문제를 해결하려고 Full Cycle Developer 모델을 도입해서 해결한 부분을 설명한 글이다. 개발한 것을 직접 운영한다는 DevOps의 원리와 마찬가지로 Full Cycle Developer는 소프트웨어의 전체 주기에 관여하면서 관련 도구를 만들어서 개선하도록 했다. 기존에는 다른 영역을 지원하더라도 자기 일이라고 생각하지 않고 지원해 준 뒤 자기 일로 돌아온다는 생각을 하고 있었는데 Full Cycle Developer는 서비스를 고객에게 제공하는 것이 자기 일이므로 이런 부분에서 비효율이 발생한다면 자동화를 하거나 도구를 만들어서 개선하도록 한다는 것이다.(영어)
  • After 5 years and $3M, here's everything we've learned from building Ghost : 오픈소스 블로그 플랫폼인 ghost를 5년간 개발한 경험을 공유한 글이다. 잘 모르고 있었지만, 킥스타터 후원으로 비영리 기구를 만들어서 오픈소스로 Ghost를 만들어서 원격으로 만들면서 수익을 내고 직원 월급을 준 과정이 적혀있다. 비영리 기구라서 파운더라고 주식을 갖고 있거나 하지 않지만 다른 회사처럼 월급을 주면서 운영하고 ghost를 만들면서 실수했던 부분과 잘했던 부분, 오픈소스 생태계에 대한 생각 등이 담겨있어서 재미있다.(영어)
  • The Economics of Writing a Technical Book : 작년 Cloud Native Infrastructure 책을 출간하고 오렐리에서 출간한 과정과 출간 후 수익이 어떤지 설명한 글이다. 공동 저자랑 Git을 이용해서 책을 쓰고 피드백 받으면서 얼마나 걸렸는지 꽤 자세히 나와 있고 출간 후 종이책이 얼마나 팔리고 책 외에 스폰서쉽으로 수익은 얼마나 벌었는지까지 구체적 수치까지 다 얘기하고 있어서 미 출판시장의 규모를 어느 정도 예측할 수 있다.(영어)

IT 업계 뉴스

프로젝트

  • reactive.how : Reactive 프로그래밍에서 사용하는 Steam, map, reduce, debounce 등을 애니메이션으로 이해하기 쉽게 설명하는 사이트.
  • black : Python 코드 포매터
  • Hangulize 2 : 외국어를 한글로 표기하는 Hangulize를 Go로 다시 만드는 프로젝트.
  • mitmproxy : HTTPS 프록시.
  • deno : V8에서 동작하는 JavaScript 런타임으로 Node.js를 만든 Ryan Dahl의 프로젝트다.

버전 업데이트

2018/06/01 03:01 2018/06/01 03:01