infra
Platform

모듈 맵

[Linux] tar/rsync를 이용한 완벽한 서버 데이터 백업과 증분 백업 가이드

0 / 37 완료

펼치기
0 / 37 완료0%

Linux · 30 / 37

[Linux] tar/rsync를 이용한 완벽한 서버 데이터 백업과 증분 백업 가이드

rsync, tar, 증분 백업 — 랜섬웨어와 디스크 장애에서 데이터를 지키는 체계

🚨INCIDENT ALERT
HIGH

새벽에 개발자가 rm -rf /var/www/html 을 프로덕션 서버에서 잘못 실행했습니다. 백업이 있었지만 마지막 백업이 2주 전이었고, 그 사이 추가된 사용자 데이터는 복구 불가능했습니다. 다른 팀에서는 랜섬웨어에 감염되어 백업까지 같은 서버에 있었기 때문에 모두 암호화됐습니다. 체계적인 3-2-1 백업 전략과 자동화된 rsync 스크립트가 있었다면, 재앙이 아닌 단순한 복구 작업으로 끝났을 겁니다.

서버 백업 & 데이터 보호

데이터 손실은 "만약에"가 아니라 "언제"의 문제입니다. 디스크는 반드시 죽고, 랜섬웨어는 예고 없이 찾아오며, 실수로 rm -rf를 잘못 입력하는 순간은 누구에게나 옵니다. 체계적인 백업은 그 순간을 재앙이 아닌 단순한 복구 작업으로 바꾸어 줍니다.

이 챕터에서는 tarrsync를 중심으로 실전에서 쓰이는 백업 전략을 처음부터 끝까지 구현합니다. 개념만 배우는 것이 아니라, 실제 운영 서버에 바로 배포할 수 있는 자동화 스크립트를 만드는 것이 목표입니다.


1. 백업 전략: 어떤 방식을 선택할 것인가

이번 챕터에서 배울 것
  • 1풀·증분·차등 백업 전략 비교와 상황별 선택 기준
  • 23-2-1 백업 원칙 — 3개 사본, 2가지 미디어, 1개 오프사이트
  • 3tar 아카이브 생성·압축·해제 및 --exclude 고급 옵션
  • 4rsync로 로컬·원격 동기화 및 SSH를 통한 암호화 전송
  • 5하드링크 기반 증분 백업으로 저장 공간 절약
  • 6cron 자동화 스크립트 작성 및 오래된 백업 보존 정책 구현
실습 환경 준비
rsync 설치 확인
rsync --version
백업 대상 디렉토리 용량 확인
du -sh /var/www /home /etc /opt 2>/dev/null
백업 저장 경로 생성
mkdir -p /backup/{daily,weekly,monthly}
cron 데몬 상태 확인
systemctl status crond 2>/dev/null || systemctl status cron
💡개념

풀 백업 vs 증분 백업 vs 차등 백업

풀 · 증분 · 차등 백업 전략 비교와 3-2-1 원칙

매일 밤 서버 전체를 풀 백업하면 200GB씩 쌓입니다. 일주일이면 1.4TB, 한 달이면 6TB가 됩니다. 비용과 시간 때문에 현실적으로 이렇게 할 수 없습니다. 그렇다고 매일 변경분만 백업하면 복구할 때 여러 날치를 순서대로 합쳐야 해서 복구 시간이 길어집니다. 백업 전략은 "저장 비용, 백업 소요 시간, 복구 소요 시간" 세 가지가 트레이드오프 관계에 있고, 서비스의 RPO/RTO 요구사항에 맞춰 선택해야 합니다.

백업에는 세 가지 기본 전략이 있으며, 각각 장단점이 다릅니다.

풀 백업 (Full Backup)

전체 데이터를 매번 새로 복사하는 방식입니다.

  • 장점: 복구가 단순하고 빠름. 백업 파일 하나면 복구 완료
  • 단점: 저장 공간을 가장 많이 차지함. 백업 시간도 가장 오래 걸림
  • 적합한 상황: 데이터 양이 적거나, 주간 기준 백업
일요일: [전체 100GB 백업]
월요일: [전체 100GB 백업]  ← 중복 데이터가 대부분
화요일: [전체 100GB 백업]  ← 비효율적

증분 백업 (Incremental Backup)

이전 백업(풀 또는 증분) 이후 변경된 파일만 백업합니다.

  • 장점: 저장 공간 최소화, 백업 시간 단축
  • 단점: 복구 시 풀 백업 + 모든 증분 백업을 순서대로 적용해야 함. 복구 과정이 복잡
  • 적합한 상황: 매일 또는 매시간 단위 백업, 대용량 데이터
일요일: [풀 백업 100GB]
월요일: [증분: 변경된 5GB만]
화요일: [증분: 변경된 3GB만]
복구 시: 일요일 풀 + 월요일 증분 + 화요일 증분 순서 적용

차등 백업 (Differential Backup)

마지막 풀 백업 이후 변경된 모든 파일을 백업합니다.

  • 장점: 복구 시 풀 백업 + 최신 차등 백업 두 개만 있으면 됨
  • 단점: 증분 백업보다 저장 공간을 더 사용함 (누적됨)
  • 적합한 상황: 복구 속도와 저장 효율 사이의 균형이 필요할 때
일요일: [풀 백업 100GB]
월요일: [차등: 일요일 이후 변경 5GB]
화요일: [차등: 일요일 이후 변경 8GB]  ← 월요일 변경분 포함
복구 시: 일요일 풀 + 화요일 차등 (두 개만 필요)

실무에서의 선택

대부분의 운영 환경에서는 혼합 전략을 사용합니다:

  • 주 1회 풀 백업 (일요일 새벽 2시)
  • 매일 증분 백업 (평일 새벽 2시)
  • 특정 중요 설정 파일은 시간별 백업

이 챕터에서는 rsync --link-dest를 활용하여 스토리지 효율은 증분 백업 수준이면서 복구는 풀 백업처럼 단순한 방식을 구현합니다.

💡개념

3-2-1 백업 원칙

3-2-1 백업 원칙 — 3개 복사본·2종 매체·1개 오프사이트

랜섬웨어 공격을 받은 서버를 복구하려 했더니 백업 서버가 같은 내부 네트워크에 있어서 함께 암호화된 사례가 있습니다. 로컬 디스크 하나에만 백업해두다가 서버가 물리적으로 손상되어 백업도 같이 날아가는 경우도 있습니다. "백업이 있다"고 생각했지만 실제로는 모두 같은 위험에 노출된 구조입니다. 3-2-1 원칙은 이런 상황을 막기 위한 최소 기준으로, 현재 백업 구성이 이 기준을 충족하는지 점검하는 체크리스트입니다.

3-2-1 규칙은 데이터 보호의 황금률입니다. 랜섬웨어, 화재, 도난 등 어떤 상황에서도 살아남을 수 있도록 설계된 원칙입니다.

3-2-1 규칙이란

  • 3: 데이터 사본을 3개 유지 (원본 1개 + 백업 2개)
  • 2: 2가지 서로 다른 미디어/저장 매체에 저장
  • 1: 1개는 반드시 외부(오프사이트)에 보관

실제 구현 예시

[3개의 사본]
사본 1: 운영 서버 (원본)
사본 2: 같은 데이터센터의 백업 서버
사본 3: 클라우드 (AWS S3, Backblaze B2, 외부 NAS)

