보조 개발 서버 구성

개요

보조 개발 서버의 RHEL 설치를 완료했다.
[[development-environment.canvas]]를 기반으로 기존 주 서버를 백업하고 지원하는 보조 서버를 구성한다.

하위 태스크

  • 보조 서버를 주 서버의 백업 서버로 구성

    • RHEL 9.8 설치·등록 상태 및 하드웨어 확인
    • leedo2 SSH 키 로그인 확인
    • leedo2 관리 권한·SSH 보안·호스트명 구성
    • 주 서버 전용 백업 키와 내부망 연결 구성
    • 프로젝트·사용자 설정·PostgreSQL 논리 백업 자동화
    • 일일 스냅샷 7개 보존 및 systemd 타이머 구성
    • 최초 백업 실행 및 실제 DB 복원 시험
    • 운영·복구 절차와 검증 결과 기록
  • 보조 서버를 1단계 웜 스탠바이 웹서버로 구성

    • 보조 서버 상시 가동·비상 웹서비스 평시 중지 원칙 결정
    • 주 서버 HTTP·Docker 헬스 판정 기준 확정
    • 보조 서버에 60초 주기 systemd 헬스체크 구성
    • 5회 연속 장애 시 정적 비상 웹서비스 자동 기동
    • 장애·기동·복구 Discord 알림 연결
    • 장애 상태에서 신규 백업을 막고 마지막 정상본 보존
    • 자동 failback 금지와 수동 승인 절차 검증
    • 공유기에서 주·보조 서버 DHCP 고정 할당
  • 2단계 외부 트래픽 전환·수동 복구 구성

    • 별도 보조 Cloudflare Tunnel과 DNS 전환 방식 확정
    • doowiki.dev Zone 자동 전환·카나리·수동 failback 검증
    • webtoonsearch.com·celebinsight.org Zone 제한 토큰 추가 및 카나리 검증
    • PostgreSQL 일일 백업 유지·허용 RPO 최대 24시간으로 확정
    • 일일 백업 기반 비파괴 수동 복구 훈련·fencing 절차 확정

백업 정책

  • 주 서버: doo-rhel (192.168.0.35)
  • 보조 서버: doo-backup (192.168.0.28)
  • 실행 방식: 보조 서버가 내부망 SSH로 주 서버 데이터를 가져오는 pull 백업
  • 백업 대상: /data/project, 핵심 /data/leedo-home 데이터, 실행 중 PostgreSQL 전체 논리 덤프, Docker·시스템 메타데이터
  • 제외 대상: node_modules, 빌드 산출물, 캐시, IDE·개발도구 런타임처럼 재생성 가능한 대용량 데이터
  • 보존 정책: 변경분 하드링크 기반 일일 스냅샷 7개
  • 검증 정책: 백업마다 gzip·체크섬 검증, 최초 구성 시 PostgreSQL 임시 컨테이너 복원 시험

구성 결과

완료일: 2026-09-19

  • 보조 서버 호스트명: doo-backup
  • OS: Red Hat Enterprise Linux 9.8, FIPS 모드 활성
  • 관리 계정: leedo2 (wheel, 키 로그인, 무암호 sudo)
  • DHCP 예약: doo-backup 192.168.0.28 / 00:d8:61:73:97:9d, doo-rhel 192.168.0.35 / f4:4d:30:b9:ae:ac
  • 외부 접속: ssh doo-backup-external
  • SSH 정책: 공개키 인증만 허용, 비밀번호·키보드 대화형 인증 비활성화
  • 주 서버 연결: 전용 RSA-4096 키, 192.168.0.28 출발지 제한, strict host key checking
  • 백업 루트: /srv/backups/doo-rhel (root:root, 권한 0700)
  • 자동 실행: doo-primary-backup.timer, 매일 03:20 KST + 최대 10분 임의 지연
  • 보존: 변경분 하드링크 기반 최근 7개 일일 스냅샷
  • 여유 공간 보호: 가용 공간이 10GiB 미만이면 새 백업을 시작하지 않음
  • 서비스 자원 보호: 낮은 CPU·I/O 우선순위, MemoryHigh=2G

