← 글 목록

notes

Tailscale

Tailscale 기반 사설 네트워크 구성·노드 연결과 운영 설정 메모.


1. Tailscale — 정확히 뭐고 왜 쓰는가

한 줄 요약

"내 디바이스만 가입할 수 있는 사설 VPN 네트워크" — 외부 인터넷에 포트를 단 하나도 열지 않고도, 본인 계정에 등록된 폰/맥/노트북끼리만 서로 직접 IP로 통신할 수 있게 만듭니다.

동작 원리 (개념)

외부 인터넷(공격자) ──❌── [맥의 공유기·방화벽]
                            │
                            └── 포트 닫혀있음, 외부 접근 불가

당신의 디바이스 ──✅── Tailscale 사설망 (100.x.x.x) ──✅── [맥의 pm2 / next:3000]
   (폰·노트북)         WireGuard 암호화 P2P

핵심:

  • 외부에 포트 노출 안 함. 공유기 설정 손댈 필요 없음. 공인 IP/도메인 불필요.
  • 계정 단위로 디바이스가 묶임. 본인이 admin 콘솔에서 추가 승인 안 한 디바이스는 절대 못 들어옴.
  • 각 디바이스에 100.x.y.z 형식의 가상 IP가 부여됨. 어디서든(LTE/외부 와이파이/지구 반대편) 그 IP로 접근 가능.
  • 모든 트래픽은 WireGuard 암호화 + 가능하면 P2P 직결(중계 안 거치고 디바이스 ↔ 디바이스).

비교: 다른 옵션과 뭐가 다른가

셋업 (5분)

맥 (서버 쪽):

brew install --cask tailscale
open -a Tailscale          # 메뉴바 아이콘 클릭 → "Log in" → Google/Apple/이메일 인증
tailscale ip -4            # 이 맥의 Tailscale IP 확인 (예: 100.64.12.34)
tailscale status           # MagicDNS 이름 확인 (예: kms-mac)

폰 (클라이언트):

  • App Store에서 Tailscale 앱 설치
  • 같은 계정 로그인 → 자동 등록
  • Tailscale 토글 ON

접속:

  • http://100.64.12.34:3000 (IP)
  • 또는 http://kms-mac:3000 (MagicDNS — 외우기 쉬움)

추가 옵션 (선택)

  • HTTPS: tailscale serve --https=443 http://localhost:3000 한 줄로 자동 인증서 발급 → https://kms-mac.tail-scale.ts.net 형태로 접속 가능
  • Funnel: 가입 안 한 외부인에게 잠깐 보여줄 때 (tailscale funnel) — 본인 요구사항엔 불필요

2. ecosystem.config.cjs — 각 옵션 뭐 하는가

ecosystem.config.cjs의 핵심 블록을 옵션별로 풀어 설명하겠습니다.

apps 배열 — 한 ecosystem에서 여러 프로세스를 동시에 정의

두 개의 앱이 들어 있습니다:

옵션별 의미

      name: "stock-auto-web",
      cwd: __dirname,
      script: "pnpm",
      args: `start -- -H 0.0.0.0 -p ${PORT}`,
      interpreter: "none",
      autorestart: true,
      max_restarts: 10,
      restart_delay: 3000,
      max_memory_restart: "1G",
      kill_timeout: 5000,
      env: baseEnv,
      out_file: path.join(__dirname, "logs/pm2-web.out.log"),
      error_file: path.join(__dirname, "logs/pm2-web.err.log"),
      merge_logs: true,
      time: true,

loop 앱은 뭐가 다른가

      name: "stock-auto-loop",
      cwd: __dirname,
      script: "pnpm",
      args: "run all:loop",
      interpreter: "none",
      autorestart: true,
      max_restarts: 10,
      restart_delay: 5000,
      kill_timeout: 10000,
      env: baseEnv,
      out_file: path.join(__dirname, "logs/pm2-loop.out.log"),
      error_file: path.join(__dirname, "logs/pm2-loop.err.log"),
      merge_logs: true,
      time: true,
  • args: "run all:loop" — 30분 주기로 batch:intel + buy-intel:live를 도는 무한 루프 스크립트
  • kill_timeout: 10000 — 10초. 인텔 수집 중간에 끊지 않도록 web(5초)보다 길게 잡음
  • max_memory_restart 없음 — 큰 데이터 다룰 일이 별로 없고 무한 루프 자체가 메모리 누적 안 됨

부가 메커니즘

pm2 save + pm2 startup

  • pm2 save: 현재 실행 중인 프로세스 목록을 ~/.pm2/dump.pm2에 저장
  • pm2 startup: macOS의 launchd에 PM2 자체를 등록. 부팅 시 launchd가 PM2를 띄우고, PM2가 저장된 목록을 자동 복원
  • → 결과적으로 재부팅·로그인 시 두 앱 자동 시작

코드 변경 → 무중단 배포

pnpm build
pm2 reload stock-auto-web      # graceful — 이전 요청 끝낸 후 새 프로세스로 교체

loop는 reload보다 restart가 안전:

pm2 restart stock-auto-loop

포트 바꿀 때

PORT=8080 pnpm pm2:restart     # ecosystem이 process.env.PORT를 읽어서 반영

다음 단계 권장 순서

  • npm i -g pm2 (pm2 글로벌 설치)
  • dev 서버(Ctrl+C) + pnpm all:loop 백그라운드 종료
  • pnpm build (이미 한 번 통과 — 변경 없으면 스킵 가능)
  • pnpm pm2:start — 두 프로세스 기동
  • pm2 startup → 출력된 sudo 명령 복붙 → pm2 save
  • pnpm pm2:status로 둘 다 online 확인
  • brew install --cask tailscale → 로그인 → 폰 앱 설치 → 같은 계정 로그인
  • 폰에서 http://<MagicDNS이름>:3000 접속 확인