운영 서버 교체 계획서
작성일: 2026-11-04 (KST)
대상: 현재 기존 192.168.10.10 / 신규 192.168.10.20 → 최종 신규 192.168.10.10 / 기존 192.168.10.30
문서 상태: 2026-11-05 KST 신규 .10 운영 인계를 완료했습니다. 기존 .30은 복구용으로 보존합니다. ZALNAS를 포함한 변경 저장소의 신규 서버 GitHub 검증·운영 배포 성공까지 확인했습니다.
0. 실행 중 확정한 구성과 진행 조건
- 기존 .10 서버도 Ubuntu 26.04.1 LTS·커널 7.0.0-42-generic으로 업그레이드하고 재부팅·SSH 재접속·기존 38개 컨테이너 복구를 확인했습니다. 기존 IP와 Tailscale 주소 및 데이터 디스크 UUID는 유지했습니다. OS·DB 복구 백업은 기존 서버 /srv/data/work-backups/ubuntu-upgrade-260401-20261104에 보존합니다.
- 신규 서버의 Ubuntu 26.04를 유지하고 Docker 29.9.3·Compose 5.7.0·Tailscale 1.104.5를 설치했습니다.
- 추가 연결한 USB 3.0/UAS 외장 SSD 약 120GB를 사용자 지시에 따라 ext4로 포맷했습니다. 포맷 당시 파티션은 /dev/sdb1이었고 재부팅 후 /dev/sda1로 열거됐습니다. 라벨은 COZYLAB_DATA이며 장치명 대신 UUID로 식별합니다.
- 외장 SSD UUID는 7807ed1d-5198-4cac-9b3f-9669a0dd4dbc이며 /srv/data에 영구 마운트합니다. 장치명 sdb 변경에 대비하여 UUID를 기준으로 연결합니다.
- 백업은 /srv/data/backups·work-backups, 첨부파일과 사이트 런타임은 /srv/data/site-data·site-runtime 아래에 보존합니다. 기존 애플리케이션 경로에는 bind mount하여 향후 업로드도 외장 SSD에 저장합니다.
- MariaDB 데이터·Docker 이미지/실행 저장소·NPM SQLite는 내장 SSD를 유지합니다.
- 외장 SSD와 사이트별 저장 경로가 준비되지 않으면 Docker를 시작하지 않는 UUID·표식·마운트 검사를 설치했습니다.
- NAS HDD1의 이전 임시 폴더 /mnt/HDD1/CozyLabServer와 전용 SMB 공유 및 /srv/backup-nas 마운트는 외장 SSD 백업 검증 후 삭제했습니다. 기존 NAS Music·Jav 공유는 별도 운영 의존성이므로 유지합니다.
- 기존 서버의 과거 백업은 외장 SSD로 직접 복사하고 원본을 보존합니다. 최종 전환 직전 DB·첨부 변경분은 다시 동기화해야 합니다.
- 신규 Tailscale 등록, 도메인별 접속과 재부팅·마운트 검증 및 새 디스크 UUID에 맞춘 저장소별 배포 검사 전환을 수행했습니다.
- 최종 인계 시 기존 서버는 .30으로 이동한 뒤 운영 작업을 중지한 복구용 서버로 유지합니다. .30 충돌 확인과 재접속 검증 후 신규 서버에 .10을 부여합니다.
- 아래 용량·OS·추가 디스크 관련 최초 조사와 권고는 외장 SSD 연결 전 기준입니다. 현재 확정 구성과 충돌하는 부분은 본 절을 우선 적용합니다.
인계 완료와 검증 기록
- 신규 서버:
192.168.10.10, 100.114.81.127, cozylab-server. 기존 서버: 192.168.10.30, 100.106.224.48, cozylab-server-old.
- 기존
.10 해제와 .30 SSH 재접속을 확인한 뒤 신규 서버에 .10을 부여했습니다. 기존 서버 Docker는 mask하고 운영 cron·백업·프록시 자동 시작을 중지했습니다.
- 신규 서버 운영 컨테이너 38개, 자동 백업 워커·스케줄러·cron 및 Tailscale Serve 사설 HTTPS를 복구했습니다. 원래 실행하지 않던 레거시 컨테이너 2개는 기존 서버에 보존하며 새 서버에서 기동하지 않았습니다.
- 인계 직전 모든 애플리케이션 테이블 297개, 실제 행 수 총 570,710행을 대조해 일치함을 확인했습니다. 이후 신규 서버에서 정상적으로 콘텐츠·접속 데이터가 갱신됩니다.
- 최종 변경 파일과 워커 볼륨 13,172개 파일, 1,009,948,483바이트의 내용 해시를 대조했습니다.
- 18개 도메인의 정상 페이지 응답 또는 관리자 로그인 이동을 확인했습니다. MariaDB·포스팅봇 3개·실행기 2개가 정상 작동합니다.
- 실행기 SQLite 복사본의 일관성 문제는 중지된 원본에서 backup API로 복원했습니다.
/healthz·/readyz와 운영 SQLite 무결성 점검이 정상입니다. 향후 SQLite는 실행 중 단순 파일 복사나 크기·시각 기반 증분만 사용하지 않고 backup API·WAL/SHM 잔여 확인·무결성 검사를 수행합니다.
- 신규 Ubuntu에서 같은 cloud-init 로그 순환 항목이 두 번 등록된 문제는 동일 설정임을 확인한 후 이전 패키지 파일을 dpkg-divert로 분리했습니다. logrotate 재실행이 성공했습니다.
- 이벤트·보드게임·샘플의 신규 서버 GitHub 배포는 성공했습니다. Cozyon 수동 배포의 production 대상과 검증된 공개 SSH 키도 갱신했습니다. 비밀번호·개인키는 유지했습니다.
- ZALNAS 배포 주소·SSH 지문·외장 SSD UUID 및 회귀 검사를 갱신해 main에 Push했습니다. 직접 관련된 배포 동작 검사와 셸 문법은 통과했습니다. 로컬 전체 Harness는 배포에 포함되지 않는 기존 PDF vendor 임시 파일과 로컬 headless 브라우저 문제에 걸렸으며 GitHub의 깨끗한 checkout에서 전체 검증과 신규 서버 배포가 성공했습니다.
- NAS
//192.168.10.3/Temp는 두 서버 /srv/codex-work에 연결했습니다. automount가 함께 표시될 때는 findmnt -t cifs --target /srv/codex-work로 실제 공유를 확인합니다.
- 명시적으로 승인받은 Temp 폴더 21개를 삭제해 약 3.22GiB를 확보했습니다.
Codex와 zalnas-social-oauth는 보존했습니다.
- OS 복구 백업은 기존 서버 원본과 NAS
Temp/Codex/backups/ubuntu-upgrade-260401-20261104에 보존했습니다. 과거 운영 백업과 웹 첨부파일은 신규 외장 SSD에서 유지합니다.
복구 시 주의
기존 .30을 켜고 IP만 되돌리지 않습니다. 인계 후 신규 .10에 새 콘텐츠·평가·접속 기록이 쌓이므로 먼저 신규 쓰기를 멈추고 최신 DB·첨부·워커 인증 상태와 SQLite 스냅샷을 기존 서버로 역동기화해야 합니다. 신규 서비스 중지와 .10 해제를 확인한 뒤 기존 서버 주소·Tailscale 이름·Docker·정기작업을 복원하고 GitHub 배포 대상·SSH 지문도 되돌립니다. 검증 전 신규 서버를 초기화하지 않습니다.
1. 권장 방향과 결론
새 서버를 .20에서 독립적으로 구축하고, 실제 도메인·데이터로 비공개 검증한 뒤, 최종 변경분을 동기화하고 기존 서버를 격리한 다음 .10을 인계하는 방식을 권장합니다.
OS 전체를 무조건 복제하는 것보다, 기존 Compose·고정 이미지·설정·영구 데이터·작업 상태를 각각 복원하는 편이 검증과 복구에 유리합니다. 도메인, URL, DB 식별자, 콘텐츠 ID, 권한, 인증서와 서비스 동작은 보존합니다. 컨테이너 ID, 물리 NIC 이름, 디스크 UUID, SSH 호스트 키, Tailscale 노드 ID까지 같게 만들 필요는 없습니다. 변경되는 식별자는 의존 설정에 명시적으로 반영합니다.
다만 현재 신규 서버는 기존 서버보다 사양과 저장공간이 작습니다. 지금 상태에서 바로 전체 운영을 대체하는 것은 권장하지 않습니다. 저장장치 보강과 실부하 검증을 전환 선행 조건으로 둡니다.
이 문서에서 NPM은 실제 운영 중인 Nginx Proxy Manager를 뜻합니다. Node.js 패키지 관리 도구 npm은 별개이며, 필요한 컨테이너 내부 버전도 함께 보존합니다.
2. 실제 확인한 서버 비교
2026-11-04 SSH 읽기 전용 조사 결과입니다. 사용량과 작업 상태는 실행 직전에 다시 확인합니다.
| 항목 |
기존 서버 .10 |
신규 서버 .20 |
판단 |
| 호스트명 |
cozylab-server |
optiplex3020 |
준비 중에는 구분되는 이름 유지 |
| OS |
Ubuntu 24.04.5 LTS |
Ubuntu 26.04.1 LTS |
OS까지 동일하지 않음 |
| 아키텍처 |
x86_64 |
x86_64 |
기존 amd64 이미지 재사용 가능, 실제 실행 검증 필요 |
| CPU |
Ryzen 7 5700X, 8코어·16스레드 |
Core i3-4160T, 2코어·4스레드 |
동시 작업·빌드·압축 처리 성능 저하 가능 |
| RAM |
약 32GB |
약 16GB |
평시 사용량만으로 최대 부하를 판단하지 않음 |
| OS 디스크 |
약 500GB NVMe, 사용 약 60GB |
약 120GB 디스크, 가용 약 96GB |
이미지·백업·복구 여유 부족 우려 |
| 별도 데이터 디스크 |
약 1TB, /srv/data, 사용 약 51GB |
없음 |
독립 데이터·백업 저장장치 필요 |
| 물리 NIC |
enp5s0 |
enp2s0 |
Netplan 파일을 그대로 복사하면 안 됨 |
| Docker Engine |
29.9.3 |
미설치 |
호환 패키지 확보 후 설치·검증 |
| Docker Compose |
v5.7.0 |
미설치 |
Compose 프로젝트 이름·파일 경로 보존 |
| Tailscale |
100.106.224.48 |
미설치 |
새로운 노드 등록 및 배포 설정 변경 필요 |
기존 서버에는 Compose 프로젝트 18개, 컨테이너 실행 중 38개·Created 1개·Exited 1개가 확인됐습니다. 중지·미실행 컨테이너를 일괄 기동하지 않고, 의도된 상태인지 분류하여 재현합니다.
주요 저장 사용량은 다음과 같습니다. 서로 상위·하위 경로이므로 단순 합산하지 않습니다.
| 경로 |
실측 대략 크기 |
이전 시 고려 |
| /srv/docker/web-platform/mariadb/data |
1.3GB |
DB 정합성을 확보한 별도 백업·복원 |
| /srv/docker/proxy |
27MB |
프록시 DB·설정·인증서 전체 |
| /srv/data/site-data |
5.5GB |
이미지·첨부·런타임 데이터 |
| /srv/data/work-backups |
40GB |
기존 복구 이력 보존 대상 |
| /srv/data/backups |
5.3GB |
백업 이력 보존 대상 |
| /srv/data/codex-runner |
73MB |
실행기 상태·인증 포함 보안 전송 |
| /var/lib/docker |
1.1GB |
영구 볼륨 누락 확인 |
| /var/lib/containerd |
35GB |
이미지·스냅샷 사용량 별도 고려 |
기존 두 파일시스템 사용량은 OS 등을 포함해 약 111GB입니다. 이것이 곧 이전할 데이터 총량이라는 뜻은 아니지만, 신규 루트 파일시스템 약 109GB에 모든 것을 여유 있게 수용할 수 있다고 볼 근거는 없습니다. 불필요한 이미지 정리는 별도 판단할 수 있으나, 용량을 맞추려고 운영 데이터나 백업을 임의 삭제하지 않습니다.
현재 컨테이너 메모리와 CPU 순간 사용량은 낮게 관찰됐습니다. 이것은 i3 서버가 운영 피크를 감당한다는 증거가 아니므로, 성능 검증을 생략하지 않습니다.
3. 전환 전에 결정할 조건
3.1 저장장치
권장 구성은 신규 OS·Docker용 SSD 500GB 이상과, 별도 데이터·백업용 1TB 이상 디스크입니다. 이는 실측 사용량과 복구 여유를 고려한 제안이며 구매·장착 전 본체의 SATA·전원·장착 가능 여부를 확인합니다.
다른 구성을 선택하더라도 다음 조건을 충족해야 합니다.
- 운영 데이터, Docker 이미지, SQL 덤프, 임시 복원본, 백업 보존분을 포함한 용량 산정표를 작성합니다.
- 계획한 최대 사용량 이후에도 각 파일시스템에 최소 20~30% 여유를 확보합니다.
- 이전 중에는 기존 서버의 데이터 디스크를 유지하여 독립적인 복구 원본으로 사용합니다.
- 기존 디스크를 바로 빼서 새 서버에 넣는 방식은 신속 복귀가 어려워 기본안으로 사용하지 않습니다.
- 운영 데이터와 유일한 백업을 같은 물리 디스크에만 두지 않습니다.
3.2 OS 선택
최대한 동일한 호스트 환경이 목적이라면 신규 서버도 기존과 같은 Ubuntu 24.04 계열로 준비하는 것이 변수 감소에 유리합니다. 다만 현재 신규 서버는 26.04이므로, 24.04로 맞추려면 재설치가 필요하며 별도 실행 결정 대상입니다.
현재 26.04를 유지해도 애플리케이션 런타임은 고정 Docker 이미지로 맞출 수 있습니다. 이 경우 Docker 패키지 지원, 커널·방화벽 동작, CIFS, Tailscale, 재부팅 자동 기동을 실제 검증해야 합니다. OS 차이와 하드웨어 교체, 런타임 업그레이드를 한 번에 혼합하지 않습니다.
3.3 성능과 서비스 범위
이전 목표는 조사에서 확인한 운영 서비스 전체입니다. 새 서버가 느리다는 이유로 테스트 사이트·워커·백업을 임의 제거하지 않습니다.
신규 서버에서 웹 요청과 DB, 두 포스팅봇, 동기화, 백업을 함께 실행하는 검증을 수행합니다. 최초 목표는 기존 서버의 비슷한 부하와 비교하여 주요 요청 지연이 20% 이상 악화되지 않고, 오류·OOM·지속적인 swap 증가가 없는 것입니다. 이는 프로젝트의 제안 합격 기준이며 실측 후 조정합니다. 통과하지 못하면 하드웨어를 보강하거나 기존 서버를 유지합니다.
4. 이전 범위
4.1 서비스와 웹사이트
다음 Compose 프로젝트 18개를 이전 매니페스트에 포함합니다.
- cozyon-proxy: Nginx Proxy Manager
- web-platform: 공용 MariaDB 및 레거시 웹 설정
- zalnas-prod-runtime
- boardgame-cozyon-runtime
- event-cozyon-runtime
- dogcare-cozyon-runtime
- cozyon-runtime
- cozylab-runtime
- cozylab-system
- bosturn-zalnas-runtime
- blogbot-zalnas-runtime
- blog-cozylab-runtime
- sample-zalnas-prod-runtime
- z1-zalnas-runtime
- z2-zalnas-runtime
- z3-zalnas-runtime
- z4-zalnas-runtime
- codex-runner
사이트 소스 디렉터리는 /srv/docker/sites의 21개가 확인됐습니다. 현재 실행되지 않는 test-* 디렉터리도 보존 여부·복원 방법을 명시합니다. Compose 활성 목록만 복사해서 테스트 환경 소스를 누락하지 않습니다.
NPM에는 활성 프록시 호스트 18개와 삭제되지 않은 인증서 레코드 25개가 확인됐습니다. 인증서 레코드 수가 유효한 인증서 수를 보장하는 것은 아니므로 유효기간·도메인·키 대응을 별도로 확인합니다.
| 프록시 호스트의 대표 도메인 |
현재 upstream |
| cozyon.kr, www.cozyon.kr |
cozyon-apache:80 |
| zalnas.kr, www.zalnas.kr |
zalnas-prod-apache:80 |
| cozylab.kr, www.cozylab.kr |
cozylab-apache:80 |
| z1.zalnas.kr ~ z4.zalnas.kr |
각 zN-zalnas-apache:80 |
| blog.cozylab.kr |
blog-cozylab-apache:80 |
| boardgame.cozyon.kr |
boardgame-cozyon-apache:80 |
| event.cozyon.kr |
event-cozyon-apache:80 |
| dogcare.cozyon.kr |
dogcare-cozyon-apache:80 |
| bosturn.zalnas.kr |
bosturn-zalnas-apache:80 |
| blogbot.zalnas.kr |
blogbot-zalnas-apache:80 |
| system.cozylab.kr |
system-apache:80 |
| sample.zalnas.kr |
sample-zalnas-apache:80 |
| cinnas.iptime.org |
192.168.10.3:80 |
| old.zalnas.kr, old.cozyon.kr |
192.168.10.3:443, HTTPS |
NAS 192.168.10.3으로 연결되는 upstream도 프록시 이전 검증 대상입니다. 해당 NAS 웹 환경 자체를 새 서버로 이전한다는 뜻은 아닙니다.
4.2 런타임과 이미지
기존 컨테이너의 실제 이미지 ID·digest를 보존합니다. 이름의 태그만 같아도 다른 이미지가 될 수 있으므로 비교합니다.
| 용도 |
확인한 이미지 |
| NPM |
docker.io/jc21/nginx-proxy-manager:2.15.1 |
| Apache |
cozylab/web-apache:2.4.68-trixie-20260831 |
| PHP-FPM·동기화 |
cozylab/web-php:8.5.12-fpm-trixie-20261026 |
| DB |
mariadb:12.3.3, 현재 digest 고정 |
| 포스팅봇 |
zalnas-content-bot-worker:0.162.0 |
| 실행기 |
codex-runner:local |
| 빌드 트리거 |
dogcare-cozyon-build-trigger:production |
로컬 빌드 이미지와 레지스트리에서 다시 받을 수 없는 이미지는 docker image save/load로 전달합니다. 복원 후 Image ID를 비교하고, 이후 재빌드할 수 있도록 Dockerfile·빌드 컨텍스트도 보존합니다.
호스트에는 node/npm 실행 파일이 확인되지 않았습니다. 따라서 호스트 npm을 임의로 설치하기보다, Node·npm·Codex CLI를 실제 사용하는 컨테이너의 버전과 인증·설정을 조사하여 동일하게 복원합니다.
컨테이너 이미지만 복사하면 영구 데이터는 옮겨지지 않습니다. 볼륨은 별도로 백업·복원합니다. Docker 볼륨 공식 문서
4.3 파일·DB·인증·호스트 구성
| 대상 |
포함 내용 |
복원 원칙 |
| /srv/docker/sites |
모든 사이트 소스·배포 스크립트 |
운영 호스트 원본 기준, NAS의 과거 소스 사용 금지 |
| /srv/docker/proxy |
compose, data, letsencrypt |
NPM DB와 인증서·설정을 한 세트로 복원 |
| /srv/docker/web-platform |
DB·Apache·PHP 설정·secrets |
DB 원시 데이터는 일반 온라인 복사에서 제외 |
| /srv/docker/secrets 및 사이트별 secrets |
DB·OAuth·API·서명키 |
암호화 전송, 원래 소유권·권한 보존, Git·보고서에서 제외 |
| /srv/docker/volumes |
사이트 런타임·세션·첨부 등 |
하위 bind mount와 실제 저장 위치 구분 |
| /srv/data 전체 |
site-data, backups, work-backups, dogcare-cozyon, codex-runner |
보존 정책을 정하고 빠짐없이 매니페스트에 포함 |
| Docker named volumes |
두 포스팅봇 인증 볼륨 등 |
소비 서비스 중지 후 안전하게 백업·복원 |
| 익명 볼륨 1개 |
소유 컨테이너와 용도 추가 확인 |
확인 전에 폐기하지 않음 |
| /srv/work/codex-runner |
실행 중·저장 작업 파일 |
실행기 작업 종료 후 최종 동기화 |
| 호스트 설정 |
fstab, Netplan, Docker/containerd, 방화벽, SSH, sysctl, cron |
신규 하드웨어에 맞게 항목별 적용 |
| 호스트 자동 작업 |
백업 worker·scheduler·timer, 프록시 시작 서비스 |
순서·실행 계정·파일 경로·환경파일까지 보존 |
| 배포 연결 |
GitHub 저장소별 workflow, SSH 키 등록, Tailscale ACL |
새 서버의 주소·지문을 확인하여 명시적으로 전환 |
Docker 29 계열에서는 이미지 저장소가 /var/lib/containerd에 있을 수 있습니다. 현재 실측도 이 경로가 35GB입니다. /var/lib/docker만 대상으로 용량을 산정하거나 복사하지 않습니다. 새 호스트에는 Docker를 정상 설치하고 이미지·볼륨을 복원하며, 실행 중인 Docker/containerd 내부 디렉터리를 통째로 덮어쓰는 방식을 피합니다. Docker daemon 저장 경로 문서
4.4 마운트와 디스크 안전 검사
기존 /srv/data UUID는 6a8d4b91-aa8b-4683-be4c-dd331402bd4c이며 표식은 /srv/data/.cozylab-storage-volume입니다.
새 디스크를 쓰면 UUID가 달라집니다. 기존 UUID를 무조건 복제하거나 검사를 삭제하지 않습니다. 새 디스크의 실제 UUID·표식을 검증하고 다음 위치의 기대값을 맞춥니다.
- 신규 서버 fstab 및 bind mount 순서
- 저장소별 배포·bootstrap·백업 스크립트
- 관련 회귀 검사와 운영 문서
- GitHub 배포 전 마운트 사전 확인
- 신규 서버에서 데이터 디스크가 없을 때 컨테이너가 빈 폴더를 만들어 정상처럼 실행하지 않도록 하는 시작 조건
/srv/docker/mounts 아래에는 NAS CIFS와 로컬 bind mount가 혼재합니다. 복사 매니페스트는 실제 데이터 원본을 기준으로 만들고 마운트 트리를 재귀적으로 따라가지 않습니다. NAS의 Music/Jav 대용량 파일을 로컬에 복사하지 않고 기존 공유를 다시 마운트합니다. 기존 Music 읽기 전용 및 Jav 접근권한과 UID/GID 매핑을 유지합니다.
5. 진행 단계
1단계: 확정 조사와 복구 원본 확보
- 전체 컨테이너의 이미지·실행 상태·Compose 파일·프로젝트명·볼륨·네트워크·별칭·healthcheck·restart 정책을 매니페스트로 저장합니다.
- 컨테이너 writable layer의 변경을 조사하고 이미지 재생성만으로 사라지는 파일을 별도 식별합니다.
- 모든 DB 이름·엔진·문자셋·collation·사용자·권한·뷰·트리거·루틴·이벤트와 업로드 실경로를 확인합니다.
- NPM upstream, 리디렉션·접근목록·stream 설정·인증서 갱신 방식·DNS 자격증명을 포함한 실제 구성을 안전하게 백업합니다.
- cron은 root와 서비스 계정, /etc/cron.*, systemd timer, 컨테이너 내부 작업, DB 이벤트까지 조사합니다.
- 공유기의 포트포워딩·DHCP 예약·MAC 고정·내부 DNS·공인 IP·IPv6 AAAA와 인바운드 방화벽 정책을 확인합니다.
- 모든 사이트의 배포 저장소, 배포 대상 주소·SSH 지문, 실행 중 Actions와 webhook·빌드 트리거를 목록화합니다.
- 서버와 별개 매체에 복구 원본을 두고 checksum·용량·복원 가능성을 검증합니다.
완료 조건: 마운트·외부 의존성이 빠지지 않은 매니페스트, 복원 가능한 백업, 콘솔 접근 및 복귀 절차가 확보됩니다.
2단계: .20 기반 환경 구축
- 저장장치를 준비하고 실제 UUID로 /srv/data와 필요한 bind mount를 구성합니다.
- OS 방침을 확정한 후 시간대 Asia/Seoul, 시간 동기화, 계정 UID/GID, 제한된 SSH·방화벽을 설정합니다.
- 기존과 호환되는 Docker·Compose·CIFS 도구·Tailscale을 설치합니다.
- 신규 Tailscale 노드로 등록합니다. 기존 tailscaled.state나 machine-id, SSH 호스트 개인키를 무조건 복제하지 않습니다.
- Docker 네트워크의 이름·서브넷·internal/egress·별칭을 복원합니다. LAN·VPN과 충돌하지 않는지 확인합니다.
- 소스·설정·secrets·이미지·영구 볼륨을 복원합니다. 비밀 파일은 내용 노출 없이 권한과 체크섬으로 검증합니다.
- 기존 서버와 같은 경로 구조를 유지해 애플리케이션 설정 변경을 최소화합니다.
- 워커·자동 발행·추천·동기화·백업·빌드 트리거·외부 전송은 비활성 상태로 준비합니다.
완료 조건: .20에서 별도 DB와 파일을 사용하는 환경이 마련되고, 운영 외부 시스템을 변경하는 작업은 실행되지 않습니다.
3단계: 1차 복사와 비공개 복원 검증
기존 .10은 계속 운영하고, .20은 복원본으로 시험합니다.
- 파일은 선택된 실제 원본 경로에 대해 소유권·ACL·xattr·심볼릭 링크·하드링크를 보존하여 1차 동기화합니다. 예: 승인된 경로별 rsync -aHAX --numeric-ids. 드라이런을 먼저 확인하고, --delete는 기본 사용하지 않습니다.
- 실행 중 DB 파일·NPM SQLite·변동 중 인증 볼륨은 일반 파일 복사로 정합성을 보장하지 않습니다.
- MariaDB는 동일 버전에서 논리 덤프·복원 리허설을 기본으로 합니다. 모든 애플리케이션 DB, 계정·권한·DEFINER, 트리거·루틴·이벤트를 포함한 복원 절차를 준비합니다.
- InnoDB는 single-transaction을 사용할 수 있지만, 비트랜잭션 테이블과 DDL이 있으면 별도 잠금·쓰기 중지가 필요합니다. DB 간 논리 관계도 같은 시점에 맞춥니다.
- 새 DB의 event_scheduler와 앱 자동 작업은 꺼둡니다. 운영용 외부 DB나 공유 데이터 경로를 실수로 사용하지 않는지 확인합니다.
- NPM SQLite는 온라인 backup API 등 정합성 있는 백업으로 시험하고, 최종 전환에서는 NPM 중지 후 data·letsencrypt를 일관된 상태로 복사합니다.
- NPM의 81 포트는 원래 .10 및 Tailscale 주소에 묶여 있습니다. 리허설용 Compose override로 .20·신규 Tailscale 주소에만 매핑합니다. 관리 포트를 인터넷 전체에 열지 않습니다.
- 시험 PC의 hosts 또는 curl --resolve로 실제 도메인을 .20에 연결합니다. Host·SNI·인증서·라우팅을 함께 검증하며 공용 DNS는 아직 바꾸지 않습니다.
- 신규 환경에서는 외부 발송·발행·DNS 인증서 갱신·CI 실행·실제 webhook 호출을 차단하거나 비활성화합니다.
- 각 사이트 로그인·읽기·검색·업로드·다운로드·영상/음악·관리자 저장·이미지·통계·예약 기능을 시험합니다. 시험 데이터는 복원본에만 기록합니다.
- 로그와 HTTP 상태, DB 레코드 수·핵심 해시, 파일 수·크기·checksum, 권한과 링크를 비교합니다.
- 재부팅 리허설을 수행하고 마운트 → DB → PHP/Apache → NPM 순서와 자동 기동을 확인합니다.
- 인증서는 재발급을 남발하지 않고 복원한 기존 인증서로 시험합니다.
리허설에서 생성한 DB와 시험 데이터는 최종 전환 원본으로 쓰지 않습니다. 최종 복원 시 구 서버의 정지 시점 데이터로 다시 구성합니다.
MariaDB 덤프의 single-transaction 및 루틴·이벤트 옵션은 공식 설명을 기준으로 실제 테이블 엔진에 맞게 사용합니다. MariaDB mariadb-dump 문서
NPM은 /data와 /etc/letsencrypt를 영구 저장 경로로 사용합니다. 프록시 DB만 옮기고 인증서를 누락하면 안 됩니다. Nginx Proxy Manager 공식 설정
4단계: 전환 직전 쓰기 동결과 최종 동기화
- 유지보수 시간, 담당자, 장애 판단 기준과 복귀 마감 시간을 확정합니다. 두 서버의 로컬 콘솔 또는 대체 접속 경로를 확보합니다.
- 모든 사이트 저장소의 운영 배포·진행 중 Actions를 확인하고, 새 배포가 들어오지 않게 일시 중지합니다. 이미 실행 중인 배포도 종료·정리합니다.
- 포스팅봇의 신규 claim을 중지하고 현재 작업은 완료를 기다립니다. 동기화 outbox 처리·빌드·백업·실행기 작업도 상태를 정리합니다. 작업 DB를 임의 삭제하거나 두 서버에서 같은 작업을 실행하지 않습니다.
- 모든 도메인의 변경 요청, 파일 업로드, webhook·API·직접 백엔드 접근까지 쓰기를 차단합니다. 프록시의 안내 화면만으로 다른 경로의 쓰기가 차단됐다고 가정하지 않습니다.
- DB 이벤트·cron·systemd 자동 작업·컨테이너 worker 등 모든 쓰기 주체를 중지합니다.
- 최종 시점의 DB 덤프를 생성하고 신규 DB에 복원합니다. 테이블 엔진별 정합성, 권한·루틴·이벤트를 검증합니다.
- 변경된 첨부·미디어·세션·작업 파일을 최종 증분 복사합니다. 두 서버의 DB와 파일이 같은 동결 시점 기준이어야 합니다.
- NPM과 인증 관련 소비자를 중지한 상태에서 프록시 데이터·인증서·필요한 인증 볼륨의 마지막 상태를 동기화합니다.
- 구 서버의 작업 큐·실행 중 claim·outbox 상태와 최종 복원 checksum을 기록합니다.
- 신규의 핵심 데이터와 구 서버가 일치하는지 확인합니다. 일치하지 않으면 IP 전환을 진행하지 않습니다.
중단 시간을 더 줄여야 한다면 사전에 MariaDB 복제 및 최종 catch-up을 별도로 설계할 수 있습니다. 현재 DB 원시 디렉터리 약 1.3GB라는 이유만으로 복잡한 복제를 기본 도입하지는 않습니다. 리허설에서 덤프·복원·검증 시간을 재어 결정합니다.
5단계: 구 서버 격리 → 신규 .10 인계
동일 LAN에서 두 서버가 동시에 192.168.10.10을 사용하지 않도록 합니다.
- 기존 서버를 전원 종료하거나 네트워크에서 물리적으로 격리합니다. 운영 컨테이너와 자동 시작 작업도 정지 상태를 확인합니다.
- 복귀용 주소가 필요하면 미사용 여부를 확인한 임시 IP를 별도로 정합니다. 임의 주소를 확정하지 않습니다.
- .10에 대한 ARP 충돌 검사와 기존 서버 격리를 확인합니다.
- 신규 서버의 enp2s0 주소를 .20/24에서 .10/24로 바꿉니다. 게이트웨이 192.168.10.1과 DNS 설정을 유지합니다.
- Netplan 구문 검사와 로컬 콘솔 복구 수단을 마련합니다. 자동 복구 타이머를 쓴다면 신규가 .20으로 되돌아갈 뿐이며, 구 서버를 자동 재가동시키지 않습니다.
- 신규 서버의 변경된 MAC을 공유기·스위치·클라이언트가 학습하도록 ARP 갱신을 확인합니다. 필요할 때만 해당 캐시·DHCP 예약·MAC 고정 정책을 수정합니다.
- 공유기 포트포워딩이 .10을 가리키고 공인 IP가 같다면 목적지 IP를 유지할 수 있습니다. 다만 실제 외부망에서 HTTP·HTTPS가 신규 MAC의 서버로 도달하는지 확인해야 합니다.
- 공인 IP·도메인이 같으면 A 레코드 변경은 보통 필요하지 않습니다. IPv6 주소나 AAAA 레코드를 쓰는 경우에는 별도로 전환·검증합니다.
- NPM의 임시 .20 포트 설정을 최종 .10으로 되돌리고 신규 Tailscale 주소 바인딩은 유지합니다.
- 신규에서 DB → 웹 런타임 → NPM을 올리되, 쓰기 및 워커는 아직 차단합니다.
- 외부망에서 모든 NPM 프록시, HTTPS 인증서, 기본 페이지, 관리자 로그인과 내부 NAS upstream을 확인합니다.
- GitHub 배포 대상을 신규 Tailscale 주소로 바꾸고 신규 SSH 호스트 지문을 고정합니다. 새 호스트 지문을 검증 없이 수락하거나 기존 키 검사를 무력화하지 않습니다.
- 쓰기를 허용하기 전 최종 진행 여부를 판단합니다. 핵심 오류가 있으면 아래 복귀 절차를 사용합니다.
- 검증 통과 후 웹 쓰기, 동기화, 포스팅봇, 예약 작업, 빌드 트리거, 백업을 순차적으로 재개합니다. 각 기능은 신규 서버 한 곳에서만 실행합니다.
6단계: 운영 안정화와 교체 종료
- 공개 서비스 재개 후 실제 관리자 저장·업로드 1건과 사본 동기화를 확인합니다.
- GitHub를 통한 실제 소스 배포 1회를 대상 사이트별 절차에 따라 확인합니다. 배포 스크립트의 새 디스크 UUID 검사와 SSH 지문 검증도 통과해야 합니다.
- 포스팅봇 인증, 모델 설정, 신규 claim, 체크포인트·완료 저장·사용량 로그가 유지되는지 확인합니다.
- 당일 예약 발행·자동 추천·백업을 한 주기 확인합니다. 중단 중 놓친 작업의 재처리는 중복 실행 여부를 먼저 확인합니다.
- CPU·메모리·swap·디스크 I/O·웹 지연·DB 오류·NPM 502/504를 관찰합니다.
- 프록시 자동 시작 서비스가 신규 Tailscale과 443 Serve 상태에서도 정상 동작하는지 확인합니다.
- 서비스 재개 이후 재부팅 검증이 필요하면 별도 유지보수 시점에 수행합니다. 사전 리허설 검증을 실제 운영 재부팅 검증으로 대신 보고하지 않습니다.
- 구 서버는 최소 7~14일 또는 한 번 이상의 정상 백업·복원 검증 완료까지 데이터가 유지된 격리 상태로 보존합니다.
- 안정화 이후에만 구 서버 재사용·디스크 정리 여부를 별도로 결정합니다.
6. Tailscale·GitHub·SSH 전환의 핵심
현재 ZALNAS 등 여러 저장소의 workflow는 LAN .10이 아니라 100.106.224.48로 배포합니다. NPM 관리 포트도 이 주소에 묶여 있습니다. 따라서 신규 서버가 LAN .10을 가져도 Tailscale 주소·SSH 호스트 지문은 자동 인계되지 않습니다.
기본안은 신규 Tailscale 노드의 새 주소를 사용하고 모든 관련 저장소의 배포 설정과 모니터링을 갱신하는 것입니다.
- 신규 노드 이름, 태그, ACL, 승인·키 만료 정책을 확인합니다.
- 현재 authorized_keys에 필요한 배포 공개키를 신규 계정에 등록합니다. 개인키를 계획서나 Git으로 옮기지 않습니다.
- Tailscale Serve/Funnel을 사용하는 경우 신규 노드 기준 설정과 URL 영향을 따로 확인합니다.
- 프록시 기동 서비스와 비밀 환경파일·접속 제한도 함께 이전합니다.
- 구 서버로 남은 배포 요청이 실행되지 않도록 전 저장소의 전환 완료를 확인합니다.
- Tailscale 상태 파일에 노드 식별용 개인키가 포함되므로 파일 복제로 동일 노드를 두 서버에서 동시에 실행하지 않습니다. 암호화 상태는 하드웨어 의존성도 있을 수 있습니다. Tailscale 상태 파일 설명
- 기존 LAN 주소에 접속하던 SSH 클라이언트는 호스트 키 변경 경고가 정상적으로 발생할 수 있습니다. 새 지문을 콘솔 또는 별도 신뢰 경로로 대조한 뒤 해당 주소의 known_hosts 항목만 갱신합니다.
7. 전환 합격 기준
| 분야 |
통과 기준 |
| 데이터 |
동결 시점 기준 DB·첨부의 누락·손상 없음, 주요 테이블·권한·문자셋·루틴 확인 |
| 컨테이너 |
의도한 18개 프로젝트 및 서비스 상태 재현, 자동 기동과 healthcheck 정상 |
| 네트워크 |
.10 단일 소유, gateway·DNS·NAS 접근 정상, Docker 네트워크 충돌 없음 |
| NPM |
18개 프록시 설정 및 인증서 대응, HTTP/HTTPS·리디렉션·접근 제한 정상 |
| 보안 |
DB 3306은 기존 localhost 제한, NPM 81은 LAN/VPN 제한 유지 |
| 웹 |
주요 사이트 공개·관리자·로그인·검색·업로드·미디어·통계 정상 |
| 콘텐츠 |
기존 ID·slug·발행 상태·예약 시간·품질 점수와 작성 지침 보존 |
| 자동 작업 |
워커·예약·동기화·백업·트리거가 단일 서버에서 정상 실행 |
| 배포 |
신규 VPN 주소·SSH 지문·스토리지 안전 검사를 사용한 실제 배포 성공 |
| 성능 |
합의한 피크 부하 기준 충족, OOM·지속 swap 증가·지연 악화 없음 |
| 복구 |
이전 시점 백업과 구 서버가 보존되고 실제 복원 리허설 통과 |
조사에서 컨테이너 수가 같다는 것만으로 합격 처리하지 않습니다. 기능·데이터·접속 경계·자동 작업까지 확인합니다.
8. 장애 시 복귀 계획
8.1 신규 서비스의 쓰기를 열기 전
쓰기 동결 이후 새 데이터가 생기지 않았다는 조건에서는 비교적 단순하게 복귀할 수 있습니다.
- 신규의 NPM·앱·워커와 외부 요청을 차단합니다.
- 신규의 .10 주소를 제거하거나 신규 NIC를 격리합니다.
- .10이 한 서버에만 존재하는지 확인합니다.
- 기존 서버의 .10과 기존 프록시·DB·웹을 복원합니다.
- 변경했던 GitHub 배포 주소·SSH 지문·VPN·네트워크 정책을 기존 값으로 복구합니다.
- 쓰기·워커를 기존 서버에서만 재개하고 외부망에서 확인합니다.
8.2 신규 서비스에서 쓰기를 허용한 후
구 서버를 다시 켜는 것만으로 복구하면 신규에서 작성된 글·회원·조회수·파일·작업 상태가 유실될 수 있습니다.
- 신규·구 서버 모두 쓰기를 차단하고 신규의 최신 DB·첨부·작업 상태를 안전하게 백업합니다.
- 신규에서 발생한 변경분과 외부 발송·예약 실행·동기화 결과를 파악합니다.
- 동일 DB 버전·스키마 호환성을 확인한 뒤 신규의 최신 일관된 DB·파일을 구 서버에 복원하는 방식을 우선 검토합니다. 필요한 경우 추가 중단 시간을 확보합니다.
- 외부에 이미 반영된 작업은 멱등키·작업 ID·발행 상태로 대조해 중복 실행을 막습니다.
- 데이터 정합성을 확인한 다음에만 구 서버에 .10을 반환합니다.
- 단순 역방향 rsync로 DB와 파일을 무조건 덮어쓰지 않습니다.
9. 예상 일정과 중단 시간
| 구간 |
계획상 시간 |
비고 |
| 조사·저장장치·OS 결정 |
반나절~1일 |
장비 조달 시간 별도 |
| 기반 구성·1차 복원 |
반나절~1일 |
전송량·설치 상태에 따라 달라짐 |
| 기능·부하·재부팅 리허설 |
반나절 이상 |
장애 수정과 재검증 포함 |
| 최종 전환 유지보수 |
우선 30~60분 창 확보 |
리허설 측정 후 확정, 보장 시간 아님 |
| 안정화 관찰 |
최소 24~48시간 |
구 서버 보존은 7~14일 이상 권장 |
전체 시간을 줄이려면 .10을 계속 서비스하면서 .20에 대부분을 미리 복원합니다. 최종 중단에는 쓰기 동결 → DB 최종 복원 → 변경 파일 전송 → IP 인계 → 핵심 검증만 남깁니다.
목표 RPO는 쓰기 동결 시점 기준 0입니다. 이것은 단일 쓰기 주체, 최종 DB·파일 일관성, 검증 성공이 전제입니다. 읽기 요청까지 포함한 완전 무중단을 보장하지 않습니다. 신규 쓰기 허용 이후의 복귀는 별도 정합성 작업이 필요합니다.
10. 전환 전에 채울 실행 기록
- 작업 책임자 / KST 전환 시각 / 중단 가능 시간:
- 신규 OS 유지 또는 재설치 결정:
- 신규 OS·Docker 디스크 / 데이터 디스크 / 백업 위치:
- 신규 /srv/data 실제 UUID 및 표식:
- 신규 Tailscale 주소 / 태그·ACL / Serve 설정:
- 신규 SSH 호스트 키 지문:
- 변경할 저장소별 workflow와 배포 설정 목록:
- 실제 파일 복사 매니페스트와 제외 경로:
- DB별 엔진·덤프·복원·검증 방식:
- NPM 인증서 갱신 방식·외부 DNS 연동:
- 콘솔 접근 방법 및 구 서버 복귀 방법:
- 리허설 실측 시간 / 부하 테스트 결과:
- 유지보수 중단·복귀 판단 담당자:
- 마지막 백업 위치·checksum 및 복원 시험 결과:
실제 비밀번호·토큰·인증서 개인키는 이 문서에 기록하지 않습니다.
11. 실행 체크리스트
- [ ] 신규 저장공간과 성능 기준 충족
- [ ] 전체 서비스·비실행 환경·외부 NAS·자동 작업 매니페스트 완료
- [ ] 독립 백업 및 복원 리허설 완료
- [ ] .20에서 전 도메인·인증서·관리자 기능 검증
- [ ] 신규 Tailscale·SSH 지문·GitHub 변경 준비 완료
- [ ] 새 UUID에 맞는 배포·백업 안전 검사 준비 완료
- [ ] 실행 중 작업 drain 및 모든 쓰기 주체 동결
- [ ] 최종 DB·파일·NPM·인증 상태 동기화 및 검증
- [ ] 기존 서버 격리 후 신규 .10 단독 인계
- [ ] 외부망 및 NAS upstream 정상 확인
- [ ] 쓰기·워커·자동 작업 단일 서버에서 순차 재개
- [ ] 신규 백업·배포 성공, 구 서버 보존
이 계획서 작성 과정에서는 두 서버의 운영 구성·DB·컨테이너를 변경하지 않았습니다. 앞서 승인받아 완료한 신규 서버 .39 → .20 IP 변경은 이번 이전 실행과 별개의 완료 작업입니다.