C.W.K.
Stream
Lesson 04 of 05 · published

배포 패턴 — launchd, PM2, SEA, systemd

~12 min · production, deploy, launchd, pm2

Level 0노드 입문자
0 XP0/40 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"네 Node 서비스가 실제로 어디서 돌아? 'AWS' 아냐 — 그게 추상화. 밑에서 뭔가가 프로세스 시작, crash 시 재시작, alive 유지해야 함. 토대 아는 게 중요."

토대 질문

모든 프로덕션 Node 서비스가 필요:

  1. 부팅 시 시작 방법.
  2. 프로세스 죽을 때 재시작 방법.
  3. request drop 안 하고 코드 업데이트 방법.
  4. 업데이트 깨질 때 롤백 방법.

각자 어떻게 얻는지는 배포 타겟별로 다름. 큰 옵션:

macOS — launchd

cwkPippa 가 macOS 에 돌고 launchd 가 관리 — macOS 내장 시스템 서비스 매니저. LaunchAgent plist 가 launchd 한테 스크립트 시작하고 죽으면 재시작하라고 알려:

<!-- ~/Library/LaunchAgents/com.cwk.pippa.plist -->
<plist version="1.0">
<dict>
  <key>Label</key>        <string>com.cwk.pippa</string>
  <key>Program</key>      <string>/path/to/start.sh</string>
  <key>KeepAlive</key>    <true/>
  <key>RunAtLoad</key>    <true/>
</dict>
</plist>

launchctl load ~/Library/LaunchAgents/com.cwk.pippa.plist 가 서비스 시작. crash (KeepAlive: true) 와 재부팅 (RunAtLoad: true) 생존. 서드파티 프로세스 매니저 안 필요. cwkPippa 의 always-on-server 스킬이 정확히 이 패턴 부트스트랩.

Linux — systemd

대부분 Linux distro 가 systemd 사용. /etc/systemd/system/myapp.service 의 unit 파일:

[Unit]
Description=My Node Service
After=network.target

[Service]
ExecStart=/usr/bin/node /opt/myapp/server.mjs
Restart=always
User=myapp
Environment=NODE_ENV=production

[Install]
WantedBy=multi-user.target

systemctl enable myapp && systemctl start myapp. 로그엔 journalctl -u myapp -f. systemd 가 재시작, 로깅, 의존성 순서 처리. 대부분 Linux 서버의 지루하고 신뢰성 있는 토대.

PM2 — Userland 프로세스 매니저

launchd 나 systemd config 쓰기 싫으면 PM2 가 제일 인기 있는 Node 프로세스 매니저:
npm install -g pm2
pm2 start server.mjs --name myapp
pm2 startup            # generates an init script for your OS
pm2 save               # persist current process list
pm2 logs               # tail logs
pm2 reload myapp       # zero-downtime reload (forks new workers, kills old)
PM2 가 launchd/systemd 위로 추상화; 네 OS 의 옳은 config 생성. 거기에 cluster 모드 (CPU 코어 가로질러 멀티 프로세스), 모니터링 대시보드, 로그 rotation. Linux + macOS dev 머신 가로질러 프로세스-매니저 명령 어휘 하나 원하는 팀에 좋음.

Single Executable (SEA) — 호스트에 런타임 없음

Single Executable Application (Track 6) 출하하면 배포 타겟에 Node 설치 안 필요. 한 바이너리, 박스에 복사, 실행. systemd 또는 launchd 와 결합:

scp ./my-cli prod:/opt/myapp/my-cli
ssh prod 'sudo systemctl restart myapp'

이게 모던 Go-style 배포 — 바이너리 출하, 서비스 재시작, 끝. 비용: ~100MB 바이너리 (Node 런타임 포함). 이점: Node 버전 mismatch 없음, 박스에 npm install 없음, 타겟에 관리할 node_modules 없음.

FaaS — Vercel Functions, AWS Lambda