웜 스탠바이 운영 모델

보조 서버 자체는 항상 켜 둔다. 평상시에는 백업과 주 서버 헬스체크만 수행하고, CPU·메모리를 사용하는 비상 웹서비스는 중지 상태로 유지한다.

보조 서버 상시 가동
  ├─ 매일 주 서버 백업
  ├─ 일정 주기로 주 서버 헬스체크
  │
  ├─ 정상
  │    └─ 비상 웹서비스 중지 상태 유지
  │
  └─ 연속 장애 판정
       ├─ 장애 알림
       ├─ 비상 웹서비스 기동
       └─ 준비된 방식으로 외부 트래픽 전환

헬스체크 운영 설정

  • 주기: 60초마다 확인한다.
  • 판정: 5회 연속 실패했을 때 장애로 판단한다. 단발성 ping 실패로 전환하지 않는다.
  • 확인 계층: 전용 SSH로 주 서버 내부에 접속해 호스트명, Docker, cloudflared, HTTP 3개, Cloudflare 원본 경로 3개, study 내부·외부 인증 경로, 핵심 컨테이너 4개를 확인한다.
  • 정상 기준: cloudflared가 실행 중이고 HTTP 3개 중 2개 이상, 외부 원본 경로 3개 중 2개 이상, study 내부·외부가 모두 예상 401, 핵심 컨테이너 4개가 모두 정상이어야 한다.
  • 장애 시: 최신 정상 백업을 보존하고 신규 pull 백업은 시도하지 않는다.
  • 복구 시: 주 서버가 다시 응답해도 자동 failback하지 않고, 데이터 상태를 확인한 후 수동 전환한다.
  • split-brain 방지: 주 서버와 보조 서버가 동시에 쓰기 가능한 상태가 되지 않도록 한다.

1단계 구성 결과

  • 헬스체크 타이머: doo-primary-health.timer 활성화
  • 상태 확인: doo-primary-health.service, 매 60초 실행
  • 비상 웹: doo-emergency-web.service, 평상시 disabled/inactive
  • 비상 페이지: Nginx 컨테이너, 보조 서버 TCP 80, LAN http://192.168.0.28/
  • 자동 기동: 5회 연속 실패 시에만 실행
  • 알림: 기존 Alertmanager용 Discord 웹훅을 root 전용 파일로 연결
  • 백업 연동: 상태가 healthy가 아니거나 마지막 성공이 180초보다 오래되면 새 백업 중단
  • 복구 대기: 주 서버 회복 시 recovered-awaiting-failback으로 전환하고 비상 웹 유지
  • 수동 복귀: 운영자가 정상 상태를 확인하고 승인해야 비상 웹 종료

