Cloud Functions 실행 환경

Cloud Functions는 Google에서 사용자를 대신해 인프라, 운영체제, 런타임 환경을 모두 처리하는 완전 관리형 서버리스 환경에서 실행됩니다. 각 Cloud 함수는 고유한 분리된 보안 실행 컨텍스트에서 실행되고 자동으로 확장되며 다른 함수와 독립된 수명 주기를 갖습니다.

런타임

Cloud Functions에서 지원하는 언어 런타임은 다음과 같습니다.

런타임 언어 버전 기본 이미지
Node.js 6(지원 중단됨) Node.js 6.16.0 Debian 8
Node.js 8 Node.js 8.15.0 Ubuntu 18.04
Node.js 10(베타) Node.js 10.15.3 Ubuntu 18.04
Python Python 3.7.1 Ubuntu 18.04
Go Go 1.11.6 Ubuntu 18.04

일반적으로 런타임은 별도의 알림이 없는 한 자동으로 업데이트됩니다. 모든 런타임은 언어 커뮤니티에 제공되는 언어 버전에 대한 자동 업데이트를 받습니다. 마찬가지로 Cloud Functions는 운영체제나 포함된 패키지와 같은 실행 환경의 다른 구성 요소를 업데이트할 수 있습니다. 이러한 업데이트는 함수를 안전하게 유지하는 데 유용합니다.

스테이트리스(Stateless) 함수

Cloud Functions는 서버리스 패러다임을 구현합니다. 즉 서버 또는 가상 머신과 같은 기반 인프라에 대한 걱정 없이 코드를 실행할 수 있습니다. Google에서 함수를 자동으로 관리하고 크기를 조정하도록 하려면 함수는 스테이트리스(Stateless)여야 합니다. 즉 하나의 함수 호출은 이전 호출에서 설정한 인메모리 상태에 의존해서는 안 됩니다. 그러나 종종 성능 최적화를 위해 기존 상태를 재사용할 수 있습니다. 자세한 내용은 도움말 및 유용한 정보의 권장사항을 참조하세요.

예를 들어 전역 변수, 메모리, 파일 시스템 또는 다른 상태를 공유하지 않는 여러 함수 인스턴스에서 호출을 처리할 수 있기 때문에, 다음 함수에서 반환된 카운터 값은 총 함수 호출 수와 일치하지 않습니다.

Node.js

// Global variable, but only shared within function instance.
let count = 0;

