Files
infra-troubleshooting/pbs-setup-backup-restore.md
thug0bin 6085c6b186 PBS 구축→외부노출→백업→복원 전체 가이드 문서화
- PBS LXC 구축 (물리 디스크 마운트, Tailscale, 듀얼 NIC)
- OPNsense Caddy API로 pbs.medivault.co.kr 외부 노출
- Fingerprint 이슈: Caddy 경유 시 Let's Encrypt 인증서 fingerprint 사용 필수
- 실측 백업 속도: CT 110MB/s, VM 115MiB/s (OPNsense 경유)
- 외부 약국(태령) 120GB VM 백업 17분
- CT 복원: 다른 VMID로 복원 + 충돌 주의사항
- 디스크 이식성: ext4 디스크 분리 후 다른 서버에서 재사용 가능
- Headscale IP 고정 할당 방법 (DB 직접 수정)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 23:57:40 +00:00

361 lines
11 KiB
Markdown

# 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=<PREAUTH_KEY> \
--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 클라이언트) |