[2가지 다른 미디어]
로컬: 서버 내부 SSD + 외장 HDD
또는: 온프레미스 서버 + 클라우드 스토리지

[1개는 오프사이트]
화재가 났을 때 같은 건물에 있는 백업은 무의미
클라우드 또는 물리적으로 다른 위치에 1개 보관

실습 전 디렉토리와 예제 파일을 먼저 준비합니다.

로컬 터미널
# 실습 디렉토리 준비
mkdir -p /tmp/linux/part5/exam_6/{source,backups,restore}
cd /tmp/linux/part5/exam_6

# 백업 대상 예제 파일 생성
echo "DB_HOST=prod-db.internal" > source/app.conf
echo "APP_PORT=8080" >> source/app.conf
cp /etc/hosts source/hosts.bak 2>/dev/null || true
echo "user: admin, role: superuser" > source/users.txt
for i in 1 2 3; do
  echo "$(date): Log entry $i" >> source/app.log
done
ls -la source/

이제 실습을 진행합니다.

랜섬웨어 방어를 위한 추가 원칙

랜섬웨어는 네트워크로 연결된 모든 드라이브를 암호화할 수 있습니다. 이를 방어하려면:

로컬 터미널
# 백업 완료 후 즉시 마운트 해제 (연결 차단)
umount /mnt/backup-drive

# 또는 백업 서버에서 풀(pull) 방식으로 백업
# 운영 서버가 백업 서버에 접근하는 것이 아니라
# 백업 서버가 운영 서버에서 데이터를 가져오는 방식

# S3 버킷에 버전 관리 활성화 (랜섬웨어 감염 이전 버전 복구 가능)
aws s3api put-bucket-versioning \
  --bucket my-backup-bucket \
  --versioning-configuration Status=Enabled

3-2-1 원칙을 따르면 어떤 단일 장애점(Single Point of Failure)이 발생해도 데이터를 복구할 수 있습니다.


2. tar: 아카이브와 압축의 기본

tar(Tape ARchive)는 Linux에서 가장 오래되고 보편적인 백업 도구입니다. 여러 파일과 디렉토리를 하나의 아카이브 파일로 묶고 압축합니다.

tar 기본 사용법: 생성, 해제, 목록 확인

아카이브 생성: -czf

로컬 터미널
# 기본 형식
tar -czf 출력파일.tar.gz 백업할_디렉토리/

# /etc 디렉토리 전체를 압축
tar -czf /backup/etc-backup.tar.gz /etc/

# 옵션 설명
# -c : create (새 아카이브 생성)
# -z : gzip 압축 적용
# -f : 파일 이름 지정 (다음 인수가 파일명)
# -v : verbose (처리 중인 파일 목록 출력)

압축 형식별 비교:

로컬 터미널
# gzip 압축 (.tar.gz 또는 .tgz) — 가장 보편적
tar -czf backup.tar.gz /data/

# bzip2 압축 (.tar.bz2) — gzip보다 압축률 높음, 느림
tar -cjf backup.tar.bz2 /data/

# xz 압축 (.tar.xz) — 최고 압축률, 가장 느림
tar -cJf backup.tar.xz /data/

# 압축 없이 묶기만 (.tar) — 빠름, 크기 큼
tar -cf backup.tar /data/

아카이브 해제: -xzf

로컬 터미널
# 현재 디렉토리에 해제
tar -xzf backup.tar.gz

# 특정 디렉토리에 해제
tar -xzf backup.tar.gz -C /restore/

# 특정 파일만 선택적으로 해제
tar -xzf backup.tar.gz etc/nginx/nginx.conf

# -C 옵션: 해제할 디렉토리 지정 (Change directory)
# -x : extract (아카이브에서 파일 추출)

내용 확인: -tzf (압축 해제 없이 목록만 보기)

로컬 터미널
# 아카이브 내 파일 목록 확인
tar -tzf backup.tar.gz

# 상세 정보와 함께 목록 확인 (권한, 소유자, 크기 포함)
tar -tzvf backup.tar.gz

# 특정 패턴의 파일만 확인
tar -tzf backup.tar.gz | grep "\.conf$"

# 아카이브 크기 확인 (실제 압축 해제 시 크기)
tar -tzf backup.tar.gz | awk '{sum += $3} END {print sum/1024/1024 " MB"}'
🔍실행 후 확인할 것
  • tar -tzf backup.tar.gz | head -10 에서 아카이브 내 파일 목록이 출력된다
  • tar -czf 실행 후 ls -lh backup.tar.gz 에서 원본보다 작은 압축 크기가 확인된다
  • tar -xzf 해제 후 diff -rq 원본경로 해제경로 에서 차이가 없어야 한다 (복구 검증)
  • ls -la backup_$(date +%Y%m%d)*.tar.gz 에서 오늘 날짜 백업 파일이 존재한다
tar 고급: --exclude와 날짜 포함 파일명 자동 생성

--exclude 옵션으로 불필요한 파일 제외

로컬 터미널
# 특정 디렉토리 제외
tar -czf backup.tar.gz /var/www/ \
  --exclude=/var/www/html/cache \
  --exclude=/var/www/html/tmp

# 특정 패턴 파일 제외 (와일드카드)
tar -czf backup.tar.gz /home/user/ \
  --exclude="*.log" \
  --exclude="*.tmp" \
  --exclude=".git"