/**
 * HTTP Cloud Function that counts how many times
 * it is executed within a specific instance.
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.executionCount = (req, res) => {
  count++;

  // Note: the total function invocation count across
  // all instances may not be equal to this value!
  res.send(`Instance execution count: ${count}`);
};

Python

# Global variable, modified within the function by using the global keyword.
count = 0

def statelessness(request):
    """
    HTTP Cloud Function that counts how many times it is executed
    within a specific instance.
    Args:
        request (flask.Request): The request object.
        <http://flask.pocoo.org/docs/1.0/api/#flask.Request>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
    """
    global count
    count += 1

    # Note: the total function invocation count across
    # all instances may not be equal to this value!
    return 'Instance execution count: {}'.format(count)

Go


// Package http provides a set of HTTP Cloud Functions samples.
package http

import (
	"fmt"
	"net/http"
)

// count is a global variable, but only shared within a function instance.
var count = 0

// ExecutionCount is an HTTP Cloud Function that counts how many times it
// is executed within a specific instance.
func ExecutionCount(w http.ResponseWriter, r *http.Request) {
	count++

	// Note: the total function invocation count across
	// all instances may not be equal to this value!
	fmt.Fprintf(w, "Instance execution count: %d", count)
}

함수 호출 전반에서 상태를 공유해야 하는 경우, 함수는 Datastore, Firestore 또는 Cloud Storage와 같은 서비스를 사용하여 데이터를 유지해야합니다. 사용 가능한 스토리지 옵션의 전체 목록은 스토리지 옵션 선택을 참조하세요.

자동 확장 및 동시 실행

Cloud Functions는 수신받은 요청을 함수의 인스턴스에 할당하여 처리합니다. 요청의 양과 기존 함수 인스턴스의 개수에 따라 Cloud Functions는 기존 인스턴스에 요청을 할당하거나 새 인스턴스를 만들 수 있습니다.

함수의 각 인스턴스는 한 번에 하나의 동시 요청만을 처리합니다. 즉, 코드가 요청을 처리하는 동안에는 다른 요청이 동일한 인스턴스로 라우팅되지 않습니다. 따라서 원래 요청이 사용자가 요청한 전체 리소스(CPU 및 메모리)를 사용할 수 있습니다.

인바운드 요청의 양이 기존 인스턴스의 개수를 초과하는 경우 Cloud Functions는 여러 개의 새로운 인스턴스를 시작하여 요청을 처리할 수 있습니다. 이러한 자동 확장을 통해 Cloud Functions는 함수의 다른 인스턴스를 사용하여 여러 요청을 동시에 처리할 수 있습니다.

동시 요청은 서로 다른 함수 인스턴스에 의해 처리되므로 변수 또는 로컬 메모리를 공유하지 않습니다. 자세한 내용은 이 문서의 뒷부분에서 설명합니다.

자동 확장의 동작 제어

Cloud Functions를 사용하면 원하는 시점에 공존할 수 있는 함수 인스턴스의 총 개수 제한을 설정할 수 있습니다. 무제한으로 확장하는 것이 바람직하지 않은 경우도 있습니다. 예를 들어 함수가 Cloud Functions와 같은 수준으로 확장할 수 없는 데이터베이스 등의 리소스에 의존하는 경우가 있습니다. 요청의 양이 급증하면 Cloud Functions에서 데이터베이스가 감당할 수 있는 양보다 많은 함수 인스턴스를 생성할 수 있습니다.

콜드 스타트

다음 두 가지 경우 새로운 함수 인스턴스가 시작됩니다.

  • 함수를 배포하는 경우

  • 새로운 함수 인스턴스가 자동으로 생성되어 부하에 맞게 확장되거나 간혹 기존 인스턴스를 대체하는 경우

새로운 함수 인스턴스를 시작하려면 런타임과 코드를 로드해야 합니다. 함수 인스턴스 시작(콜드 스타트)을 포함하는 요청은 기존 함수 인스턴스에 대한 요청보다 느릴 수 있습니다. 하지만 함수가 일정한 부하를 받는 경우 함수가 자주 충돌하여 함수 환경을 재시작해야 하는 것이 아니라면 콜드 스타트 횟수는 일반적으로 무시할 수 있습니다. 오류를 적절히 처리하여 콜드 스타트를 방지하는 방법을 알아보려면 오류를 참조하세요.

함수 인스턴스 수명

진행 중인 트래픽 부족으로 인해 인스턴스의 수가 줄어들거나 함수가 충돌하지 않는 한 일반적으로 함수 인스턴스를 실행하는 환경은 탄력적이며 후속 함수 호출에 의해 재사용됩니다. 즉 하나의 함수 실행이 끝나면 다른 함수 호출이 동일한 함수 인스턴스에 의해 처리될 수 있습니다. 따라서 가능하면 전역 범위의 호출에서 상태를 캐시하는 것이 좋습니다. 다음 호출이 동일한 함수 인스턴스에 도달한다는 보장이 없으므로 이 캐시 없이도 함수가 동작할 준비가 되어 있어야 합니다(스테이트리스(Stateless) 함수 참조).

함수 범위와 전역 범위 비교

단일 함수 호출 시 진입점으로 선언된 함수의 본문만 실행하게 됩니다. 함수 정의를 포함하는 것으로 기대되는 함수 파일의 전역 범위는 모든 콜드 스타트마다 실행되지만 인스턴스가 이미 초기화된 경우에는 실행되지 않습니다.

Node.js

// Global (instance-wide) scope
// This computation runs at instance cold-start
const instanceVar = heavyComputation();

/**
 * HTTP function that declares a variable.
 *
 * @param {Object} req request context.
 * @param {Object} res response context.
 */
exports.scopeDemo = (req, res) => {
  // Per-function scope
  // This computation runs every time this function is called
  const functionVar = lightComputation();

  res.send(`Per instance: ${instanceVar}, per function: ${functionVar}`);
};

Python

# Global (instance-wide) scope
# This computation runs at instance cold-start
instance_var = heavy_computation()

def scope_demo(request):
    """
    HTTP Cloud Function that declares a variable.
    Args:
        request (flask.Request): The request object.
        <http://flask.pocoo.org/docs/1.0/api/#flask.Request>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
    """

    # Per-function scope
    # This computation runs every time this function is called
    function_var = light_computation()
    return 'Instance: {}; function: {}'.format(instance_var, function_var)

Go


// h is in the global (instance-wide) scope.
var h string

// init runs during package initialization. So, this will only run during an
// an instance's cold start.
func init() {
	h = heavyComputation()
}

