Files
playuniare.sori.studio/docs/deploy.md

434 lines
12 KiB
Markdown

# UGREEN NAS Docker 배포 안내
## 문서 목적
이 문서는 터미널과 Docker에 익숙하지 않은 사용자가 Play Uniare의 테스트
버전과 운영 버전을 UGREEN NAS에서 실행하고 업데이트하는 절차를 설명한다.
현재 프로젝트는 Vue 정적 프런트엔드만 포함한다. 실시간 서버가 추가되면
`compose.yaml`에 백엔드 서비스와 상태 저장소를 함께 추가한다.
## 배포 방식
```text
사용자 브라우저
↓
NAS 포트 또는 리버스 프록시
↓
Play Uniare 컨테이너의 Nginx
├── / → Vue 빌드 결과
└── /card-assets/ → 기존 Uniare 카드 이미지
```
Docker 이미지를 만들 때 Node.js로 Vue를 빌드하고, 실행할 때는 가벼운
Nginx가 빌드 결과를 제공한다.
카드 이미지는 다음 기존 폴더를 복사하지 않고 읽기 전용으로 연결한다.
```text
/volume1/docker/projects/apps/uniare/config/www/images
```
## NAS에서 처음 한 번 준비하기
### 1. Docker 앱 설치
UGREEN NAS의 앱 센터에서 Docker 또는 Container Manager에 해당하는 앱을
설치한다. 모델과 UGOS 버전에 따라 앱 이름이 다를 수 있다.
### 2. 프로젝트 저장 폴더 준비
NAS 공유 폴더에 다음과 같은 폴더를 만든다.
```text
/volume1/docker/projects/apps/
├── uniare/
│ └── config/
│ └── www/
│ ├── index.html
│ └── images/
└── playuniare/
```
이 프로젝트의 NAS 경로는 다음으로 고정한다.
```text
/volume1/docker/projects/apps/playuniare
```
### 3. SSH 사용 여부 결정
UGREEN 관리 화면에서 Docker Compose 프로젝트를 직접 등록할 수 있다면 GUI를
우선 사용한다. GUI에서 Git 저장소를 직접 받을 수 없거나 업데이트가 어려운
경우에만 SSH 터미널을 사용한다.
SSH를 활성화할 때는 관리자 화면에서 포트와 계정을 확인한다. NAS 비밀번호를
문서, `.env`, Git 저장소에 기록하지 않는다.
## 테스트 버전 실행
테스트 버전은 외부 도메인에 연결하기 전에 NAS 내부에서 확인하는 용도다.
### 1. NAS에 프로젝트 받기
NAS 터미널에서 프로젝트 저장 폴더로 이동한다.
```bash
cd /volume1/docker/projects/apps
```
처음 한 번만 저장소를 복제한다.
```bash
git clone https://git.sori.studio/zenn/playuniare.sori.studio.git playuniare
cd playuniare
```
비공개 저장소가 인증을 요구하면 비밀번호 대신 Git 서버에서 발급한 접근
토큰을 사용하는 것을 권장한다. 토큰을 명령문이나 문서에 저장하지 않는다.
### 2. NAS 전용 환경변수 파일 만들기
프로젝트를 처음 설치한 경우 예시 파일을 복사한다.
```bash
cp .env.example .env
```
기본 포트는 다음과 같다.
```text
PLAYUNIARE_TEST_HTTP_PORT=41808
PLAYUNIARE_PRODUCTION_HTTP_PORT=41809
```
NAS에서 이미 사용 중인 포트라면 `.env`를 열어 앞쪽 숫자만 다른 값으로
변경한다. `.env`는 Git에 올라가지 않으므로 NAS별 설정을 안전하게 유지할 수
있다.
현재 Docker 컨테이너가 공개하고 있는 포트는 다음 명령으로 확인한다.
```bash
docker ps --format "table {{.Names}}\t{{.Ports}}"
```
사용 중인 숫자를 피해서 `1024`보다 큰 포트를 선택한다.
### 3. 테스트 컨테이너 빌드 및 실행
```bash
docker compose \
-f compose.test.yaml \
-f compose.test.nas.yaml \
up -d --build
```
이 명령은 다음 작업을 한 번에 수행한다.
- 최신 소스 코드로 Docker 이미지 생성
- 테스트 컨테이너 실행
- NAS의 `PLAYUNIARE_TEST_HTTP_PORT`와 컨테이너의 80 포트 연결
### 4. 브라우저에서 확인
NAS의 내부 IP가 `192.168.0.20`이라면 다음 주소로 접속한다.
```text
http://192.168.0.20:41808
```
실제 NAS IP와 `.env`에 입력한 테스트 포트에 맞춰 접속한다.
### 5. 테스트 로그 확인
화면이 열리지 않으면 다음을 실행한다.
```bash
docker compose \
-f compose.test.yaml \
-f compose.test.nas.yaml \
ps
docker compose \
-f compose.test.yaml \
-f compose.test.nas.yaml \
logs --tail=100
```
`Up` 또는 `running` 상태인지 확인한다.
### 6. 테스트 버전 중지
```bash
docker compose \
-f compose.test.yaml \
-f compose.test.nas.yaml \
down
```
컨테이너는 제거되지만 소스 코드와 Docker 이미지는 남아 있다.
## 운영 버전 최초 배포
### 1. 배포 전 로컬 검증
개발 컴퓨터에서 다음 명령이 성공해야 한다.
```bash
npm install
npm run build
```
그 후 변경 사항을 커밋하고 원격 저장소에 푸시한다.
### 2. NAS에서 운영 컨테이너 실행
NAS 프로젝트 폴더에서 실행한다.
```bash
cd /volume1/docker/projects/apps/playuniare
git pull
docker compose \
-f compose.yaml \
-f compose.nas.yaml \
up -d --build
```
운영 컨테이너는 기본적으로 NAS의 `41809` 포트를 사용한다. `.env`의
`PLAYUNIARE_PRODUCTION_HTTP_PORT`를 변경했다면 해당 포트를 사용한다.
```text
http://NAS_IP:41809
```
### 3. 도메인 연결
UGREEN NAS 또는 사용 중인 리버스 프록시에서 다음과 같이 연결한다.
```text
외부 도메인: https://playuniare.sori.studio
내부 대상: http://127.0.0.1:41809
```
- 외부 접속은 HTTPS를 사용한다.
- `.env`에서 운영 포트를 변경했다면 리버스 프록시의 내부 대상 포트도 같은
값으로 변경한다.
- 공유기에서 컨테이너 포트를 그대로 외부에 노출하기보다 리버스 프록시를
사용한다.
- 인증서 자동 갱신이 설정되어 있는지 확인한다.
### 4. 카드 이미지 연결 확인
브라우저에서 실제 카드 파일 하나를 직접 연다.
```text
http://NAS_IP:41809/card-assets/ua03/EX01BT_HTR-2-001.png
```
이미지가 보이면 읽기 전용 마운트와 Nginx 경로가 정상이다. 파일명이 실제로
존재하는 카드인지 먼저 확인한다.
이미지가 보이지 않으면 NAS 터미널에서 원본 경로를 확인한다.
```bash
ls /volume1/docker/projects/apps/uniare/config/www/images
```
시리즈 폴더가 보여야 한다. 다음 명령으로 컨테이너 내부 연결도 확인한다.
```bash
docker exec playuniare-web ls /srv/uniare-card-images
```
이 경로 역시 같은 시리즈 폴더를 보여야 한다.
## 새 버전 업데이트
### 안전한 권장 순서
1. 개발 컴퓨터에서 `npm run build`를 실행한다.
2. 변경 내용을 Git에 커밋하고 푸시한다.
3. NAS에서 테스트 버전을 먼저 업데이트한다.
4. 테스트 주소에서 주요 기능을 확인한다.
5. 문제가 없으면 운영 버전을 업데이트한다.
### 테스트 버전 업데이트
```bash
cd /volume1/docker/projects/apps/playuniare
git pull
docker compose \
-f compose.test.yaml \
-f compose.test.nas.yaml \
up -d --build
```
브라우저에서 `http://NAS_IP:41808`을 확인한다. `.env`에서 테스트 포트를
변경했다면 해당 포트로 접속한다.
### 운영 버전 업데이트
```bash
cd /volume1/docker/projects/apps/playuniare
git pull
docker compose \
-f compose.yaml \
-f compose.nas.yaml \
up -d --build
```
브라우저에서 실제 도메인을 확인한다.
### 사용하지 않는 Docker 이미지 정리
업데이트를 여러 번 하면 이전 이미지가 NAS 용량을 차지할 수 있다.
```bash
docker image prune
```
삭제 대상 확인 질문이 나오면 내용을 읽고 `y`를 입력한다. `-a` 옵션이나
볼륨 삭제 명령은 데이터 유실 가능성이 있으므로 이 문서에서는 사용하지 않는다.
## 이전 버전으로 되돌리기
문제가 생기면 먼저 현재 커밋을 확인한다.
```bash
git log --oneline -10
```
정상 동작했던 커밋 식별자를 확인한 뒤 다음처럼 전환한다.
```bash
git checkout 정상커밋식별자
docker compose \
-f compose.yaml \
-f compose.nas.yaml \
up -d --build
```
복구가 끝난 뒤 개발 컴퓨터에서 문제를 수정하고 정상 브랜치에 새 커밋을
만든다. NAS에서 임의로 소스 파일을 직접 수정하지 않는다.
다시 최신 `main`으로 돌아갈 때:
```bash
git checkout main
git pull
docker compose \
-f compose.yaml \
-f compose.nas.yaml \
up -d --build
```
## 운영 점검 명령
```bash
docker compose -f compose.yaml -f compose.nas.yaml ps
docker compose -f compose.yaml -f compose.nas.yaml logs --tail=100
docker compose -f compose.yaml -f compose.nas.yaml restart
```
- `ps`: 실행 상태 확인
- `logs`: 최근 오류 확인
- `restart`: 같은 이미지로 컨테이너 재시작
## `port is already allocated` 오류
다음 오류는 Docker 이미지 빌드 문제가 아니라 NAS 호스트 포트 충돌이다.
```text
Bind for 0.0.0.0:<포트> failed: port is already allocated
```
먼저 실패하며 생성된 테스트 컨테이너를 정리한다.
```bash
docker compose \
-f compose.test.yaml \
-f compose.test.nas.yaml \
down
```
현재 사용 중인 Docker 포트를 확인한다.
```bash
docker ps --format "table {{.Names}}\t{{.Ports}}"
```
`.env`에서 테스트 포트를 사용하지 않는 숫자로 변경한다.
```text
PLAYUNIARE_TEST_HTTP_PORT=41818
```
그다음 다시 실행한다.
```bash
docker compose \
-f compose.test.yaml \
-f compose.test.nas.yaml \
up -d --build
```
컨테이너 내부의 80 포트는 다른 컨테이너와 같아도 괜찮다. `.env`에 설정하는
NAS 호스트 포트만 겹치지 않으면 된다.
## 배포 체크리스트
- [ ] 로컬 `npm run build` 성공
- [ ] 관련 문서 업데이트
- [ ] Git 커밋과 푸시 완료
- [ ] 테스트 컨테이너 재빌드
- [ ] 테스트 URL 주요 기능 확인
- [ ] 운영 컨테이너 재빌드
- [ ] 실제 도메인 HTTPS 접속 확인
- [ ] `/card-assets/<series>/<fileName>` 이미지 접속 확인
- [ ] 카드 이미지 마운트가 읽기 전용인지 확인
- [ ] 브라우저 새로고침과 직접 URL 접근 확인
- [ ] 오류 로그 확인
- [ ] `.env`, 토큰, 비밀번호가 Git에 없는지 확인
## 향후 실시간 서버 추가 시
프런트엔드만 있을 때는 Nginx 컨테이너 하나면 충분하다. Socket.IO 서버가
추가되면 다음 항목이 필요하다.
- Node.js 서버 컨테이너
- WebSocket을 지원하는 리버스 프록시 설정
- 프런트엔드와 서버의 환경변수
- 방 상태 백업 또는 Redis 도입 여부
- 컨테이너 재시작 시 방이 종료된다는 안내
로컬 개발 서버의 기본 포트는 `31873`으로 정한다. Docker 네트워크 안에서는
다른 컨테이너가 같은 내부 포트를 사용해도 문제가 없다. 충돌하는 것은 NAS
호스트에 공개한 포트다.
운영 환경에서는 Socket.IO 서버 포트를 NAS 외부에 직접 공개하지 않는 것을
원칙으로 한다.
```text
브라우저
→ https://playuniare.sori.studio/socket.io
→ Play Uniare Nginx
→ Docker 내부 Socket.IO 서버:31873
```
이 구조에서는 NAS의 다른 서비스가 호스트 포트 `31873`을 사용 중이어도
Socket.IO 컨테이너가 해당 포트를 호스트에 공개하지 않는 한 충돌하지 않는다.
실시간 서버 구현 시 `expose`만 사용하고 `ports`는 추가하지 않는 방향을
우선한다.
이 구조가 변경되면 반드시 이 문서와 `docs/history.md`를 함께 수정한다.
## NAS 전용 Compose 파일을 분리한 이유
- `compose.yaml`: 어느 컴퓨터에서도 검사하고 빌드할 수 있는 공통 운영 설정
- `compose.nas.yaml`: NAS에만 존재하는 카드 이미지 절대 경로 연결
- `compose.test.yaml`: 공통 테스트 설정
- `compose.test.nas.yaml`: 테스트 컨테이너의 NAS 카드 이미지 연결
개발 Mac에는 `/volume1/...` 경로가 없으므로 NAS 전용 파일을 사용하지 않는다.
NAS에서는 반드시 공통 파일과 NAS 전용 파일을 함께 지정한다.