# 여러 제외 패턴을 파일로 관리
cat > /etc/backup-exclude.txt << 'EOF'
*.log
*.tmp
*.cache
/var/www/html/cache/*
/var/www/html/uploads/tmp/*
node_modules/
.git/
__pycache__/
*.pyc
EOF

tar -czf backup.tar.gz /var/www/ \
  --exclude-from=/etc/backup-exclude.txt

# --exclude-vcs: git, svn 등 버전관리 디렉토리 자동 제외
tar -czf backup.tar.gz /home/user/project/ \
  --exclude-vcs

날짜 포함 파일명 자동 생성 패턴

실무에서는 백업 파일에 날짜와 시간을 포함시켜 관리합니다:

로컬 터미널
# 기본 날짜 패턴 (YYYY-MM-DD)
DATE=$(date +%Y-%m-%d)
tar -czf /backup/etc-${DATE}.tar.gz /etc/
# 결과: /backup/etc-2026-03-26.tar.gz

# 시간까지 포함 (YYYY-MM-DD_HH-MM-SS)
DATETIME=$(date +%Y-%m-%d_%H-%M-%S)
tar -czf /backup/www-${DATETIME}.tar.gz /var/www/
# 결과: /backup/www-2026-03-26_02-30-00.tar.gz

# 호스트명 포함 (다중 서버 백업 관리 시 유용)
HOSTNAME=$(hostname -s)
DATE=$(date +%Y-%m-%d)
tar -czf /backup/${HOSTNAME}-etc-${DATE}.tar.gz /etc/
# 결과: /backup/web01-etc-2026-03-26.tar.gz

# 백업 후 파일 크기 확인
BACKUP_FILE="/backup/etc-${DATE}.tar.gz"
tar -czf "${BACKUP_FILE}" /etc/ 2>/dev/null
echo "백업 완료: ${BACKUP_FILE} ($(du -sh ${BACKUP_FILE} | cut -f1))"

백업 무결성 검증

로컬 터미널
# 아카이브 무결성 테스트 (실제 해제 없이 오류 확인)
tar -tzf /backup/etc-2026-03-26.tar.gz > /dev/null && \
  echo "백업 무결성 OK" || echo "백업 파일 손상됨!"

# MD5 체크섬으로 전송 후 무결성 확인
md5sum /backup/etc-2026-03-26.tar.gz > /backup/etc-2026-03-26.tar.gz.md5
# 나중에 검증 시:
md5sum -c /backup/etc-2026-03-26.tar.gz.md5

3. rsync: 효율적인 동기화와 원격 백업

rsync는 단순한 복사 도구가 아닙니다. 변경된 부분만 전송하는 델타 알고리즘을 사용하여 대역폭과 시간을 절약합니다.

rsync 기본 옵션 마스터하기

핵심 옵션 조합: -avz

로컬 터미널
# 기본 로컬 동기화
rsync -avz /source/ /destination/

# 옵션 설명
# -a : archive 모드 (권한, 타임스탬프, 심볼릭링크, 소유자 등 모두 보존)
#      -rlptgoD 의 단축형
# -v : verbose (상세 출력)
# -z : 전송 시 압축 (네트워크 전송 시 유용, 로컬에서는 오히려 느릴 수 있음)

# -a 가 포함하는 것들:
# -r : 재귀적으로 디렉토리 처리
# -l : 심볼릭 링크 보존
# -p : 파일 권한(permission) 보존
# -t : 타임스탬프 보존
# -g : 그룹 소유권 보존
# -o : 파일 소유자 보존 (root 권한 필요)
# -D : 디바이스 파일과 특수 파일 보존

--delete: 원본에서 삭제된 파일 동기화

로컬 터미널
# --delete 없이: 원본에서 삭제해도 목적지에 남아있음
rsync -avz /source/ /destination/

# --delete: 원본에 없는 파일은 목적지에서도 삭제 (진정한 동기화)
rsync -avz --delete /source/ /destination/

# 주의: 실수로 원본을 지웠다면 목적지에서도 삭제됨
# 사용 전 반드시 --dry-run으로 확인

--progress: 전송 진행 상황 표시

로컬 터미널
# 각 파일의 전송 진행률 표시
rsync -avz --progress /source/ /destination/

# 전체 전송 통계만 보기 (--info=progress2)
rsync -avz --info=progress2 /source/ /destination/

# 출력 예시:
#     1,234,567  45%   12.34MB/s    0:00:08 (xfr#5, to-chk=42/50)
# [전송 바이트] [%] [속도]       [남은시간] [파일번호/전체]

--dry-run: 실제 실행 전 미리 확인

로컬 터미널
# 실제로 아무것도 변경하지 않고 무엇이 바뀔지 시뮬레이션
rsync -avz --dry-run --delete /source/ /destination/

# 짧은 형태: -n
rsync -avzn --delete /source/ /destination/

# dry-run 결과 예시:
# sending incremental file list
# deleting old-file.txt       ← 삭제될 파일
# new-file.txt                ← 새로 추가될 파일
# modified-file.conf          ← 변경된 파일
# sent 1,234 bytes  received 456 bytes

# 실전 워크플로우: 항상 dry-run 먼저, 확인 후 실제 실행
rsync -avz --dry-run --delete /var/www/ /backup/www/ && \
  read -p "위 내용이 맞습니까? [y/N] " confirm && \
  [ "$confirm" = "y" ] && \
  rsync -avz --delete /var/www/ /backup/www/

중요한 경로 슬래시 주의사항

로컬 터미널
# /source/ (슬래시 있음): source 디렉토리의 내용을 복사
rsync -avz /source/ /destination/
# /destination/ 안에 파일들이 직접 위치

# /source (슬래시 없음): source 디렉토리 자체를 복사
rsync -avz /source /destination/
# /destination/source/ 디렉토리가 생성됨
rsync over SSH: 원격 서버 동기화

SSH를 통한 원격 백업

로컬 터미널
# 기본 원격 동기화 (로컬 → 원격)
rsync -avz -e ssh /local/data/ user@remote-server:/remote/backup/

# -e ssh : SSH를 전송 프로토콜로 사용
# 또는 더 명시적으로:
rsync -avz -e "ssh" /local/data/ user@remote-server:/remote/backup/

# SSH 포트가 기본(22)이 아닌 경우
rsync -avz -e "ssh -p 2222" /local/data/ user@192.168.1.100:/backup/

# SSH 키 파일 명시 (자동화 스크립트에서 필수)
rsync -avz -e "ssh -i /root/.ssh/backup_key -p 22" \
  /local/data/ backup@192.168.1.100:/backup/

# 원격 → 로컬 (원격에서 풀링)
rsync -avz -e ssh user@remote-server:/remote/data/ /local/backup/

원격 백업 실전 옵션 조합

로컬 터미널
# 운영 서버에서 백업 서버로 전체 동기화
rsync -avz \
  --delete \
  --progress \
  --exclude="*.log" \
  --exclude="*.tmp" \
  --exclude="cache/" \
  -e "ssh -i /root/.ssh/backup_key" \
  /var/www/ \
  backup@192.168.1.200:/backup/web/

# 백업 완료 후 원격 서버에서 압축 (선택사항)
# SSH 원격 명령 실행
ssh -i /root/.ssh/backup_key backup@192.168.1.200 \
  "tar -czf /backup/web-$(date +%Y-%m-%d).tar.gz /backup/web/"

scp로 단일 파일 전송

rsync가 디렉토리 동기화에 특화되었다면, scp는 단일 파일 전송에 간편합니다:

SSH 접속 후
# 로컬 → 원격 단일 파일 전송
scp /etc/nginx/nginx.conf user@remote-server:/tmp/

# 원격 → 로컬 다운로드
scp user@remote-server:/var/log/app.log /tmp/

# 디렉토리 전체 전송 (-r 옵션)
scp -r /etc/nginx/ user@remote-server:/backup/nginx/

# 특정 포트 지정 (-P 대문자)
scp -P 2222 /backup/db.sql user@remote-server:/restore/

# 여러 파일을 한 번에 전송
scp /etc/hosts /etc/fstab user@remote-server:/backup/etc/

# 전송 진행 상황 표시 (-v)
scp -v /backup/large-file.tar.gz user@remote-server:/backup/

4. 증분 백업 구현: rsync --link-dest

하드링크 기반 증분 백업 구현

rsync --link-dest는 증분 백업의 효율성과 풀 백업의 단순성을 동시에 제공합니다. 변경된 파일만 실제로 복사하고, 변경되지 않은 파일은 이전 백업과 하드링크로 연결합니다.

하드링크 기반 백업의 원리

[월요일 풀 백업]          [화요일 증분 백업]
/backup/2026-03-23/       /backup/2026-03-24/
├── file-a.conf (실제)    ├── file-a.conf → 하드링크 (공유, 추가 공간 없음)
├── file-b.log  (실제)    ├── file-b.log  → 하드링크 (공유)
└── file-c.py   (실제)    └── file-c.py   (새로 복사, 변경됨)

화요일 백업 실제 사용 공간: file-c.py 크기만큼만
하지만 /backup/2026-03-24/ 는 완전한 스냅샷처럼 보임!

--link-dest 구현

로컬 터미널
#!/bin/bash
# 하드링크 기반 증분 백업 스크립트

SOURCE="/var/www/"
BACKUP_DIR="/backup/snapshots"
DATE=$(date +%Y-%m-%d_%H-%M-%S)
CURRENT="${BACKUP_DIR}/${DATE}"
LATEST="${BACKUP_DIR}/latest"

# 이전 백업이 있으면 하드링크 기반으로, 없으면 풀 백업
if [ -d "${LATEST}" ]; then
  rsync -avz \
    --delete \
    --link-dest="${LATEST}" \
    /var/www/ \
    "${CURRENT}/"
  echo "증분 백업 완료: ${CURRENT}"
else
  rsync -avz \
    --delete \
    /var/www/ \
    "${CURRENT}/"
  echo "최초 풀 백업 완료: ${CURRENT}"
fi

# latest 심볼릭 링크 업데이트
rm -f "${LATEST}"
ln -s "${CURRENT}" "${LATEST}"

# 공간 확인
echo "전체 백업 공간: $(du -sh ${BACKUP_DIR} | cut -f1)"
echo "최신 스냅샷: $(du -sh ${CURRENT} | cut -f1)"

실제 공간 절약 확인

로컬 터미널
# 각 스냅샷이 실제로 사용하는 공간 확인
# du -sh 는 하드링크 공유 공간을 중복 계산할 수 있음
# du -sh --separate-dirs 또는 각각 확인

ls -la /backup/snapshots/
# drwxr-xr-x  2026-03-23_02-00-00
# drwxr-xr-x  2026-03-24_02-00-00
# drwxr-xr-x  2026-03-25_02-00-00
# lrwxrwxrwx  latest -> 2026-03-25_02-00-00

# 전체 실제 사용량
du -sh /backup/snapshots/
# 105M  /backup/snapshots/   ← 3일치인데 105M (풀 백업 100M + 증분 5M)

5. 백업 서버 완전 자동화

cron + rsync 전체 백업 자동화 스크립트

백업 우선순위: 무엇을 먼저 백업할 것인가

서버에서 가장 먼저 백업해야 할 파일들:

로컬 터미널
# 1순위: 시스템 설정 파일
/etc/hosts          # DNS 매핑
/etc/crontab        # 예약 작업
/etc/fstab          # 파일시스템 마운트
/etc/passwd         # 사용자 계정
/etc/sudoers        # sudo 권한
/etc/ssh/           # SSH 서버 설정 및 키
/etc/nginx/         # Nginx 설정
/etc/apache2/       # Apache 설정 (해당하는 경우)
/etc/mysql/         # MySQL 설정

# 2순위: 애플리케이션 데이터
/var/www/           # 웹 서버 파일
/home/              # 사용자 홈 디렉토리
/opt/               # 서드파티 소프트웨어

# 3순위: 데이터베이스
# DB는 파일 직접 복사가 아닌 전용 덤프 도구 사용
mysqldump --all-databases > /backup/db-$(date +%Y-%m-%d).sql
pg_dumpall > /backup/postgres-$(date +%Y-%m-%d).sql

완전한 자동화 백업 스크립트

로컬 터미널
#!/bin/bash
# /usr/local/bin/backup.sh
# 서버 자동 백업 스크립트 — cron으로 매일 새벽 2시 실행

set -euo pipefail  # 오류 시 즉시 종료, 미정의 변수 오류, 파이프 오류 감지

# ============ 설정 섹션 ============
BACKUP_SERVER="backup@192.168.1.200"
BACKUP_KEY="/root/.ssh/backup_key"
REMOTE_BASE="/backup/$(hostname -s)"
LOCAL_TEMP="/tmp/backup-staging"
LOG_FILE="/var/log/backup.log"
NOTIFY_EMAIL="admin@example.com"
RETENTION_DAYS=30

# ============ 함수 정의 ============
log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "${LOG_FILE}"
}

send_alert() {
  echo "$1" | mail -s "[백업 실패] $(hostname -s) $(date +%Y-%m-%d)" \
    "${NOTIFY_EMAIL}" 2>/dev/null || true
}

check_disk_space() {
  local path="$1"
  local min_gb="$2"
  local available_kb
  available_kb=$(df "${path}" | awk 'NR==2 {print $4}')
  local available_gb=$((available_kb / 1024 / 1024))
  if [ "${available_gb}" -lt "${min_gb}" ]; then
    log "경고: ${path} 여유 공간 부족 (${available_gb}GB < ${min_gb}GB 필요)"
    return 1
  fi
  return 0
}

# ============ 실행 시작 ============
DATE=$(date +%Y-%m-%d_%H-%M-%S)
log "===== 백업 시작: ${DATE} ====="

# 최소 여유 공간 확인 (5GB 이상 필요)
if ! check_disk_space "/tmp" 5; then
  send_alert "백업 실패: /tmp 공간 부족"
  exit 1
fi

# 임시 스테이징 디렉토리 생성
mkdir -p "${LOCAL_TEMP}"

# ============ 1단계: 시스템 설정 파일 tar 백업 ============
log "1단계: 시스템 설정 파일 백업 시작"
CONFIG_BACKUP="${LOCAL_TEMP}/configs-${DATE}.tar.gz"

tar -czf "${CONFIG_BACKUP}" \
  --exclude="/etc/ssl/private" \
  /etc/hosts \
  /etc/fstab \
  /etc/crontab \
  /etc/cron.d/ \
  /etc/ssh/sshd_config \
  /etc/nginx/ \
  /etc/passwd \
  /etc/group \
  /etc/sudoers \
  2>/dev/null || {
    log "경고: 일부 설정 파일 백업 실패 (권한 문제일 수 있음)"
  }

log "설정 파일 백업 완료: $(du -sh ${CONFIG_BACKUP} | cut -f1)"

# ============ 2단계: 데이터베이스 덤프 ============
log "2단계: 데이터베이스 백업 시작"
if command -v mysqldump &>/dev/null; then
  DB_BACKUP="${LOCAL_TEMP}/mysql-${DATE}.sql.gz"
  mysqldump --defaults-file=/root/.my.cnf \
    --all-databases \
    --single-transaction \
    --routines \
    --triggers \
    2>/dev/null | gzip > "${DB_BACKUP}" || {
      log "경고: MySQL 백업 실패"
    }
  log "MySQL 백업 완료: $(du -sh ${DB_BACKUP} | cut -f1)"
fi

# ============ 3단계: 웹 파일 rsync 동기화 ============
log "3단계: 웹 파일 원격 동기화 시작"
LATEST_LINK="${REMOTE_BASE}/latest"

# SSH 연결 테스트
if ! ssh -i "${BACKUP_KEY}" -o ConnectTimeout=10 \
     -o BatchMode=yes "${BACKUP_SERVER}" "echo ok" &>/dev/null; then
  log "오류: 백업 서버 SSH 연결 실패"
  send_alert "백업 서버 SSH 연결 실패"
  exit 1
fi

# 원격에 최신 백업이 있으면 --link-dest로 증분 백업
if ssh -i "${BACKUP_KEY}" "${BACKUP_SERVER}" \
   "[ -d '${LATEST_LINK}' ]" 2>/dev/null; then
  rsync -avz \
    --delete \
    --exclude="*.log" \
    --exclude="cache/" \
    --exclude="*.tmp" \
    --link-dest="${LATEST_LINK}" \
    -e "ssh -i ${BACKUP_KEY}" \
    /var/www/ \
    "${BACKUP_SERVER}:${REMOTE_BASE}/snapshots/${DATE}/" \
    2>&1 | tail -5 | tee -a "${LOG_FILE}"
else
  log "최초 풀 백업 시작"
  rsync -avz \
    --delete \
    -e "ssh -i ${BACKUP_KEY}" \
    /var/www/ \
    "${BACKUP_SERVER}:${REMOTE_BASE}/snapshots/${DATE}/" \
    2>&1 | tail -5 | tee -a "${LOG_FILE}"
fi

# latest 심볼릭 링크 업데이트
ssh -i "${BACKUP_KEY}" "${BACKUP_SERVER}" \
  "rm -f '${LATEST_LINK}' && ln -s '${REMOTE_BASE}/snapshots/${DATE}' '${LATEST_LINK}'"

log "웹 파일 동기화 완료"

# ============ 4단계: 스테이징 파일을 원격으로 전송 ============
log "4단계: 설정/DB 백업 파일 원격 전송"
scp -i "${BACKUP_KEY}" "${LOCAL_TEMP}"/*.tar.gz "${LOCAL_TEMP}"/*.sql.gz \
  "${BACKUP_SERVER}:${REMOTE_BASE}/archives/" 2>/dev/null || true

# ============ 5단계: 보존 정책 적용 (오래된 백업 삭제) ============
log "5단계: ${RETENTION_DAYS}일 이상 지난 백업 삭제"
ssh -i "${BACKUP_KEY}" "${BACKUP_SERVER}" \
  "find '${REMOTE_BASE}/archives/' -type f -mtime +${RETENTION_DAYS} -delete && \
   find '${REMOTE_BASE}/snapshots/' -maxdepth 1 -type d -mtime +${RETENTION_DAYS} \
   -exec rm -rf {} \; 2>/dev/null || true"

# ============ 정리 ============
rm -rf "${LOCAL_TEMP}"
log "===== 백업 완료: $(date '+%Y-%m-%d %H:%M:%S') ====="

cron 등록

로컬 터미널
# crontab 편집
crontab -e

# 매일 새벽 2시 백업 실행
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

# 매주 일요일 새벽 1시 풀 백업 (스크립트 내에서 요일 판단 가능)
0 1 * * 0 /usr/local/bin/full-backup.sh >> /var/log/backup.log 2>&1

# 1시간마다 중요 설정 파일만 백업
0 * * * * tar -czf /backup/etc-hourly-$(date +\%H).tar.gz /etc/ 2>/dev/null

# /etc/crontab 에 추가 (시스템 전체 cron)
# /etc/cron.d/backup 파일로 관리 권장
cat > /etc/cron.d/server-backup << 'EOF'
# 서버 백업 작업
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=admin@example.com

# 매일 새벽 2시 자동 백업
0 2 * * * root /usr/local/bin/backup.sh
EOF

6. 보존 정책(Retention Policy)

오래된 백업 자동 삭제 및 보존 정책 구현

백업은 쌓이면 디스크를 가득 채웁니다. 보존 정책을 통해 필요한 기간만큼만 유지합니다.

find -mtime를 사용한 오래된 파일 삭제

로컬 터미널
# 30일 이상 된 파일 삭제 (파일만, 디렉토리 제외)
find /backup/archives/ -type f -mtime +30 -delete

# 옵션 설명:
# -type f   : 파일만 (디렉토리 제외)
# -mtime +30: 수정 시간이 30일 초과인 것
#             +30: 30일 초과, 30: 정확히 30일, -30: 30일 미만

# 삭제 전 대상 파일 목록 확인 (항상 먼저!)
find /backup/archives/ -type f -mtime +30 -ls

# 특정 패턴의 파일만 삭제
find /backup/archives/ -name "*.tar.gz" -mtime +30 -delete
find /backup/archives/ -name "*.sql.gz" -mtime +7 -delete  # DB는 7일

# 빈 디렉토리도 정리
find /backup/snapshots/ -type d -empty -delete

# 날짜 기반 스냅샷 디렉토리 삭제 (30일 이상)
find /backup/snapshots/ -maxdepth 1 -type d -mtime +30 \
  -exec rm -rf {} \; 2>/dev/null

세분화된 보존 정책

로컬 터미널
#!/bin/bash
# /usr/local/bin/retention-cleanup.sh
# 백업 보존 정책: 일별 7개, 주별 4개, 월별 12개

BACKUP_DIR="/backup/archives"

# 7일 이상 된 일별 백업 삭제
find "${BACKUP_DIR}/daily/" -type f -mtime +7 -delete
echo "7일 이상 일별 백업 삭제 완료"

# 28일 이상 된 주별 백업 삭제
find "${BACKUP_DIR}/weekly/" -type f -mtime +28 -delete
echo "28일 이상 주별 백업 삭제 완료"

# 365일 이상 된 월별 백업 삭제
find "${BACKUP_DIR}/monthly/" -type f -mtime +365 -delete
echo "365일 이상 월별 백업 삭제 완료"

# 백업 디스크 사용량 리포트
echo ""
echo "=== 백업 스토리지 현황 ==="
du -sh "${BACKUP_DIR}"/*/ 2>/dev/null
df -h "${BACKUP_DIR}"

