본문 바로가기
AI 빅데이터/Google Cloud Platform

[GCP] Scheduler와 Cloud Function으로 노트북 인스턴스 스케줄링

by 마고커 2020. 4. 22.


AI를 개발하다보면 오랫동안 인스턴스를 켜 두어야 하는 경우가 있다. 혹은 깜박 있고 퇴근한다거나. 이럴 때 문제되는 것이 대부분의 AI인스턴스 가격들이 만만치 않다는 것이다. (V100 채용한 기본모델이 한시간에 4불 안팎) 켜둔 채 퇴근하고 아침에 출근하면 대략 7~8만원이 그냥 없어지는 것이다. GCP에서 Instance를 스케줄링을 해 주는 기능은 직접적으로 제공되고 있지 않지만, Cloud Function, Pub/Sub, Cloud Scheduler를 엮으면 (번거롭지만) 해결 가능하다.

 

위와 같은 구조인데, 우선 Cloud Scheduler의 명령을 Cloud Function으로 전달할 Topic을 Cloud Pub/Sub으로 만들어 두자. 간단히 test-run(시작), test-stop(정지)으로 정의하였다.

 

인스턴스를 시작/정지하는 Function을 구현해야 Cloud Function에서 구현해야 하는데, 아래의 node.js 코드를 index.js와 package.json으로 복사한다.

 

const Buffer = require('safe-buffer').Buffer;
const Compute = require('@google-cloud/compute');
const compute = new Compute();

/**
 * Starts a Compute Engine instance.
 *
 * Expects a PubSub message with JSON-formatted event data containing the
 * following attributes:
 *  zone - the GCP zone the instance is located in.
 *  instance - the name of the instance.
 *
 * @param {!object} event Cloud Function PubSub message event.
 * @param {!object} callback Cloud Function PubSub callback indicating completion.
 */
exports.startInstancePubSub = (event, callback) => {
  try {
    const pubsubMessage = event.data;
    const payload = _validatePayload(
      JSON.parse(Buffer.from(pubsubMessage.data, 'base64').toString())
    );
    compute
      .zone(payload.zone)
      .vm(payload.instance)
      .start()
      .then(data => {
        // Operation pending.
        const operation = data[0];
        return operation.promise();
      })
      .then(() => {
        // Operation complete. Instance successfully started.
        const message = 'Successfully started instance ' + payload.instance;
        console.log(message);
        callback(null, message);
      })
      .catch(err => {
        console.log(err);
        callback(err);
      });
  } catch (err) {
    console.log(err);
    callback(err);
  }
};

/**
 * Validates that a request payload contains the expected fields.
 *
 * @param {!object} payload the request payload to validate.
 * @returns {!object} the payload object.
 */
function _validatePayload(payload) {
  if (!payload.zone) {
    throw new Error(`Attribute 'zone' missing from payload`);
  } else if (!payload.instance) {
    throw new Error(`Attribute 'instance' missing from payload`);
  }
  return payload;
}

<instance start - index.js>

 

{
  "name": "cloud-functions-schedule-instance",
  "version": "0.0.1",
  "private": true,
  "license": "Apache-2.0",
  "author": "Google Inc.",
  "repository": {
    "type": "git",
    "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git"
  },
  "engines": {
    "node": ">=8"
  },
  "scripts": {
    "test": "mocha test/*.test.js --timeout=20000"
  },
  "devDependencies": {
    "@google-cloud/nodejs-repo-tools": "^3.0.0",
    "mocha": "^5.2.0",
    "proxyquire": "^2.0.0",
    "sinon": "^7.0.0"
  },
  "dependencies": {
    "@google-cloud/compute": "^0.11.0",
    "safe-buffer": "^5.1.2"
  }
}

<instance stop - package.json>

 

Cloud Function - start-instance 설정

pub/sub(test-run)을 통해 시작 명령을 받을 것이고, 정의한 startInstancePubSub을 시작하기로 해 둔다. 제대로 동작하는 지 확인하기 위해 배포 뒤 테스트를 해 볼 수 있는데, 테스트 파라미터는 base64형태로 도달한다.

 

 

