# PBS 구축 → 외부 노출 → 백업 → 복원 전체 가이드 > 날짜: 2026-04-09 > 환경: Proxmox VE 9, PBS 3.4.8, OPNsense Caddy, Headscale VPN ## 1. 전체 아키텍처 ``` [아산힐링탑 PVE2] [pqserver (Dell R740)] CT 201 (shc) ──────┐ CT 130 (PBS) CT 200 (dev) ──────┤ 백업 ├── /mnt/datastore (sdb1, 931GB) CT 106 (neo4j)──────┤──────────→ │ │ │ [태령약국 PVE] │ │ 복원 VM 201 (SERVER)─────┘ └──────→ CT 400, 401, 402 (pqserver local-lvm) 경로: 약국 PVE → 인터넷 → pbs.medivault.co.kr (OPNsense Caddy) → 192.168.1.130:8007 (PBS CT) → /mnt/datastore/backups (sdb1 물리 디스크) ``` ## 2. PBS LXC 구축 (pqserver) ### 2-1. 물리 디스크 확인 + 마운트 ```bash # 미사용 디스크 확인 lsblk -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE # → sdb 931G, sdb1 ext4, 마운트 안 됨 # 기존 데이터 확인 (goodpharm 627MB — 보존) mkdir -p /mnt/pbs-disk mount /dev/sdb1 /mnt/pbs-disk ls /mnt/pbs-disk/ # goodpharm/ 확인 # fstab 영구 마운트 echo 'UUID=2e087ebd-e0bc-437f-b3b2-5c77dd33ad3e /mnt/pbs-disk ext4 defaults 0 2' >> /etc/fstab # PBS 데이터스토어 디렉토리 생성 mkdir -p /mnt/pbs-disk/backups ``` ### 2-2. LXC 생성 ```bash pct create 130 local:vztmpl/ubuntu-24.04-standard_24.04-2_amd64.tar.zst \ --hostname pbs-local \ --cores 2 --memory 1024 \ --rootfs local-lvm:8 \ --net0 name=eth0,bridge=vmbr0,ip=dhcp \ --nameserver 8.8.8.8 \ --password 'trajet6640' \ --unprivileged 0 \ --features nesting=1 ``` **주의**: PBS LXC는 `--unprivileged 0` (privileged). 백업/복원에 root 권한 필요. ### 2-3. TUN 디바이스 (Tailscale용) ```bash # CT 중지 상태에서 echo 'lxc.cgroup2.devices.allow: c 10:200 rwm' >> /etc/pve/lxc/130.conf echo 'lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file' >> /etc/pve/lxc/130.conf ``` ### 2-4. sdb1 마운트 포인트 연결 ```bash pct set 130 -mp0 /mnt/pbs-disk/backups,mp=/mnt/datastore pct start 130 # 확인 pct exec 130 -- df -h /mnt/datastore # → /dev/sdb1 916G 627M 869G 1% /mnt/datastore ``` ### 2-5. Tailscale 설치 + Headscale 등록 ```bash pct exec 130 -- bash -c ' apt-get update -qq && apt-get install -y curl curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/jammy.noarmor.gpg | tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/jammy.tailscale-keyring.list | tee /etc/apt/sources.list.d/tailscale.list >/dev/null apt-get update -qq && apt-get install -y tailscale systemctl enable --now tailscaled tailscale up --login-server=http://head.pharmq.kr \ --authkey= \ --accept-routes --accept-dns=false \ --hostname=pbs-pqserver ' ``` ### 2-6. IP 고정 할당 (100.64.0.100) Headscale은 순차 IP 할당이라 원하는 IP를 직접 지정 불가. **DB 직접 수정** 방식: ```bash # 1. Headscale Docker 중지 docker stop headscale # 2. SQLite DB에서 IP 변경 sqlite3 /srv/headscale-tailscale-replacement/data/db.sqlite \ "UPDATE nodes SET ipv4 = '100.64.0.100' WHERE given_name = 'pbs-pqserver';" # 3. Headscale 재시작 docker start headscale # 4. CT에서 tailscaled 재시작 (새 IP 반영) pct exec 130 -- systemctl restart tailscaled pct exec 130 -- tailscale ip -4 # → 100.64.0.100 ``` ### 2-7. 듀얼 NIC (OPNsense 외부 노출용) ```bash # pqserver 브릿지 확인: vmbr0=192.168.0.x(ipTIME), vmbr1=WAN 직결 # 다른 CT 참고: net1은 vmbr0에 192.168.1.x 고정 IP pct set 130 -net1 name=eth1,bridge=vmbr0,gw=192.168.1.1,ip=192.168.1.130/24,type=veth pct reboot 130 ``` 속도 비교: | NIC | 경로 | Download | Upload | |-----|------|----------|--------| | eth0 (192.168.0.3) | ipTIME | 34 Mbps | 302 Mbps | | eth1 (192.168.1.130) | **OPNsense** | **123 Mbps** | 321 Mbps | ### 2-8. PBS 설치 ```bash pct exec 130 -- bash -c ' apt-get install -y curl wget gnupg2 wget -qO /etc/apt/trusted.gpg.d/proxmox-release-bookworm.gpg \ https://enterprise.proxmox.com/debian/proxmox-release-bookworm.gpg echo "deb http://download.proxmox.com/debian/pbs bookworm pbs-no-subscription" \ > /etc/apt/sources.list.d/pbs.list apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y proxmox-backup-server ' ``` ### 2-9. 데이터스토어 생성 + 비밀번호 설정 ```bash pct exec 130 -- bash -c ' proxmox-backup-manager datastore create pbs-local /mnt/datastore/backups echo "root:trajet6640" | chpasswd ' # 인증 테스트 curl -sk -d "username=root@pam&password=trajet6640" \ https://100.64.0.100:8007/api2/json/access/ticket ``` ### 최종 PBS 정보 | 항목 | 값 | |------|-----| | CT VMID | 130 | | hostname | pbs-local | | LAN IP | 192.168.0.3 (ipTIME) | | OPNsense IP | 192.168.1.130 | | VPN IP | 100.64.0.100 | | 웹 UI | https://pbs.medivault.co.kr (외부) | | 인증 | root@pam / trajet6640 | | 데이터스토어 | pbs-local (869GB) | | 디스크 | /dev/sdb1 (931GB, ext4) | ## 3. OPNsense Caddy로 외부 도메인 노출 ### 3-1. OPNsense Caddy API로 리버스프록시 추가 OPNsense는 GUI로 Caddy 관리. **Caddyfile 직접 수정 금지** (GUI 저장 시 덮어씀). API 사용 (opnsense-admin MCP): ```python from opnsense_api import OPNsense op = OPNsense() # 리버스프록시 추가 op.caddy_add_reverse_proxy('pbs.medivault.co.kr', '192.168.1.130', 8007) # TLS insecure 설정 (PBS 자체 서명 인증서) handles = op.get('caddy/ReverseProxy/searchHandle') for h in handles['rows']: if '192.168.1.130' in h.get('ToDomain', ''): op.post(f'/api/caddy/reverse_proxy/setHandle/{h["uuid"]}', { 'handle': {'HttpTls': '1', 'HttpTlsInsecureSkipVerify': '1'} }) # Caddy 재적용 op.caddy_reconfigure() ``` ### 3-2. OPNsense Caddy TLS 특성 | 항목 | pharmq.kr Caddy (LXC 103) | OPNsense Caddy | |------|---------------------------|----------------| | TLS 챌린지 | **DNS-01** (Cloudflare API) | **HTTP-01** (Let's Encrypt) | | 와일드카드 | ✅ `*.pharmq.kr` | ❌ 불가 | | 서브도메인 | 자동 (와일드카드) | **개별 등록 필수** | | 도메인 | `*.pharmq.kr` | `*.medivault.co.kr` (개별) | ### 3-3. Cloudflare DNS `pbs.medivault.co.kr` → `59.30.154.182` (OPNsense 공인 IP) - ⚠️ **프록시 모드: DNS only** (회색 구름) — HTTP-01 인증서 발급에 필요 ## 4. Fingerprint 이슈 (핵심 트러블슈팅) ### 문제 PVE에서 PBS를 `pbs.medivault.co.kr:443`으로 등록할 때: ```bash pvesm add pbs PBS-PQ \ --server pbs.medivault.co.kr --port 443 \ --fingerprint 83:86:78:49:... # PBS 자체 인증서 fingerprint # → 에러: fingerprint '2D:30:06:82:...' not verified, abort! ``` ### 원인 ``` PVE → HTTPS → Caddy (Let's Encrypt 인증서) → HTTPS → PBS (자체 서명 인증서) ↑ ↑ PVE가 보는 인증서 PBS 자체 인증서 = Caddy 인증서 = 다른 fingerprint ``` PVE가 연결할 때 보는 인증서는 **Caddy의 Let's Encrypt 인증서**이지, PBS 자체 인증서가 아님. 따라서 **Caddy 인증서의 fingerprint**를 사용해야 함. ### 해결 ```bash # Caddy(Let's Encrypt) 인증서 fingerprint 가져오기 echo | openssl s_client -connect pbs.medivault.co.kr:443 2>/dev/null | \ openssl x509 -fingerprint -sha256 -noout | sed 's/.*=//' # → 2D:30:06:82:5D:F7:66:1F:5A:91:01:CD:F6:9B:AB:E4:... # 이 fingerprint로 PVE에 등록 pvesm add pbs PBS-PQ \ --server pbs.medivault.co.kr --port 443 \ --datastore pbs-local \ --username root@pam --password trajet6640 \ --fingerprint 2D:30:06:82:5D:F7:66:1F:... ``` ### ⚠️ 주의: Let's Encrypt 인증서 갱신 시 Let's Encrypt 인증서는 90일마다 자동 갱신. 갱신되면 **fingerprint가 바뀜**. → 모든 PVE에서 PBS 스토리지 재등록 필요. 대안: - VPN(Tailscale) 경유로 직접 연결하면 PBS 자체 인증서 사용 → fingerprint 고정 - Caddy에서 고정 인증서 사용 (권장하지 않음) ## 5. 약국 PVE에서 PBS 등록 + 백업 ### 5-1. PBS 등록 ```bash # Caddy fingerprint 가져오기 FINGERPRINT=$(echo | openssl s_client -connect pbs.medivault.co.kr:443 2>/dev/null | \ openssl x509 -fingerprint -sha256 -noout | sed 's/.*=//') # PVE에 PBS 스토리지 추가 pvesm add pbs PBS-PQ \ --server pbs.medivault.co.kr \ --port 443 \ --datastore pbs-local \ --username root@pam \ --password trajet6640 \ --fingerprint "$FINGERPRINT" # 확인 pvesm status -storage PBS-PQ ``` ### 5-2. 백업 ```bash # CT 백업 vzdump 201 --storage PBS-PQ --mode snapshot --compress zstd \ --notes-template 'CT201-백업설명' # VM 백업 vzdump 201 --storage PBS-PQ --mode snapshot --compress zstd \ --notes-template 'VM201-SERVER-백업설명' ``` ### 5-3. 실측 백업 속도 | 소스 | 대상 | 크기 | 시간 | 속도 | 경로 | |------|------|------|------|------|------| | 힐링탑 CT 201 | PBS-PQ | 1.8 GiB | 2분 6초 | ~110 MB/s | OPNsense 직접 | | 힐링탑 CT 106 | PBS-PQ | ~1 GiB | ~2분 | ~110 MB/s | OPNsense 직접 | | 힐링탑 CT 200 | PBS-PQ | ~10 GiB | 9분 11초 | ~120 MB/s | OPNsense 직접 | | 태령약국 VM 201 | PBS-PQ | 120 GiB | 17분 27초 | ~115 MiB/s | 인터넷 직접 | ## 6. 복원 ### 6-1. CT 복원 (다른 VMID로) ```bash # PBS 백업 목록 확인 pvesm list PBS-Local --content backup | grep 'ct/' # CT 201 백업 → CT 401로 복원 pct restore 401 PBS-Local:backup/ct/201/2026-04-09T15:19:55Z \ --storage local-lvm --unprivileged 1 # 확인 pct status 401 pct config 401 ``` ### 6-2. 복원 시 주의사항 - **hostname 충돌**: 원본과 같은 hostname으로 복원됨 → 시작 전 변경 필요 - **네트워크 충돌**: 같은 DHCP 환경이면 IP 충돌 가능 → 고정 IP 설정 권장 - **Tailscale 충돌**: 원본과 동시에 Tailscale 실행하면 충돌 → 복원된 CT에서 `tailscale logout` 후 재등록 ### 6-3. 실제 복원 결과 ``` pqserver에 복원된 CT: CT 400 ← ct/200 (ubuntu-dev, DEV) 14 GiB CT 401 ← ct/201 (ubuntu-test, shc) 3.5 GiB CT 402 ← ct/106 (neo4j) 1.9 GiB ``` ## 7. 디스크 이식성 sdb1(ext4)에 PBS 데이터스토어가 만들어져있으므로: 1. pqserver에서 sdb 물리적으로 분리 2. 다른 서버에 장착 3. `mount /dev/sdX1 /mnt/datastore` 4. PBS에서 데이터스토어 등록: `proxmox-backup-manager datastore create pbs-local /mnt/datastore/backups` 5. 기존 백업 데이터 모두 인식됨 **PBS 메타데이터가 디스크 안에 저장**되므로 서버가 바뀌어도 데이터 유실 없음. ## 8. 관련 파일/설정 위치 | 항목 | 위치 | |------|------| | PBS CT 설정 | pqserver `/etc/pve/lxc/130.conf` | | PBS 데이터스토어 | `/mnt/pbs-disk/backups` (sdb1) | | PVE fstab | pqserver `/etc/fstab` (sdb1 마운트) | | Headscale DB | `/srv/headscale-tailscale-replacement/data/db.sqlite` | | OPNsense Caddy | OPNsense GUI → Services → Caddy | | opnsense-admin | `/srv/opnsense-admin/` (API 클라이언트) |