[클라우드 인프라 구축기] 서버 자동화 스크립트 작성하기

이태수·2026년 2월 2일
post-thumbnail

TL;DR

  • cron과 Bash 스크립트로 서버 운영을 자동화한다.
  • 디스크 용량 모니터링, 로그 정리, DB 백업을 자동화해서 수동 작업을 최소화하고 장애를 예방한다.

📚 클라우드 인프라 구축기 시리즈
1. KakaoCloud 2-Tier 아키텍처 설계
2. GitHub Actions로 CI/CD 구축하기
3. SSH 브루트포스 5,227건 대응기
4. Nginx 설정으로 SSL Labs A+ 달성하기
5. 서버 자동화 스크립트 ⬅️ 현재 글


1. 들어가며

서버를 운영하다 보면 반복적인 작업이 생긴다.

  • 디스크 용량 확인
  • 오래된 로그 삭제
  • DB 백업

이런 작업을 매번 수동으로 하면 시간도 들고 잊어버리기 쉽다.
cron과 Bash 스크립트로 자동화하면 이런 문제를 해결할 수 있다.


1.1 자동화 대상

작업목적주기
디스크 모니터링용량 부족 사전 감지매일
로그 정리불필요한 로그 삭제매주
헬스체크서비스 상태 확인매일
DB 백업데이터 손실 방지매일
Docker 정리미사용 이미지/캐시 삭제매주

1.2 스크립트 위치

~/scripts/
├── disk-alert.sh       # 디스크 용량 알림
├── weekly-cleanup.sh   # 주간 정리 (로그 + Docker)
├── healthcheck.sh      # 서비스 헬스체크
└── backup-db.sh        # DB 백업 (/usr/local/bin/)

2. 디스크 모니터링 스크립트


2.1 문제 상황

서버 디스크가 가득 차면 서비스가 중단된다.
Docker 로그, 시스템 로그가 쌓이면서 용량이 부족해지는 경우가 많다.


2.2 스크립트 작성

~/scripts/disk-alert.sh

#!/bin/bash

# 설정
THRESHOLD=80  # 경고 기준 (%)
LOG_FILE="/var/log/disk-alert.log"

# 디스크 사용량 확인
USAGE=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')

# 로그 기록
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Disk usage: ${USAGE}%" >> $LOG_FILE

# 임계치 초과 시 경고
if [ "$USAGE" -gt "$THRESHOLD" ]; then
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARNING: Disk usage exceeded ${THRESHOLD}%" >> $LOG_FILE

    # 용량 많이 차지하는 디렉토리 확인
    echo "Top 5 directories:" >> $LOG_FILE
    du -h --max-depth=1 / 2>/dev/null | sort -hr | head -5 >> $LOG_FILE
fi

2.3 스크립트 설명

명령어설명
df /루트 파티션 용량 확인
awk 'NR==2 {print $5}'두 번째 줄의 5번째 컬럼 (사용률) 추출
sed 's/%//'% 기호 제거
du -h --max-depth=11단계 깊이까지 디렉토리 용량 확인

2.4 실행 권한 부여

chmod +x ~/scripts/disk-alert.sh

2.5 실행 결과


3. 주간 정리 스크립트


3.1 정리 대상

