별책부록 6편
별책부록 6편
systemd + pm2 + Cloudflare Tunnel
SSH를 끊어도 봇은 24/7 작동합니다
📑 이 챕터에서 다룰 내용

운영자가 SSH를 끊어도 봇은 24/7 작동합니다. 1탄 v2 메타 원칙 "Day 0 시스템 자동 운영"의 봇 적용이에요.

📘 6편 개요
  • 예상 시간: 1시간
  • 핵심: 자동 재시작 + 영구 운영 + (선택) 외부 노출
  • 도구: pm2 (Node.js 봇) + systemd (Python 봇) + Cloudflare Tunnel (선택)
5-1 Node.js 봇 — pm2 🔗

pm2 설치

💻 터미널에 입력하세요
# 글로벌 설치
sudo npm install -g pm2

# 확인
pm2 --version

pm2 ecosystem 설정

💻 ecosystem.config.cjs 생성
cd ~/tsv-bot

# ecosystem 파일 생성
cat > ecosystem.config.cjs << 'EOF'
module.exports = {
  apps: [{
    name: 'tsv-discord-bot',
    script: './dist/bot.js',
    instances: 1,
    exec_mode: 'fork',

    // 자동 재시작
    autorestart: true,
    watch: false,
    max_memory_restart: '500M',

    // 환경 변수
    env: {
      NODE_ENV: 'production',
    },

    // 로그
    out_file: './logs/pm2-out.log',
    error_file: './logs/pm2-error.log',
    log_date_format: 'YYYY-MM-DD HH:mm:ss',
    merge_logs: true,

    // 재시작 정책
    min_uptime: '10s',
    max_restarts: 10,
    restart_delay: 5000,  // 5초 대기
  }]
};
EOF

# 빌드 후 시작
npm run build
pm2 start ecosystem.config.cjs

# 상태 확인
pm2 status
pm2 logs tsv-discord-bot

# 부팅 시 자동 시작
pm2 startup
# 안내된 명령어 복사 + sudo 실행
pm2 save

pm2 운영 명령

💻 pm2 주요 명령
pm2 status                  # 상태
pm2 logs tsv-discord-bot    # 실시간 로그
pm2 restart tsv-discord-bot # 재시작
pm2 stop tsv-discord-bot    # 중지
pm2 delete tsv-discord-bot  # 삭제
pm2 monit                   # CPU·메모리 monitor
5-2 Python 봇 — systemd 🔗

systemd service 파일

💻 /etc/systemd/system/tsv-tg-bot.service 생성
# /etc/systemd/system/tsv-tg-bot.service
sudo tee /etc/systemd/system/tsv-tg-bot.service > /dev/null << 'EOF'
[Unit]
Description=TSV Telegram Bot (Python)
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/tsv-tg-bot
Environment="PATH=/home/ubuntu/tsv-tg-bot/.venv/bin"

# 가상환경 Python 사용
ExecStart=/home/ubuntu/tsv-tg-bot/.venv/bin/python /home/ubuntu/tsv-tg-bot/src/bot.py

# 자동 재시작
Restart=always
RestartSec=10

# 보안
NoNewPrivileges=true
PrivateTmp=true

# 메모리 제한 (24GB 중 500MB)
MemoryMax=500M

# 로그
StandardOutput=append:/home/ubuntu/tsv-tg-bot/logs/systemd-out.log
StandardError=append:/home/ubuntu/tsv-tg-bot/logs/systemd-error.log

[Install]
WantedBy=multi-user.target
EOF

# systemd 재로드
sudo systemctl daemon-reload

# 서비스 활성화 + 시작
sudo systemctl enable tsv-tg-bot
sudo systemctl start tsv-tg-bot

# 상태 확인
sudo systemctl status tsv-tg-bot

systemd 운영 명령

💻 systemd 주요 명령
sudo systemctl status tsv-tg-bot   # 상태
sudo systemctl restart tsv-tg-bot  # 재시작
sudo systemctl stop tsv-tg-bot     # 중지
sudo systemctl disable tsv-tg-bot  # 부팅 시 자동 시작 X

# 로그 (실시간)
sudo journalctl -u tsv-tg-bot -f