base encode 해 주는 사이트(https://www.base64encode.org/)에서 실행할 instance를 base64로 바꾸어준다.

{"zone":"asia-northeast3-a", "instance":"instance-1"}

--> eyJ6b25lIjoiYXNpYS1ub3J0aGVhc3QzLWEiLCAiaW5zdGFuY2UiOiJpbnN0YW5jZS0xIn0=

변환된 base64 코드를 아래 형식으로 묶어 테스트를 실행하면 제대로 instance가 시작되었다는 메시지가 나온다.

 

instance 생성 테스트

 

일단 다시 중지 해 두고 Cloud Scheduler로 실행할 수 있도록 구성하는데 이는 상대적으로 아주 간단하다.

 

Cloud scheduler 구성

pub/sub에서 topic으로 정의하고 cloud function에서도 subscribe한 test-run을 '주제'에 적어 주고, 페이로드에는 base64로 encoding 했던 데이터를 그대로 적어준다. (pub/sub을 통해 전송되는 동안 base64로 변환된다.) 빈도에는 어떤 주기로 실행할지를 정의하는데, 일단 매 시간 58분에 실행하도록 해 두었다. 매일 9시라면 '0 9 * * *' 로 정의하면 된다.

 

 

포스팅 하고 있는 밤 11시 58분이 되자 해당 인스턴스가 정상 실행된 것이 확인되었다.

 

Stop Instance도 똑같은 방법으로 구성하면 되는데, node.js 스크립트만 아래로 바꾸어 주고, pub/sub topic으로 test-stop을 지정해 주면 된다.

 

const Buffer = require('safe-buffer').Buffer;
const Compute = require('@google-cloud/compute');
const compute = new Compute();

/**
 * Stops a Compute Engine instance.
 *
 * Expects a PubSub message with JSON-formatted event data containing the
 * following attributes:
 *  zone - the GCP zone the instance is located in.
 *  instance - the name of the instance.
 *
 * @param {!object} event Cloud Function PubSub message event.
 * @param {!object} callback Cloud Function PubSub callback indicating completion.
 */
exports.stopInstancePubSub = (event, callback) => {
  try {
    const pubsubMessage = event.data;
    const payload = _validatePayload(
      JSON.parse(Buffer.from(pubsubMessage.data, 'base64').toString())
    );
    compute
      .zone(payload.zone)
      .vm(payload.instance)
      .stop()
      .then(data => {
        // Operation pending.
        const operation = data[0];
        return operation.promise();
      })
      .then(() => {
        // Operation complete. Instance successfully stopped.
        const message = 'Successfully stopped instance ' + payload.instance;
        console.log(message);
        callback(null, message);
      })
      .catch(err => {
        console.log(err);
        callback(err);
      });
  } catch (err) {
    console.log(err);
    callback(err);
  }
};

/**
 * Validates that a request payload contains the expected fields.
 *
 * @param {!object} payload the request payload to validate.
 * @returns {!object} the payload object.
 */
function _validatePayload(payload) {
  if (!payload.zone) {
    throw new Error(`Attribute 'zone' missing from payload`);
  } else if (!payload.instance) {
    throw new Error(`Attribute 'instance' missing from payload`);
  }
  return payload;
}

<Stop Instance - index.js>

 

{
  "name": "cloud-functions-schedule-instance",
  "version": "0.0.1",
  "private": true,
  "license": "Apache-2.0",
  "author": "Google Inc.",
  "repository": {
    "type": "git",
    "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git"
  },
  "engines": {
    "node": ">=8"
  },
  "scripts": {
    "test": "mocha test/*.test.js --timeout=20000"
  },
  "devDependencies": {
    "@google-cloud/nodejs-repo-tools": "^3.0.0",
    "mocha": "^5.2.0",
    "proxyquire": "^2.0.0",
    "sinon": "^7.0.0"
  },
  "dependencies": {
    "@google-cloud/compute": "^0.11.0",
    "safe-buffer": "^5.1.2"
  }
}

<Stop Instance - package.json>

 

다른 설정은 유사하고, 매시 3분에 Instance를 정지하도록 해 두었다. 

 

Cloud Function - Stop Instance

 

아래와 같이 오전 12시 3분에 종료가 정상적으로 시작되는 것을 확인할 수 있다. 

 



댓글