외부 트래픽 전환 구성

  • 주 Tunnel: doo-blog (568d4e19-2e7c-4aa3-b443-7df39efde252)
  • 보조 Tunnel: doo-backup-emergency (89fb9dd7-30ab-4ed8-afe2-21512360a169)
  • 보조 connector: FIPS 빌드 cloudflared 2026.9.1, doo-emergency-tunnel.service, 평상시 inactive
  • 전환 방식: 5회 연속 장애 시 비상 Nginx와 보조 Tunnel을 시작하고 Cloudflare DNS CNAME을 보조 Tunnel로 변경한다.
  • 복구 방식: 주 서버가 회복해도 DNS를 보조 Tunnel에 유지하며, --ack-failback 승인 시에만 주 Tunnel로 복귀한다.
  • 엣지 전파 보호: failback 시 3회 연속 외부 검증 후 60초 동안 보조 Tunnel을 유지하고 다시 3회 검증한 뒤 종료한다.
  • 식별 방법: 보조 페이지 /healthz는 doo-backup-emergency ok, 모든 응답은 X-Doo-Origin: doo-backup-emergency를 반환한다.
  • 현재 자동 전환 대상: webtoonsearch.com, doowiki.dev, www.doowiki.dev, celebinsight.org, www.celebinsight.org, study.doowiki.dev
  • 기존 리다이렉트 유지: www.webtoonsearch.com은 Cloudflare의 308 → webtoonsearch.com 규칙을 그대로 사용하므로 DNS 전환 대상에서 제외한다.
  • 운영 제외: sentence.doowiki.dev, newsorder.doowiki.dev는 운영하지 않기로 결정해 Cloudflare DNS·Tunnel ingress와 주 서버 런타임에서 제거했다.
  • 인증 분리: doowiki.dev는 Zone 한정 cert.pem, webtoonsearch.com·celebinsight.org는 두 Zone의 DNS:편집만 허용된 사용자 API 토큰을 사용한다.
  • 토큰 보관: /etc/doo-primary-health/cloudflare_dns_token, root:root, 권한 0600; API 검증 결과 active, 노출 Zone은 정확히 2개다.
  • 레코드 보호: API 전환은 같은 이름의 TXT를 유지하고 A/AAAA/CNAME 라우팅 레코드 한 건만 Tunnel CNAME으로 변경한다. 라우팅 레코드가 여러 건이면 중단한다.
  • Zone 보호: 전환 스크립트는 DNS_ZONE 또는 API_DNS_ZONES에 명시되지 않은 호스트 변경을 거부한다.
  • 반복 검증: 세 failover-test.* 카나리 레코드는 평상시 주 Tunnel을 가리키며, 운영 호스트를 건드리지 않고 재시험할 수 있다.

newsorder·sentence 운영 종료

  • 종료일: 2026-09-19
  • 공개 경로: sentence.doowiki.dev, newsorder.doowiki.dev CNAME과 주 Cloudflare Tunnel ingress 삭제
  • 애플리케이션: PM2의 newsorder-local-web, newsorder-reddit-scraper 등록 삭제 후 시작 목록 저장
  • 배치: Reddit 수집 timer 4개와 service template을 비활성화하고 /home/leedo/.local/share/retired-services/newsorder-20260919/systemd-user/로 이동
  • 데이터베이스: newsorder-postgres의 재시작 정책을 no로 변경하고 컨테이너 중지
  • 보존 범위: /data/project/side/pythonparsing 프로젝트 파일과 newsorder_postgres_data Docker volume은 삭제하지 않음
  • 최종 백업: 20260919T162226+0900/databases/newsorder-postgres.sql.gz의 gzip·SHA-256 검증 통과
  • 영향 분리: study.doowiki.dev는 별도 127.0.0.1:3137 origin과 CNAME을 유지하며 외부 401 정상 응답 확인
  • 감시 강화: 보조 서버 헬스체크에 study 내부·외부 401을 독립 필수 조건으로 추가

상태 전이 검증

  1. 정상 주 서버: healthy, 비상 웹 중지
  2. 강제 실패 1~4회: degraded, 비상 웹 중지
  3. 강제 실패 5회: emergency, 비상 웹 기동
  4. 주 서버 회복: recovered-awaiting-failback, 비상 웹 유지
  5. 복구 대기 중 수동 백업: health gate가 차단
  6. 수동 failback 승인: healthy, 비상 웹 종료
  7. Cloudflare 카나리 강제 장애: failover-test.doowiki.dev, failover-test.webtoonsearch.com, failover-test.celebinsight.org가 모두 보조 Tunnel의 HTTPS 응답과 식별 헤더 반환
  8. 세 Zone 카나리 DNS: 보조 Tunnel ID로 전환된 뒤 각 Zone의 주 Tunnel ID로 복귀
  9. 주 서버 회복: 카나리는 보조 Tunnel을 계속 사용하고 자동 failback하지 않음
  10. 수동 승인: 60초 엣지 전파 유예 후 카나리가 주 Tunnel 404로 복귀, 비상 웹·Tunnel 중지

비상 페이지의 localhost·LAN·Cloudflare 외부 HTTPS 응답과 systemd 격리 환경의 정기 헬스체크 실행까지 확인했다. 최초 로컬 상태 전이 시험에서는 Discord 알림을 발송하지 않았고, 외부 카나리 통합 시험에서는 연결된 알림 경로를 포함해 검증했다.

