Browsing Tag

manvscloud

NCLOUD

[NCLOUD] VPC ID기반 다수 서버 Start/Stop Scheduling

안녕하세요. ManVSCloud 김수현입니다.

클라우드 환경에서 비용 최적화는 매우 중요한 요소입니다.
특히 개발이나 QA 환경과 같이 24시간 운영이 필요하지 않은 서버들의 경우 업무 시간에만 운영하고 그 외 시간에는 중지하는 것이 효율적인 비용 관리 방법입니다.
이전에 이를 적용하기 위해 네이버 클라우드에서 서버를 시작/중지하는 방법을 공유한 적이 있습니다.

이전에 공유드린 방식은 개별 서버를 지정하여 시작/중지하는 방식이었습니다.
이는 소수의 서버만 관리할 때는 효과적이었으나 다수의 서버를 관리해야 하는 경우에는 몇 가지 한계점이 있었습니다.

1) 서버가 많아질수록 설정해야 할 대상이 증가하여 관리가 복잡해집니다.
2) 새로운 서버가 추가될 때마다 스케줄링 설정을 추가해야 합니다.
3) 개발/QA/운영 등 환경별로 서버를 구분하여 관리하기가 어렵습니다.

이러한 문제를 해결하기 위해 VPC ID를 기반으로 하는 새로운 스케줄링 방식을 추가로 공유드리고자 합니다.
VPC ID 기반 스케줄링은 특정 VPC 내의 모든 서버를 한 번에 제어할 수 있으며 예외 처리가 필요한 서버는 별도로 지정할 수 있어 더욱 효율적인 서버 관리가 가능합니다.
이 포스팅에서는 네이버 클라우드의 Cloud Functions를 활용하여 VPC ID 기반의 서버 스케줄링 방식을 구현하는 방법을 상세히 설명하도록 하겠습니다.


  • VPC ID 기반 스케줄링 구현 개요

VPC ID 기반 스케줄링은 Cloud Functions를 사용하여 구현되며, 크게 서버 시작(Start)과 중지(Stop) 두 가지 기능으로 구성됩니다.

각 기능은 다음과 같은 프로세스로 동작합니다.

  1. VPC 번호를 기반으로 해당 VPC 내의 모든 서버 인스턴스 조회
  2. 예외 처리할 서버 목록 확인
  3. 현재 서버 상태 확인 및 필요한 작업(시작/중지) 수행
  4. 작업 결과 반환
  • 주요 기능 상세 설명

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&regionCode=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 – 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&regionCode=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&regionCode=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이 최적의 방식입니다.


VPC ID 기반의 서버 스케줄링 방식은 기존 개별 서버 지정 방식의 한계를 극복하고 다음과 같은 이점을 제공합니다.

  • VPC 단위의 통합 관리로 다수 서버의 효율적 제어가 가능합니다.
  • 예외 서버 지정을 통해 유연한 운영이 가능합니다.
  • 서버 상태 확인 및 상세한 결과 제공으로 안정적인 운영을 보장합니다.
  • 불필요한 서버 운영 시간을 줄여 비용을 최적화할 수 있습니다.

이러한 방식은 특히 개발/QA 환경에서 시간대별로 서버 관리를 원하는 경우 운영 효율성을 크게 향상시킬 수 있기때문에 많이 공유 및 활용되길 바랍니다.

긴 글 읽어주셔서 감사합니다.