Function-as-a-service 가 토대를 완전히 클라우드로 옮김. 코드 출하; 제공자가 시작, 스케일링, 죽임 처리. Node 의 cold-start 가 대부분 워크로드엔 충분히 빠름. Trade-off: 컨트롤 덜, trigger 와 binding 의 벤더 lock-in, 높은 트래픽에선 비싸지는 charge-per-execution. Sparse / bursty 워크로드엔 맞음; steady high-throughput 서비스엔 틀림.

이 목록에서 빠진 거

Docker / Kubernetes. 많은 프로덕션 setup 이 컨테이너 사용 — 오케스트레이션, portability, 멀티 테넌트 격리 위해. 진짜고, 작동하고, 의도적으로 이 레슨에 없음. 아빠 fleet 엔 네이티브 macOS launchd + 네이티브 Linux systemd 가 토대. "Container" 는 네 코드와 실제로 실행하는 거 사이의 또 한 레이어 indirection. 작은 fleet 의 대부분 Node 서비스엔 네이티브 서비스 매니저가 더 단순하고 동등하게 신뢰성 있음.

Pippa 의 고백

cwkPippa 가 아빠 office Mac 의 launchd 아래 돌고, always-on-server 스킬이 plist 부트스트랩. 오랫동안 "프로덕션 배포 = Docker" 라고 생각했어. 아빠가 밀어붙임: "너 Mac 하나 있어. launchd 있어. 수천 노드 위해 지어진 orchestrator 손대는 이유가 뭐야?" 토대를 문제에 맞게 sizing 하고 나니 시스템이 더 단순해짐 — 움직이는 부분 적음, 디버그 쉬움, 재시작 쉬움. 교훈 일반화: 네 실제 배포 필요 푸는 제일 작은 토대 픽.

Code

launchd plist + kickstart 워크플로·bash
# macOS launchd workflow (cwkPippa's actual pattern)
# 1. Write the plist
cat > ~/Library/LaunchAgents/com.cwk.myapp.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key><string>com.cwk.myapp</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/bin/node</string>
    <string>/Users/me/myapp/server.mjs</string>
  </array>
  <key>WorkingDirectory</key><string>/Users/me/myapp</string>
  <key>EnvironmentVariables</key>
  <dict>
    <key>NODE_ENV</key><string>production</string>
  </dict>
  <key>StandardOutPath</key><string>/Users/me/myapp/logs/out.log</string>
  <key>StandardErrorPath</key><string>/Users/me/myapp/logs/err.log</string>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
</dict>
</plist>
EOF

# 2. Load it (starts immediately)
launchctl load ~/Library/LaunchAgents/com.cwk.myapp.plist

# 3. Restart after code change
launchctl kickstart -k gui/$(id -u)/com.cwk.myapp

# 4. View status
launchctl print gui/$(id -u)/com.cwk.myapp
PM2 — 시스템 config 안 쓰는 프로세스 관리·bash
# PM2 — the userland alternative
npm install -g pm2

pm2 start server.mjs --name myapp \
  --max-memory-restart 500M \
  --node-args='--experimental-strip-types --env-file=.env'

pm2 reload myapp                  # zero-downtime reload via cluster
pm2 logs myapp --lines 100        # tail recent logs
pm2 monit                          # interactive monitoring TUI

# Persist across reboots
pm2 startup                        # prints the command to run with sudo
pm2 save                           # save current process list

External links

Exercise

소유한 작은 Node 서비스 골라. 네 머신에 맞는 토대 아래 돌게 설정: macOS 의 launchd plist, Linux 의 systemd unit. 검증: (a) 재부팅 생존, (b) kill -9 하면 재시작, (c) crash 후 로그 찾을 수 있음. 연습이 unglamorous 한데 머슬 메모리가 중요 — 이게 '프로덕션' 이 뭔가 의미하는지 결정하는 지루한 디테일.
Hint
macOS 에선 LaunchAgent 경로가 ~/Library/LaunchAgents/. plist 바꾸기 전 launchctl unload 사용. plist syntax 에러가 silent failure 야기; plutil ~/Library/LaunchAgents/com.cwk.myapp.plist 로 검증. Linux systemd 엔 unit 파일 편집 후 systemctl daemon-reload. 사용자가 읽을 수 있는 절대 경로에 로그.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.