여러 profile PM2 프로세스가 실행 중에도 Gateway 릴리스 빌드가 thread 생성 한도에 걸리지 않도록 운영 runtime 기본 PID 상한을 512로 높이고 문서와 예제를 맞춘다.
184 lines
10 KiB
Markdown
184 lines
10 KiB
Markdown
# Sammo Core2026 Docker deployment
|
|
|
|
이 저장소는 새 호스트에서 `.env` 하나로 Core2026 Gateway, PostgreSQL, Redis,
|
|
Caddy와 Git/PM2 release-controller를 기동하기 위한 공개 배포 골격입니다. 게임
|
|
profile은 관리자 화면에서 각각 다른 branch 또는 commit을 선택해 DB 유지 배포,
|
|
DB 초기화 배포와 rollback을 수행합니다.
|
|
|
|
## 현재 도메인 배치
|
|
|
|
`dev-sam2026.hided.net`은 실제 외부 Core2026 서비스입니다. 이 저장소로 띄운
|
|
로컬 E2E Docker stack은 `dev-sam-e2e.hided.net`을 사용하며, 외부 Caddy가 TLS를
|
|
종료한 뒤 `172.30.1.54:14999`의 HTTP listener로 호스트 전체를 전달합니다.
|
|
Docker Caddy가 `/gateway/`, `/che/`, `/hwe/` 및 `/image/*`를 내부 서비스와
|
|
자산으로 분기합니다.
|
|
|
|
E2E `.env`의 비밀이 아닌 ingress 값은 다음과 같습니다.
|
|
|
|
```dotenv
|
|
DOMAIN=dev-sam-e2e.hided.net
|
|
PUBLIC_SCHEME=https
|
|
CADDY_SITE_ADDRESS=http://dev-sam-e2e.hided.net
|
|
HTTP_PORT=14999
|
|
```
|
|
|
|
외부 Caddy는 원래 `Host` header와 path prefix를 보존해야 합니다. 외부 확인은
|
|
`https://dev-sam-e2e.hided.net/gateway/api/healthz`를 사용합니다. `HTTPS_PORT`는
|
|
외부 proxy가 사용하지 않지만 Compose port 충돌을 피할 별도 값으로 둡니다.
|
|
|
|
Compose는 `DOMAIN`과 `PUBLIC_SCHEME`으로 `GATEWAY_PUBLIC_URL`과
|
|
`KAKAO_REDIRECT_URI`를 만듭니다. 외부 route만 바꾸면 이미 실행 중인 runtime의
|
|
process 환경은 바뀌지 않으므로, 도메인을 변경한 뒤 runtime과 Caddy container를
|
|
재생성하고 OAuth 시작 응답이
|
|
`https://dev-sam-e2e.hided.net/gateway/oauth/callback`을 사용하는지 확인합니다.
|
|
|
|
## 빠른 시작
|
|
|
|
Docker Engine과 Compose plugin이 설치된 Linux 호스트에서 다음을 실행합니다.
|
|
|
|
```sh
|
|
cp .env.example .env
|
|
# .env의 domain, Core2026 URL, Kakao key와 모든 비밀값을 교체합니다.
|
|
./scripts/check.sh
|
|
docker compose up -d --build --wait
|
|
docker compose ps
|
|
```
|
|
|
|
DNS의 `DOMAIN` A/AAAA record가 호스트를 가리키고 80/443 TCP 및 443 UDP가
|
|
열려 있으면 Caddy가 인증서를 자동 발급합니다. Kakao Developers에는
|
|
`https://DOMAIN/gateway/oauth/callback`을 redirect URI로 등록합니다. 첫 기동은
|
|
Core2026 clone, frozen-lockfile install, build와 Gateway migration 때문에 수 분이
|
|
걸릴 수 있습니다.
|
|
|
|
외부 reverse proxy가 TLS를 종료하고 이 stack의 HTTP port로 전달한다면
|
|
`PUBLIC_SCHEME=https`, `CADDY_SITE_ADDRESS=http://DOMAIN`을 함께 설정합니다.
|
|
예를 들어 host `14999`로 전달할 때는 `HTTP_PORT=14999`로 두고, 사용하지 않는
|
|
`HTTPS_PORT`는 충돌하지 않는 별도 host port로 지정합니다. 외부 proxy는 원래
|
|
`Host` header를 보존해야 합니다.
|
|
|
|
runtime은 기본적으로 memory/swap 각각 4 GiB, CPU 4개, PID 512개 상한을
|
|
가집니다. Gateway와 여러 profile의 PM2 process가 실행 중인 상태에서도 release
|
|
build의 Node/Rolldown thread를 수용하기 위한 값입니다. 호스트 용량에 맞춰
|
|
`RUNTIME_*_LIMIT`을 조정할 수 있지만 0 또는 무제한으로 두지 않습니다. PM2
|
|
process/restart 수가 예상보다 증가하면 로그 수집보다 runtime 중지가 우선입니다.
|
|
|
|
초기 빌드와 profile worktree 빌드는 기본 Node heap 1536 MiB,
|
|
`RAYON_NUM_THREADS=1` 안에서 실행됩니다. `RUNTIME_NODE_OPTIONS`의 heap 상한은
|
|
512~2048 MiB, `RUNTIME_RAYON_NUM_THREADS`는 1~4만 허용됩니다. 턴 데몬만 더 큰
|
|
heap이 필요하면 `TURN_DAEMON_NODE_OPTIONS`를 512~4096 MiB 범위에서 지정합니다.
|
|
이 값은 Gateway orchestrator가 생성하는 turn-daemon PM2 process에만
|
|
`NODE_OPTIONS`로 적용되며 API·frontend·worker와 build는 계속
|
|
`RUNTIME_NODE_OPTIONS`를 사용합니다. 큰 값을 쓰기 전에 `RUNTIME_MEMORY_LIMIT`과
|
|
동일한 swap 상한을 함께 늘리고 실제 container 총사용량을 확인합니다.
|
|
|
|
`scripts/check.sh`는 example placeholder, 짧은 비밀값, 잘못된 domain/email,
|
|
repository credential 조합을 실제 값 출력 없이 거부합니다. 최소 길이는 DB·Redis
|
|
password 24자, game/bootstrap token 32자, 최초 관리자 password 16자입니다.
|
|
배포 전 무작위 값은 예를 들어 `openssl rand -base64 36`으로 생성합니다.
|
|
|
|
`INITIAL_ADMIN_*`은 사용자 table이 비어 있을 때만 superuser를 한 번 생성합니다.
|
|
그 뒤 `/gateway/admin/server-operations`에서 `che`, `kwe`, `pwe`, `twe`, `nya`,
|
|
`pya`, `hwe`마다 source branch/commit을 독립 선택합니다. `DB 유지 배포`는 현
|
|
시즌을 보존하고, `DB 초기화 배포`는 scenario를 다시 seed합니다. Gateway 자체의
|
|
배포와 이전 commit rollback은 같은 화면의 별도 release 영역에서 처리됩니다.
|
|
초기 profile의 `<profile>:default`에서 `default`는 현재 scenario가 아니라 변경되지
|
|
않는 인스턴스 키입니다. 현재 scenario는 DB 초기화 배포가 성공한 뒤 Gateway DB의
|
|
별도 필드에 기록됩니다.
|
|
|
|
이미지 서비스 연동에는 서버 간 접근 URL `IMAGE_SERVICE_URL`, 브라우저 공개 URL
|
|
`IMAGE_PUBLIC_URL`과 서로 다른 두 secret 파일이 필요합니다. 업로드 secret은
|
|
Gateway 전용 아이콘과 game-api 편집기 첨부에만 사용하고, sync secret은 Gitea
|
|
webhook 누락 시 `docker compose exec runtime pnpm sync:image`로 현재 이미지
|
|
branch의 fast-forward를 요청할 때만 사용합니다. 두 파일은 runtime에 read-only로
|
|
mount되며 원문은 환경 변수나 브라우저 bundle에 들어가지 않습니다.
|
|
|
|
`IMAGE_PUBLIC_URL`의 기본값은 `https://sam-image.hided.net`입니다. runtime은
|
|
이를 Gateway·game frontend의 공개 이미지 origin과 사용자 아이콘 URL에 함께
|
|
주입합니다. 정적 게임 이미지는 `/game`, 공용 아이콘은 `/icons`, Core2026
|
|
사용자 아이콘은 서버 발급 경로 `users/core2026/<파일>`을 `/icons` base 아래에
|
|
붙여 읽습니다. 업로드 권한값은
|
|
URL query나 `VITE_*`가 아니라 `IMAGE_UPLOAD_CORE2026_SECRET_FILE`이 가리키는
|
|
mode 0600 secret 파일에만 둡니다.
|
|
|
|
## 데이터와 복구 경계
|
|
|
|
- PostgreSQL, Redis, Core clone/worktree, PM2 상태와 Caddy 인증서는 named
|
|
volume에 보존됩니다. 새 user icon은 외부 이미지 서비스가 보존하며 runtime의
|
|
user icon volume은 기존 설치 호환용입니다.
|
|
- `/image/*`는 앱 artifact가 아니며 `data/image/`의 별도 운영 자산을 제공합니다.
|
|
- 일반 `docker compose down`은 volume을 보존합니다. `down --volumes`는 DB와
|
|
release 상태를 삭제하는 파괴적 명령이므로 backup 없이 실행하지 않습니다.
|
|
- 앱 rollback은 Prisma migration을 되돌리지 않습니다. 이전 앱과 새 schema의
|
|
호환성을 배포 전에 확인합니다.
|
|
- Core2026 저장소가 비공개라면 credential을 Git에 추가하지 않습니다. HTTPS는
|
|
`.env`의 `CORE_REPOSITORY_USERNAME`과 read-only
|
|
`CORE_REPOSITORY_TOKEN`을 함께 설정합니다. URL에 credential을 포함하지 않으며
|
|
runtime은 0600 파일로 옮긴 뒤 원래 env를 제거하고 `GIT_ASKPASS`로만
|
|
전달합니다.
|
|
- SSH deploy key를 사용할 때는 `CORE_SSH_PRIVATE_KEY_BASE64`와
|
|
`CORE_SSH_KNOWN_HOSTS_BASE64`를 함께 설정합니다. 각각 `base64 -w0`로 인코딩한
|
|
read-only key와 검증한 known_hosts 내용입니다. HTTPS와 SSH mode를 동시에
|
|
설정할 수 없습니다. 원래 base64 env는 파일 생성 직후 제거됩니다. 생성된
|
|
credential/key 파일은 runtime volume에서 0600으로 관리되고 해당 mode를
|
|
해제한 다음 기동하면 제거됩니다.
|
|
|
|
상태 확인과 로그:
|
|
|
|
```sh
|
|
docker compose ps
|
|
docker compose logs --tail=200 runtime caddy
|
|
docker compose exec runtime pnpm --filter @sammo-ts/release-controller status
|
|
```
|
|
|
|
Caddy의 runtime logger는 reverse proxy 오류에도 요청 정보를 남길 수 있으므로
|
|
`X-Session-Token` 값을 encoder 단계에서 삭제합니다. 장애 로그를 수집할 때도
|
|
인증 헤더, cookie, token과 secret을 보고서나 채팅에 복사하지 않습니다.
|
|
|
|
## 개발 bind 모드
|
|
|
|
로컬 Core2026 checkout을 container에 bind하고 DB/Redis/Caddy는 같은 구성으로
|
|
사용할 수 있습니다. `.env`에 `CORE_DEV_PATH=/absolute/path/to/core2026`,
|
|
host 사용자의 `DEV_UID`/`DEV_GID`, `PUBLIC_SCHEME=http`, `DOMAIN=localhost`를
|
|
추가한 뒤 실행합니다. 기본 UID/GID는 `1000:1000`입니다.
|
|
|
|
```sh
|
|
docker compose -f compose.yaml -f compose.dev.yaml up -d --build
|
|
docker compose exec runtime pnpm --filter @sammo-ts/gateway-api dev
|
|
docker compose exec runtime pnpm --filter @sammo-ts/gateway-frontend dev --host 0.0.0.0
|
|
```
|
|
|
|
개발 override의 runtime은 의존성과 Prisma client를 준비한 뒤 대기합니다. 필요한
|
|
watch process를 별도 shell에서 실행합니다. 운영 release-controller를 시험하려면
|
|
override 없이 production mode를 사용해야 하며, bind checkout의 Git metadata에
|
|
container worktree 경로를 등록하지 않도록 주의합니다.
|
|
|
|
`compose.dev.yaml`은 `RUNTIME_MODE=development`와 모든 service의
|
|
`restart: no`를 literal로 고정합니다. `.env`의 `RUNTIME_MODE`로 이를 덮어쓸 수
|
|
없습니다. production PM2 검증에 개발 override를 섞지 않습니다. 개발 runtime에도
|
|
기본 4 GiB/4 CPU/256 PID 상한이 있으며 `DEV_RUNTIME_*_LIMIT`으로 더 낮출 수
|
|
있습니다.
|
|
|
|
운영 entrypoint와 PM2를 검증할 때는 개발 override 대신 전용 smoke override를
|
|
사용합니다. 이 override는 clone mode를 유지하면서 모든 service를 `restart: no`로
|
|
고정하고 runtime 상한을 최대 4 GiB/4 CPU/256 PID로 제한합니다.
|
|
|
|
```sh
|
|
docker compose -f compose.yaml -f compose.smoke.yaml up -d --build --wait
|
|
```
|
|
|
|
검증 shell에는 EXIT/INT/TERM trap과 전체 timeout을 두고, 정상·실패 어느 경우든
|
|
`docker compose stop`으로 끝냅니다. `down --volumes`는 사용하지 않습니다.
|
|
|
|
## 설정 검증
|
|
|
|
`.env`를 채운 뒤 실제 값을 출력하지 않는 검사를 실행합니다.
|
|
|
|
```sh
|
|
./scripts/check.sh
|
|
```
|
|
|
|
검사는 production/development Compose model과 Caddyfile 구문을 확인합니다.
|
|
다른 env 파일은 `ENV_FILE=/path/to/file ./scripts/check.sh`로 검사합니다.
|
|
`docker compose config` 전체 출력에는 펼쳐진 비밀값이 포함될 수 있으므로 CI
|
|
artifact나 이슈에 그대로 첨부하지 않습니다.
|