// ScopeDemo is an example of using globally and locally
// scoped variables in a function.
func ScopeDemo(w http.ResponseWriter, r *http.Request) {
	l := lightComputation()
	fmt.Fprintf(w, "Global: %q, Local: %q", h, l)
}

새로운 함수 인스턴스(및 새로운 함수 인스턴스의 모든 후속 생성)에서 함수 코드가 호출되기 전에 전역 범위가 정확히 한 번 실행되었다고 가정할 수 있습니다. 그러나 전역 범위 실행의 총 횟수 또는 타이밍은 Google에서 관리하는 자동 확장에 의존하므로 이를 신뢰해서는 안 됩니다.

함수 실행 타임라인

함수는 함수가 실행되는 동안에만 요청된 리소스(CPU 및 메모리)에 액세스할 수 있습니다. 실행 기간이 아닌 시점에 실행되는 코드는 실행이 보장되지 않으며 언제든지 중단될 수 있습니다. 따라서 항상 함수 실행의 끝을 정확히 알리고 그 이후에는 코드를 실행하지 않아야 합니다. HTTP 함수백그라운드 함수를 참조하세요.

예를 들어 HTTP 응답 전송 이후에 실행된 코드는 언제든지 중단될 수 있습니다.

Node.js

/**
 * HTTP Cloud Function that may not completely
 * execute due to early HTTP response
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.afterResponse = (req, res) => {
  res.end();

  // This statement may not execute
  console.log('Function complete!');
};

실행 보장

일반적으로 함수는 수신 이벤트가 있을 때마다 한 번씩 호출됩니다. 그러나 오류 시나리오가 다양하기 때문에 Cloud Functions는 항상 단일 호출을 보장하지 않습니다.

단일 이벤트에 대한 함수의 최대 또는 최소 호출 횟수는 함수 유형에 따라 다릅니다.

  • HTTP 함수는 최대 한 번만 호출됩니다. 이는 HTTP 호출의 동기식 특성 때문이며 함수 호출을 처리하는 도중 발생하는 모든 오류는 재시도 없이 반환됩니다. HTTP 함수 호출자는 필요한 경우 오류를 처리하고 재시도합니다.

  • 백그라운드 함수는 최소 한 번 이상 호출됩니다. 이는 응답을 기다리는 호출자가 없는 이벤트 처리의 비동기적인 특성 때문입니다. 이벤트 전달을 위해 시스템에서 백그라운드 함수를 두 번 이상 호출하는 경우도 있습니다. 백그라운드 함수 호출이 오류로 인해 실패하는 경우 해당 함수에 실패 시 재시도가 사용 설정되어 있지 않으면 다시 호출되지 않습니다.

실행을 재시도할 때 함수가 제대로 작동하도록 하려면 함수가 멱등성을 갖도록 구현하여 이벤트가 여러 번 전송되더라도 원하는 결과(및 부작용)를 내도록 할 수 있습니다. 즉 HTTP 함수의 경우 호출자가 HTTP 함수 엔드포인트에 호출을 다시 시도하더라도 원하는 값을 반환합니다. 함수가 멱등성을 갖도록 하는 방법에 대한 자세한 내용은 백그라운드 함수 재시도를 참조하세요.

오류

함수가 함수 유형에 따라 오류를 알리도록 하기 위한 권장 방법은 다음과 같습니다.

  • HTTP 함수는 오류를 나타내는 적절한 HTTP 상태 코드를 반환해야 합니다. 예시는 HTTP 함수를 참조하세요.

  • 백그라운드 함수는 오류 메시지를 로깅하고 반환해야 합니다. 이에 대한 예시는 백그라운드 함수를 참조하세요.

이와 같은 방법으로 오류가 반환되면 오류를 반환한 함수 인스턴스는 정상 작동하는 것으로 라벨이 표시되며 필요한 경우 추가 요청을 처리할 수 있습니다.

코드 또는 호출한 다른 코드가 포착되지 않은 예외를 발생시키거나 현재 프로세스를 중단하면 함수 인스턴스는 다음 호출을 처리하기 전에 재시작될 수 있습니다. 이 경우 콜드 스타트가 더 많이 발생되어 지연 시간이 길어질 수 있으므로 권장하지 않습니다.

Cloud Functions의 오류를 신고하는 방법에 대한 자세한 내용은 오류 신고를 참조하세요.

시간 제한

함수 실행 시간은 함수 배포 시 지정할 수 있는 시간 제한 기간을 통해 제한됩니다. 기본적으로 함수는 1분 후에 시간이 초과되지만 이 기간을 최대 9분까지 연장할 수 있습니다.

함수 실행 시 제한시간이 초과되면 오류 상태가 즉시 호출자로 반환됩니다. 시간이 초과된 함수 인스턴스에서 사용하는 CPU 리소스가 제한되고 요청 처리가 즉시 일시중지될 수 있습니다. 일시중지된 작업은 후속 요청에서 진행될 수도 있고 진행되지 않을 수도 있으므로 예기치 않은 부작용이 발생할 수 있습니다.

아래 스니펫에는 함수 실행 시작 후 2분 뒤에 실행되도록 예약된 코드가 포함됩니다. 제한 시간을 1분으로 설정하면 코드가 실행되지 않을 수 있습니다.

Node.js

/**
 * HTTP Cloud Function that may not completely
 * execute due to function execution timeout
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.afterTimeout = (req, res) => {
  setTimeout(() => {
    // May not execute if function's timeout is <2 minutes
    console.log('Function running...');
    res.end();
  }, 120000); // 2 minute delay
};

Python

def timeout(request):
    print('Function running...')
    time.sleep(120)

    # May not execute if function's timeout is <2 minutes
    print('Function completed!')
    return 'Function completed!'

Go


// Package tips contains tips for writing Cloud Functions in Go.
package tips

import (
	"fmt"
	"log"
	"net/http"
	"time"
)

// Timeout sleeps for 2 minutes and may time out before finishing.
func Timeout(w http.ResponseWriter, r *http.Request) {
	log.Println("Function execution started...")
	time.Sleep(2 * time.Minute)
	log.Println("Function completed!")
	fmt.Fprintln(w, "Function completed!")
}

경우에 따라 위 코드가 예기치 않은 방식으로 성공적으로 실행될 수 있습니다. 함수가 시간 초과되는 경우를 가정해 보세요. CPU 제한을 통해 요청을 처리하는 인스턴스가 일시중지됩니다. 대기 중인 작업이 일시중지됩니다. 후속 요청이 동일한 인스턴스로 라우팅되면 작업이 재개되고 Function running...이 로그에 표시됩니다.

이러한 동작의 일반적인 증상은 한 요청의 작업과 로그가 후속 요청으로 '유출'되는 것으로 보이는 것입니다. 일시중지된 작업이 반드시 재개되는 것은 아니기 때문에 이러한 동작에 의존해서는 안 됩니다. 대신 다음 기법을 사용하여 함수의 시간 초과를 방지할 수 있습니다.

  1. 제한 시간을 예상하는 함수 실행 시간보다 길게 설정합니다.
  2. 실행 중 남은 시간을 추적하고 정리 및 종료를 일찍 수행합니다.

gcloud 명령줄 도구를 사용하여 함수의 최대 실행 시간을 설정하려면 배포 시 --timeout 플래그를 사용합니다.

gcloud functions deploy FUNCTION_NAME --timeout=TIMEOUT FLAGS...

위의 명령어에서 FLAGS...는 함수 배포 중에 전달하는 다른 옵션을 나타냅니다. deploy 명령어의 전체 참조는 gcloud functions deploy를 확인하세요.

다음과 같이 Cloud Console에서 함수를 생성하는 동안 시간 제한을 설정할 수도 있습니다.

  1. Cloud Console의 Cloud Functions 개요 페이지로 이동합니다.

  2. 함수 만들기를 클릭합니다.

  3. 함수의 필수 필드를 작성합니다.

  4. 더보기를 클릭하여 고급 설정을 확인합니다.

  5. 시간 제한 필드에 값을 입력합니다.

파일 시스템

함수 실행 환경은 실행 가능한 함수 파일 및 로컬 종속 항목과 같은 배포된 함수 패키지에 있는 파일 및 디렉터리를 포함합니다. 이러한 파일은 읽기 전용 디렉터리에서 사용할 수 있으며 이는 함수 파일의 위치에 따라 결정될 수 있습니다. 함수의 디렉터리는 현재 작업 디렉터리와 다를 수 있습니다.

다음은 함수 디렉터리에 있는 파일 목록의 예시입니다.

Node.js

const fs = require('fs');

/**
 * HTTP Cloud Function that lists files in the function directory
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.listFiles = (req, res) => {
  fs.readdir(__dirname, (err, files) => {
    if (err) {
      console.error(err);
      res.sendStatus(500);
    } else {
      console.log('Files', files);
      res.sendStatus(200);
    }
  });
};

Python

def list_files(request):
    import os
    from os import path

    root = path.dirname(path.abspath(__file__))
    children = os.listdir(root)
    files = [c for c in children if path.isfile(path.join(root, c))]
    return 'Files: {}'.format(files)

Go


// Package tips contains tips for writing Cloud Functions in Go.
package tips

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

// ListFiles lists the files in the current directory.
func ListFiles(w http.ResponseWriter, r *http.Request) {
	files, err := ioutil.ReadDir("./")
	if err != nil {
		http.Error(w, "Unable to read files", http.StatusInternalServerError)
		log.Printf("ioutil.ListFiles: %v", err)
		return
	}
	fmt.Fprintln(w, "Files:")
	for _, f := range files {
		fmt.Fprintf(w, "\t%v\n", f.Name())
	}
}

또한 함수와 함께 배포된 다른 파일에서 코드를 로드할 수 있습니다.

파일 시스템에서 작성할 수 있는 유일한 부분은 임시 파일을 함수 인스턴스에 저장하는 데 사용할 수 있는 /tmp 디렉터리입니다. 이는 'tmpfs' 볼륨으로 알려진 로컬 디스크 마운트 지점으로 볼륨에 기록된 데이터가 메모리에 저장되는 곳입니다. 이는 함수를 위해 프로비저닝된 메모리 리소스를 사용합니다.

파일 시스템의 나머지 부분은 읽기 전용이며 함수에서 액세스할 수 있습니다.

네트워크

함수는 런타임 또는 타사 제공업체가 제공하는 표준 라이브러리를 사용하여 공개 인터넷에 액세스할 수 있습니다. 예를 들어 다음과 같이 HTTP 엔드포인트를 호출할 수 있습니다.

Node.js

const fetch = require('node-fetch');

/**
 * HTTP Cloud Function that makes an HTTP request
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.makeRequest = async (req, res) => {
  const url = 'https://example.com'; // URL to send the request to
  const externalRes = await fetch(url);
  res.sendStatus(externalRes.ok ? 200 : 500);
};

Python

def make_request(request):
    """
    HTTP Cloud Function that makes another HTTP request.
    Args:
        request (flask.Request): The request object.
        <http://flask.pocoo.org/docs/1.0/api/#flask.Request>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
    """
    import requests

    # The URL to send the request to
    url = 'http://example.com'

    # Process the request
    response = requests.get(url)
    response.raise_for_status()
    return 'Success!'

Go


// Package http provides a set of HTTP Cloud Functions samples.
package http

import (
	"fmt"
	"net/http"
	"time"
)

var urlString = "https://example.com"

// client is used to make HTTP requests with a 10 second timeout.
// http.Clients should be reused instead of created as needed.
var client = &http.Client{
	Timeout: 10 * time.Second,
}

// MakeRequest is an example of making an HTTP request. MakeRequest uses a
// single http.Client for all requests to take advantage of connection
// pooling and caching. See https://godoc.org/net/http#Client.
func MakeRequest(w http.ResponseWriter, r *http.Request) {
	resp, err := client.Get(urlString)
	if err != nil {
		http.Error(w, "Error making request", http.StatusInternalServerError)
		return
	}
	if resp.StatusCode != http.StatusOK {
		msg := fmt.Sprintf("Bad StatusCode: %d", resp.StatusCode)
		http.Error(w, msg, http.StatusInternalServerError)
		return
	}
	fmt.Fprintf(w, "ok")
}

네트워킹 최적화에 설명된 대로 함수 호출 시 네트워크 연결을 재사용하세요 하지만 연결을 2분간 사용하지 않으면 시스템에 의해 종료될 수 있으며 이후 종료된 연결을 사용하려고 시도하면 '연결 재설정' 오류가 발생합니다. 코드는 종료된 연결을 제대로 처리하는 라이브러리를 사용하거나 하위 수준의 네트워킹 구조를 사용하는 경우 이를 명시적으로 처리해야 합니다.

여러 함수

배포된 각 함수는 동일한 소스 파일에서 배포된 함수를 포함하여 다른 모든 함수와 격리됩니다. 특히 메모리, 전역 변수, 파일 시스템 및 다른 상태를 공유하지 않습니다.

배포된 함수 간에 데이터를 공유하기 위해 Datastore, Firestore 또는 Cloud Storage와 같은 스토리지 서비스를 사용할 수 있습니다. 또는 적절한 트리거를 사용하여 한 함수에서 다른 함수를 호출할 수 있습니다. 예를 들어 HTTP 함수의 엔드포인트에 HTTP를 요청하거나 Pub/Sub 주제에 메시지를 게시하여 Pub/Sub 함수를 트리거합니다.