백업 작업은 성공으로 끝났는데 복구할 때 파일을 읽을 수 없다면, 백업 완료 후 발생한 저장 장치의 데이터 손상이 원인일 수 있습니다. 스냅샷 개수만 확인하면 작업이 실행되었는지는 알 수 있지만, 데이터 팩이 여전히 온전한지는 입증할 수 없습니다. 스토리지 관리자는 저장소 구조가 올바른지, 모든 데이터를 읽을 수 있는지, 복구한 내용이 기준 데이터와 일치하는지에 대한 세 가지 독립적인 근거를 확보해야 합니다.
이 글에서는 Debian 12, restic, Python 3를 사용해 임시 디렉터리에서 정상 상태 검사와 손상 탐지 실습을 진행합니다. 운영 데이터는 읽지 않고, 디스크를 포맷하거나 기존 백업 저장소를 수정하지도 않습니다. 이 실습은 물리 서버의 파일 백업에 적합합니다. 실행 중인 데이터베이스는 먼저 데이터베이스 자체 기능으로 일관성 있는 백업을 만든 뒤 파일 백업 절차에 포함해야 합니다.
1. 각 검사가 무엇을 입증하는지 먼저 구분하기
restic은 파일 내용을 암호화된 데이터 팩으로 구성하고 스냅샷, 인덱스, 참조 관계를 관리합니다. 일반적인 check는 주로 저장소 구조를 검사하며, check --read-data는 여기에 더해 모든 데이터 팩을 읽고 검증합니다. 두 검사는 서로 대체할 수 없으며, 복구 테스트 역시 생략해서는 안 됩니다.
복구 후 비교는 선택한 스냅샷에서 빈 디렉터리로 예상한 파일을 다시 만들어 낼 수 있는지 확인하는 절차입니다. 저장소에 남아 있는 모든 과거 데이터 팩이 온전한지 확인하는 검사는 아닙니다. 반대로 전체 읽기에 성공했다고 해서 백업할 디렉터리를 올바르게 선택했는지, 제외 규칙이 필요한 데이터까지 제외하지는 않았는지, 애플리케이션 데이터 자체에 일관성이 있는지까지 입증되는 것은 아닙니다.
미러링은 일부 장치 장애가 발생해도 접근을 이어 갈 수 있게 하고, 백업은 과거 버전을 보존하며, 무결성 검사는 이상을 찾아냅니다. 어느 것도 실제 복구를 대신할 수는 없습니다.
OnuCloud의 AMD 물리 서버에는 각각 직접 연결된 1TB NVMe 두 개가 기본 제공되며, 하드웨어 RAID는 없습니다. 같은 서버의 두 디스크에 원본 디렉터리와 백업 저장소를 나누어 두면 단일 디스크 장애의 영향을 일부 분리할 수 있습니다. 하지만 독립된 장애 영역에 보관하는 원격지 백업은 아닙니다. 아래의 로컬 저장소는 작업 절차를 검증하는 용도로만 사용합니다.
2. 반복 가능한 격리 테스트 환경 만들기
먼저 도구를 설치합니다. 이후 실습 명령은 임시 변수가 사라지지 않도록 같은 Bash 세션에서 실행합니다. 설치에는 관리자 권한이 필요하지만, 실습 자체는 일반 계정으로 진행해도 됩니다.
sudo apt-get update
sudo apt-get install restic python3
umask 077
LAB=$(mktemp -d /var/tmp/restic-lab.XXXXXXXX)
export LAB
export RESTIC_REPOSITORY="$LAB/repository"
export RESTIC_PASSWORD_FILE="$LAB/password"
mkdir -p "$LAB/source"
python3 - <<'PY'
import os
import secrets
from pathlib import Path
root = Path(os.environ["LAB"])
(root / "password").write_text(secrets.token_hex(32) + "\n")
(root / "source" / "record.txt").write_text(
"backup verification\n", encoding="utf-8"
)
(root / "source" / "payload.bin").write_bytes(os.urandom(1024 * 1024))
PY
restic version
restic init
(
cd "$LAB"
restic backup source
)
restic snapshots
난수 파일은 테스트 데이터 전체가 아주 작은 크기로 압축되는 것을 막고, 저장소에 실제 데이터 내용이 들어가도록 합니다. 암호 파일은 저장소 디렉터리 밖에 있으므로 저장소를 복사해도 복호화 자격 증명이 함께 복사되지 않습니다.
여기에는 스냅샷이 하나뿐이므로 이후 명령에서 latest를 사용할 수 있습니다. 운영 환경에서는 명확한 스냅샷 ID를 기록해야 합니다. 그렇지 않으면 검사 도중 새 백업이 완료될 때 복구 대상이 바뀔 수 있습니다. 임시 실습용 암호를 만들었다고 해서 정식 키 보관 체계까지 갖춘 것으로 보아서는 안 됩니다. 운영 저장소의 복호화 자격 증명은 별도로 보관하고 필요할 때 확실히 꺼내 쓸 수 있어야 합니다.
3. 전체 읽기와 복구 내용 각각 검증하기
먼저 구조를 검사한 뒤 모든 데이터 팩을 읽습니다. 종료 코드를 별도로 처리하지 않는 파이프라인에 출력을 바로 연결하지 마세요. 마지막 명령이 성공하면서 앞선 검사 실패가 가려질 수 있습니다.
restic check
restic check --read-data
restic restore latest --target "$LAB/restored"
diff -r -- "$LAB/source" "$LAB/restored/source"
네 단계 모두 종료 코드 0으로 끝나야 하며, 정상이라면 diff는 차이점을 출력하지 않습니다. 이번 테스트 데이터에는 일반 파일만 있으므로 내용과 디렉터리 비교만으로 충분합니다. 실제 백업에서는 소유자, 권한, 링크 관계, 애플리케이션에 필요한 확장 속성도 별도로 확인해야 합니다.
복구는 반드시 새로 만든 빈 디렉터리에 수행하고 원본 파일을 덮어쓰지 않아야 합니다. 기존 파일이 백업 누락을 가릴 수 있고, 새로 복구한 데이터가 운영 디렉터리를 오염시킬 수도 있기 때문입니다. 완료 후에는 스냅샷 ID, 도구 버전, 검사 출력, 비교 결과를 보관합니다. ‘작업 성공’ 화면 캡처 한 장만으로는 나중에 어떤 대상을 실제로 복구했는지 확인하기 어렵습니다.
check부터 실패했다면 이후의 정상 검증 절차를 중단하고 당시 상태를 보존해야 합니다. 구조 검사는 통과했지만 전체 읽기에 실패했다면 데이터 팩을 읽을 수 있는지와 내용의 무결성을 중점적으로 조사해야 합니다. 스냅샷 목록이 정상이라는 이유로 저장소를 계속 사용해서는 안 됩니다.
4. 복사본에만 손상을 주고 검사 실패 확인하기
음성 테스트는 검사 절차가 실제로 문제를 찾아내는지 입증하기 위한 과정입니다. 먼저 실습 저장소에 대한 쓰기를 멈춘 뒤 독립적인 복사본을 만듭니다. 하드 링크를 이용한 복사는 금지합니다. 복사본을 수정하면 원본 저장소도 함께 바뀔 수 있기 때문입니다. 다음 명령은 기록 시 복사 기능을 명시적으로 끕니다. 실습 데이터의 양이 작아 두 데이터 사본의 경계를 이해하기 쉽습니다.
cp -a --reflink=never "$LAB/repository" "$LAB/damaged"
python3 - <<'PY'
import os
from pathlib import Path
data = Path(os.environ["LAB"]) / "damaged" / "data"
pack = next(p for p in sorted(data.rglob("*")) if p.is_file())
with pack.open("r+b") as f:
f.seek(pack.stat().st_size // 2)
pos = f.tell()
original = f.read(1)
if not original:
raise RuntimeError("Empty data pack")
f.seek(pos)
f.write(bytes([original[0] ^ 1]))
print(pack.name)
PY
if restic -r "$LAB/damaged" check --read-data \
>"$LAB/damaged-check.log" 2>&1; then
printf '%s\n' 'UNEXPECTED_SUCCESS'
else
printf '%s\n' 'EXPECTED_FAILURE'
fi
cat "$LAB/damaged-check.log"
restic check --read-data
예상 결과는 복사본 검사가 0이 아닌 종료 코드로 끝나고, 로그에 데이터 팩 손상이 표시되는 것입니다. 원본 저장소 검사는 여전히 성공해야 합니다. 스크립트가 출력한 실패 표시만 보지 말고, 실패 원인이 암호 오류, 경로 오류, 권한 부족이 아니라 실제 손상인지도 확인해야 합니다.
이 단계가 입증하는 것은 손상을 발견할 수 있다는 사실이지, 손상을 복구할 수 있다는 사실이 아닙니다. 데이터 팩의 한 바이트가 바뀌면 그 안의 객체 하나 또는 여러 개에 영향을 줄 수 있습니다. 이를 근거로 운영 장애에서 영향을 받는 데이터의 비율을 추산해서는 안 되며, 이 복사본을 다시 백업 대상으로 사용해서도 안 됩니다.
5. 실습 결과를 운영 검사 범위와 주의사항으로 연결하기
전체 읽기는 디스크 처리량을 사용하며, 원격 저장소라면 네트워크 대역폭도 사용합니다. 실행 전에 저장소 크기, 검사 범위, 서비스 부하를 기록하고 부하가 낮은 시간대를 선택한 뒤 서비스 지연 시간을 관찰합니다. 트래픽 무제한이 전송 속도 무제한을 뜻하지는 않으며, 무결성 검사에 자원이 들지 않는다는 뜻도 아닙니다.
대규모 저장소는 데이터 팩을 여러 차례로 나누어 읽을 수 있습니다. 다만 매번 검사한 범위를 기록해 최종적으로 모든 데이터를 포함했는지 확인해야 합니다. 한 번의 표본 검사가 성공했다고 전체 저장소가 검사를 통과한 것으로 기록해서는 안 됩니다. 또한 정리, 복구 등 저장소 내용을 바꾸는 작업과 검사 작업이 계획 없이 동시에 실행되지 않도록 해야 합니다.
손상을 발견하면 먼저 저장소를 수정하는 작업을 일시 중지하고 로그와 복사본을 보존한 다음, 파일 시스템, 장치, 전송 경로를 점검합니다. 복구 가능성을 판단하는 데 필요한 증거가 줄어들 수 있으므로 forget, prune 또는 복구 명령을 즉시 실행하지 마세요.
최종 검증에서는 세 가지 결과를 모두 확보해야 합니다. 정상 저장소의 전체 데이터를 읽을 수 있어야 하고, 지정한 스냅샷을 복구해 내용 비교를 통과해야 하며, 손상된 복사본은 명확히 거부되어야 합니다. 실습 파일은 기록을 보관한 뒤 경로를 직접 확인하고 삭제할 수 있습니다. 암호, 로그, 복구한 테스트 데이터는 모두 통제된 절차에 따라 취급해야 하는 자료입니다.
자주 묻는 질문
restic check가 성공하면 모든 백업 데이터를 읽은 것인가요?
아닙니다. 기본 check는 주로 저장소 구조와 참조 관계를 검사합니다. 모든 데이터 팩을 읽고 검증하려면 --read-data가 필요하며, 격리 복구와 내용 비교는 별도로 수행해야 합니다.
저장소 손상이 발견되면 바로 정리해도 되나요?
먼저 저장소를 변경하는 작업을 멈추고 로그와 저장소 복사본을 보존해야 합니다. 저장 장치와 전송 경로를 점검하기 전에 forget, prune 또는 복구 명령을 실행하지 않습니다.
AMD EPYC 7543P 전용 서버
월 $472부터