# 로그 (최근 100줄)
sudo journalctl -u tsv-tg-bot -n 100
5-3 Cloudflare Tunnel — 외부 노출 (선택) 🔗

언제 필요한가

📘 Cloudflare Tunnel 필요 여부

필요한 경우

  • Telegram webhook 사용 시 (polling X → webhook)
  • 봇 외에 web admin 페이지 운영 시
  • 외부 사용자 접근 시 (HTTPS 필요)

필요하지 않은 경우

  • 봇만 운영 (polling으로 충분)
  • Discord·Telegram 봇은 outbound만 — Tunnel 의무 X

설치 + 설정

💻 Cloudflare Tunnel 설치 및 설정
# 1. cloudflared 설치 (ARM64)
curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64.deb
sudo dpkg -i cloudflared.deb
rm cloudflared.deb

# 2. Cloudflare 로그인
cloudflared tunnel login
# 브라우저에서 인증 → 도메인 선택

# 3. Tunnel 생성
cloudflared tunnel create tsv-bot-tunnel
# Tunnel ID 출력 (안전 보관)
# 예: 1234abcd-...

# 4. DNS 라우팅
cloudflared tunnel route dns tsv-bot-tunnel bot.example.com

# 5. 설정 파일
mkdir -p ~/.cloudflared
cat > ~/.cloudflared/config.yml << 'EOF'
tunnel: 1234abcd-...
credentials-file: /home/ubuntu/.cloudflared/1234abcd-....json

ingress:
  - hostname: bot.example.com
    service: http://localhost:3000
  - service: http_status:404
EOF

# 6. 서비스 등록
sudo cloudflared service install
sudo systemctl enable cloudflared
sudo systemctl start cloudflared

# 7. 상태 확인
sudo systemctl status cloudflared

Telegram webhook 전환 (선택)

💻 src/bot.py — Polling → Webhook 전환
# src/bot.py 수정
# Polling → Webhook

import os
from telegram.ext import Application

WEBHOOK_URL = os.environ.get("WEBHOOK_URL")  # https://bot.example.com
PORT = int(os.environ.get("PORT", 3000))

def main():
    token = os.environ["TELEGRAM_TOKEN"]
    app = Application.builder().token(token).build()

    app.add_handler(CommandHandler("ask", ask_command))

    if WEBHOOK_URL:
        # Webhook 모드 (외부 노출)
        app.run_webhook(
            listen="0.0.0.0",
            port=PORT,
            url_path=token,
            webhook_url=f"{WEBHOOK_URL}/{token}",
        )
    else:
        # Polling 모드 (기본)
        app.run_polling()
5-4 운영 종합 🔗

모니터링 명령

💻 전체 모니터링
# === Node.js 봇 ===
pm2 status
pm2 logs tsv-discord-bot --lines 50

# === Python 봇 ===
sudo systemctl status tsv-tg-bot
sudo journalctl -u tsv-tg-bot -n 50

# === 시스템 ===
htop                    # CPU·메모리
df -h                   # 디스크
free -h                 # RAM
sudo ufw status         # 방화벽
sudo fail2ban-client status  # 차단 IP

# === Cloudflare Tunnel (사용 시) ===
sudo systemctl status cloudflared

자동 재시작 검증

💻 강제 종료 후 자동 재시작 확인
# Node.js (pm2)
pm2 stop tsv-discord-bot
sleep 5
pm2 status  # 자동으로 'stopped' 상태 (수동 stop이라 재시작 X)

# 강제 kill 시 자동 복구
pm2 start tsv-discord-bot
ps aux | grep node
sudo kill -9 <pid>
sleep 10
pm2 status  # 자동 'online' 복구 ✅

# Python (systemd) — 동일 검증
sudo systemctl start tsv-tg-bot
ps aux | grep python
sudo kill -9 <pid>
sleep 15
sudo systemctl status tsv-tg-bot  # 자동 'active (running)' ✅

로그 회전 설정

💻 로그 회전 설정
# pm2 로그 회전
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7
pm2 set pm2-logrotate:compress true