보존 정책을 cron에 등록

로컬 터미널
# 매일 새벽 3시 정리 작업 (백업 완료 후)
cat >> /etc/cron.d/server-backup << 'EOF'

# 백업 보존 정책 적용
0 3 * * * root /usr/local/bin/retention-cleanup.sh >> /var/log/backup.log 2>&1
EOF

7. 트러블슈팅

증상

tar: /backup/www-2026-03-26.tar.gz: No space left on device
rsync: [sender] write error: No space left on device (28)
rsync error: error in file IO (code 11)

원인 파악

로컬 터미널
# 디스크 사용량 전체 확인
df -h
# Filesystem      Size  Used Avail Use% Mounted on
# /dev/sdb1        50G   50G     0 100% /backup    ← 풀!

# 어디서 공간을 먹고 있는지 찾기
du -sh /backup/* | sort -rh | head -20

# inode 부족도 확인 (파일 수가 너무 많은 경우)
df -i

즉시 공간 확보

로컬 터미널
# 1. 가장 오래된 백업부터 삭제
ls -lt /backup/archives/ | tail -10  # 가장 오래된 것 확인
find /backup/archives/ -type f -mtime +14 -delete

# 2. 압축 없이 저장된 tar 파일 재압축
find /backup/ -name "*.tar" -size +100M | while read f; do
  gzip "$f" && echo "압축 완료: $f"
done

# 3. 로그 파일 정리 (백업 로그 포함)
find /var/log/ -name "*.gz" -mtime +30 -delete
journalctl --vacuum-time=7d  # systemd 저널 7일 이상 삭제

# 4. 중복 하드링크 백업 공간 계산 오류 주의
# du 는 하드링크를 중복 계산할 수 있음
# 실제 공간은 du --count-links 또는 ncdu 로 확인
ncdu /backup/  # interactive disk usage 분석 (설치 필요)

예방: 백업 전 공간 체크

로컬 터미널
#!/bin/bash
# 백업 시작 전 공간 확인 함수
check_space_before_backup() {
  local backup_path="$1"
  local source_path="$2"

  # 원본 크기 계산
  local source_size_kb
  source_size_kb=$(du -sk "${source_path}" | cut -f1)

  # 백업 경로 여유 공간
  local available_kb
  available_kb=$(df "${backup_path}" | awk 'NR==2 {print $4}')

  # 원본의 120% 필요 (압축 후 크기 + 여유분)
  local required_kb=$((source_size_kb * 120 / 100))

  if [ "${available_kb}" -lt "${required_kb}" ]; then
    echo "오류: 공간 부족!"
    echo "필요: $((required_kb / 1024))MB, 여유: $((available_kb / 1024))MB"
    exit 1
  fi

  echo "공간 확인 OK: 여유 $((available_kb / 1024))MB"
}

check_space_before_backup /backup /var/www/

증상

rsync: failed to set permissions on "/destination/file.conf":
  Operation not permitted (1)
rsync: chown "/destination/file.txt" failed: Operation not permitted (1)

또는 파일이 전송되었지만 소유자가 달라진 경우:

로컬 터미널
ls -la /destination/
# -rw-r--r-- 1 root root  file.conf   ← 원래는 www-data:www-data 여야 함

원인

  • 백업 대상 서버에 해당 사용자(www-data 등)가 없는 경우
  • root 권한 없이 rsync를 실행하는 경우
  • NFS나 특수 파일시스템에서 권한 설정 불가

해결 방법

로컬 터미널
# 방법 1: 권한/소유권 동기화 건너뜀 (단순 파일 내용만 복사)
rsync -avz --no-perms --no-owner --no-group \
  /source/ /destination/

# 방법 2: 목적지에서 특정 소유권으로 강제 지정
rsync -avz --chown=www-data:www-data \
  /source/ /destination/

# 방법 3: 권한은 유지하되 소유자는 현재 사용자로
rsync -rltz --no-owner --no-group \
  /source/ /destination/
# -rl: 재귀 + 심볼릭링크
# -t: 타임스탬프
# -z: 압축

# 방법 4: rsync 후 별도로 소유권 일괄 변경
rsync -avz /source/ /destination/
chown -R www-data:www-data /destination/

# 방법 5: sudo를 통한 rsync (원격)
rsync -avz -e "ssh -i /root/.ssh/key" \
  --rsync-path="sudo rsync" \
  /source/ user@remote:/destination/

파일 복원 시 소유권 처리

로컬 터미널
# tar 해제 시 소유권 보존 (root로 실행 필요)
tar -xzf backup.tar.gz -C /restore/ --same-owner

# tar 해제 시 소유권 무시 (현재 사용자로 복원)
tar -xzf backup.tar.gz -C /restore/ --no-same-owner

"file changed as we read it" 경고

rsync warning: some files vanished before they could be transferred (code 24)
file has vanished: "/source/temp-file.tmp"
file changed as we read it: "/source/app.log"

원인: rsync가 파일 목록을 스캔한 후 실제 전송 사이에 파일이 변경되거나 삭제됨. 주로 활성 로그 파일이나 임시 파일에서 발생.

로컬 터미널
# 해결책 1: 오류 코드 24(vanished files)를 무시
rsync -avz /source/ /destination/
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ] || [ $EXIT_CODE -eq 24 ]; then
  echo "백업 성공 (일부 파일 변경 무시)"
else
  echo "백업 실패: 코드 ${EXIT_CODE}"
  exit 1
fi

# 해결책 2: 로그 파일 등 변경되는 파일 제외
rsync -avz \
  --exclude="*.log" \
  --exclude="*.pid" \
  --exclude="/var/run/" \
  /source/ /destination/

# 해결책 3: --checksum 으로 파일 변경 감지 방식 변경
# (크기+시간 대신 체크섬으로 비교 — 느리지만 정확)
rsync -avz --checksum /source/ /destination/

대용량 파일 전송 중단 후 재개: --partial, --append

로컬 터미널
# 문제: 네트워크 끊김으로 대용량 파일 전송 중단
# 기본 rsync는 불완전한 파일을 삭제하고 처음부터 다시 시작

# 해결책: --partial 옵션으로 중단된 파일 보존 후 재개
rsync -avz --partial \
  -e "ssh -i /root/.ssh/key" \
  /backup/large-database.sql.gz \
  backup@remote:/backup/

# --partial-dir: 불완전한 파일을 임시 디렉토리에 보관
rsync -avz --partial-dir=/tmp/rsync-partial \
  -e "ssh -i /root/.ssh/key" \
  /backup/ backup@remote:/backup/

# --append: 파일 끝부분에 이어서 전송 (로그처럼 추가만 되는 파일)
rsync -avz --append \
  /var/log/app.log backup@remote:/backup/logs/

# 권장 조합: --partial + --append-verify (무결성 검증 포함)
rsync -avz \
  --partial \
  --append-verify \
  --progress \
  -e "ssh -i /root/.ssh/key" \
  /backup/large-file.tar.gz \
  backup@remote:/backup/

# 자동 재시도 스크립트
for i in 1 2 3 4 5; do
  rsync -avz --partial --progress \
    -e "ssh -i /root/.ssh/key" \
    /backup/ backup@remote:/backup/ && break
  echo "시도 ${i} 실패, 30초 후 재시도..."
  sleep 30
done

rsync 종료 코드 참조

로컬 터미널
# 주요 rsync 종료 코드
# 0  : 성공
# 1  : 문법 오류
# 2  : 프로토콜 비호환
# 10 : I/O 오류 (파일 읽기/쓰기 실패)
# 11 : 파일 I/O 오류
# 12 : 스트림 프로토콜 오류
# 23 : 일부 파일 전송 실패 (권한 오류 등)
# 24 : 일부 파일이 전송 중 사라짐 (경고 수준)
# 25 : 삭제 작업 실패
# 30 : 타임아웃

8. 실무 적용

💼
실무 맥락
현업 패턴

현실적인 온프레미스 백업 아키텍처

중소규모 회사의 Linux 서버를 관리하는 상황을 가정해봅니다. 예산이 제한되어 있고, 클라우드는 일부만 사용하며, 물리 서버 2대와 외장 NAS가 있습니다.

[운영 서버: web01]          [백업 서버: backup01]
192.168.1.10                192.168.1.200
├── /var/www/ (웹파일)  →   ├── /backup/web01/snapshots/
├── /etc/ (설정)       →   └── /backup/web01/archives/
└── MySQL DB           →
                            ↓ (주간)
                      [외장 NAS: 오프사이트]
                      192.168.1.220
                      └── /nas/backup/ (3-2-1의 세 번째 사본)

실제 구현 체크리스트

로컬 터미널
# 1. SSH 키 기반 인증 설정 (비밀번호 없이 자동 접속)
ssh-keygen -t ed25519 -f /root/.ssh/backup_key -N ""
ssh-copy-id -i /root/.ssh/backup_key.pub backup@192.168.1.200

# 2. 백업 스크립트 권한 설정
chmod 700 /usr/local/bin/backup.sh
chown root:root /usr/local/bin/backup.sh

# 3. 로그 로테이션 설정
cat > /etc/logrotate.d/backup << 'EOF'
/var/log/backup.log {
    weekly
    rotate 8
    compress
    delaycompress
    missingok
    notifempty
}
EOF

# 4. 백업 모니터링: 마지막 백업 시간 확인 스크립트
cat > /usr/local/bin/check-backup.sh << 'SCRIPT'
#!/bin/bash
LAST_BACKUP=$(stat -c %Y /backup/latest 2>/dev/null || echo 0)
NOW=$(date +%s)
DIFF=$(( (NOW - LAST_BACKUP) / 3600 ))

if [ $DIFF -gt 26 ]; then
  echo "경고: 마지막 백업이 ${DIFF}시간 전입니다!"
  exit 1
else
  echo "OK: 마지막 백업 ${DIFF}시간 전"
fi
SCRIPT
chmod +x /usr/local/bin/check-backup.sh

# 5. 복구 훈련 (분기별 1회 권장)
# 실제 복구를 테스트하지 않은 백업은 백업이 아님
# 테스트 환경에서 실제 복구 절차 수행

백업 복구 절차 문서화

로컬 터미널
# /etc/backup-recovery-procedure.txt
# 이 파일은 인쇄해서 서버실에 보관할 것

=== 서버 복구 절차 ===

1. 백업 서버 접속
   ssh backup@192.168.1.200

2. 사용 가능한 스냅샷 확인
   ls -la /backup/web01/snapshots/

3. 특정 날짜의 파일 복구
   rsync -avz /backup/web01/snapshots/2026-03-25_02-00-00/ \
     web01:/restore/

4. 설정 파일 복구
   tar -xzf /backup/web01/archives/configs-2026-03-25_02-00-00.tar.gz \
     -C /restore/

5. DB 복구
   gunzip < /backup/web01/archives/mysql-2026-03-25_02-00-00.sql.gz \
     | mysql -u root -p
💼
실무 맥락
현업 패턴

CI/CD 파이프라인에 백업 통합

현대적인 DevOps 환경에서는 배포 전후로 백업을 자동화합니다:

로컬 터미널
#!/bin/bash
# deploy.sh — 배포 스크립트에 백업 통합

DEPLOY_TAG="$1"
BACKUP_SERVER="backup@backup01"

echo "=== 배포 전 백업 시작 ==="

# 배포 전 스냅샷 생성
SNAPSHOT_NAME="pre-deploy-${DEPLOY_TAG}-$(date +%Y%m%d-%H%M%S)"

rsync -avz \
  --link-dest="${BACKUP_SERVER}:/backup/app/latest" \
  -e "ssh -i /root/.ssh/backup_key" \
  /var/www/app/ \
  "${BACKUP_SERVER}:/backup/app/snapshots/${SNAPSHOT_NAME}/"

# 배포 실행
echo "=== 배포 실행 중 ==="
./actual-deploy.sh "${DEPLOY_TAG}"

DEPLOY_EXIT=$?

if [ $DEPLOY_EXIT -ne 0 ]; then
  echo "=== 배포 실패! 롤백 시작 ==="
  rsync -avz \
    -e "ssh -i /root/.ssh/backup_key" \
    "${BACKUP_SERVER}:/backup/app/snapshots/${SNAPSHOT_NAME}/" \
    /var/www/app/
  echo "롤백 완료: ${SNAPSHOT_NAME}"
else
  echo "=== 배포 성공: ${DEPLOY_TAG} ==="
fi

Grafana/Prometheus로 백업 상태 모니터링

로컬 터미널
# 백업 완료 시 메트릭 푸시 (Prometheus Pushgateway 사용)
BACKUP_STATUS=$?  # 0: 성공, 그 외: 실패
BACKUP_SIZE=$(du -sb /backup/ | cut -f1)

cat << EOF | curl --data-binary @- http://pushgateway:9091/metrics/job/backup/instance/$(hostname)
# HELP backup_last_success_timestamp 마지막 성공한 백업의 Unix 타임스탬프
# TYPE backup_last_success_timestamp gauge
backup_last_success_timestamp $(date +%s)

# HELP backup_size_bytes 전체 백업 크기 (바이트)
# TYPE backup_size_bytes gauge
backup_size_bytes ${BACKUP_SIZE}

# HELP backup_exit_code 마지막 백업 종료 코드
# TYPE backup_exit_code gauge
backup_exit_code ${BACKUP_STATUS}
EOF

백업 스크립트 테스트 자동화

로컬 터미널
# 백업 스크립트의 기능을 자동으로 검증하는 테스트
#!/bin/bash
# /usr/local/bin/test-backup.sh

FAIL=0

# 테스트 1: 백업 파일이 오늘 생성되었는지
TODAY=$(date +%Y-%m-%d)
if ls /backup/archives/*${TODAY}* &>/dev/null; then
  echo "PASS: 오늘의 백업 파일 존재"
else
  echo "FAIL: 오늘의 백업 파일 없음"
  FAIL=1
fi

# 테스트 2: 최신 스냅샷이 24시간 내에 생성되었는지
LATEST_MTIME=$(stat -c %Y /backup/snapshots/latest 2>/dev/null || echo 0)
NOW=$(date +%s)
AGE=$(( (NOW - LATEST_MTIME) / 3600 ))
if [ $AGE -lt 25 ]; then
  echo "PASS: 최신 스냅샷 ${AGE}시간 전 생성"
else
  echo "FAIL: 최신 스냅샷이 ${AGE}시간 전 (24시간 초과)"
  FAIL=1
fi

# 테스트 3: tar 아카이브 무결성 검증
LATEST_TAR=$(ls -t /backup/archives/*.tar.gz 2>/dev/null | head -1)
if [ -n "$LATEST_TAR" ] && tar -tzf "$LATEST_TAR" > /dev/null 2>&1; then
  echo "PASS: tar 아카이브 무결성 OK"
else
  echo "FAIL: tar 아카이브 손상 또는 없음"
  FAIL=1
fi

exit $FAIL

9. 핵심 명령어 빠른 참조

로컬 터미널
# ============ tar 명령어 ============

# 생성 (gzip 압축)
tar -czf backup.tar.gz /path/to/source/

# 생성 (날짜 포함)
tar -czf backup-$(date +%Y-%m-%d).tar.gz /path/

# 생성 (특정 파일 제외)
tar -czf backup.tar.gz /path/ --exclude="*.log" --exclude="cache/"

# 내용 확인 (해제 없이)
tar -tzf backup.tar.gz

# 해제 (현재 위치)
tar -xzf backup.tar.gz

# 해제 (특정 위치)
tar -xzf backup.tar.gz -C /restore/

# ============ rsync 명령어 ============

# 로컬 동기화 (기본)
rsync -avz /source/ /destination/

# 삭제 포함 완전 동기화
rsync -avz --delete /source/ /destination/

# 미리 보기 (dry-run)
rsync -avzn --delete /source/ /destination/

# SSH 원격 동기화
rsync -avz -e "ssh -i /root/.ssh/key" /source/ user@remote:/dest/

# 증분 백업 (하드링크)
rsync -avz --link-dest=/backup/latest /source/ /backup/$(date +%Y-%m-%d)/

# 부분 전송 재개
rsync -avz --partial --progress /source/ user@remote:/dest/

# 권한 무시
rsync -avz --no-perms --no-owner --no-group /source/ /dest/

# ============ scp 명령어 ============

# 단일 파일 전송
scp /local/file.conf user@remote:/remote/path/

# 원격에서 다운로드
scp user@remote:/remote/file.conf /local/path/

# 디렉토리 전송
scp -r /local/dir/ user@remote:/remote/

# ============ 보존 정책 ============

# 30일 이상 파일 삭제
find /backup/ -type f -mtime +30 -delete

# 대상 확인 후 삭제
find /backup/ -type f -mtime +30 -ls
find /backup/ -type f -mtime +30 -delete

# 빈 디렉토리 정리
find /backup/ -type d -empty -delete
💡개념

복구 리허설 — RTO/RPO 정의와 정기 복구 테스트

RTO·RPO 정의 — 허용 가능한 복구 시간과 데이터 손실 범위

백업이 있어도 복구가 된다는 검증 없이는 백업이 없는 것과 같습니다. 실제 장애가 나기 전에 정기적으로 복구를 연습해야 합니다.

RTO/RPO 정의:

지표의미예시
RPO (Recovery Point Objective)허용 가능한 최대 데이터 손실 시간"최대 1시간치 데이터 손실 허용"
RTO (Recovery Time Objective)허용 가능한 최대 복구 소요 시간"2시간 내 서비스 복구"

무결성 검증 — 백업 직후 자동 검증:

로컬 터미널
# tar 백업 무결성 검증 (압축 해제 없이 테스트)
tar -tzf /backup/app_20260405.tar.gz > /dev/null && echo "OK" || echo "CORRUPT"

# SHA256 체크섬 생성 및 검증
sha256sum /backup/app_20260405.tar.gz > /backup/app_20260405.tar.gz.sha256

# 나중에 검증
sha256sum -c /backup/app_20260405.tar.gz.sha256
# /backup/app_20260405.tar.gz: OK

# 자동화된 백업+검증 스크립트 핵심
BACKUP_FILE="/backup/app_$(date +%Y%m%d).tar.gz"
tar -czf "$BACKUP_FILE" /opt/myapp/
if sha256sum "$BACKUP_FILE" > "${BACKUP_FILE}.sha256"; then
    echo "[OK] 백업 완료 및 무결성 검증 성공: $BACKUP_FILE"
else
    echo "[ERROR] 백업 무결성 검증 실패!" >&2
    exit 1
fi

정기 복구 테스트 체크리스트:

로컬 터미널
# 매월 1회 복구 테스트 절차 (스테이징 환경에서)
# 1. 최신 백업 파일 확인
ls -lth /backup/ | head -5

# 2. 복구 환경 준비 (스테이징 서버 또는 임시 디렉토리)
mkdir -p /tmp/restore-test

# 3. 실제 복구 실행
tar -xzf /backup/app_$(date +%Y%m%d).tar.gz -C /tmp/restore-test

# 4. 복구된 데이터 검증
ls -la /tmp/restore-test/opt/myapp/
diff -rq /opt/myapp/ /tmp/restore-test/opt/myapp/ && echo "복구 데이터 일치"

# 5. 복구 소요 시간 기록 → RTO 달성 여부 확인
💡개념

백업 암호화 — gpg/age로 랜섬웨어 대응

백업 암호화 흐름 — gpg/age로 랜섬웨어 대응

백업 파일 자체가 암호화되어 있지 않으면 랜섬웨어나 내부자 유출 시 백업 데이터도 위험합니다.

gpg 대칭 암호화 (패스워드 기반):

로컬 터미널
# 암호화 (AES256)
gpg --symmetric --cipher-algo AES256 \
    --batch --passphrase "$(cat /etc/backup/passphrase)" \
    /backup/app_20260405.tar.gz
# → /backup/app_20260405.tar.gz.gpg 생성

# 복호화
gpg --batch --passphrase "$(cat /etc/backup/passphrase)" \
    -d /backup/app_20260405.tar.gz.gpg > /tmp/restored.tar.gz

age — 더 현대적인 암호화 도구 (권장):

로컬 터미널
# age 설치
sudo apt install age  # 또는 sudo dnf install age

# 공개키 기반 암호화 (수신자 공개키 필요)
age-keygen -o /etc/backup/backup.key  # 키 쌍 생성
age -r "$(grep 'public key' /etc/backup/backup.key | awk '{print $4}')" \
    -o /backup/app_20260405.tar.gz.age \
    /backup/app_20260405.tar.gz

# 복호화 (개인키 필요)
age -d -i /etc/backup/backup.key \
    -o /tmp/restored.tar.gz \
    /backup/app_20260405.tar.gz.age

백업 불변성 (Immutability) — 랜섬웨어 방어:

로컬 터미널
# 오브젝트 스토리지 불변 락 (AWS S3 Object Lock)
aws s3api put-object-lock-configuration \
    --bucket backup-bucket \
    --object-lock-configuration '{"ObjectLockEnabled":"Enabled","Rule":{"DefaultRetention":{"Mode":"COMPLIANCE","Days":30}}}'

# Linux chattr으로 파일 불변 설정 (로컬 백업)
sudo chattr +i /backup/app_20260405.tar.gz
# → root도 삭제/수정 불가 (랜섬웨어 방어)
lsattr /backup/app_20260405.tar.gz
# ----i--------e-- /backup/app_20260405.tar.gz

마무리

이 챕터에서 다룬 내용을 정리합니다:

  1. 백업 전략 — 풀/증분/차등 백업의 장단점과 현실적인 선택 기준
  2. 3-2-1 원칙 — 데이터 보호의 황금률과 랜섬웨어 방어
  3. tar — 아카이브 생성, 해제, 목록 확인, 날짜 파일명 패턴
  4. rsync — 로컬/원격 동기화, --delete, --dry-run, --progress
  5. rsync --link-dest — 스토리지 효율적인 스냅샷 백업 구현
  6. 자동화 — cron + 전체 백업 스크립트로 무인 운영
  7. 보존 정책 — find -mtime으로 오래된 백업 자동 정리
  8. 트러블슈팅 — Disk Full, 소유권 문제, 전송 재개

가장 중요한 것은 백업은 한 번 설정하고 끝이 아니라는 것입니다. 정기적으로 복구 테스트를 수행하고, 백업 로그를 모니터링하며, 보존 정책이 제대로 작동하는지 확인하는 것이 실제 운영의 핵심입니다. 테스트하지 않은 백업은 존재하지 않는 것과 같습니다.

다음 모듈에서는 서버 하드웨어 스펙을 lscpu, dmidecode, /proc 파일시스템으로 5분 안에 파악하는 방법을 다룹니다.

지식 확인

퀴즈 — 5문제

Q1

rsync로 /data/ 디렉토리를 원격 서버 192.168.1.10의 /backup/ 경로에 동기화할 때, 원본에서 삭제된 파일도 대상에서 제거하려면 어떤 옵션을 추가해야 하는가?

Q2

현재 디렉토리 /var/www를 backup.tar.gz 파일로 압축 아카이브할 때 올바른 tar 명령어는?

Q3

3-2-1 백업 규칙을 올바르게 설명한 것은?

Q4

cron을 이용해 매일 새벽 2시에 rsync 백업 스크립트(/opt/backup.sh)를 실행하는 crontab 설정으로 올바른 것은?

Q5

증분 백업(incremental backup)과 차등 백업(differential backup)의 복구 과정 차이로 올바른 것은?

0 / 5 답변

🧪 실습으로 확인하기

새 서버 인수인계 — 처음 30분

초급

낯선 Linux 서버를 인수받았을 때 OS, 서비스, 로그를 빠르게 파악하는 루틴을 직접 수행한다.

30📋 3단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

linux중급 · 65
[Linux] 주기적 서버 헬스체크와 장애 데몬 자동 재시작 스크립트
Linux 트랙 계속
docker입문 · 30
[Docker] 백엔드 개발자에게 Docker와 컨테이너 가상화가 필수인 이유
Docker 트랙 시작점