비상 웹서비스 단계

  1. 1단계 — 안내 페이지: 데이터 쓰기가 없는 정적 장애·점검 안내 페이지를 자동 기동한다.
  2. 2단계 — 제한 서비스: 읽기 전용 또는 제한된 기능만 제공하고 운영자가 전환을 승인한다.
  3. 3단계 — 수동 서비스 복구: 필요한 경우에만 최신 일일 백업 복원과 fencing 확인 후 운영자가 수동 적용한다.

PostgreSQL RPO 결정

  • 결정일: 2026-09-19
  • 선택: PostgreSQL 스트리밍 복제나 WAL 전달을 구성하지 않고 현재 일일 논리 백업을 유지한다.
  • 백업 시각: 매일 03:20 KST, 최신 정상 스냅샷 7개 보존
  • 허용 RPO: 장애 시 최대 약 24시간의 데이터 손실을 허용한다.
  • 장애 대응: 비상 안내 페이지는 자동 전환하지만 실제 DB 서비스는 자동 기동하지 않는다. 필요 시 최신 정상 덤프를 보조 서버에 수동 복원한다.
  • 재검토 조건: 최대 24시간 손실을 허용할 수 없게 되거나 무중단에 가까운 서비스 전환이 필요해질 때 복제 방식을 다시 검토한다.

일일 백업 기반 수동 복구 훈련

  • 훈련일: 2026-09-19
  • 방식: 운영 서비스와 트래픽을 건드리지 않고 보조 서버의 격리된 임시 Podman 컨테이너에 실제 덤프를 복원한다.
  • 복원 대상: 최신 정상 스냅샷 20260919T162226+0900의 practical-coach-private-postgres.sql.gz
  • 사전 검증: SHA-256 체크섬 통과
  • 복원 환경: 원본과 같은 postgres:17-alpine, 별도 복원 관리자 계정 사용
  • 결과: 복원 성공, 비템플릿 데이터베이스 3개 확인, 임시 컨테이너 자동 삭제 확인
  • 운영 영향: 주 서버 DB·웹서비스·Cloudflare 트래픽 변경 없음

실제 장애에서 쓰기 서비스를 복구할 때는 아래 순서를 지킨다.

  1. 주 서버의 전원·네트워크 또는 쓰기 애플리케이션을 차단한다.
  2. SSH·헬스체크·Cloudflare 원본 경로를 통해 주 서버가 더 이상 쓰기 요청을 처리하지 않음을 확인한다.
  3. 최신 정상 백업의 체크섬을 검증하고 보조 서버 PostgreSQL에 복원한다.
  4. 데이터 확인 후에만 보조 서버를 새 쓰기 주체로 지정한다.
  5. 실제 애플리케이션을 기동하고 최종 확인 후 Cloudflare 트래픽을 전환한다.
  6. 기존 주 서버는 데이터 재동기화와 운영자 승인 전까지 자동 기동하지 않는다.

현재 운영 정책에서는 자동 전체 서비스 failover를 목표로 하지 않는다. 위 절차는 데이터 복원이 필요한 실제 장애에 사용하는 수동 복구 runbook이다.

트래픽과 데이터 전제조건

  • doowiki.dev, webtoonsearch.com, celebinsight.org 세 Zone은 별도 Cloudflare Tunnel과 DNS 전환 자동화를 통해 외부 요청이 보조 비상 페이지로 유입된다.
  • 세 Zone 카나리에서 보조 응답과 주 Tunnel 복귀를 확인했으며, 운영 호스트의 실제 장애 전환은 동일한 경로를 사용한다.
  • PostgreSQL은 일일 백업 방식을 유지하고 최대 약 24시간의 RPO를 허용하기로 확정했다.
  • 전체 서비스 복구가 필요하면 주 서버 쓰기를 차단한 뒤 최신 정상 덤프를 수동 복원하고, 데이터 확인 후 보조 서버를 쓰기 주체로 승격한다.