# Python 봇 로그 (logrotate)
sudo tee /etc/logrotate.d/tsv-tg-bot > /dev/null << 'EOF'
/home/ubuntu/tsv-tg-bot/logs/*.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    su ubuntu ubuntu
}
EOF

# 강제 회전 테스트
sudo logrotate -f /etc/logrotate.d/tsv-tg-bot
5-5 ssh 종료 검증 — 봇 안 죽는 자리 🔗
💻 최종 검증 — ssh 끊어도 봇이 살아있는지 확인
# 모든 setup 완료 후 검증

# 1. 두 봇 작동 확인
pm2 status                          # tsv-discord-bot: online
sudo systemctl status tsv-tg-bot    # active (running)

# 2. ssh 종료
exit  # 또는 ctrl-D

# 3. 다시 ssh 접속
ssh ubuntu@your-ip

# 4. 봇 여전히 작동 확인
pm2 status                          # ★ 여전히 online ✅
sudo systemctl status tsv-tg-bot    # ★ 여전히 active ✅

# 5. Discord / Telegram 에서 봇 응답 테스트
# !ask 안녕 / /ask 안녕
🎉 운영 상태 확인
  • Node.js 디스코드 봇: pm2 (자동 재시작 + 로그 회전)
  • Python Telegram 봇: systemd (자동 재시작 + 메모리 보호)
  • (선택) Cloudflare Tunnel: 외부 노출 + HTTPS 무료
  • 서버 재부팅 → 두 봇 자동 시작
  • 강제 kill → 5~10초 안에 자동 복구
  • ssh 종료 → 봇 영향 X
5-6 함정 + 트러블슈팅 🔗
⚠️ 주요 함정 5가지

[함정 1] pm2 startup 후 sudo 명령 실행 X

  • 증상: 서버 재부팅 시 봇 자동 시작 X
  • 해결: pm2 startup 출력 명령 그대로 sudo 실행 + pm2 save

[함정 2] systemd User 잘못 지정

  • 증상: Permission denied 에러
  • 해결: User=ubuntu (또는 본인 사용자) 정확 명시 + 권한 확인

[함정 3] 가상환경 path 오류

  • 증상: ModuleNotFoundError
  • 해결: ExecStart 절대 경로 + Environment PATH 명시

[함정 4] Cloudflare Tunnel ARM64 패키지

  • 증상: x86_64 .deb 설치 실패
  • 해결: cloudflared-linux-arm64.deb 명시 다운로드

[함정 5] 메모리 누수

  • 증상: 시간 지나면 메모리 폭증 + OOM kill
  • 해결: max_memory_restart (pm2) 또는 MemoryMax (systemd) 설정 → 자동 재시작으로 복구
📌 6편 정리
  • 1️⃣ pm2: Node.js 봇(디스코드) 자동 재시작 + 로그 회전 + 부팅 시 자동 시작
  • 2️⃣ systemd: Python 봇(Telegram) 자동 재시작 + 메모리 보호 + 부팅 시 자동 시작
  • 3️⃣ Cloudflare Tunnel: webhook 모드 또는 web admin 필요 시 선택 적용
  • 4️⃣ 자동 재시작 검증: 강제 kill → 5~10초 안에 자동 복구 ✅
  • 5️⃣ 로그 회전: pm2-logrotate 10M/7일 + logrotate 일별/7일
  • 6️⃣ 메모리 보호: pm2 max_memory_restart 500M + systemd MemoryMax=500M
  • 7️⃣ ssh 종료 검증: exit 후 재접속 → 두 봇 여전히 작동 ✅
📘 3 도구 분담 정리
  • pm2: Node.js 봇 (디스코드)
  • systemd: Python 봇 (Telegram)
  • Cloudflare Tunnel: 외부 노출 (선택)

pm2 + systemd + Cloudflare Tunnel = SSH를 끊어도 봇은 안 죽는 자리 ✅

🎉 1탄 v2 메타 원칙 일관

"Day 0 시스템" — 운영자 휴식 동안 시스템 자체 안정 운영 → 봇 적용 완료

다층 보호 (pm2 + systemd + max_memory + logrotate) = E1 다층 패턴 일관

다음 편: 07편 — Position C 5차원 강화 (베팅·픽 자동 검출 + /about·/terms·/privacy)

📘
별책부록 도우미
질문하기 OK
안녕하세요! systemd + pm2 + Cloudflare Tunnel에 대해 무엇이든 물어보세요. 본문에서 찾아 답변해드릴게요. 👇