지금까지 Docker + Nginx + Prometheus + Grafana + Loki + HTTPS까지 구축했다. 설정 파일이 10개가 넘고, Docker 볼륨에는 대시보드·메트릭 데이터·로그가 쌓여 있다. 이걸 다 날리면 처음부터 다시 해야 한다. 서버 디스크 장애, 잘못된 docker compose down -v(볼륨까지 삭제), 설정 파일 실수 — 어느 하나만 터져도 몇 시간짜리 복구 작업이 된다.
그래서 매일 새벽 3시에 자동으로 전체 백업을 돌리고, 7일치만 보관하고 나머지는 삭제하는 자동화 스크립트를 만든다.
| # | 항목 | Before | After |
|---|---|---|---|
| 1 | 백업 스크립트 | 없음 | NEW /opt/backups/backup.sh |
| 2 | 자동 실행 | 없음 | NEW cron 매일 03:00 |
| 3 | Docker 설정 백업 | 미보호 | docker-lab + monitoring 설정 전체 |
| 4 | Docker 볼륨 백업 | 미보호 | Prometheus, Grafana, Loki 데이터 |
| 5 | 시스템 설정 백업 | 미보호 | SSH, Fail2ban, 방화벽 규칙 |
| 6 | 보존 정책 | 없음 | NEW 7일 이상 자동 삭제 |
| 7 | 백업 로깅 | 없음 | NEW /opt/backups/backup.log |
백업 스크립트는 크게 세 종류의 데이터를 수집한다: Docker 설정 파일, Docker 볼륨 데이터, 시스템 보안 설정. 이 세 가지를 하나의 tar.gz 파일로 압축하고, 7일 이상 된 파일은 자동 삭제한다.
이 파일들은 인프라 전체를 정의하는 설계도다. 이게 없으면 컨테이너를 다시 띄울 수 없다. 이전 글들에서 하나하나 만들었던 파일들이 전부 여기 포함된다.
| 경로 | 파일 | 역할 |
|---|---|---|
| /opt/docker-lab/ | docker-compose.yml | 웹 서비스 스택 정의 (Nginx + Backend) |
| /opt/docker-lab/ | nginx.conf | Nginx HTTPS + 리버스 프록시 설정 |
| /opt/docker-lab/ | Dockerfile | Python 백엔드 이미지 빌드 스크립트 |
| /opt/docker-lab/ | app.py | Python API 서버 코드 |
| /opt/docker-lab/ | index.html | 프론트엔드 페이지 |
| /opt/docker-lab/ssl/ | server.crt, server.key | SSL 인증서 + 개인키 |
| /opt/monitoring/ | docker-compose.yml | 모니터링 스택 정의 (8개 서비스) |
| /opt/monitoring/ | prometheus.yml | Prometheus 스크래핑 설정 |
| /opt/monitoring/ | loki-config.yml | Loki 로그 저장 설정 |
| /opt/monitoring/ | promtail-config.yml | Promtail 로그 수집 설정 |
Docker Named Volume은 컨테이너가 삭제돼도 데이터가 남는 영속 저장소다. 하지만 docker compose down -v를 실행하면 볼륨까지 같이 날아간다. Grafana에서 몇 시간 걸려 만든 대시보드, Prometheus에 쌓인 메트릭 데이터, Loki의 로그가 전부 사라진다. 그래서 볼륨 백업이 필요하다.
| 볼륨명 | 서비스 | 데이터 내용 |
|---|---|---|
| monitoring_prometheus_data | Prometheus | 시계열 메트릭 데이터 (TSDB) |
| monitoring_grafana_data | Grafana | 대시보드, 데이터소스, 사용자 설정 |
| monitoring_loki_data | Loki | 수집된 로그 데이터 + 인덱스 |
tar로 압축하는 게 공식 권장 방식이다. Alpine은 5MB짜리 초경량 이미지라 부담이 없다.이전 글(Rocky Linux 서버 보안 설정)에서 구성한 SSH 보안, Fail2ban, 방화벽 규칙도 백업해야 한다. 서버를 재설치하면 이 설정들도 전부 날아가기 때문이다. 방화벽 규칙은 파일이 아니라 런타임 상태이므로 firewall-cmd --list-all로 텍스트 덤프를 뜬다.
| 파일 | 역할 |
|---|---|
| /etc/ssh/sshd_config.d/99-security.conf | SSH 보안 설정 (루트 로그인 차단, 비밀번호 인증 비활성화 등) |
| /etc/fail2ban/jail.local | 침입 방지 규칙 (SSH 브루트포스 차단) |
| /root/user_manage.sh | 사용자 관리 자동화 스크립트 |
| firewall-rules.txt | 현재 방화벽 규칙 스냅샷 (런타임 덤프) |
이 스크립트가 /opt/backups/backup.sh이고, 실행 권한 755로 설정한다. 핵심 동작은 임시 디렉토리에 모든 파일을 모은 뒤 한 번에 tar.gz로 압축하는 것이다. 왜 임시 디렉토리를 쓰냐면, mktemp -d로 만들면 고유한 경로가 보장되어서 동시에 두 번 실행돼도 충돌하지 않고, 스크립트가 중간에 실패해도 임시 파일이 원래 디렉토리를 오염시키지 않는다.
#!/bin/bash # Rocky Linux Infrastructure Backup Script # 매일 자동 실행 (cron) BACKUP_DIR=/opt/backups DATE=$(date '+%Y%m%d_%H%M%S') BACKUP_FILE="backup_${DATE}.tar.gz" LOG_FILE="${BACKUP_DIR}/backup.log" log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE" } log "===== Backup Start =====" # 1. Docker 설정 파일 백업 TEMP_DIR=$(mktemp -d) mkdir -p "${TEMP_DIR}/docker-lab" mkdir -p "${TEMP_DIR}/monitoring" # docker-lab 설정 cp /opt/docker-lab/docker-compose.yml "${TEMP_DIR}/docker-lab/" cp /opt/docker-lab/nginx.conf "${TEMP_DIR}/docker-lab/" cp /opt/docker-lab/Dockerfile "${TEMP_DIR}/docker-lab/" cp /opt/docker-lab/app.py "${TEMP_DIR}/docker-lab/" cp /opt/docker-lab/index.html "${TEMP_DIR}/docker-lab/" cp -r /opt/docker-lab/ssl "${TEMP_DIR}/docker-lab/" # monitoring 설정 cp /opt/monitoring/docker-compose.yml "${TEMP_DIR}/monitoring/" cp /opt/monitoring/prometheus.yml "${TEMP_DIR}/monitoring/" cp /opt/monitoring/loki-config.yml "${TEMP_DIR}/monitoring/" cp /opt/monitoring/promtail-config.yml "${TEMP_DIR}/monitoring/" log "Config files copied" # 2. Docker 볼륨 백업 docker run --rm \ -v monitoring_prometheus_data:/source/prometheus:ro \ -v monitoring_grafana_data:/source/grafana:ro \ -v monitoring_loki_data:/source/loki:ro \ -v "${TEMP_DIR}:/backup" \ alpine tar czf /backup/volumes.tar.gz -C /source . log "Docker volumes backed up" # 3. 시스템 설정 백업 mkdir -p "${TEMP_DIR}/system" cp /etc/ssh/sshd_config.d/99-security.conf "${TEMP_DIR}/system/" 2>/dev/null cp /etc/fail2ban/jail.local "${TEMP_DIR}/system/" 2>/dev/null cp /root/user_manage.sh "${TEMP_DIR}/system/" 2>/dev/null firewall-cmd --list-all > "${TEMP_DIR}/system/firewall-rules.txt" 2>/dev/null log "System configs backed up" # 4. 압축 tar czf "${BACKUP_DIR}/${BACKUP_FILE}" -C "${TEMP_DIR}" . rm -rf "${TEMP_DIR}" log "Archive created: ${BACKUP_FILE}" # 5. 7일 이상 된 백업 삭제 find "${BACKUP_DIR}" -name "backup_*.tar.gz" -mtime +7 -delete DELETED=$(find "${BACKUP_DIR}" -name "backup_*.tar.gz" -mtime +7 2>/dev/null | wc -l) log "Old backups cleaned (deleted: ${DELETED})" # 6. 백업 파일 크기 기록 SIZE=$(du -sh "${BACKUP_DIR}/${BACKUP_FILE}" | cut -f1) log "Backup size: ${SIZE}" log "===== Backup Complete =====" echo "Backup complete: ${BACKUP_FILE} (${SIZE})"
| 단계 | 동작 | 왜 필요한가 |
|---|---|---|
| 1 | 임시 디렉토리 생성 | mktemp -d로 고유 경로 보장 — 동시 실행 충돌 방지 |
| 2 | 설정 파일 복사 | docker-lab, monitoring 설정 파일을 한 곳에 수집 |
| 3 | Docker 볼륨 백업 | Alpine 컨테이너로 볼륨 데이터를 tar 압축 — 호스트에서 직접 접근 불가하므로 |
| 4 | 시스템 설정 복사 | SSH, Fail2ban, 방화벽 규칙 — 서버 재설치 시 필요 |
| 5 | 전체 압축 | 수집한 모든 파일을 하나의 tar.gz로 묶어 관리 용이 |
| 6 | 임시 디렉토리 삭제 | 디스크 낭비 방지 |
| 7 | 오래된 백업 삭제 | 7일 초과 파일 자동 제거 — 디스크 가득 차는 것 방지 |
| 8 | 로그 기록 | 백업 성공/실패를 추적할 수 있도록 |
2>/dev/null을 붙인 이유는 해당 파일이 없을 수도 있기 때문이다. Fail2ban을 아직 설치 안 했거나, SSH 보안 설정을 안 했으면 에러가 나지만 스크립트가 중단되지 않고 넘어간다.스크립트를 만들었으면 매일 자동으로 실행되게 해야 한다. /etc/cron.d/ 디렉토리에 파일을 만드는 방식이 crontab -e보다 관리하기 좋다 — 파일 단위로 관리하면 어떤 cron이 설정되어 있는지 한눈에 보이고, 버전 관리(Git)도 가능하다.
/etc/cron.d/infrastructure-backup — 이 파일 이름은 자유지만, 무슨 작업인지 알 수 있는 이름이 좋다.
0 3 * * * root /opt/backups/backup.sh
| 필드 | 값 | 의미 |
|---|---|---|
| 분 | 0 | 정각 |
| 시 | 3 | 새벽 3시 |
| 일 | * | 매일 |
| 월 | * | 매월 |
| 요일 | * | 모든 요일 |
| 사용자 | root | root 권한으로 실행 (Docker + 시스템 파일 접근 필요) |
:ro(읽기 전용)로 마운트하기 때문에 서비스에 영향을 주지 않지만, I/O 부하를 최소화하기 위해 비피크 시간을 선택하는 게 좋다.# 1. 백업 디렉토리 생성 $ sudo mkdir -p /opt/backups # 2. 백업 스크립트 생성 (위 코드 작성) $ sudo vi /opt/backups/backup.sh # 3. 실행 권한 부여 $ sudo chmod 755 /opt/backups/backup.sh # 4. cron 작업 등록 $ echo '0 3 * * * root /opt/backups/backup.sh' | sudo tee /etc/cron.d/infrastructure-backup # 5. 테스트 실행 $ sudo /opt/backups/backup.sh
$ sudo /opt/backups/backup.sh Backup complete: backup_20260308_201445.tar.gz (22M)
$ ls -lah /opt/backups/backup_*.tar.gz -rw-r--r--. 1 root root 22M Mar 8 20:14 /opt/backups/backup_20260308_201445.tar.gz
[2026-03-08 20:14:50] ===== Backup Start =====
[2026-03-08 20:14:50] Config files copied
[2026-03-08 20:14:57] Docker volumes backed up
[2026-03-08 20:14:57] System configs backed up
[2026-03-08 20:14:57] Archive created: backup_20260308_201445.tar.gz
[2026-03-08 20:14:57] Old backups cleaned (deleted: 0)
[2026-03-08 20:14:57] Backup size: 22M
[2026-03-08 20:14:57] ===== Backup Complete =====
backup_20260308_201445.tar.gz ├── docker-lab/ │ ├── docker-compose.yml │ ├── nginx.conf │ ├── Dockerfile │ ├── app.py │ ├── index.html │ └── ssl/ │ ├── server.crt │ └── server.key ├── monitoring/ │ ├── docker-compose.yml │ ├── prometheus.yml │ ├── loki-config.yml │ └── promtail-config.yml ├── system/ │ ├── 99-security.conf │ ├── jail.local │ ├── user_manage.sh │ └── firewall-rules.txt └── volumes.tar.gz ├── prometheus/ (Prometheus TSDB 데이터) ├── grafana/ (대시보드, 설정) └── loki/ (로그 데이터)
find -mtime +7 -delete는 "수정 시간이 7일을 초과한 파일을 삭제"하는 명령이다. 백업 파일이 매일 22MB씩 쌓이면 7일 기준 약 154MB만 차지한다. 보존 기간을 늘리고 싶으면 +7을 +30으로 바꾸면 된다.
| 항목 | 설정 |
|---|---|
| 보존 기간 | 7일 |
| 삭제 방식 | find -mtime +7 -delete |
| 실행 시점 | 백업 완료 후 자동 실행 |
| 예상 디스크 사용량 | 약 22MB x 7일 = ~154MB |
백업은 복원할 수 있어야 의미가 있다. 전체 복원과 개별 파일 복원 두 가지 시나리오를 정리한다.
# 백업 파일 압축 해제 $ cd /tmp $ tar xzf /opt/backups/backup_YYYYMMDD_HHMMSS.tar.gz # 1. Docker 설정 파일 복원 $ sudo cp -r docker-lab/* /opt/docker-lab/ $ sudo cp -r monitoring/* /opt/monitoring/ # 2. 시스템 설정 복원 $ sudo cp system/99-security.conf /etc/ssh/sshd_config.d/ $ sudo cp system/jail.local /etc/fail2ban/ $ sudo cp system/user_manage.sh /root/ $ sudo systemctl restart sshd fail2ban # 3. Docker 볼륨 복원 # ⚠️ 주의: 기존 볼륨 데이터가 삭제됨 $ cd /opt/monitoring && docker compose down $ docker volume rm monitoring_prometheus_data monitoring_grafana_data monitoring_loki_data # 볼륨 재생성 및 데이터 복원 $ docker volume create monitoring_prometheus_data $ docker volume create monitoring_grafana_data $ docker volume create monitoring_loki_data $ docker run --rm \ -v monitoring_prometheus_data:/target/prometheus \ -v monitoring_grafana_data:/target/grafana \ -v monitoring_loki_data:/target/loki \ -v /tmp:/backup:ro \ alpine sh -c "tar xzf /backup/volumes.tar.gz -C /target" # 4. 서비스 재시작 $ cd /opt/docker-lab && docker compose up -d $ cd /opt/monitoring && docker compose up -d
docker volume rm은 기존 볼륨 데이터를 완전히 삭제한다. 복원 전에 현재 데이터가 정말 필요 없는지 확인하고 실행해야 한다.전체 복원이 아니라 특정 파일 하나만 꺼내고 싶을 때는 tar에 경로를 지정하면 된다.
# nginx.conf만 추출 $ tar xzf /opt/backups/backup_YYYYMMDD_HHMMSS.tar.gz docker-lab/nginx.conf # prometheus.yml만 추출 $ tar xzf /opt/backups/backup_YYYYMMDD_HHMMSS.tar.gz monitoring/prometheus.yml
/opt/backups/ ├── backup.sh # 백업 스크립트 (755) ├── backup.log # 백업 실행 로그 └── backup_20260308_201445.tar.gz # 백업 파일 (22MB) /etc/cron.d/ └── infrastructure-backup # cron 작업 정의
# 수동 백업 실행 $ sudo /opt/backups/backup.sh # 백업 로그 확인 $ cat /opt/backups/backup.log # 백업 파일 목록 확인 $ ls -lah /opt/backups/backup_*.tar.gz # 백업 파일 내용 확인 (압축 해제 없이) $ tar tzf /opt/backups/backup_YYYYMMDD_HHMMSS.tar.gz # cron 작업 확인 $ cat /etc/cron.d/infrastructure-backup # 디스크 사용량 확인 $ du -sh /opt/backups/
현재 구성은 단일 서버 로컬 백업이다. 서버 자체가 날아가면 백업도 같이 날아간다. 프로덕션 환경에서는 아래 사항을 추가로 적용해야 한다.
| 항목 | 권장 사항 | 왜 필요한가 |
|---|---|---|
| 원격 백업 | rsync/scp로 외부 서버에 백업 복사 | 서버 장애 시 로컬 백업도 유실되므로 |
| 백업 암호화 | gpg 또는 openssl로 백업 파일 암호화 | SSL 개인키 등 민감 정보 포함 |
| 백업 무결성 | sha256sum 체크섬 파일 생성 | 전송 중 파일 손상 감지 |
| 알림 | 백업 실패 시 텔레그램/이메일 알림 | 실패를 모르고 지나치면 의미 없음 |
| 증분 백업 | 전체 백업 + 증분 백업 조합 | 볼륨 데이터가 커지면 디스크/시간 절약 |
| 복원 테스트 | 정기적 복원 테스트 | 복원이 안 되는 백업은 백업이 아님 |
| 모니터링 | Prometheus에서 백업 디스크 사용량 추적 | 디스크 가득 차기 전에 감지 |
:ro(읽기 전용)로 마운트하기 때문에 기존 서비스 컨테이너는 정상 동작한다. 다만 볼륨 데이터의 쓰기가 진행 중인 시점의 스냅샷이므로, 극히 일부 데이터가 불완전할 수 있다. 이를 방지하려면 서비스를 잠시 중지하고 백업하는 "콜드 백업" 방식을 써야 한다.cat /opt/backups/backup.log에서 매일 03:00에 로그가 찍히는지 확인한다. 또는 grep CRON /var/log/cron으로 cron 데몬의 실행 기록을 확인할 수 있다. cron 파일 권한이 잘못되면(너무 넓은 퍼미션) cron이 무시하므로, /etc/cron.d/ 파일은 644로 설정하고 소유자는 root여야 한다.docker compose up -d를 실행하면 된다. 단, 현재 구성은 로컬 백업이라 서버 디스크가 날아가면 백업도 함께 유실되므로, 반드시 원격 백업을 추가해야 한다.server.key(SSL 개인키)가 백업에 포함된다. 백업 파일이 유출되면 SSL 암호화가 무력화될 수 있다. 프로덕션에서는 gpg -c backup_날짜.tar.gz로 백업 파일 자체를 암호화하거나, 원격 전송 시 반드시 암호화된 채널(scp, rsync over SSH)을 사용해야 한다.