대상명령어효과
시스템 로그journalctl --vacuum-time=7d7일 이전 로그 삭제
Docker 캐시docker system prune -f미사용 컨테이너/네트워크 삭제
Docker 빌더 캐시docker builder prune -f빌드 캐시 삭제
임시 파일rm -rf /tmp/*임시 파일 삭제

3.2 스크립트 작성

~/scripts/weekly-cleanup.sh

#!/bin/bash

LOG_FILE="/var/log/weekly-cleanup.log"
echo "====== Weekly Cleanup Started: $(date) ======" >> $LOG_FILE

# 1. 시스템 로그 정리 (7일 이전)
echo "[$(date '+%H:%M:%S')] Cleaning system logs..." >> $LOG_FILE
sudo journalctl --vacuum-time=7d >> $LOG_FILE 2>&1

# 2. Docker 정리
echo "[$(date '+%H:%M:%S')] Cleaning Docker..." >> $LOG_FILE
docker system prune -f >> $LOG_FILE 2>&1
docker builder prune -f >> $LOG_FILE 2>&1

# 3. 임시 파일 정리
echo "[$(date '+%H:%M:%S')] Cleaning temp files..." >> $LOG_FILE
sudo rm -rf /tmp/* 2>/dev/null

# 4. 정리 후 디스크 용량 확인
echo "[$(date '+%H:%M:%S')] Disk usage after cleanup:" >> $LOG_FILE
df -h / >> $LOG_FILE

echo "====== Weekly Cleanup Completed: $(date) ======" >> $LOG_FILE

3.3 실행 결과

$ tail -20 /var/log/weekly-cleanup.log

freed 0B는 "정리할 게 없다"는 뜻이라 오히려 자동화가 잘 되고 있다는 증거이다.
매주 금요일에 돌면서 쌓이기 전에 미리 정리하고 있고 잘 동작하고 있다.


3.4 실제 정리 효과

프로젝트 운영 중 실제로 정리한 결과:

$ sudo journalctl --vacuum-time=7d
Vacuuming done, freed 411.5M of archived journals from /var/log/journal
대상정리 전정리 후절약
시스템 로그427.5MB16MB411.5MB
Docker 캐시~2GB~500MB~1.5GB

4. 헬스체크 스크립트


4.1 목적

서비스가 정상 작동하는지 매일 확인한다. Backend, Nginx, 디스크, 메모리 상태를 한눈에 파악할 수 있다.


4.2 체크 항목

항목확인 방법정상 기준
BackendHTTP 상태코드200
Nginxsystemctl 상태active
Diskdf 사용률80% 미만
Memoryfree 사용량-

4.3 스크립트 작성

~/scripts/healthcheck.sh

#!/bin/bash

LOG_FILE=~/logs/health-$(date +%Y%m%d).log
mkdir -p ~/logs

echo "=== Health Check: $(date) ===" >> $LOG_FILE

# Backend 체크
BACKEND=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/)
if [ "$BACKEND" = "200" ]; then
    echo "✅ Backend: OK" >> $LOG_FILE
else
    echo "❌ Backend: FAIL ($BACKEND)" >> $LOG_FILE
fi

# Nginx 체크
NGINX=$(sudo systemctl is-active nginx)
echo "📦 Nginx: $NGINX" >> $LOG_FILE

# 디스크 사용량
DISK=$(df / | tail -1 | awk '{print $5}')
echo "💾 Disk: $DISK" >> $LOG_FILE

# 메모리
MEM=$(free -h | grep Mem | awk '{print $3"/"$2}')
echo "🧠 Memory: $MEM" >> $LOG_FILE

4.4 스크립트 설명

명령어설명
curl -s -o /dev/null -w "%{http_code}"HTTP 상태코드만 출력
systemctl is-active서비스 상태 확인 (active/inactive)
df / \| tail -1 \| awk '{print $5}'디스크 사용률 추출
free -h \| grep Mem메모리 사용량 확인

4.5 실행 결과

$ ~/scripts/healthcheck.sh && cat ~/logs/health-$(date +%Y%m%d).log
=== Health Check: Mon Feb  2 09:00:00 UTC 2026 ===
✅ Backend: OK
📦 Nginx: active
💾 Disk: 19%
🧠 Memory: 456Mi/15Gi

5. DB 백업 스크립트


5.1 백업 전략

항목설정
백업 주기평일 오전 8시
보관 기간7일
백업 위치/var/backups/postgresql
백업 형식SQL 덤프 (gzip 압축)

5.2 환경변수 파일 생성

스크립트에 비밀번호를 직접 넣는 것은 보안상 좋지 않다.
별도 환경변수 파일을 사용한다.


/etc/backup-db.env

# root만 읽을 수 있도록 설정
sudo touch /etc/backup-db.env
sudo chmod 600 /etc/backup-db.env

# 내용 작성
sudo vi /etc/backup-db.env
DB_PASSWORD="your-password-here"

5.3 스크립트 작성

/usr/local/bin/backup-db.sh

#!/bin/bash

# 환경변수 로드 (비밀번호 분리)
source /etc/backup-db.env

# 설정
BACKUP_DIR="/var/backups/postgresql"
DB_NAME="moviesir"
DB_USER="movigation"
RETENTION_DAYS=7

# 백업 디렉토리 생성
mkdir -p $BACKUP_DIR

# 백업 파일명 (날짜 포함)
BACKUP_FILE="$BACKUP_DIR/${DB_NAME}_$(date +%Y%m%d_%H%M%S).sql.gz"

# PostgreSQL 덤프 + gzip 압축
echo "[$(date)] Starting backup..."
PGPASSWORD=$DB_PASSWORD pg_dump -h localhost -U $DB_USER $DB_NAME | gzip > $BACKUP_FILE

# 백업 성공 확인
if [ $? -eq 0 ]; then
    echo "[$(date)] Backup completed: $BACKUP_FILE"
    echo "[$(date)] File size: $(du -h $BACKUP_FILE | cut -f1)"
else
    echo "[$(date)] Backup FAILED!"
    exit 1
fi

# 오래된 백업 삭제 (7일 이전)
echo "[$(date)] Removing old backups..."
find $BACKUP_DIR -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete

# 남은 백업 파일 목록
echo "[$(date)] Current backups:"
ls -lh $BACKUP_DIR/*.sql.gz 2>/dev/null

5.4 스크립트 설명

항목설명
source /etc/backup-db.env비밀번호를 별도 파일에서 로드 (보안)
/var/backups/postgresql시스템 표준 백업 경로 사용
gzip압축으로 용량 ~75% 절약
$?이전 명령어 종료 코드 확인 (에러 처리)

5.5 백업 파일 예시

$ ls /var/backups/postgresql/

6. Cron 설정


6.1 Cron 기본 문법

분  시  일  월  요일  명령어
*   *   *   *   *     command
필드범위설명
0-59실행 시각 (분)
0-23실행 시각 (시)
1-31실행 날짜
1-12실행 월
요일0-70,7=일요일, 1=월요일

6.2 Cron 설정 방법


# crontab 편집 (root 권한 필요 시)
sudo crontab -e

# 아래 내용 추가
# 평일 오전 8시: DB 백업 (GPU 서버)
0 8 * * 1-5 /usr/local/bin/backup-db.sh >> /var/log/db-backup.log 2>&1

# 평일 오전 9:30: 헬스체크 (App 서버)
30 9 * * 1-5 /home/ubuntu/scripts/healthcheck.sh

# 평일 오후 5:30: 디스크 용량 체크 (App 서버)
30 17 * * 1-5 /home/ubuntu/scripts/disk-alert.sh

# 매주 금요일 오후 5시: 주간 정리 (App 서버)
0 17 * * 5 /home/ubuntu/scripts/weekly-cleanup.sh

💡 시간 설정 참고

일반적으로 DB 백업은 새벽 3시(트래픽 최저 시간)에 실행하는 것이 업계 표준이다. 하지만 이 프로젝트의 서버는 09:00~18:00에만 운영되므로,
서버 시작 직후인 오전 8시에 백업을 실행하도록 설정했다.
운영 환경에 맞게 조정하면 된다.


6.3 Cron 설정 확인

# 현재 crontab 확인
sudo crontab -l

# cron 서비스 상태 확인
sudo systemctl status cron

6.4 Cron 로그 확인

# cron 실행 로그
grep CRON /var/log/syslog | tail -20

7. 트러블슈팅


7.1 백업 스크립트 실패 (0바이트 파일)

문제: 백업 파일이 0바이트로 생성됨

$ ls -lh ~/backup/
-rw-r--r-- 1 ubuntu ubuntu 0 Jan 19 03:00 moviesir_20260119_030000.sql.gz

원인: DB 비밀번호 변경 후 스크립트 미업데이트

해결:

# 스크립트의 비밀번호 업데이트
sudo vi /usr/local/bin/backup-db.sh
# DB_PASSWORD 값 수정

# 수동 실행으로 테스트
/usr/local/bin/backup-db.sh

교훈: DB 비밀번호 변경 시 백업 스크립트도 함께 업데이트해야 한다.


7.2 Cron 스크립트가 실행 안 됨

문제: crontab에 등록했는데 스크립트가 실행되지 않음

체크리스트:

확인 사항명령어
실행 권한ls -l ~/scripts/*.sh
cron 서비스 상태sudo systemctl status cron
스크립트 경로절대 경로 사용 여부
환경변수cron은 최소 환경변수만 가짐

해결:

# 1. 실행 권한 부여
chmod +x ~/scripts/*.sh

# 2. 절대 경로 사용
# Bad:  ./scripts/disk-alert.sh
# Good: /home/ubuntu/scripts/disk-alert.sh

# 3. 환경변수 명시 (필요 시)
PATH=/usr/local/bin:/usr/bin:/bin
0 3 * * * /usr/local/bin/backup-db.sh

7.3 journalctl 정리 권한 오류

문제: journalctl --vacuum-time 실행 시 권한 오류

$ journalctl --vacuum-time=7d
Failed to rotate journal: Permission denied

해결:

# sudo 사용
sudo journalctl --vacuum-time=7d

8. 운영 팁

8.1 스크립트 작성 원칙

원칙설명
로그 남기기언제 무엇을 했는지 기록
에러 처리실패 시 알 수 있도록
멱등성여러 번 실행해도 같은 결과
주석 작성나중에 봐도 이해할 수 있게

8.2 모니터링 확인 주기

항목확인 주기방법
백업 성공 여부매일로그 파일 확인
디스크 용량매주df -h
cron 실행 여부매주grep CRON /var/log/syslog

8.3 알림 추가 (선택)

Slack이나 Discord로 알림을 보내면 더 편하다:

# Slack Webhook 예시
curl -X POST -H 'Content-type: application/json' \
  --data '{"text":"[서버] 디스크 용량 80% 초과!"}' \
  $SLACK_WEBHOOK_URL

9. 결과

9.1 자동화 전후 비교

항목BeforeAfter
백업수동 (가끔 잊음)매일 자동
로그 정리디스크 가득 찬 후 정리매주 자동
디스크 모니터링문제 발생 후 인지사전 감지

9.2 절약 효과

항목효과
시간주 1시간 → 0시간 (자동화)
디스크매주 ~2GB 정리
안정성디스크 풀 장애 예방

10. 마무리

서버 자동화의 핵심은 반복 작업을 스크립트로 만들고 cron으로 예약하는 것이다.

단계내용
1반복 작업 파악
2Bash 스크립트 작성
3수동 실행으로 테스트
4cron에 등록
5로그로 실행 확인

작은 스크립트부터 시작해서 점점 자동화 범위를 늘려가면 된다.


부록: 전체 스크립트


A. disk-alert.sh

#!/bin/bash
THRESHOLD=80
LOG_FILE="/var/log/disk-alert.log"

USAGE=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Disk usage: ${USAGE}%" >> $LOG_FILE

if [ "$USAGE" -gt "$THRESHOLD" ]; then
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARNING: Disk usage exceeded ${THRESHOLD}%" >> $LOG_FILE
    du -h --max-depth=1 / 2>/dev/null | sort -hr | head -5 >> $LOG_FILE
fi

B. weekly-cleanup.sh

#!/bin/bash
LOG_FILE="/var/log/weekly-cleanup.log"
echo "====== Weekly Cleanup: $(date) ======" >> $LOG_FILE

sudo journalctl --vacuum-time=7d >> $LOG_FILE 2>&1
docker system prune -f >> $LOG_FILE 2>&1
docker builder prune -f >> $LOG_FILE 2>&1
sudo rm -rf /tmp/* 2>/dev/null

df -h / >> $LOG_FILE
echo "====== Completed ======" >> $LOG_FILE

C. healthcheck.sh

#!/bin/bash
LOG_FILE=~/logs/health-$(date +%Y%m%d).log
mkdir -p ~/logs

echo "=== Health Check: $(date) ===" >> $LOG_FILE

BACKEND=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/)
if [ "$BACKEND" = "200" ]; then
    echo "✅ Backend: OK" >> $LOG_FILE
else
    echo "❌ Backend: FAIL ($BACKEND)" >> $LOG_FILE
fi

NGINX=$(sudo systemctl is-active nginx)
echo "📦 Nginx: $NGINX" >> $LOG_FILE

DISK=$(df / | tail -1 | awk '{print $5}')
echo "💾 Disk: $DISK" >> $LOG_FILE

MEM=$(free -h | grep Mem | awk '{print $3"/"$2}')
echo "🧠 Memory: $MEM" >> $LOG_FILE

D. backup-db.sh

#!/bin/bash
source /etc/backup-db.env

BACKUP_DIR="/var/backups/postgresql"
DB_NAME="moviesir"
DB_USER="movigation"
RETENTION_DAYS=7

mkdir -p $BACKUP_DIR
BACKUP_FILE="$BACKUP_DIR/${DB_NAME}_$(date +%Y%m%d_%H%M%S).sql.gz"

PGPASSWORD=$DB_PASSWORD pg_dump -h localhost -U $DB_USER $DB_NAME | gzip > $BACKUP_FILE

if [ $? -eq 0 ]; then
    echo "[$(date)] Backup completed: $(du -h $BACKUP_FILE | cut -f1)"
else
    echo "[$(date)] Backup FAILED!"
    exit 1
fi

find $BACKUP_DIR -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete

E. crontab 설정

# GPU 서버 (sudo crontab -e)
0 8 * * 1-5 /usr/local/bin/backup-db.sh >> /var/log/db-backup.log 2>&1

# App 서버 (sudo crontab -e)
30 9 * * 1-5 /home/ubuntu/scripts/healthcheck.sh
30 17 * * 1-5 /home/ubuntu/scripts/disk-alert.sh
0 17 * * 5 /home/ubuntu/scripts/weekly-cleanup.sh

GitHub


이 글은 스나이퍼팩토리 카카오클라우드 AIaaS 마스터 클래스 2기 프로젝트 경험을 바탕으로 작성되었습니다.

0개의 댓글