Files

392 lines
9.0 KiB
Markdown

# UGREEN NAS 운영 배포 안내
## 운영 원칙
- 기능 테스트는 개발 Mac에서 수행한다.
- NAS에는 운영 컨테이너만 실행한다.
- 로컬 빌드와 Docker 검증을 통과한 커밋만 NAS에 배포한다.
- NAS에서 소스 코드를 직접 수정하지 않는다.
- 운영 주소는 현재 `http://playuniare.sori.studio`다.
- HTTPS는 인증서와 리버스 프록시 구성이 완료된 뒤 전환한다.
현재 프로젝트는 Vue 정적 프런트엔드와 Node.js Socket.IO 서버를 같은
Compose 프로젝트로 배포한다.
## 배포 구조
```text
사용자 브라우저
↓
http://playuniare.sori.studio
↓
NAS 리버스 프록시
↓
127.0.0.1:41809
↓
Play Uniare Nginx 컨테이너
├── / → Vue 빌드 결과
├── /card-assets/ → 기존 Uniare 카드 이미지
└── /socket.io/ → Docker 내부 playuniare-server:31873
```
`31873`은 Docker 내부 통신에만 사용하며 NAS 호스트에는 공개하지 않는다.
외부에서는 웹 주소와 같은 `http://playuniare.sori.studio/socket.io` 경로로
접속한다.
카드 이미지는 복사하지 않고 다음 폴더를 읽기 전용으로 연결한다.
```text
/volume1/docker/projects/apps/uniare/config/www/images
```
## 로컬 배포 전 검증
개발 Mac의 프로젝트 폴더에서 실행한다.
```bash
npm install
npm run build
docker compose build
```
필요하면 로컬 Docker 컨테이너도 실행한다.
```bash
docker compose up -d
```
브라우저에서 다음 주소를 확인한다.
```text
http://localhost:41809
```
로컬 Mac에는 NAS 카드 폴더가 없으므로 `/card-assets` 확인은 NAS 배포 후
수행한다.
검증이 끝나면 로컬 컨테이너를 종료한다.
```bash
docker compose down
```
## NAS 최초 설치
### 1. SSH로 NAS 접속
개발 Mac 터미널에서 실행한다.
```bash
ssh NAS사용자명@192.168.50.146
```
실제 SSH 사용자명은 NAS 설정에 맞게 사용한다.
### 2. 저장소 복제
NAS 터미널에서 실행한다.
```bash
cd /volume1/docker/projects/apps
git clone https://git.sori.studio/zenn/playuniare.sori.studio.git playuniare
cd playuniare
```
비공개 저장소가 인증을 요구하면 Git 서버의 접근 토큰을 사용한다. 토큰을
문서나 `.env`에 저장하지 않는다.
### 3. NAS 환경변수 파일 생성
```bash
cp .env.example .env
```
기본 내용:
```text
PLAYUNIARE_PRODUCTION_HTTP_PORT=41809
VITE_CARD_ASSET_BASE_URL=/card-assets
```
운영 Socket.IO 컨테이너는 내부 포트 `31873`을 사용한다. 브라우저는 현재
도메인의 `/socket.io`를 사용하므로 별도 공개 Socket.IO 주소가 필요하지
않다.
### 4. 포트 사용 여부 확인
```bash
docker ps --format "table {{.Names}}\t{{.Ports}}"
```
`41809`가 이미 사용 중이면 `.env`의 값을 다른 포트로 바꾼다.
```text
PLAYUNIARE_PRODUCTION_HTTP_PORT=41819
```
이 경우 리버스 프록시 대상 포트도 같은 값으로 변경해야 한다.
### 5. 운영 컨테이너 실행
```bash
docker compose \
-f compose.yaml \
-f compose.nas.yaml \
up -d --build
```
상태 확인:
```bash
docker compose \
-f compose.yaml \
-f compose.nas.yaml \
ps
```
`playuniare-web`과 `playuniare-server`가 모두 `healthy` 또는 `running`이면
정상이다.
### 6. 리버스 프록시 연결
현재 연결 기준:
```text
외부 주소: http://playuniare.sori.studio
내부 대상: http://127.0.0.1:41809
```
`.env`에서 포트를 변경했다면 내부 대상 포트도 변경한다.
### 7. 배포 확인
메인 화면:
```text
http://playuniare.sori.studio
```
카드 이미지:
```text
http://playuniare.sori.studio/card-assets/ua01/EX02BT_CGH-2-007.png
```
두 주소가 모두 열리면 정적 배포와 카드 자산 연결은 정상이다. 추가로 서로
다른 브라우저 또는 시크릿 창 두 개를 열어 한쪽에서 방을 만들고 다른 쪽에서
코드로 참가해 실시간 연결을 확인한다.
## 새 버전 업데이트
### 개발 Mac
1. 기능을 구현한다.
2. 관련 문서를 수정한다.
3. `npm run build`를 실행한다.
4. 필요한 경우 로컬 Docker로 검증한다.
5. Git에 커밋하고 푸시한다.
### NAS
```bash
cd /volume1/docker/projects/apps/playuniare
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
```
마지막으로 실제 도메인에서 메인 화면과 주요 기능을 확인한다.
## 기존 NAS 테스트 컨테이너 제거
이전에 `playuniare-test-web`을 실행했다면 한 번만 정리한다.
```bash
docker rm -f playuniare-test-web
```
컨테이너가 없다는 메시지가 나오면 이미 제거된 것이므로 무시해도 된다.
테스트용 Docker 네트워크가 남아 있다면 확인 후 제거한다.
```bash
docker network ls
docker network rm playuniare-test_default
```
네트워크가 사용 중이거나 존재하지 않으면 무리하게 삭제하지 않는다.
## 이전 버전으로 되돌리기
최근 커밋 확인:
```bash
git log --oneline -10
```
정상 동작했던 커밋으로 전환:
```bash
git checkout 정상커밋식별자
docker compose \
-f compose.yaml \
-f compose.nas.yaml \
up -d --build
```
복구 후 개발 Mac에서 문제를 수정해 새로운 커밋을 만든다. 다시 최신
`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
```
최근 로그:
```bash
docker compose -f compose.yaml -f compose.nas.yaml logs --tail=100
```
재시작:
```bash
docker compose -f compose.yaml -f compose.nas.yaml restart
```
중지 및 제거:
```bash
docker compose -f compose.yaml -f compose.nas.yaml down
```
## 포트 충돌
다음 오류는 NAS 호스트 포트가 다른 서비스에 사용 중이라는 뜻이다.
```text
Bind for 0.0.0.0:<포트> failed: port is already allocated
```
사용 중인 포트 확인:
```bash
docker ps --format "table {{.Names}}\t{{.Ports}}"
```
`.env`의 `PLAYUNIARE_PRODUCTION_HTTP_PORT`를 사용하지 않는 숫자로 변경하고
다시 실행한다. 컨테이너 내부의 80 포트는 다른 컨테이너와 같아도 괜찮다.
## 카드 이미지가 403인 경우
로그에 다음 내용이 있으면 NAS ACL에 의한 읽기 권한 문제다.
```text
open() "/srv/uniare-card-images/..." failed (13: Permission denied)
```
현재 이미지는 카드 파일 소유자 UID `1000`과 맞춘 `cardreader` 사용자를
사용한다. 최신 소스를 받고 반드시 이미지를 다시 빌드한다.
```bash
git pull
docker compose -f compose.yaml -f compose.nas.yaml up -d --build
```
Nginx 워커 확인:
```bash
docker exec playuniare-web ps aux
```
`nginx: worker process`가 `cardreader`로 표시되어야 한다.
카드 읽기 확인:
```bash
docker exec playuniare-web \
su -s /bin/sh cardreader -c \
'head -c 10 /srv/uniare-card-images/ua01/EX02BT_CGH-2-007.png >/dev/null && echo READ_OK'
```
## 데이터 마이그레이션
현재는 데이터베이스가 없으므로 마이그레이션할 데이터도 없다. 방 상태 저장,
사용자 계정 또는 덱 저장 데이터베이스가 추가되면
`docs/migrations.md`의 원칙을 따른다.
NAS에서 SQL이나 임시 명령을 직접 입력하는 방식은 초기에는 빠르지만, 같은
변경을 다시 재현하거나 복구하기 어렵다. 운영 데이터가 생긴 뒤에는 반드시
Git에 포함된 버전 관리 마이그레이션 코드를 실행하는 방식으로 전환한다.
## 배포 체크리스트
- [ ] 로컬 `npm run build` 성공
- [ ] 필요한 로컬 Docker 검증 성공
- [ ] 관련 문서 업데이트
- [ ] Git 커밋과 푸시 완료
- [ ] NAS `git pull` 완료
- [ ] 운영 컨테이너 재빌드
- [ ] 컨테이너 상태와 로그 확인
- [ ] `http://playuniare.sori.studio` 확인
- [ ] `/card-assets/<series>/<fileName>` 확인
- [ ] 두 브라우저의 방 생성·참가와 준비 상태 동기화 확인
- [ ] `.env`, 토큰, 비밀번호가 Git에 없는지 확인
## 실시간 서버 구조
브라우저에 Socket.IO 서버 포트를 직접 공개하지 않는다.
```text
브라우저
→ http://playuniare.sori.studio/socket.io
→ Play Uniare Nginx
→ Docker 내부 Socket.IO 서버:31873
```
Docker 내부에서는 다른 컨테이너와 같은 포트를 사용해도 충돌하지 않는다.
Socket.IO 서비스는 `ports` 대신 내부 통신용 `expose`를 사용한다.
실시간 서버 로그만 확인하려면 다음 명령을 사용한다.
```bash
docker compose \
-f compose.yaml \
-f compose.nas.yaml \
logs --tail=100 playuniare-server
```
현재 방 상태는 서버 메모리에만 저장된다. `playuniare-server`를 재시작하거나
재배포하면 진행 중인 방은 종료되므로, 이용자가 없는 시간에 업데이트한다.