안녕하세요. ManVSCloud 김수현입니다.
클라우드 환경에서 비용 최적화는 매우 중요한 요소입니다.
특히 개발이나 QA 환경과 같이 24시간 운영이 필요하지 않은 서버들의 경우 업무 시간에만 운영하고 그 외 시간에는 중지하는 것이 효율적인 비용 관리 방법입니다.
이전에 이를 적용하기 위해 네이버 클라우드에서 서버를 시작/중지하는 방법을 공유한 적이 있습니다.
- 이전 포스팅 : [NCLOUD] Cloud Function으로 원하는 시간대에 서버를 시작하고 중지하자
: https://manvscloud.com/?p=1803
이전에 공유드린 방식은 개별 서버를 지정하여 시작/중지하는 방식이었습니다.
이는 소수의 서버만 관리할 때는 효과적이었으나 다수의 서버를 관리해야 하는 경우에는 몇 가지 한계점이 있었습니다.
1) 서버가 많아질수록 설정해야 할 대상이 증가하여 관리가 복잡해집니다.
2) 새로운 서버가 추가될 때마다 스케줄링 설정을 추가해야 합니다.
3) 개발/QA/운영 등 환경별로 서버를 구분하여 관리하기가 어렵습니다.
이러한 문제를 해결하기 위해 VPC ID를 기반으로 하는 새로운 스케줄링 방식을 추가로 공유드리고자 합니다.
VPC ID 기반 스케줄링은 특정 VPC 내의 모든 서버를 한 번에 제어할 수 있으며 예외 처리가 필요한 서버는 별도로 지정할 수 있어 더욱 효율적인 서버 관리가 가능합니다.
이 포스팅에서는 네이버 클라우드의 Cloud Functions를 활용하여 VPC ID 기반의 서버 스케줄링 방식을 구현하는 방법을 상세히 설명하도록 하겠습니다.
VPC ID 기반 서버 스케줄링 프로세스
- VPC ID 기반 스케줄링 구현 개요
VPC ID 기반 스케줄링은 Cloud Functions를 사용하여 구현되며, 크게 서버 시작(Start)과 중지(Stop) 두 가지 기능으로 구성됩니다.
각 기능은 다음과 같은 프로세스로 동작합니다.
- VPC 번호를 기반으로 해당 VPC 내의 모든 서버 인스턴스 조회
- 예외 처리할 서버 목록 확인
- 현재 서버 상태 확인 및 필요한 작업(시작/중지) 수행
- 작업 결과 반환
- 주요 기능 상세 설명
1) 서버 인스턴스 조회 (get_server_instances)
def get_server_instances(access_key, secret_key, vpc_no):
# API 호출을 위한 기본 설정
timestamp = str(int(time.time() * 1000))
uri = f"/vserver/v2/getServerInstanceList?responseFormatType=json®ionCode=KR&vpcNo={vpc_no}"
이 함수는 지정된 VPC 내의 모든 서버 인스턴스 정보를 조회합니다.
NCLOUD API를 통해 서버 목록을 가져오며 각 서버의 현재 상태 정보도 함께 조회됩니다.
2) 서버 시작 기능
def start_servers(access_key, secret_key, server_instance_list):
if not server_instance_list:
return {"message": "No servers to start"}
NSTOP 상태인 서버들을 대상으로 시작 작업을 수행합니다.
이미 실행 중인 서버는 제외되며 예외 처리 목록에 포함된 서버도 시작 대상에서 제외됩니다.
3) 서버 중지 기능
def stop_servers(access_key, secret_key, server_instance_list):
if not server_instance_list:
return {"message": "No servers to stop"}
RUN 상태인 서버들을 대상으로 중지 작업을 수행합니다.
이미 중지된 서버는 제외되며 예외 처리 목록에 포함된 서버는 중지 대상에서 제외됩니다.
- 예외 처리 및 안전장치
1) 서버 상태 확인
instance_status = instance.get('serverInstanceStatus', {}).get('code', '')
if instance_status == "NSTOP": # 시작 함수의 경우
servers_to_start.append(instance_no)
if instance_status == "RUN": # 중지 함수의 경우
servers_to_stop.append(instance_no)
각 작업 수행 전 서버의 현재 상태를 확인하여 불필요한 API 호출을 방지하고 안정적인 작업 수행을 보장합니다.
2) 예외 서버 관리
excluded_instances = ["11111111", "222222222", "33333333"] # 예시 ID
if instance_no in excluded_instances:
continue
특정 서버를 예외 처리하여 스케줄링 대상에서 제외할 수 있습니다.
이는 24시간 운영이 필요한 서버나 특별한 관리가 필요한 서버를 위한 기능입니다.
- 실행 결과 관리
각 작업의 실행 결과는 상세한 정보를 포함하여 반환됩니다.
– 제외된 서버 목록
– 작업 대상에서 제외된 서버와 그 상태
– 실제 작업이 수행된 서버 목록
– API 응답 결과
전체 코드 및 Cloud Functions 설정
- Cloud Functions – Action
// VPC ID 기반 서버 시작
import hashlib
import hmac
import base64
import requests
import time
import json
def get_server_instances(access_key, secret_key, vpc_no):
timestamp = str(int(time.time() * 1000))
secret_key_bytes = bytes(secret_key, 'UTF-8')
method = "GET"
api_server = "https://ncloud.apigw.ntruss.com"
uri = f"/vserver/v2/getServerInstanceList?responseFormatType=json®ionCode=KR&vpcNo={vpc_no}"
message = method + " " + uri + "\n" + timestamp + "\n" + access_key
message = bytes(message, 'UTF-8')
signing_key = base64.b64encode(hmac.new(secret_key_bytes, message, digestmod=hashlib.sha256).digest())
http_header = {
'x-ncp-apigw-signature-v2': signing_key,
'x-ncp-apigw-timestamp': timestamp,
'x-ncp-iam-access-key': access_key
}
response = requests.get(api_server + uri, headers=http_header)
return response.json()
def start_servers(access_key, secret_key, server_instance_list):
if not server_instance_list:
return {"message": "No servers to start"}
timestamp = str(int(time.time() * 1000))
secret_key_bytes = bytes(secret_key, 'UTF-8')
method = "GET"
api_server = "https://ncloud.apigw.ntruss.com"
# Create server list query string
server_query = ""
for idx, server_id in enumerate(server_instance_list, 1):
server_query += f"&serverInstanceNoList.{idx}={server_id}"
uri = f"/vserver/v2/startServerInstances?regionCode=KR{server_query}&responseFormatType=json"
message = method + " " + uri + "\n" + timestamp + "\n" + access_key
message = bytes(message, 'UTF-8')
signing_key = base64.b64encode(hmac.new(secret_key_bytes, message, digestmod=hashlib.sha256).digest())
http_header = {
'x-ncp-apigw-signature-v2': signing_key,
'x-ncp-apigw-timestamp': timestamp,
'x-ncp-iam-access-key': access_key
}
response = requests.get(api_server + uri, headers=http_header)
return response.json()
def main(args):
access_key = args["NCLOUD_ACCESS_KEY"]
secret_key = args["NCLOUD_SECRET_KEY"]
vpc_no = "1111" # VPC 번호
excluded_instances = ["111111", "222222", "333333"] # 제외할 인스턴스 목록
# VPC의 모든 서버 인스턴스 조회
instance_response = get_server_instances(access_key, secret_key, vpc_no)
# 서버 인스턴스 목록 추출
server_instances = instance_response['getServerInstanceListResponse']['serverInstanceList']
# 제외할 인스턴스를 제외하고, NSTOP 상태인 서버만 필터링
servers_to_start = []
skipped_servers = []
for instance in server_instances:
instance_no = instance['serverInstanceNo']
instance_status = instance.get('serverInstanceStatus', {}).get('code', '')
if instance_no in excluded_instances:
continue
# NSTOP 상태의 서버만 시작 목록에 추가
if instance_status == "NSTOP":
servers_to_start.append(instance_no)
else:
skipped_servers.append({
"serverInstanceNo": instance_no,
"status": instance_status
})
# 서버 시작 실행
result = {
"status": "success",
"result": {
"excluded": excluded_instances,
"skipped_servers": skipped_servers
},
"success": True
}
if servers_to_start:
start_response = start_servers(access_key, secret_key, servers_to_start)
result["result"]["message"] = f"Starting {len(servers_to_start)} servers"
result["result"]["servers_started"] = servers_to_start
result["result"]["api_response"] = start_response
else:
result["result"]["message"] = "No servers to start"
return result
// VPC ID 기반 서버 중지
import hashlib
import hmac
import base64
import requests
import time
import json
def get_server_instances(access_key, secret_key, vpc_no):
timestamp = str(int(time.time() * 1000))
secret_key_bytes = bytes(secret_key, 'UTF-8')
method = "GET"
api_server = "https://ncloud.apigw.ntruss.com"
uri = f"/vserver/v2/getServerInstanceList?responseFormatType=json®ionCode=KR&vpcNo={vpc_no}"
message = method + " " + uri + "\n" + timestamp + "\n" + access_key
message = bytes(message, 'UTF-8')
signing_key = base64.b64encode(hmac.new(secret_key_bytes, message, digestmod=hashlib.sha256).digest())
http_header = {
'x-ncp-apigw-signature-v2': signing_key,
'x-ncp-apigw-timestamp': timestamp,
'x-ncp-iam-access-key': access_key
}
response = requests.get(api_server + uri, headers=http_header)
return response.json()
def stop_servers(access_key, secret_key, server_instance_list):
if not server_instance_list:
return {"message": "No servers to stop"}
timestamp = str(int(time.time() * 1000))
secret_key_bytes = bytes(secret_key, 'UTF-8')
method = "GET"
api_server = "https://ncloud.apigw.ntruss.com"
# Create server list query string
server_query = ""
for idx, server_id in enumerate(server_instance_list, 1):
server_query += f"&serverInstanceNoList.{idx}={server_id}"
uri = f"/vserver/v2/stopServerInstances?regionCode=KR{server_query}&responseFormatType=json"
message = method + " " + uri + "\n" + timestamp + "\n" + access_key
message = bytes(message, 'UTF-8')
signing_key = base64.b64encode(hmac.new(secret_key_bytes, message, digestmod=hashlib.sha256).digest())
http_header = {
'x-ncp-apigw-signature-v2': signing_key,
'x-ncp-apigw-timestamp': timestamp,
'x-ncp-iam-access-key': access_key
}
response = requests.get(api_server + uri, headers=http_header)
return response.json()
def main(args):
access_key = args["NCLOUD_ACCESS_KEY"]
secret_key = args["NCLOUD_SECRET_KEY"]
vpc_no = "1111" # VPC 번호
excluded_instances = ["1111", "2222"] # 제외할 인스턴스 목록
# VPC의 모든 서버 인스턴스 조회
instance_response = get_server_instances(access_key, secret_key, vpc_no)
# 서버 인스턴스 목록 추출
server_instances = instance_response['getServerInstanceListResponse']['serverInstanceList']
# 제외할 인스턴스를 제외하고, RUN 상태인 서버만 필터링
servers_to_stop = []
skipped_servers = []
for instance in server_instances:
instance_no = instance['serverInstanceNo']
instance_status = instance.get('serverInstanceStatus', {}).get('code', '')
if instance_no in excluded_instances:
continue
# RUN 상태의 서버만 중지 목록에 추가
if instance_status == "RUN":
servers_to_stop.append(instance_no)
else:
skipped_servers.append({
"serverInstanceNo": instance_no,
"status": instance_status
})
# 결과 응답 준비
result = {
"status": "success",
"result": {
"excluded": excluded_instances,
"skipped_servers": skipped_servers
},
"success": True
}
# 서버 중지 실행
if servers_to_stop:
stop_response = stop_servers(access_key, secret_key, servers_to_stop)
result["result"]["message"] = f"Stopping {len(servers_to_stop)} servers"
result["result"]["servers_stopped"] = servers_to_stop
result["result"]["api_response"] = stop_response
else:
result["result"]["message"] = "No servers to stop"
return result
// VPC ID 기반 서버 시작/중지 공통 디폴트 파라미터
{"NCLOUD_ACCESS_KEY": "VPC조회 및 서버 시작,중지 권한이 있는 Access Key", "NCLOUD_SECRET_KEY": "VPC조회 및 서버 시작,중지 권한이 있는 Access Key의 Secret Key"}
Cloud Functions Action 생성 시 디폴트 파라미터에 NCLOUD_ACCESS_KEY와 NCLOUD_SECRET_KEY를 추가해줍니다. 당연히 해당 액세스 키는 콘솔 접속이 불가능한 Programmatic User로 생성되어야 하며 위 코드를 정상적으로 실행할 수 있도록 VPC 조회 및 서버 시작, 중지 권한이 정책으로 포함되어야 합니다.
- Cloud Functions – Trigger
: cron
원하는 시간대에 중지, 원하는 시간대에 시작해야하므로 Trigger는 Cron이 최적의 방식입니다.
Personal Comments
VPC ID 기반의 서버 스케줄링 방식은 기존 개별 서버 지정 방식의 한계를 극복하고 다음과 같은 이점을 제공합니다.
- VPC 단위의 통합 관리로 다수 서버의 효율적 제어가 가능합니다.
- 예외 서버 지정을 통해 유연한 운영이 가능합니다.
- 서버 상태 확인 및 상세한 결과 제공으로 안정적인 운영을 보장합니다.
- 불필요한 서버 운영 시간을 줄여 비용을 최적화할 수 있습니다.
이러한 방식은 특히 개발/QA 환경에서 시간대별로 서버 관리를 원하는 경우 운영 효율성을 크게 향상시킬 수 있기때문에 많이 공유 및 활용되길 바랍니다.
긴 글 읽어주셔서 감사합니다.
