안녕하세요 MANVSCLOUD 김수현입니다.
지난 포스팅에서 우리는 관리형 데이터베이스의 백업 성공/실패 여부를 Slack으로 알림을 받을 수 있도록 기능을 구현해보았습니다. 백업 알림을 통해 문제 상황을 빠르게 인지할 수 있게 되었지만 여전히 백업 실패 자체는 해결되지 않았습니다. 이제 이어서 백업 실패 문제를 어떻게 근본적으로 해결할 수 있을지 이야기해보고자 합니다.
잘 만들어진 쉘 스크립트 하나
오늘도 역시 PostgreSQL을 기준으로 작성하며 pg_dump 유틸리티를 이용할 것입니다. 이전 포스팅에서 Cloud Functions을 활용했지만 이번에는 다른 접근 방식을 취하겠습니다. Cloud Functions의 경우 TimeOut 설정값이 짧은 편이라 백업 및 업로드 시간 소요가 길어지면 백업 실패로 이어질 수 있기 때문에 이번에는 서버에서 직접 쉘 스크립트를 실행시키는 방식으로 준비했습니다.
추가로 제가 작성한 쉘 스크립트는 데이터베이스 패스워드를 직접 스크립트에 하드코딩하거나 환경변수로 노출시키지 않기 위해 Secret Manager를 사용하는 기준으로 작성되었습니다. 이는 보안 모범 사례를 따르는 접근법으로 민감한 자격 증명을 안전하게 관리할 수 있게 해줍니다. Secret Manager를 사용하고 있지 않다면 스크립트를 적절히 수정하여 다른 보안 방식을 활용해주세요.
- 사전 준비 및 확인 사항
이 스크립트를 실행하기 전에 아래 네 가지 항목을 확인해주세요:
1. Ubuntu 22.04 기준 pg_dump 사용을 위한 준비: PostgreSQL 클라이언트 도구 설치가 필요합니다.
apt install postgresql-client
2. AWS CLI 설치 불필요: 이 스크립트는 AWS CLI 없이도 Object Storage와 통신할 수 있도록 작성되었습니다. curl 명령을 활용하여 직접 API 호출을 수행합니다. (물론 설치가 되어있어도 정상 동작합니다.)
3. 네트워크 연결 설정: 스크립트를 실행하는 서버에서 Cloud DB에 연결할 수 있도록 ACG(Access Control Group) 설정이 필요합니다.
4. 환경 변수 설정: 다음 환경 변수를 미리 설정해두어야 합니다.
– ACCESS_KEY, SECRET_KEY : Secret Manager GetValue 권한 및 Object Storage Upload 권한이 있는 API 키
– SECRET_ID : Secret Manager의 ID
- 백업 스크립트
: 아래는 PostgreSQL 데이터베이스를 백업 및 압축하고 Object Storage에 업로드하는 전체 쉘 스크립트입니다
#!/bin/bash
# Secret Manager에서 비밀번호 가져오기 함수
get_db_password() {
# 환경 변수 확인
if [ -z "$ACCESS_KEY" ] || [ -z "$SECRET_KEY" ] || [ -z "$SECRET_ID" ]; then
echo "필요한 환경 변수가 설정되지 않았습니다."
echo "다음 환경 변수를 설정해주세요: ACCESS_KEY, SECRET_KEY, SECRET_ID"
exit 1
fi
# 현재 시간을 밀리초로 변환
TIMESTAMP=$(date +%s%3N)
# API 호출 정보
API_HOST="secretmanager.apigw.ntruss.com"
REQUEST_URL="/api/v1/secrets/${SECRET_ID}/values"
REQUEST_METHOD="GET"
# 서명 생성 (API Gateway signature v2)
create_signature() {
local method=$1
local url=$2
local timestamp=$3
local access_key=$4
local secret_key=$5
# 서명 대상 문자열 생성
local message="${method} ${url}
${timestamp}
${access_key}"
# Base64로 인코딩된 HMAC-SHA256 서명 생성
local signature=$(echo -n "${message}" | openssl dgst -sha256 -hmac "${secret_key}" -binary | base64)
echo ${signature}
}
# 서명 생성
SIGNATURE=$(create_signature ${REQUEST_METHOD} ${REQUEST_URL} ${TIMESTAMP} ${ACCESS_KEY} ${SECRET_KEY})
# API 호출
response=$(curl -s \
-H "x-ncp-apigw-timestamp:${TIMESTAMP}" \
-H "x-ncp-iam-access-key:${ACCESS_KEY}" \
-H "x-ncp-apigw-signature-v2:${SIGNATURE}" \
-H "Content-Type:application/json" \
"https://${API_HOST}${REQUEST_URL}")
# 응답에서 active의 cdbPassword 추출
password=$(echo "${response}" | python -c '
import json, sys
try:
data = json.load(sys.stdin)
if data["code"] == "SUCCESS":
active_json = json.loads(data["data"]["decryptedSecretChain"]["active"])
print(active_json["cdbPassword"])
else:
print("Error: " + json.dumps(data), file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error parsing response: {e}", file=sys.stderr)
print(sys.stdin.read(), file=sys.stderr)
sys.exit(1)
')
# 오류 확인
if [ $? -ne 0 ]; then
echo "비밀번호 가져오기 실패"
exit 1
fi
echo "$password"
}
# 메인 백업 스크립트
main() {
# 데이터베이스 연결 정보
DB_HOST="pg-32lqva.vpc-cdb-kr.ntruss.com"
DB_USER="manvscloud"
DB_PORT="15432"
DB_NAME="manvscloud"
BUCKET_NAME="manvscloud-psql-backup"
# Secret Manager에서 DB 비밀번호 가져오기
echo "Secret Manager에서 데이터베이스 비밀번호를 가져오는 중..."
DB_PASSWORD=$(get_db_password)
if [ -z "$DB_PASSWORD" ]; then
echo "데이터베이스 비밀번호를 가져오지 못했습니다."
exit 1
fi
echo "비밀번호를 성공적으로 가져왔습니다."
# 현재 날짜와 시간 정보 가져오기
CURRENT_DATE=$(date +"%Y%m%d%H%M")
YEAR=$(date +"%Y")
MONTH=$(date +"%m")
DAY=$(date +"%d")
# 백업 파일 경로 및 이름 설정
BACKUP_DIR="/tmp"
BACKUP_FILE="${DB_NAME}_${CURRENT_DATE}.sql"
COMPRESSED_FILE="${BACKUP_FILE}.gz"
OBJECT_PATH="${YEAR}/${MONTH}/${DAY}/${COMPRESSED_FILE}"
# 백업 디렉토리가 없으면 생성
mkdir -p ${BACKUP_DIR}
echo "PostgreSQL 데이터베이스 백업을 시작합니다..."
# pg_dump를 사용하여 데이터베이스 백업
PGPASSWORD="${DB_PASSWORD}" pg_dump -h ${DB_HOST} -U ${DB_USER} -p ${DB_PORT} -d ${DB_NAME} -F p > ${BACKUP_DIR}/${BACKUP_FILE}
# 백업 성공 여부 확인
if [ $? -ne 0 ]; then
echo "데이터베이스 백업 실패"
exit 1
fi
echo "데이터베이스 백업이 완료되었습니다. 파일 압축을 시작합니다..."
# 백업 파일 압축
gzip -f ${BACKUP_DIR}/${BACKUP_FILE}
# 압축 성공 여부 확인
if [ $? -ne 0 ]; then
echo "백업 파일 압축 실패"
exit 1
fi
echo "파일 압축이 완료되었습니다. NAVER Cloud Object Storage에 업로드를 시작합니다..."
# curl을 사용하여 Object Storage에 업로드
echo "curl을 사용하여 Object Storage에 업로드합니다..."
# S3 날짜 형식 (RFC 1123)
DATE=$(date -u +"%a, %d %b %Y %H:%M:%S GMT")
# 업로드할 파일 경로
FILE_PATH="${BACKUP_DIR}/${COMPRESSED_FILE}"
# Content-Type 결정
CONTENT_TYPE="application/gzip"
# 버킷과 오브젝트 이름
RESOURCE="/${BUCKET_NAME}/${OBJECT_PATH}"
# 서명 생성을 위한 문자열
STRING_TO_SIGN="PUT\n\n${CONTENT_TYPE}\n${DATE}\n${RESOURCE}"
# HMAC-SHA1 서명 생성
SIGNATURE=$(echo -en "${STRING_TO_SIGN}" | openssl sha1 -hmac "${SECRET_KEY}" -binary | base64)
# curl을 사용하여 파일 업로드
UPLOAD_RESULT=$(curl -s -X PUT \
-T "${FILE_PATH}" \
-H "Host: kr.object.ncloudstorage.com" \
-H "Date: ${DATE}" \
-H "Content-Type: ${CONTENT_TYPE}" \
-H "Authorization: AWS ${ACCESS_KEY}:${SIGNATURE}" \
"https://kr.object.ncloudstorage.com${RESOURCE}")
# 업로드 성공 여부 확인 (HTTP 상태 코드로 확인)
CURL_EXIT_CODE=$?
if [ ${CURL_EXIT_CODE} -ne 0 ]; then
echo "Object Storage 업로드 실패: curl 오류 (코드: ${CURL_EXIT_CODE})"
echo "응답: ${UPLOAD_RESULT}"
exit 1
fi
# 업로드가 성공적으로 완료되었는지 확인
# (curl이 성공적으로 완료되면 빈 응답을 반환함)
if [[ -n "${UPLOAD_RESULT}" && "${UPLOAD_RESULT}" == *error* ]]; then
echo "Object Storage 업로드 실패: ${UPLOAD_RESULT}"
exit 1
fi
echo "백업 파일이 성공적으로 NAVER Cloud Object Storage에 업로드되었습니다."
echo "경로: ${BUCKET_NAME}/${OBJECT_PATH}"
# 임시 백업 파일 정리
rm -f ${BACKUP_DIR}/${COMPRESSED_FILE}
echo "임시 파일이 정리되었습니다. 백업 프로세스가 완료되었습니다."
}
# 스크립트 실행
main
실제 환경에 맞게 스크립트를 수정하여 사용하시려면 main() 함수 시작 부분에 있는 데이터베이스 연결 정보와 버킷 정보를 변경해주셔야 합니다.
main() {
# 데이터베이스 연결 정보
DB_HOST="example.vpc-cdb-kr.ntruss.com"
DB_USER="manvscloud"
DB_PORT="15432"
DB_NAME="manvscloud"
BUCKET_NAME="manvscloud-psql-backup"
백업이 정상적으로 완료되면 지정한 Object Storage에 데이터베이스 백업이 있어야합니다.

# 스크립트 동작 방식 상세 설명
이 스크립트는 크게 다음과 같은 순서로 동작합니다:
- Secret Manager에서 데이터베이스 비밀번호 검색:
- NAVER Cloud Secret Manager API를 호출하여 데이터베이스 비밀번호를 안전하게 가져옵니다.
- API 호출 시 적절한 서명을 생성하여 인증을 수행합니다.
- 응답에서 Python 스크립트를 이용해 비밀번호 값을 추출합니다.
- 데이터베이스 백업 생성:
- pg_dump 명령어를 사용하여 데이터베이스의 백업을 생성합니다.
- 백업 파일은 임시 디렉토리(/tmp)에 저장됩니다. (DB 및 볼륨 용량에 따라 경로 변경 필수)
- 날짜와 시간을 포함한 파일명으로 생성하여 백업 버전 관리가 용이하게 합니다.
- 백업 파일 압축:
- 생성된 SQL 백업 파일을 gzip으로 압축하여 용량을 줄입니다.
- 생성된 SQL 백업 파일을 gzip으로 압축하여 용량을 줄입니다.
- Object Storage에 업로드:
- AWS S3 호환 API를 통해 NAVER Cloud Object Storage에 백업 파일을 업로드합니다.
- 년/월/일 형식의 폴더 구조로 저장하여 백업 파일을 체계적으로 관리합니다.
- curl을 사용하여 직접 API 호출을 수행하므로 AWS CLI가 필요하지 않습니다.
- 정리 및 완료:
- 업로드가 완료된 후 임시 파일을 삭제하여 디스크 공간을 확보합니다.
이제 작성된 스크립트를 실행하여 테스트해보고 정상적으로 실행된다면 다음과 같은 방법으로 자동화할 수 있습니다.
1) Crontab을 활용한 정기 백업 스케줄링
# 매일 새벽 2시에 백업 스크립트 실행 0 2 * * * /path/to/backup_script.sh >> /var/log/db_backup.log 2>&1
2) 기존 알림 시스템과의 통합
: 지난 포스팅에서 구현한 백업 성공/실패 알림 기능을 업그레이드하여 관리형 데이터베이스 백업 실패 시 자동으로 이 스크립트를 실행하도록 구성할 수 있습니다.
3) 백업 수명 주기 관리
: Object Storage의 수명 주기 정책을 설정하여 오래된 백업을 자동으로 삭제하거나 Archive Storage로 이동시킬 수 있습니다.
Personal Comments
오늘 포스팅을 통해 관리형 데이터베이스에서 제공하는 백업이 실패하더라도 우리는 자체적인 백업 시스템을 구축하여 데이터를 안전하게 보관할 수 있게 되었습니다.
물론 이 방식에도 관리형 데이터베이스의 기본 백업 기능을 완전히 대체할 수는 없는 한계가 있습니다. 특히 특정 시점으로의 복구(Point-in-Time Recovery) 같은 고급 기능은 직접 구현하기 복잡할 수 있습니다. 따라서 가능하다면 관리형 백업과 이 스크립트를 병행하여 이중 백업 체계를 구축하는 것이 가장 이상적입니다.
이 글이 관리형 데이터베이스 백업에 대한 고민을 해결하는 데 도움이 되길 바랍니다.
긴 글 읽어주셔서 감사합니다.

No Comments