feat: [academy] PM2 프로세스 관리 가이드 페이지 추가
This commit is contained in:
888
resources/views/academy/pm2-guide.blade.php
Normal file
888
resources/views/academy/pm2-guide.blade.php
Normal file
@@ -0,0 +1,888 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'PM2 프로세스 관리')
|
||||
|
||||
@push('styles')
|
||||
<style>
|
||||
.academy-img-hover {
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
.academy-img-hover:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.help-balloon-trigger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
border: 1.5px solid #93c5fd;
|
||||
vertical-align: middle;
|
||||
margin-left: 4px;
|
||||
transition: all 0.15s ease;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
}
|
||||
.help-balloon-trigger:hover {
|
||||
background: #1d4ed8;
|
||||
color: white;
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
.help-balloon {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: calc(100% + 10px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%) scale(0.95);
|
||||
background: #1e293b;
|
||||
color: #f1f5f9;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 1.6;
|
||||
padding: 10px 14px;
|
||||
border-radius: 10px;
|
||||
max-width: 300px;
|
||||
min-width: 200px;
|
||||
width: max-content;
|
||||
white-space: normal;
|
||||
word-break: keep-all;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
|
||||
z-index: 40;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease, transform 0.15s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
.help-balloon::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border: 7px solid transparent;
|
||||
border-top-color: #1e293b;
|
||||
}
|
||||
.help-balloon-trigger.is-active .help-balloon {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) scale(1);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
background: #1e1e2e;
|
||||
color: #a6e3a1;
|
||||
border-radius: 0.75rem;
|
||||
padding: 1rem 1.25rem;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.7;
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
}
|
||||
.code-block .code-comment { color: #6c7086; }
|
||||
.code-block .code-keyword { color: #cba6f7; }
|
||||
.code-block .code-string { color: #a6e3a1; }
|
||||
.code-block .code-flag { color: #89b4fa; }
|
||||
.code-block .code-path { color: #f9e2af; }
|
||||
.code-block .code-output { color: #9399b2; }
|
||||
.code-block .code-danger { color: #f38ba8; }
|
||||
.code-block .code-prompt { color: #89dceb; }
|
||||
|
||||
.cmd-card {
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.cmd-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-6xl mx-auto">
|
||||
|
||||
{{-- ============================================================ --}}
|
||||
{{-- 히어로 배너 --}}
|
||||
{{-- ============================================================ --}}
|
||||
<div class="rounded-2xl overflow-hidden mb-8 shadow-lg" style="background: linear-gradient(135deg, #1a2a3a 0%, #0f172a 100%);">
|
||||
<div class="flex items-center" style="flex-wrap: wrap;">
|
||||
<div style="flex: 1 1 300px; padding: 2rem 2.5rem;">
|
||||
<div class="flex items-center gap-2 text-sm mb-2" style="color: #93c5fd;">
|
||||
<span>아카데미</span>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /></svg>
|
||||
<span style="color: #bfdbfe;">PM2 프로세스 관리</span>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold mb-2" style="color: #ffffff;">PM2 프로세스 관리</h1>
|
||||
<p class="text-sm" style="color: #cbd5e1;">Node.js 앱을 24시간 안정적으로 유지하는 프로세스 매니저 — 비개발자를 위한 실전 가이드</p>
|
||||
</div>
|
||||
<div class="shrink-0" style="width: 240px; padding: 1.5rem;">
|
||||
<div class="text-center" style="font-size: 100px; line-height: 1;">
|
||||
<span style="filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3));">⚙️</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-8">
|
||||
{{-- ============================================================ --}}
|
||||
{{-- 좌측 고정 목차 (TOC) --}}
|
||||
{{-- ============================================================ --}}
|
||||
<nav class="hidden lg:block shrink-0" style="width: 220px;">
|
||||
<div class="sticky top-24">
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-xl p-5">
|
||||
<h2 class="font-semibold text-blue-800 mb-3 flex items-center gap-2 text-sm">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" /></svg>
|
||||
목차
|
||||
</h2>
|
||||
<div class="space-y-0.5 text-xs">
|
||||
<a href="#what-is-pm2" class="block text-blue-700 hover:text-blue-900 py-1 font-medium">1. PM2란?</a>
|
||||
<a href="#pm2-analogy" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">비유로 이해하기</a>
|
||||
<a href="#pm2-3-problems" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">3가지 해결 포인트</a>
|
||||
<a href="#pm2-sam-role" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">SAM에서의 역할</a>
|
||||
|
||||
<a href="#sam-pm2-structure" class="block text-blue-700 hover:text-blue-900 py-1 font-medium mt-2">2. SAM에서 PM2 사용 구조</a>
|
||||
<a href="#structure-diagram" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">구조 다이어그램</a>
|
||||
<a href="#structure-note" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">사용 환경</a>
|
||||
|
||||
<a href="#pm2-commands" class="block text-blue-700 hover:text-blue-900 py-1 font-medium mt-2">3. 자주 쓰는 PM2 명령어</a>
|
||||
<a href="#cmd-list" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">pm2 list</a>
|
||||
<a href="#cmd-start-restart" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">start / restart</a>
|
||||
<a href="#cmd-stop-delete" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">stop / delete</a>
|
||||
<a href="#cmd-logs-monit" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">logs / monit</a>
|
||||
<a href="#cmd-save-startup" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">save / startup</a>
|
||||
|
||||
<a href="#sam-deploy" class="block text-blue-700 hover:text-blue-900 py-1 font-medium mt-2">4. SAM 배포와 PM2</a>
|
||||
<a href="#deploy-flow" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">배포 흐름</a>
|
||||
<a href="#deploy-why-local" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">로컬 빌드 이유</a>
|
||||
|
||||
<a href="#troubleshooting" class="block text-blue-700 hover:text-blue-900 py-1 font-medium mt-2">5. 트러블슈팅</a>
|
||||
<a href="#trouble-not-found" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">앱을 못 찾을 때</a>
|
||||
<a href="#trouble-errored" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">errored 상태</a>
|
||||
<a href="#trouble-reboot" class="block text-blue-600 hover:text-blue-800 py-0.5 pl-3">재부팅 후 복구</a>
|
||||
|
||||
<a href="#comparison" class="block text-blue-700 hover:text-blue-900 py-1 font-medium mt-2">6. PM2 vs 다른 방법 비교</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{{-- ============================================================ --}}
|
||||
{{-- 우측 콘텐츠 --}}
|
||||
{{-- ============================================================ --}}
|
||||
<div class="flex-1 min-w-0 space-y-10">
|
||||
|
||||
{{-- 모바일 목차 --}}
|
||||
<div class="lg:hidden bg-blue-50 border border-blue-200 rounded-xl p-4 mb-6">
|
||||
<details>
|
||||
<summary class="font-semibold text-blue-800 text-sm cursor-pointer flex items-center gap-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" /></svg>
|
||||
목차 보기
|
||||
</summary>
|
||||
<nav class="mt-3 space-y-1 text-sm">
|
||||
<a href="#what-is-pm2" class="block text-blue-700 hover:text-blue-900 py-1">1. PM2란? — 왜 PM2를 쓰는가</a>
|
||||
<a href="#sam-pm2-structure" class="block text-blue-700 hover:text-blue-900 py-1">2. SAM에서 PM2 사용 구조</a>
|
||||
<a href="#pm2-commands" class="block text-blue-700 hover:text-blue-900 py-1">3. 자주 쓰는 PM2 명령어</a>
|
||||
<a href="#sam-deploy" class="block text-blue-700 hover:text-blue-900 py-1">4. SAM 배포와 PM2</a>
|
||||
<a href="#troubleshooting" class="block text-blue-700 hover:text-blue-900 py-1">5. 트러블슈팅</a>
|
||||
<a href="#comparison" class="block text-blue-700 hover:text-blue-900 py-1">6. PM2 vs 다른 방법 비교</a>
|
||||
</nav>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
{{-- ============================================================ --}}
|
||||
{{-- 1. PM2란? --}}
|
||||
{{-- ============================================================ --}}
|
||||
<section id="what-is-pm2" class="scroll-mt-20">
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
||||
<span class="w-8 h-8 bg-blue-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">1</span>
|
||||
PM2란? — 왜 PM2를 쓰는가
|
||||
</h2>
|
||||
|
||||
<div id="pm2-analogy" class="scroll-mt-20 mb-8">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
||||
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
|
||||
PM2 = Node.js 프로세스 매니저
|
||||
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon"><strong>프로세스(Process)</strong>란 현재 실행 중인 프로그램을 뜻한다. 크롬 브라우저를 열면 크롬 프로세스가 생기는 것처럼, Node.js 앱을 실행하면 Node.js 프로세스가 생긴다.</span></span>
|
||||
</h3>
|
||||
<div class="bg-blue-50 rounded-lg p-4 border border-blue-100 mb-4">
|
||||
<p class="font-semibold text-blue-800 mb-2">비유: "프로그램이 꺼지면 자동으로 다시 켜주는 비서"</p>
|
||||
<p class="text-xs text-blue-900 leading-relaxed">
|
||||
회사에서 중요한 기계가 돌아가고 있다고 상상해보자. 이 기계가 갑자기 멈추면 누군가 와서 다시 켜줘야 한다.
|
||||
PM2는 이 <strong>"누군가"</strong>의 역할을 자동으로 해주는 프로그램이다.
|
||||
기계(Node.js 앱)가 멈추면 자동으로 다시 켜주고, 기계의 상태를 계속 감시해준다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
|
||||
<p class="font-semibold text-amber-800 mb-2">PM2 없이 실행하면 어떤 문제가?</p>
|
||||
<p class="text-xs text-amber-900 leading-relaxed mb-2">
|
||||
PM2 없이 <code class="bg-amber-100 px-1.5 py-0.5 rounded text-xs">npm start</code>로 직접 실행하면 다음 문제가 발생한다:
|
||||
</p>
|
||||
<ul class="text-xs text-amber-800 space-y-1">
|
||||
<li>1. <strong>터미널을 닫으면 앱이 꺼진다</strong> — SSH 접속을 끊으면 실행 중이던 앱도 함께 종료된다.</li>
|
||||
<li>2. <strong>에러가 나면 앱이 중단된다</strong> — 예상치 못한 오류가 발생하면 앱이 멈추고, 누군가 수동으로 다시 켜야 한다.</li>
|
||||
<li>3. <strong>상태 확인이 어렵다</strong> — 앱이 잘 돌고 있는지, CPU/메모리를 얼마나 쓰는지 알기 어렵다.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="pm2-3-problems" class="scroll-mt-20 mb-8">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
||||
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
|
||||
PM2가 해결하는 3가지 문제
|
||||
</h3>
|
||||
<div class="grid gap-3" style="grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));">
|
||||
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="w-6 h-6 bg-green-500 text-white rounded-full flex items-center justify-center text-xs font-bold">1</span>
|
||||
<p class="font-bold text-green-800 text-sm">자동 재시작</p>
|
||||
</div>
|
||||
<p class="text-xs text-green-700">앱이 에러로 멈추면(crash) PM2가 <strong>자동으로 다시 시작</strong>해준다. 새벽 3시에 에러가 나도 자동 복구된다.</p>
|
||||
</div>
|
||||
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="w-6 h-6 bg-green-500 text-white rounded-full flex items-center justify-center text-xs font-bold">2</span>
|
||||
<p class="font-bold text-green-800 text-sm">백그라운드 실행</p>
|
||||
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon"><strong>백그라운드(Background)</strong>란 화면에 보이지 않지만 뒤에서 계속 실행되는 것을 뜻한다. 스마트폰에서 음악 앱을 끄지 않고 다른 앱을 사용해도 음악이 계속 나오는 것과 같다.</span></span>
|
||||
</div>
|
||||
<p class="text-xs text-green-700">터미널(SSH)을 닫아도 앱이 <strong>꺼지지 않고 계속 실행</strong>된다. 서버에 접속하지 않아도 24시간 유지된다.</p>
|
||||
</div>
|
||||
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="w-6 h-6 bg-green-500 text-white rounded-full flex items-center justify-center text-xs font-bold">3</span>
|
||||
<p class="font-bold text-green-800 text-sm">모니터링</p>
|
||||
</div>
|
||||
<p class="text-xs text-green-700">앱의 <strong>상태, CPU 사용량, 메모리 사용량, 로그</strong>를 한눈에 확인할 수 있다. 문제가 생기면 빠르게 원인을 파악할 수 있다.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="pm2-sam-role" class="scroll-mt-20">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
||||
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
|
||||
SAM에서의 역할
|
||||
</h3>
|
||||
<div class="bg-indigo-50 rounded-lg p-4 border border-indigo-100">
|
||||
<p class="text-xs text-indigo-900 leading-relaxed">
|
||||
SAM 프로젝트에서 PM2는 <strong>SAM React (Next.js) 프론트엔드</strong>를 운영 서버에서 24시간 유지하는 역할을 한다.
|
||||
사용자가 SAM 웹사이트에 접속하면, PM2가 관리하는 Next.js 앱이 화면을 보여주는 것이다.
|
||||
PM2가 없으면 서버 관리자가 매번 수동으로 앱을 켜고 감시해야 하지만, PM2 덕분에 이 과정이 <strong>완전히 자동화</strong>된다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{-- ============================================================ --}}
|
||||
{{-- 2. SAM에서 PM2 사용 구조 --}}
|
||||
{{-- ============================================================ --}}
|
||||
<section id="sam-pm2-structure" class="scroll-mt-20">
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
||||
<span class="w-8 h-8 bg-blue-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">2</span>
|
||||
SAM에서 PM2 사용 구조
|
||||
</h2>
|
||||
|
||||
<div id="structure-diagram" class="scroll-mt-20 mb-8">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
||||
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
|
||||
구조 다이어그램
|
||||
</h3>
|
||||
<p class="text-xs text-gray-600 mb-3">
|
||||
운영 서버(114.203.209.83)에서 사용자의 요청이 처리되는 과정이다.
|
||||
PM2는 Node.js(Next.js) 앱을 관리하며, Nginx가 사용자의 요청을 PM2가 관리하는 앱으로 전달한다.
|
||||
</p>
|
||||
<div class="code-block mb-4"><span class="code-comment"> 사용자 브라우저 (크롬, 사파리 등)</span>
|
||||
<span class="code-comment"> │</span>
|
||||
<span class="code-comment"> ▼</span>
|
||||
<span class="code-keyword"> ┌─────────────────────────────────────┐</span>
|
||||
<span class="code-keyword"> │</span> <span class="code-string">Nginx</span> (웹서버, 포트 <span class="code-flag">80</span>/<span class="code-flag">443</span>) <span class="code-keyword">│</span>
|
||||
<span class="code-keyword"> │</span> - 사용자 요청을 받아서 전달하는 역할 <span class="code-keyword">│</span>
|
||||
<span class="code-keyword"> └────────────────┬────────────────────┘</span>
|
||||
<span class="code-comment"> │ 프록시 (요청 전달)</span>
|
||||
<span class="code-comment"> ▼</span>
|
||||
<span class="code-keyword"> ┌─────────────────────────────────────┐</span>
|
||||
<span class="code-keyword"> │</span> <span class="code-danger">PM2</span> → <span class="code-string">Node.js</span> (Next.js, 포트 <span class="code-flag">3000</span>) <span class="code-keyword">│</span>
|
||||
<span class="code-keyword"> │</span> - SAM 프론트엔드 화면 생성 <span class="code-keyword">│</span>
|
||||
<span class="code-keyword"> │</span> - PM2가 이 앱을 감시/관리 <span class="code-keyword">│</span>
|
||||
<span class="code-keyword"> └────────────────┬────────────────────┘</span>
|
||||
<span class="code-comment"> │ API 호출</span>
|
||||
<span class="code-comment"> ▼</span>
|
||||
<span class="code-keyword"> ┌─────────────────────────────────────┐</span>
|
||||
<span class="code-keyword"> │</span> <span class="code-string">API 서버</span> (Laravel, PHP) <span class="code-keyword">│</span>
|
||||
<span class="code-keyword"> │</span> - 데이터 처리 & DB 접근 <span class="code-keyword">│</span>
|
||||
<span class="code-keyword"> └─────────────────────────────────────┘</span></div>
|
||||
|
||||
<div class="bg-blue-50 rounded-lg p-3 border border-blue-100 text-xs text-blue-800">
|
||||
<strong>요약:</strong> 사용자가 SAM에 접속하면 Nginx가 요청을 받고,
|
||||
PM2가 관리하는 Next.js 앱이 화면을 생성하고, 필요한 데이터는 API 서버에서 가져온다.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="structure-note" class="scroll-mt-20">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
||||
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
|
||||
PM2 사용 환경
|
||||
</h3>
|
||||
<div class="grid gap-3" style="grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));">
|
||||
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
|
||||
<p class="font-bold text-green-800 mb-2">운영 서버 (114.203.209.83)</p>
|
||||
<p class="text-xs text-green-700">PM2를 <strong>사용한다</strong>. 서버에 Node.js와 PM2가 직접 설치되어 있으며, <code class="bg-green-100 px-1 rounded">sam-react</code>라는 이름으로 Next.js 앱을 관리한다.</p>
|
||||
</div>
|
||||
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200">
|
||||
<p class="font-bold text-gray-800 mb-2">로컬 Docker 환경 (내 PC)</p>
|
||||
<p class="text-xs text-gray-600">PM2를 <strong>사용하지 않는다</strong>. 로컬에서는 Docker 컨테이너가 React 앱을 실행하므로 PM2가 필요 없다. <code class="bg-gray-100 px-1 rounded">npm run dev</code>로 개발 서버를 실행한다.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{-- ============================================================ --}}
|
||||
{{-- 3. 자주 쓰는 PM2 명령어 --}}
|
||||
{{-- ============================================================ --}}
|
||||
<section id="pm2-commands" class="scroll-mt-20">
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
||||
<span class="w-8 h-8 bg-blue-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">3</span>
|
||||
자주 쓰는 PM2 명령어
|
||||
</h2>
|
||||
|
||||
<div class="bg-amber-50 rounded-lg p-3 border border-amber-100 text-xs text-amber-800 mb-6">
|
||||
<strong>참고:</strong> 아래 명령어들은 모두 <strong>운영 서버</strong>에 SSH로 접속한 후 실행한다. 로컬(내 PC)에서는 사용하지 않는다.
|
||||
</div>
|
||||
|
||||
{{-- pm2 list --}}
|
||||
<div id="cmd-list" class="scroll-mt-20 mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
||||
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
|
||||
pm2 list — 실행 중인 앱 목록 확인
|
||||
</h3>
|
||||
<div class="border border-gray-200 rounded-lg p-4 cmd-card mb-3">
|
||||
<p class="text-xs text-gray-600 mb-3">현재 PM2가 관리하고 있는 모든 앱의 상태를 표 형태로 보여준다. 앱 이름, 상태(online/errored/stopped), CPU 사용량, 메모리 사용량을 한눈에 확인할 수 있다.</p>
|
||||
<div class="code-block"><span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">list</span>
|
||||
<span class="code-output">┌────┬──────────────┬─────────────┬─────────┬──────────┬───────────┐</span>
|
||||
<span class="code-output">│ id │ name │ mode │ status │ cpu │ memory │</span>
|
||||
<span class="code-output">├────┼──────────────┼─────────────┼─────────┼──────────┼───────────┤</span>
|
||||
<span class="code-output">│ 0 │ sam-react │ fork │ online │ 0.1% │ 120.5 MB │</span>
|
||||
<span class="code-output">└────┴──────────────┴─────────────┴─────────┴──────────┴───────────┘</span></div>
|
||||
</div>
|
||||
<div class="bg-blue-50 rounded-lg p-3 border border-blue-100 text-xs text-blue-800">
|
||||
<strong>상태 의미:</strong>
|
||||
<code class="bg-blue-100 px-1 rounded">online</code> = 정상 실행 중,
|
||||
<code class="bg-red-100 px-1 rounded text-red-700">errored</code> = 에러 발생,
|
||||
<code class="bg-gray-100 px-1 rounded text-gray-700">stopped</code> = 수동으로 중지됨
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- pm2 start / restart --}}
|
||||
<div id="cmd-start-restart" class="scroll-mt-20 mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
||||
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
|
||||
pm2 start / pm2 restart — 앱 시작 & 재시작
|
||||
</h3>
|
||||
<div class="border border-gray-200 rounded-lg p-4 cmd-card mb-3">
|
||||
<p class="text-xs text-gray-600 mb-3"><strong>start</strong>는 새로운 앱을 PM2에 등록하고 실행한다. <strong>restart</strong>는 이미 등록된 앱을 껐다가 다시 켠다.</p>
|
||||
<div class="code-block"><span class="code-comment"># 새 앱 시작 (PM2에 처음 등록할 때)</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">start</span> <span class="code-path">npm</span> <span class="code-flag">--name</span> <span class="code-string">sam-react</span> <span class="code-flag">--</span> <span class="code-string">start</span>
|
||||
<span class="code-comment"># 해석: npm start 명령을 "sam-react"라는 이름으로 PM2에 등록하여 실행</span>
|
||||
|
||||
<span class="code-comment"># 이미 등록된 앱 재시작 (배포 후 주로 사용)</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">restart</span> <span class="code-path">sam-react</span>
|
||||
<span class="code-comment"># 해석: sam-react 앱을 껐다가 다시 켠다 (코드 변경 반영)</span></div>
|
||||
</div>
|
||||
<div class="bg-green-50 rounded-lg p-3 border border-green-100 text-xs text-green-800">
|
||||
<strong>실무 팁:</strong> 배포 시에는 보통 <code class="bg-green-100 px-1 rounded">pm2 restart sam-react</code>만 사용한다. <code>pm2 start</code>는 최초 1회만 실행하면 된다.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- pm2 stop / delete --}}
|
||||
<div id="cmd-stop-delete" class="scroll-mt-20 mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
||||
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
|
||||
pm2 stop / pm2 delete — 앱 중지 & 삭제
|
||||
</h3>
|
||||
<div class="border border-gray-200 rounded-lg p-4 cmd-card mb-3">
|
||||
<p class="text-xs text-gray-600 mb-3"><strong>stop</strong>은 앱 실행을 중지하지만 PM2 목록에는 남아있다. <strong>delete</strong>는 PM2 목록에서 완전히 제거한다.</p>
|
||||
<div class="code-block"><span class="code-comment"># 앱 중지 (목록에는 남아있음, 나중에 pm2 start로 다시 실행 가능)</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">stop</span> <span class="code-path">sam-react</span>
|
||||
|
||||
<span class="code-comment"># 앱 삭제 (PM2 목록에서 완전히 제거)</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">delete</span> <span class="code-path">sam-react</span>
|
||||
<span class="code-comment"># 삭제 후 다시 사용하려면 pm2 start로 새로 등록해야 한다</span></div>
|
||||
</div>
|
||||
<div class="bg-red-50 rounded-lg p-3 border border-red-200 text-xs text-red-800">
|
||||
<strong>주의:</strong> <code class="bg-red-100 px-1 rounded">pm2 stop</code>을 실행하면 <strong>SAM 프론트엔드가 접속 불가</strong>해진다. 유지보수 목적 외에는 실행하지 않는다.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- pm2 logs / monit --}}
|
||||
<div id="cmd-logs-monit" class="scroll-mt-20 mb-6">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
||||
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
|
||||
pm2 logs / pm2 monit — 로그 & 모니터링
|
||||
</h3>
|
||||
<div class="border border-gray-200 rounded-lg p-4 cmd-card mb-3">
|
||||
<p class="text-xs text-gray-600 mb-3"><strong>logs</strong>는 앱의 실시간 출력(로그)을 보여준다. 에러 원인 파악에 필수적이다. <strong>monit</strong>은 CPU, 메모리, 로그를 한 화면에서 실시간으로 볼 수 있는 대시보드이다.</p>
|
||||
<div class="code-block"><span class="code-comment"># 실시간 로그 보기 (Ctrl+C로 종료)</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">logs</span> <span class="code-path">sam-react</span>
|
||||
<span class="code-output">0|sam-react | ▲ Next.js 14.x</span>
|
||||
<span class="code-output">0|sam-react | - Local: http://localhost:3000</span>
|
||||
<span class="code-output">0|sam-react | ✓ Ready in 2.3s</span>
|
||||
|
||||
<span class="code-comment"># 최근 100줄만 보기</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">logs</span> <span class="code-path">sam-react</span> <span class="code-flag">--lines 100</span>
|
||||
|
||||
<span class="code-comment"># 실시간 모니터링 대시보드 (Ctrl+C로 종료)</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">monit</span>
|
||||
<span class="code-comment"># CPU, 메모리, 로그를 한 화면에서 실시간 확인</span></div>
|
||||
</div>
|
||||
<div class="bg-green-50 rounded-lg p-3 border border-green-100 text-xs text-green-800">
|
||||
<strong>실무 팁:</strong> 앱에 문제가 생기면 가장 먼저 <code class="bg-green-100 px-1 rounded">pm2 logs sam-react</code>를 실행하여 에러 메시지를 확인한다.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- pm2 save / startup --}}
|
||||
<div id="cmd-save-startup" class="scroll-mt-20">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
||||
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
|
||||
pm2 save / pm2 startup — 서버 재부팅 대비
|
||||
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon"><strong>pm2 save</strong>는 현재 PM2가 관리하는 앱 목록을 파일로 저장한다. <strong>pm2 startup</strong>은 서버가 부팅될 때 PM2를 자동으로 시작하도록 시스템에 등록한다. 이 두 명령을 함께 사용하면 서버 재부팅 후에도 앱이 자동으로 살아난다.</span></span>
|
||||
</h3>
|
||||
<div class="border border-gray-200 rounded-lg p-4 cmd-card mb-3">
|
||||
<p class="text-xs text-gray-600 mb-3">서버가 재부팅되면 PM2도 함께 꺼진다. 재부팅 후 앱이 자동으로 살아나게 하려면 아래 두 명령을 실행해야 한다.</p>
|
||||
<div class="code-block"><span class="code-comment"># 현재 프로세스 목록 저장 (재부팅 후 자동 복구용)</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">save</span>
|
||||
<span class="code-output">[PM2] Saving current process list...</span>
|
||||
<span class="code-output">[PM2] Successfully saved in /home/pro/.pm2/dump.pm2</span>
|
||||
|
||||
<span class="code-comment"># 부팅 시 PM2 자동 시작 설정 (최초 1회만 실행)</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">startup</span>
|
||||
<span class="code-comment"># 시스템 서비스로 등록 — 서버 켜질 때 PM2가 자동 실행된다</span></div>
|
||||
</div>
|
||||
<div class="bg-amber-50 rounded-lg p-3 border border-amber-100 text-xs text-amber-800">
|
||||
<strong>중요:</strong> PM2에 새 앱을 추가하거나 삭제한 후에는 반드시 <code class="bg-amber-100 px-1 rounded">pm2 save</code>를 실행해야 한다. 그래야 서버 재부팅 후에도 변경된 목록이 유지된다.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 명령어 요약 표 --}}
|
||||
<div class="mt-6 bg-gray-50 rounded-lg p-4 border border-gray-200">
|
||||
<p class="font-bold text-gray-800 mb-3 text-sm">PM2 명령어 요약표</p>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-xs border-collapse">
|
||||
<thead>
|
||||
<tr class="bg-gray-100">
|
||||
<th class="border border-gray-200 px-3 py-2 text-left">명령어</th>
|
||||
<th class="border border-gray-200 px-3 py-2 text-left">설명</th>
|
||||
<th class="border border-gray-200 px-3 py-2 text-left">사용 예시</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">pm2 list</td>
|
||||
<td class="border border-gray-200 px-3 py-2">실행 중인 앱 목록 확인</td>
|
||||
<td class="border border-gray-200 px-3 py-2">앱 이름, 상태, CPU/메모리 표시</td>
|
||||
</tr>
|
||||
<tr class="bg-gray-50">
|
||||
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">pm2 start</td>
|
||||
<td class="border border-gray-200 px-3 py-2">앱 시작</td>
|
||||
<td class="border border-gray-200 px-3 py-2 font-mono text-xs">pm2 start npm --name sam-react -- start</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">pm2 restart</td>
|
||||
<td class="border border-gray-200 px-3 py-2">앱 재시작</td>
|
||||
<td class="border border-gray-200 px-3 py-2 font-mono text-xs">pm2 restart sam-react</td>
|
||||
</tr>
|
||||
<tr class="bg-gray-50">
|
||||
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">pm2 stop</td>
|
||||
<td class="border border-gray-200 px-3 py-2">앱 중지</td>
|
||||
<td class="border border-gray-200 px-3 py-2 font-mono text-xs">pm2 stop sam-react</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">pm2 delete</td>
|
||||
<td class="border border-gray-200 px-3 py-2">앱 삭제 (목록에서 제거)</td>
|
||||
<td class="border border-gray-200 px-3 py-2 font-mono text-xs">pm2 delete sam-react</td>
|
||||
</tr>
|
||||
<tr class="bg-gray-50">
|
||||
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">pm2 logs</td>
|
||||
<td class="border border-gray-200 px-3 py-2">실시간 로그 보기</td>
|
||||
<td class="border border-gray-200 px-3 py-2 font-mono text-xs">pm2 logs sam-react</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">pm2 monit</td>
|
||||
<td class="border border-gray-200 px-3 py-2">실시간 모니터링 대시보드</td>
|
||||
<td class="border border-gray-200 px-3 py-2">CPU, 메모리, 로그 실시간 확인</td>
|
||||
</tr>
|
||||
<tr class="bg-gray-50">
|
||||
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">pm2 save</td>
|
||||
<td class="border border-gray-200 px-3 py-2">현재 프로세스 목록 저장</td>
|
||||
<td class="border border-gray-200 px-3 py-2">서버 재부팅 후 자동 복구용</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">pm2 startup</td>
|
||||
<td class="border border-gray-200 px-3 py-2">부팅 시 PM2 자동 시작 설정</td>
|
||||
<td class="border border-gray-200 px-3 py-2">시스템 서비스로 등록</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{-- ============================================================ --}}
|
||||
{{-- 4. SAM 배포와 PM2 --}}
|
||||
{{-- ============================================================ --}}
|
||||
<section id="sam-deploy" class="scroll-mt-20">
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
||||
<span class="w-8 h-8 bg-blue-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">4</span>
|
||||
SAM 배포와 PM2
|
||||
</h2>
|
||||
|
||||
<div id="deploy-flow" class="scroll-mt-20 mb-8">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
||||
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
|
||||
배포 흐름
|
||||
</h3>
|
||||
<p class="text-xs text-gray-600 mb-3">
|
||||
SAM React 코드를 수정한 후, 사용자에게 반영하기까지의 전체 흐름이다.
|
||||
<code class="bg-gray-100 px-1.5 py-0.5 rounded text-xs">deploy.sh</code> 스크립트가 이 과정을 자동으로 처리한다.
|
||||
</p>
|
||||
<div class="code-block mb-4"><span class="code-comment"> 배포 흐름 (deploy.sh가 자동으로 처리)</span>
|
||||
|
||||
<span class="code-keyword"> ① 로컬에서 코드 수정 & git commit</span>
|
||||
<span class="code-comment"> │</span>
|
||||
<span class="code-comment"> ▼</span>
|
||||
<span class="code-keyword"> ② ./deploy.sh 실행</span>
|
||||
<span class="code-comment"> │</span>
|
||||
<span class="code-comment"> ├──▶</span> <span class="code-string">③ git pull</span> <span class="code-comment">(최신 코드 받기)</span>
|
||||
<span class="code-comment"> │</span>
|
||||
<span class="code-comment"> ├──▶</span> <span class="code-string">④ npm run build</span> <span class="code-comment">(로컬에서 빌드)</span>
|
||||
<span class="code-comment"> │</span>
|
||||
<span class="code-comment"> ├──▶</span> <span class="code-string">⑤ .next 폴더 압축 → 서버 업로드</span>
|
||||
<span class="code-comment"> │</span>
|
||||
<span class="code-comment"> ├──▶</span> <span class="code-string">⑥ 서버에서 압축 해제</span>
|
||||
<span class="code-comment"> │</span>
|
||||
<span class="code-comment"> └──▶</span> <span class="code-danger">⑦ pm2 restart sam-react</span> <span class="code-comment">(앱 재시작!)</span>
|
||||
|
||||
<span class="code-comment"> → 사용자가 새 화면을 볼 수 있게 됨</span></div>
|
||||
|
||||
<div class="bg-blue-50 rounded-lg p-3 border border-blue-100 text-xs text-blue-800 mb-4">
|
||||
<strong>핵심:</strong> deploy.sh 스크립트가 마지막 단계에서 자동으로 <code class="bg-blue-100 px-1 rounded">pm2 restart sam-react</code>를 호출한다. 따라서 배포 시 수동으로 PM2 명령을 실행할 필요가 없다.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="deploy-why-local" class="scroll-mt-20">
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
||||
<span class="w-2 h-2 bg-blue-400 rounded-full"></span>
|
||||
서버에서 빌드하지 않는 이유
|
||||
</h3>
|
||||
<div class="bg-red-50 rounded-lg p-4 border border-red-200 mb-4">
|
||||
<p class="font-semibold text-red-800 mb-2">서버 빌드 금지!</p>
|
||||
<p class="text-xs text-red-700 leading-relaxed">
|
||||
SAM 운영 서버의 스펙은 <strong>2코어 CPU, 3.8GB RAM</strong>이다.
|
||||
Next.js 빌드(<code class="bg-red-100 px-1 rounded">npm run build</code>)는 메모리를 많이 사용하는 작업으로,
|
||||
이 스펙에서 실행하면 <strong>20분 이상 소요</strong>되거나 <strong>메모리 부족으로 실패</strong>한다.
|
||||
그래서 빌드는 로컬(내 PC)에서 하고, 빌드된 결과물(.next 폴더)만 서버에 올리는 방식을 사용한다.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid gap-3" style="grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));">
|
||||
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
|
||||
<p class="font-bold text-green-800 mb-2">올바른 방법 (로컬 빌드)</p>
|
||||
<div class="code-block" style="font-size: 11px;"><span class="code-comment"># 로컬(내 PC)에서</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">npm run build</span>
|
||||
<span class="code-comment"># 빌드 결과물을 서버로 전송</span>
|
||||
<span class="code-comment"># PM2 재시작</span></div>
|
||||
</div>
|
||||
<div class="bg-red-50 rounded-lg p-4 border border-red-200">
|
||||
<p class="font-bold text-red-800 mb-2">잘못된 방법 (서버 빌드)</p>
|
||||
<div class="code-block" style="font-size: 11px;"><span class="code-comment"># 서버에서 직접 빌드 — 금지!</span>
|
||||
<span class="code-danger">$ ssh 서버</span>
|
||||
<span class="code-danger">$ npm run build</span>
|
||||
<span class="code-comment"># → 메모리 부족, 서버 먹통 위험!</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{-- ============================================================ --}}
|
||||
{{-- 5. 트러블슈팅 --}}
|
||||
{{-- ============================================================ --}}
|
||||
<section id="troubleshooting" class="scroll-mt-20">
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
||||
<span class="w-8 h-8 bg-blue-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">5</span>
|
||||
트러블슈팅 — "이럴 땐 어떻게?"
|
||||
</h2>
|
||||
|
||||
{{-- 문제 1 --}}
|
||||
<div id="trouble-not-found" class="scroll-mt-20 mb-6">
|
||||
<div class="border border-red-200 rounded-lg p-4 cmd-card">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="w-6 h-6 bg-red-500 text-white rounded-full flex items-center justify-center text-xs font-bold">1</span>
|
||||
<p class="font-bold text-gray-800 text-sm">Process or Namespace sam-react not found</p>
|
||||
</div>
|
||||
<div class="code-block mb-3"><span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">restart</span> <span class="code-path">sam-react</span>
|
||||
<span class="code-danger">[PM2][ERROR] Process or Namespace sam-react not found</span></div>
|
||||
<div class="bg-amber-50 rounded-lg p-3 border border-amber-100 text-xs mb-3">
|
||||
<p class="font-semibold text-amber-800 mb-1">원인:</p>
|
||||
<p class="text-amber-700">PM2에 <code class="bg-amber-100 px-1 rounded">sam-react</code>라는 앱이 등록되어 있지 않다. PM2가 재설치되었거나, <code class="bg-amber-100 px-1 rounded">pm2 delete</code>로 삭제된 경우 발생한다.</p>
|
||||
</div>
|
||||
<div class="bg-green-50 rounded-lg p-3 border border-green-200 text-xs">
|
||||
<p class="font-semibold text-green-800 mb-1">해결:</p>
|
||||
<div class="code-block" style="font-size: 11px;"><span class="code-comment"># Next.js 프로젝트 폴더로 이동</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">cd</span> <span class="code-path">/home/webservice/react</span>
|
||||
|
||||
<span class="code-comment"># PM2에 앱을 새로 등록하고 시작</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">start</span> <span class="code-path">npm</span> <span class="code-flag">--name</span> <span class="code-string">sam-react</span> <span class="code-flag">--</span> <span class="code-string">start</span>
|
||||
|
||||
<span class="code-comment"># 목록 저장 (재부팅 대비)</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">save</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 문제 2 --}}
|
||||
<div id="trouble-errored" class="scroll-mt-20 mb-6">
|
||||
<div class="border border-red-200 rounded-lg p-4 cmd-card">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="w-6 h-6 bg-red-500 text-white rounded-full flex items-center justify-center text-xs font-bold">2</span>
|
||||
<p class="font-bold text-gray-800 text-sm">PM2 앱 상태가 errored</p>
|
||||
</div>
|
||||
<div class="code-block mb-3"><span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">list</span>
|
||||
<span class="code-output">│ 0 │ sam-react │ fork │</span> <span class="code-danger">errored</span> <span class="code-output">│ 0% │ 0 B │</span></div>
|
||||
<div class="bg-amber-50 rounded-lg p-3 border border-amber-100 text-xs mb-3">
|
||||
<p class="font-semibold text-amber-800 mb-1">원인:</p>
|
||||
<p class="text-amber-700">Node.js 앱(Next.js)에 에러가 발생하여 실행이 중단된 상태이다. 코드 오류, 의존성 문제, 환경 변수 누락 등이 원인일 수 있다.</p>
|
||||
</div>
|
||||
<div class="bg-green-50 rounded-lg p-3 border border-green-200 text-xs">
|
||||
<p class="font-semibold text-green-800 mb-1">해결:</p>
|
||||
<div class="code-block" style="font-size: 11px;"><span class="code-comment"># 1. 먼저 에러 로그를 확인한다</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">logs</span> <span class="code-path">sam-react</span> <span class="code-flag">--lines 50</span>
|
||||
|
||||
<span class="code-comment"># 2. 에러 원인을 파악한 후 수정</span>
|
||||
<span class="code-comment"># 3. 앱 재시작</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">restart</span> <span class="code-path">sam-react</span>
|
||||
|
||||
<span class="code-comment"># 4. 상태 확인 (online이면 정상)</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">list</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 문제 3 --}}
|
||||
<div id="trouble-reboot" class="scroll-mt-20 mb-6">
|
||||
<div class="border border-red-200 rounded-lg p-4 cmd-card">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="w-6 h-6 bg-red-500 text-white rounded-full flex items-center justify-center text-xs font-bold">3</span>
|
||||
<p class="font-bold text-gray-800 text-sm">서버 재부팅 후 앱이 안 뜸</p>
|
||||
</div>
|
||||
<div class="code-block mb-3"><span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">list</span>
|
||||
<span class="code-output">┌────┬──────┬──────┬──────┬─────┬────────┐</span>
|
||||
<span class="code-output">│ id │ name │ mode │ ... │ ... │ ... │</span>
|
||||
<span class="code-output">└────┴──────┴──────┴──────┴─────┴────────┘</span>
|
||||
<span class="code-comment">(목록이 비어있음)</span></div>
|
||||
<div class="bg-amber-50 rounded-lg p-3 border border-amber-100 text-xs mb-3">
|
||||
<p class="font-semibold text-amber-800 mb-1">원인:</p>
|
||||
<p class="text-amber-700">이전에 <code class="bg-amber-100 px-1 rounded">pm2 save</code>를 실행하지 않아서, PM2가 재부팅 후 어떤 앱을 실행해야 하는지 모르는 상태이다.</p>
|
||||
</div>
|
||||
<div class="bg-green-50 rounded-lg p-3 border border-green-200 text-xs">
|
||||
<p class="font-semibold text-green-800 mb-1">해결:</p>
|
||||
<div class="code-block" style="font-size: 11px;"><span class="code-comment"># 1. Next.js 프로젝트 폴더로 이동</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">cd</span> <span class="code-path">/home/webservice/react</span>
|
||||
|
||||
<span class="code-comment"># 2. 앱 다시 시작</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">start</span> <span class="code-path">npm</span> <span class="code-flag">--name</span> <span class="code-string">sam-react</span> <span class="code-flag">--</span> <span class="code-string">start</span>
|
||||
|
||||
<span class="code-comment"># 3. 반드시 저장! (다음 재부팅에 대비)</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">save</span>
|
||||
|
||||
<span class="code-comment"># 4. (선택) 부팅 시 PM2 자동 시작 확인</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">startup</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 문제 4 --}}
|
||||
<div id="trouble-port" class="scroll-mt-20">
|
||||
<div class="border border-red-200 rounded-lg p-4 cmd-card">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="w-6 h-6 bg-red-500 text-white rounded-full flex items-center justify-center text-xs font-bold">4</span>
|
||||
<p class="font-bold text-gray-800 text-sm">포트 3000 이미 사용 중</p>
|
||||
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon"><strong>포트(Port)</strong>는 네트워크에서 프로그램을 식별하는 번호이다. 아파트 호수처럼, 한 서버 안에서 여러 프로그램이 각자 다른 포트 번호를 사용한다. Next.js는 기본적으로 포트 3000을 사용한다.</span></span>
|
||||
</div>
|
||||
<div class="code-block mb-3"><span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">start</span> <span class="code-path">npm</span> <span class="code-flag">--name</span> <span class="code-string">sam-react</span> <span class="code-flag">--</span> <span class="code-string">start</span>
|
||||
<span class="code-danger">Error: listen EADDRINUSE: address already in use :::3000</span></div>
|
||||
<div class="bg-amber-50 rounded-lg p-3 border border-amber-100 text-xs mb-3">
|
||||
<p class="font-semibold text-amber-800 mb-1">원인:</p>
|
||||
<p class="text-amber-700">이전에 실행했던 Node.js 프로세스가 아직 포트 3000을 점유하고 있다. PM2에서 제대로 종료되지 않은 잔여 프로세스가 남아있는 경우 발생한다.</p>
|
||||
</div>
|
||||
<div class="bg-green-50 rounded-lg p-3 border border-green-200 text-xs">
|
||||
<p class="font-semibold text-green-800 mb-1">해결:</p>
|
||||
<div class="code-block" style="font-size: 11px;"><span class="code-comment"># 1. 기존 앱을 PM2에서 완전히 제거</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">delete</span> <span class="code-path">sam-react</span>
|
||||
|
||||
<span class="code-comment"># 2. 혹시 남아있는 Node.js 프로세스 확인</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">ss</span> <span class="code-flag">-tulnp</span> | <span class="code-keyword">grep</span> <span class="code-string">:3000</span>
|
||||
|
||||
<span class="code-comment"># 3. 다시 시작</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">cd</span> <span class="code-path">/home/webservice/react</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">start</span> <span class="code-path">npm</span> <span class="code-flag">--name</span> <span class="code-string">sam-react</span> <span class="code-flag">--</span> <span class="code-string">start</span>
|
||||
|
||||
<span class="code-comment"># 4. 목록 저장</span>
|
||||
<span class="code-prompt">$</span> <span class="code-keyword">pm2</span> <span class="code-string">save</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{-- ============================================================ --}}
|
||||
{{-- 6. PM2 vs 다른 방법 비교 --}}
|
||||
{{-- ============================================================ --}}
|
||||
<section id="comparison" class="scroll-mt-20">
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
||||
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
||||
<span class="w-8 h-8 bg-blue-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">6</span>
|
||||
PM2 vs 다른 방법 비교
|
||||
</h2>
|
||||
|
||||
<p class="text-xs text-gray-600 mb-4">
|
||||
Node.js 앱을 실행하는 여러 방법을 비교한 표이다. PM2가 모든 면에서 우수하기 때문에 SAM에서 PM2를 선택했다.
|
||||
</p>
|
||||
|
||||
<div class="overflow-x-auto mb-6">
|
||||
<table class="w-full text-xs border-collapse">
|
||||
<thead>
|
||||
<tr class="bg-gray-100">
|
||||
<th class="border border-gray-200 px-3 py-2 text-left">실행 방법</th>
|
||||
<th class="border border-gray-200 px-3 py-2 text-center">백그라운드 실행</th>
|
||||
<th class="border border-gray-200 px-3 py-2 text-center">자동 재시작</th>
|
||||
<th class="border border-gray-200 px-3 py-2 text-center">모니터링</th>
|
||||
<th class="border border-gray-200 px-3 py-2 text-center">로그 관리</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">npm start</td>
|
||||
<td class="border border-gray-200 px-3 py-2 text-center text-red-500 font-bold">X</td>
|
||||
<td class="border border-gray-200 px-3 py-2 text-center text-red-500 font-bold">X</td>
|
||||
<td class="border border-gray-200 px-3 py-2 text-center text-red-500 font-bold">X</td>
|
||||
<td class="border border-gray-200 px-3 py-2 text-center text-red-500 font-bold">X</td>
|
||||
</tr>
|
||||
<tr class="bg-gray-50">
|
||||
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">nohup npm start &
|
||||
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon"><strong>nohup</strong>은 터미널을 닫아도 프로세스가 종료되지 않게 하는 명령어이다. <strong>&</strong>는 백그라운드 실행을 의미한다.</span></span>
|
||||
</td>
|
||||
<td class="border border-gray-200 px-3 py-2 text-center text-green-500 font-bold">O</td>
|
||||
<td class="border border-gray-200 px-3 py-2 text-center text-red-500 font-bold">X</td>
|
||||
<td class="border border-gray-200 px-3 py-2 text-center text-red-500 font-bold">X</td>
|
||||
<td class="border border-gray-200 px-3 py-2 text-center text-red-500 font-bold">X</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-200 px-3 py-2 font-mono font-bold">screen / tmux
|
||||
<span class="help-balloon-trigger" onclick="toggleBalloon(this)">?<span class="help-balloon"><strong>screen</strong>과 <strong>tmux</strong>는 터미널 세션을 유지해주는 도구이다. 터미널을 닫아도 세션이 유지되지만, 자동 재시작이나 모니터링 기능은 없다.</span></span>
|
||||
</td>
|
||||
<td class="border border-gray-200 px-3 py-2 text-center text-green-500 font-bold">O</td>
|
||||
<td class="border border-gray-200 px-3 py-2 text-center text-red-500 font-bold">X</td>
|
||||
<td class="border border-gray-200 px-3 py-2 text-center text-red-500 font-bold">X</td>
|
||||
<td class="border border-gray-200 px-3 py-2 text-center text-red-500 font-bold">X</td>
|
||||
</tr>
|
||||
<tr class="bg-blue-50">
|
||||
<td class="border border-blue-200 px-3 py-2 font-mono font-bold text-blue-800">PM2</td>
|
||||
<td class="border border-blue-200 px-3 py-2 text-center text-green-600 font-bold">O</td>
|
||||
<td class="border border-blue-200 px-3 py-2 text-center text-green-600 font-bold">O</td>
|
||||
<td class="border border-blue-200 px-3 py-2 text-center text-green-600 font-bold">O</td>
|
||||
<td class="border border-blue-200 px-3 py-2 text-center text-green-600 font-bold">O</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="bg-indigo-50 rounded-lg p-4 border border-indigo-100">
|
||||
<p class="font-semibold text-indigo-800 mb-2">결론</p>
|
||||
<p class="text-xs text-indigo-900 leading-relaxed">
|
||||
<code class="bg-indigo-100 px-1 rounded">npm start</code>는 개발 중에만 사용하는 방법이다.
|
||||
실제 서비스를 운영할 때는 백그라운드 실행, 자동 재시작, 모니터링, 로그 관리를 모두 지원하는
|
||||
<strong>PM2가 최적의 선택</strong>이다.
|
||||
SAM에서도 이러한 이유로 PM2를 사용하여 Next.js 프론트엔드를 안정적으로 운영하고 있다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{{-- Hover Preview --}}
|
||||
<div id="hover-preview">
|
||||
<img id="hover-preview-img" src="" alt="">
|
||||
<div class="hover-caption" id="hover-preview-caption"></div>
|
||||
</div>
|
||||
|
||||
{{-- Lightbox --}}
|
||||
<div id="lightbox" onclick="closeLightbox()">
|
||||
<img id="lightbox-img" src="" alt="">
|
||||
</div>
|
||||
|
||||
@include('components.academy-glossary', ['domain' => 'pm2-guide'])
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
(function() {
|
||||
// 도움말 풍선 토글
|
||||
window.toggleBalloon = function(el) {
|
||||
var wasActive = el.classList.contains('is-active');
|
||||
document.querySelectorAll('.help-balloon-trigger.is-active').forEach(function(t) {
|
||||
t.classList.remove('is-active');
|
||||
});
|
||||
if (!wasActive) {
|
||||
el.classList.add('is-active');
|
||||
}
|
||||
};
|
||||
|
||||
// 풍선 외부 클릭 시 닫기
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!e.target.closest('.help-balloon-trigger')) {
|
||||
document.querySelectorAll('.help-balloon-trigger.is-active').forEach(function(t) {
|
||||
t.classList.remove('is-active');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Lightbox
|
||||
window.openLightbox = function(el) {
|
||||
var lb = document.getElementById('lightbox');
|
||||
var img = document.getElementById('lightbox-img');
|
||||
img.src = el.src;
|
||||
img.alt = el.alt;
|
||||
lb.classList.add('is-open');
|
||||
document.body.style.overflow = 'hidden';
|
||||
};
|
||||
|
||||
window.closeLightbox = function() {
|
||||
var lb = document.getElementById('lightbox');
|
||||
lb.classList.remove('is-open');
|
||||
document.body.style.overflow = '';
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
closeLightbox();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@endpush
|
||||
Reference in New Issue
Block a user