최초 검증 결과

  • 최신 검증 스냅샷: 20260919T162226+0900
  • 프로젝트: 6.0GB
  • 사용자 핵심 데이터: 4.5GB
  • PostgreSQL 논리 덤프: 4개, 압축 후 715MB
  • 체크섬: DB 덤프·Docker/시스템 메타데이터 전체 통과
  • 실제 복원 시험: newsorder-postgres 덤프를 PostgreSQL 16 임시 컨테이너에 복원, 데이터베이스 2개 확인
  • 증분 검증: 동일 파일 inode·link count 2 확인, 프로젝트 변경분 12개·약 88KB만 재전송
  • 복원 시험 컨테이너: 검증 후 자동 삭제 확인
  • 디스크: 128GB SSD, 백업 후 약 53GB 여유, SMART overall PASSED

운영 명령

# 타이머와 다음 실행 시각
sudo systemctl status doo-primary-backup.timer
sudo systemctl list-timers doo-primary-backup.timer
 
# 수동 백업
sudo systemctl start doo-primary-backup.service
 
# 최근 실행 로그
sudo journalctl -u doo-primary-backup.service -n 200 --no-pager
 
# 최신 스냅샷
sudo readlink -f /srv/backups/doo-rhel/latest
sudo cat /srv/backups/doo-rhel/latest/MANIFEST.txt
 
# 최신 스냅샷에서 가장 작은 PostgreSQL 덤프를 실제 복원 시험
sudo /usr/local/sbin/doo-backup-restore-test
 
# 웜 스탠바이 상태와 다음 헬스체크
sudo /usr/local/sbin/doo-primary-health-status
sudo systemctl status doo-primary-health.timer
sudo systemctl list-timers doo-primary-health.timer
 
# 즉시 헬스체크
sudo systemctl start doo-primary-health.service
 
# 헬스체크·비상 웹 로그
sudo journalctl -u doo-primary-health.service -n 200 --no-pager
sudo journalctl -u doo-emergency-web.service -n 200 --no-pager
sudo journalctl -u doo-emergency-tunnel.service -n 200 --no-pager
 
# 외부 트래픽 전환 상태
sudo /usr/local/sbin/doo-cloudflare-failover status
 
# 두 Zone 제한 토큰의 상태·노출 범위 확인
sudo /usr/local/sbin/doo-cloudflare-failover verify-token
 
# 주 서버 복구 확인 후 수동 failback 승인
sudo /usr/local/sbin/doo-primary-health-check --ack-failback

주의 및 후속 권장

  • 장기 보류: 외장/원격 저장소를 이용한 3-2-1 사본은 현재 운영에 당장 필요한 항목은 아니므로 보류한다. 화재·도난·동시 전원 장애까지 대비해야 할 필요가 생기면 다시 검토한다.
  • 두 서버는 DHCP 클라이언트 구성을 유지하되 공유기에서 MAC 기준으로 고정 할당했다. 2026-09-19 확인 시 doo-backup은 192.168.0.28, doo-rhel은 192.168.0.35를 유지했고 두 서버 모두 게이트웨이 192.168.0.1 통신이 정상이었다.
  • 백업에는 서비스 설정과 자격 증명이 포함될 수 있으므로 /srv/backups/doo-rhel의 root 전용 권한을 유지한다.
  • 보조 서버는 상시 가동하되 비상 웹과 보조 Tunnel은 평상시 중지한다. doowiki.dev, webtoonsearch.com, celebinsight.org 세 Zone의 자동 외부 안내 페이지 전환은 활성화했다.

프롬프트

/Users/doo-air/Documents/Obsidian/DooBrain/KnowledgeBase/99_TODO/development-environment 를 보고 현재 보조 서버 예정 PC에 접속하여 백업 서버 구성을 완료해야해

해당 작업을 하위 테스트로 만들어서 KnowledgeBase/01_Inbox/보조 개발 서버 구성 하위에 추가해줘

현재 ssh는 ssh -i ~/.ssh/root-rhel -p 2223 [email protected] 로 접속 가능
leedo2 계정으로 사용 가능하도록 셋팅해줘

댓글

첫 번째 댓글을 남겨보세요.