안녕하세요. MANVSCLOUD 김수현입니다.
이전에 [NCLOUD] CLOUD FUNCTIONS을 이용한 서버 이미지 백업 자동화 및 SLACK 알림(https://manvscloud.com/?p=2138)과 [NCLOUD] 백업용 서버 이미지 보관일 설정 및 삭제 자동화(https://manvscloud.com/?p=2287)에 대해 알아보았습니다. 그러나 어느 날부터 서버 이미지 백업이 갑자기 실패하기 시작했고 원인을 조사한 결과 Hypervisor가 KVM인 g3세대 서버에서는 기존 API를 사용한 서버 이미지 생성 및 삭제가 불가능하다는 사실을 발견했습니다.

이에 따라 오늘은 XEN과 KVM 모두에서 작동하는 업그레이드된 서버 이미지 백업 자동화 방법을 공유하려 합니다.
Trigger
이전 포스팅과 같이 Cloud Functions을 사용하는 방식은 동일합니다.
Action을 작성하기 앞서 Trigger를 먼저 생성해줍시다.

- 타입 : Cron
0 1 * * *
Cron 실행 옵션을 0 1 * * *로 설정하면 매일 새벽 1시에 스크립트가 실행됩니다.
물론 필요에 따라 이 설정을 조정하여 원하는 시간에 스크립트가 실행되도록 변경할 수 있습니다.
Action
g3 타입 서버에서 기존 이미지 생성/삭제 API가 작동하지 않지만 새로운 API는 g2와 g3 타입 모두에서 문제없이 동작합니다.
따라서 새 API를 사용하여 소스 코드를 업데이트할 예정입니다.
사용할 런타임은 Python 3.11이며 생성 시 이전에 생성한 트리거와 연결되어야 합니다.
import requests
import time
import hashlib
import hmac
import base64
import json
from datetime import datetime, timedelta
def get_timestamp():
return str(int(time.time() * 1000))
def generate_signing_key(secret_key, message):
return base64.b64encode(hmac.new(secret_key, message, digestmod=hashlib.sha256).digest())
def api_request(api_server, uri, access_key, secret_key, method="GET"):
timestamp = get_timestamp()
message = bytes(f"{method} {uri}\n{timestamp}\n{access_key}", 'UTF-8')
secret_key = bytes(secret_key, 'UTF-8')
signingKey = generate_signing_key(secret_key, message)
http_header = {
'x-ncp-apigw-signature-v2': signingKey,
'x-ncp-apigw-timestamp': timestamp,
'x-ncp-iam-access-key': access_key
}
if method.upper() == "GET":
response = requests.get(api_server + uri, headers=http_header)
else:
response = requests.post(api_server + uri, headers=http_header)
return response.json()
def send_slack_message(webhook_url, message):
payload = {'text': message}
response = requests.post(webhook_url, json=payload)
if response.status_code != 200:
print(f"Error sending message to Slack: {response.text}")
def create_server_image(serverInstanceNo, access_key, secret_key, api_server, webhook_url):
today = time.strftime("%Y%m%d")
serverImageName = f"backup-{serverInstanceNo}-{today}"
uri = f"/vserver/v2/createServerImage?regionCode=KR&serverInstanceNo={serverInstanceNo}&serverImageName={serverImageName}&responseFormatType=json"
response = api_request(api_server, uri, access_key, secret_key)
if 'responseError' in response:
message = f"Backup creation failed for Server Instance No: {serverInstanceNo}. Error: {response['responseError']}"
send_slack_message(webhook_url, message)
else:
print(f"Backup creation for Server Instance No: {serverInstanceNo} succeeded.")
def delete_server_image(serverImageNo, serverImageName, access_key, secret_key, api_server, webhook_url):
uri = f"/vserver/v2/deleteServerImage?regionCode=KR&serverImageNoList.1={serverImageNo}&responseFormatType=json"
response = api_request(api_server, uri, access_key, secret_key)
if 'responseError' in response:
message = f"Failed to delete Server Image: {serverImageName}. Error: {response['responseError']}"
send_slack_message(webhook_url, message)
else:
print(f"Deleted Server Image: {serverImageName} successfully.")
def main(args):
access_key = args["NCLOUD_ACCESS_KEY"]
secret_key = args["NCLOUD_SECRET_KEY"]
webhook_url = args["webhook_url"]
exclude_patterns = args["exclude_patterns"]
api_server = "https://ncloud.apigw.ntruss.com"
serverInstanceNos_response = api_request(
api_server,
"/vserver/v2/getServerInstanceList?regionCode=KR&responseFormatType=json",
access_key,
secret_key
)
for serverInstance in serverInstanceNos_response.get('getServerInstanceListResponse', {}).get('serverInstanceList', []):
if serverInstance['serverInstanceNo'] not in exclude_patterns:
create_server_image(serverInstance['serverInstanceNo'], access_key, secret_key, api_server, webhook_url)
time.sleep(1)
images_response = api_request(
api_server,
"/vserver/v2/getServerImageList?regionCode=KR&responseFormatType=json",
access_key,
secret_key
)
current_date = datetime.now()
# 이미지 삭제 주기 설정
# days=7인 Actions 동작일로부터 7일이 지난 이미지 삭제
one_day_ago = current_date - timedelta(days=7)
for image in images_response.get('getServerImageListResponse', {}).get('serverImageList', []):
image_name = image['serverImageName']
if 'backup' in image_name:
image_date_str = image_name.split('-')[-1]
image_date = datetime.strptime(image_date_str, '%Y%m%d')
if image_date < one_day_ago and not any(exclude_pattern in image_name for exclude_pattern in exclude_patterns):
delete_server_image(image['serverImageNo'], image_name, access_key, secret_key, api_server, webhook_url)
time.sleep(1)
return {
"message": "Function executed successfully"
}
이미지 삭제 주기는 기본적으로 Trigger가 실행된 후 7일이 지난 이미지를 삭제하도록 설정됩니다. 즉 백업 이미지의 보관 기간이 7일인데요. 이미지 보관일 변경이 필요한 경우 days=7이라고 되어있는 부분의 7을 원하는 보관 기간으로 변경해줄 수 있습니다.
단 모든 이미지가 7일 후에 자동으로 삭제되는 것은 아닙니다. 정해진 Naming 규칙(backup-InstanceNo-Date, 예: backup-11111111-20240403)을 따르는 이미지만이 삭제 대상이 됩니다. 이 Naming 규칙과 다른 이미지의 경우 설정된 기간이 지나도 삭제되지 않습니다.
기본 파라미터 설정은 다음과 같습니다:
{"NCLOUD_ACCESS_KEY":"YOUR_ACCESS_KEY","NCLOUD_SECRET_KEY":"YOUR_SECRET_KEY","exclude_patterns":["11111111","22222222"],"webhook_url":"https://hooks.slack.com/services/WEBHOOK/URLURLURLURLURLURLURLURLURLURL"}
- NCLOUD_ACCESS_KEY : Server Image 생성 및 삭제 권한을 가진 액세스 키
- NCLOUD_SECRET_KEY : Server Image 생성 및 삭제 권한을 가진 액세스 키의 시크릿 키
- exclude_patterns : 서버 이미지 백업 자동화 예외 대상 서버 지정
ex) [“111111111”] 또는 [“111111111”, “2222222”] - webhook_url : 알림을 받을 Slack의 Webhook URL

암호화가 필요한 부분은 KMS(Key Management Service)에서 키를 생성 후 해당 키를 이용하여 파라미터를 암호화할 수 있습니다.
이 모든 설정을 완료하고 Action을 생성하면, 트리거가 실행될 때마다 이미지 백업이 자동으로 이루어지고 오래된 백업 이미지는 자동으로 삭제됩니다.
Personal Comments
오늘은 g3세대 서버를 포함한 모든 서버 유형에 대해 서버 이미지 백업을 자동화하는 개선된 방법을 공유해보았습니다.
동일한 문제로 백업이 자동화되지 않고 있다면 이 포스팅이 좋은 참고 자료가 되길 바랍니다.
긴 글 읽어주셔서 감사합니다.
