Cloud 함수 작성

Cloud Functions는 Node.js, Python, Go, 자바, .NET, Ruby, PHP 프로그래밍 언어로 작성할 수 있으며 언어별 런타임에서 실행됩니다. Cloud Functions 실행 환경은 선택한 런타임에 따라 다릅니다. 런타임 개요 페이지에서는 각 런타임 환경의 세부정보를 확인할 수 있습니다.

직접 사용해 보기

Google Cloud를 처음 사용하는 경우 계정을 만들어 실제 시나리오에서 Cloud Functions의 성능을 평가할 수 있습니다. 신규 고객에게는 워크로드를 실행, 테스트, 배포하는 데 사용할 수 있는 $300의 무료 크레딧이 제공됩니다.

Cloud Functions 무료로 사용해 보기

Cloud Functions 유형

Cloud Functions에는 HTTP 함수와 이벤트 기반 함수의 두 가지 유형이 있습니다. 이벤트 기반 함수는 작성된 Cloud Functions 런타임에 따라 백그라운드 함수 또는 CloudEvent 함수일 수 있습니다.

HTTP 함수

HTTP 함수는 표준 HTTP 요청에서 호출됩니다. 이러한 HTTP 요청은 GET, PUT, POST, DELETE, OPTIONS 같은 일반적인 HTTP 요청 메서드의 응답과 지원 처리를 기다립니다. Cloud Functions를 사용하면 TLS 인증서가 자동으로 프로비저닝되므로 보안 연결을 통해 모든 HTTP 함수를 호출할 수 있습니다.

자세한 내용은 HTTP 함수 작성을 참조하세요.

예를 들면 다음과 같습니다.

Node.js

const functions = require('@google-cloud/functions-framework');
const escapeHtml = require('escape-html');

/**
 * Responds to an HTTP request using data from the request body parsed according
 * to the "content-type" header.
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
functions.http('helloHttp', (req, res) => {
  res.send(`Hello ${escapeHtml(req.query.name || req.body.name || 'World')}!`);
});

Python

from flask import escape
import functions_framework

@functions_framework.http
def hello_http(request):
    """HTTP Cloud Function.
    Args:
        request (flask.Request): The request object.
        <https://flask.palletsprojects.com/en/1.1.x/api/#incoming-request-data>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response>.
    """
    request_json = request.get_json(silent=True)
    request_args = request.args

    if request_json and 'name' in request_json:
        name = request_json['name']
    elif request_args and 'name' in request_args:
        name = request_args['name']
    else:
        name = 'World'
    return 'Hello {}!'.format(escape(name))

Go


// Package helloworld provides a set of Cloud Functions samples.
package helloworld

import (
	"encoding/json"
	"fmt"
	"html"
	"net/http"
)

// HelloHTTP is an HTTP Cloud Function with a request parameter.
func HelloHTTP(w http.ResponseWriter, r *http.Request) {
	var d struct {
		Name string `json:"name"`
	}
	if err := json.NewDecoder(r.Body).Decode(&d); err != nil {
		fmt.Fprint(w, "Hello, World!")
		return
	}
	if d.Name == "" {
		fmt.Fprint(w, "Hello, World!")
		return
	}
	fmt.Fprintf(w, "Hello, %s!", html.EscapeString(d.Name))
}

자바


import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.logging.Logger;

public class HelloHttp implements HttpFunction {
  private static final Logger logger = Logger.getLogger(HelloHttp.class.getName());

  private static final Gson gson = new Gson();

  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException {
    // Check URL parameters for "name" field
    // "world" is the default value
    String name = request.getFirstQueryParameter("name").orElse("world");

    // Parse JSON request and check for "name" field
    try {
      JsonElement requestParsed = gson.fromJson(request.getReader(), JsonElement.class);
      JsonObject requestJson = null;

      if (requestParsed != null && requestParsed.isJsonObject()) {
        requestJson = requestParsed.getAsJsonObject();
      }

      if (requestJson != null && requestJson.has("name")) {
        name = requestJson.get("name").getAsString();
      }
    } catch (JsonParseException e) {
      logger.severe("Error parsing JSON: " + e.getMessage());
    }

    var writer = new PrintWriter(response.getWriter());
    writer.printf("Hello %s!", name);
  }
}

C#

using Google.Cloud.Functions.Framework;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;

namespace HelloHttp
{
    public class Function : IHttpFunction
    {
        private readonly ILogger _logger;

        public Function(ILogger<Function> logger) =>
            _logger = logger;

        public async Task HandleAsync(HttpContext context)
        {
            HttpRequest request = context.Request;
            // Check URL parameters for "name" field
            // "world" is the default value
            string name = ((string) request.Query["name"]) ?? "world";

            // If there's a body, parse it as JSON and check for "name" field.
            using TextReader reader = new StreamReader(request.Body);
            string text = await reader.ReadToEndAsync();
            if (text.Length > 0)
            {
                try
                {
                    JsonElement json = JsonSerializer.Deserialize<JsonElement>(text);
                    if (json.TryGetProperty("name", out JsonElement nameElement) &&
                        nameElement.ValueKind == JsonValueKind.String)
                    {
                        name = nameElement.GetString();
                    }
                }
                catch (JsonException parseException)
                {
                    _logger.LogError(parseException, "Error parsing JSON request");
                }
            }

            await context.Response.WriteAsync($"Hello {name}!");
        }
    }
}

Ruby

require "functions_framework"
require "cgi"
require "json"

FunctionsFramework.http "hello_http" do |request|
  # The request parameter is a Rack::Request object.
  # See https://www.rubydoc.info/gems/rack/Rack/Request
  name = request.params["name"] ||
         (JSON.parse(request.body.read)["name"] rescue nil) ||
         "World"
  # Return the response body as a string.
  # You can also return a Rack::Response object, a Rack response array, or
  # a hash which will be JSON-encoded into a response.
  "Hello #{CGI.escape_html name}!"
end

PHP

<?php

use Google\CloudFunctions\FunctionsFramework;
use Psr\Http\Message\ServerRequestInterface;

// Register the function with Functions Framework.
// This enables omitting the `FUNCTIONS_SIGNATURE_TYPE=http` environment
// variable when deploying. The `FUNCTION_TARGET` environment variable should
// match the first parameter.
FunctionsFramework::http('helloHttp', 'helloHttp');

function helloHttp(ServerRequestInterface $request): string
{
    $name = 'World';
    $body = $request->getBody()->getContents();
    if (!empty($body)) {
        $json = json_decode($body, true);
        if (json_last_error() != JSON_ERROR_NONE) {
            throw new RuntimeException(sprintf(
                'Could not parse body: %s',
                json_last_error_msg()
            ));
        }
        $name = $json['name'] ?? $name;
    }
    $queryString = $request->getQueryParams();
    $name = $queryString['name'] ?? $name;

    return sprintf('Hello, %s!', htmlspecialchars($name));
}

이벤트 기반 함수

Cloud Functions는 이벤트 기반 함수를 사용하여 Pub/Sub 주제의 메시지 또는 Cloud Storage 버킷의 변경사항과 같은 클라우드 인프라의 이벤트를 처리합니다.

Cloud Functions는 두 가지 하위 유형의 이벤트 기반 함수를 지원합니다.

아래에 설명된 대로 사용하는 하위 유형은 함수 대상 런타임에 의해 관리됩니다.

백그라운드 함수

Node.js, Python, Go, 자바 Cloud Functions 런타임용으로 작성된 이벤트 기반 함수를 백그라운드 함수라고 합니다. 자세한 내용은 백그라운드 함수 작성을 참조하세요.

예를 들면 다음과 같습니다.

Node.js

/**
 * Background Cloud Function to be triggered by Pub/Sub.
 * This function is exported by index.js, and executed when
 * the trigger topic receives a message.
 *
 * @param {object} message The Pub/Sub message.
 * @param {object} context The event metadata.
 */
exports.helloPubSub = (message, context) => {
  const name = message.data
    ? Buffer.from(message.data, 'base64').toString()
    : 'World';

  console.log(`Hello, ${name}!`);
};

Python

def hello_pubsub(event, context):
    """Background Cloud Function to be triggered by Pub/Sub.
    Args:
         event (dict):  The dictionary with data specific to this type of
                        event. The `@type` field maps to
                         `type.googleapis.com/google.pubsub.v1.PubsubMessage`.
                        The `data` field maps to the PubsubMessage data
                        in a base64-encoded string. The `attributes` field maps
                        to the PubsubMessage attributes if any is present.
         context (google.cloud.functions.Context): Metadata of triggering event
                        including `event_id` which maps to the PubsubMessage
                        messageId, `timestamp` which maps to the PubsubMessage
                        publishTime, `event_type` which maps to
                        `google.pubsub.topic.publish`, and `resource` which is
                        a dictionary that describes the service API endpoint
                        pubsub.googleapis.com, the triggering topic's name, and
                        the triggering event type
                        `type.googleapis.com/google.pubsub.v1.PubsubMessage`.
    Returns:
        None. The output is written to Cloud Logging.
    """
    import base64

    print("""This Function was triggered by messageId {} published at {} to {}
    """.format(context.event_id, context.timestamp, context.resource["name"]))

    if 'data' in event:
        name = base64.b64decode(event['data']).decode('utf-8')
    else:
        name = 'World'
    print('Hello {}!'.format(name))

Go


// Package helloworld provides a set of Cloud Functions samples.
package helloworld

import (
	"context"
	"log"
)

// PubSubMessage is the payload of a Pub/Sub event.
// See the documentation for more details:
// https://cloud.google.com/pubsub/docs/reference/rest/v1/PubsubMessage
type PubSubMessage struct {
	Data []byte `json:"data"`
}

// HelloPubSub consumes a Pub/Sub message.
func HelloPubSub(ctx context.Context, m PubSubMessage) error {
	name := string(m.Data) // Automatically decoded from base64.
	if name == "" {
		name = "World"
	}
	log.Printf("Hello, %s!", name)
	return nil
}

자바


import com.google.cloud.functions.BackgroundFunction;
import com.google.cloud.functions.Context;
import com.google.events.cloud.pubsub.v1.Message;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.logging.Level;
import java.util.logging.Logger;

public class HelloPubSub implements BackgroundFunction<Message> {
  private static final Logger logger = Logger.getLogger(HelloPubSub.class.getName());

  @Override
  public void accept(Message message, Context context) {
    String name = "world";
    if (message != null && message.getData() != null) {
      name = new String(
          Base64.getDecoder().decode(message.getData().getBytes(StandardCharsets.UTF_8)),
          StandardCharsets.UTF_8);
    }
    logger.info(String.format("Hello %s!", name));
    return;
  }
}

CloudEvent 함수

.NET, Ruby, PHP 런타임용으로 작성된 이벤트 기반 함수를 CloudEvent 함수라고 합니다. 자세한 내용은 CloudEvent 함수 작성을 참조하세요.

예를 들면 다음과 같습니다.

C#

using CloudNative.CloudEvents;
using Google.Cloud.Functions.Framework;
using Google.Events.Protobuf.Cloud.PubSub.V1;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;

namespace HelloPubSub
{
    public class Function : ICloudEventFunction<MessagePublishedData>
    {
        private readonly ILogger _logger;

        public Function(ILogger<Function> logger) =>
            _logger = logger;

        public Task HandleAsync(CloudEvent cloudEvent, MessagePublishedData data, CancellationToken cancellationToken)
        {
            string nameFromMessage = data.Message?.TextData;
            string name = string.IsNullOrEmpty(nameFromMessage) ? "world" : nameFromMessage;
            _logger.LogInformation("Hello {name}", name);
            return Task.CompletedTask;
        }
    }
}

Ruby

require "functions_framework"
require "base64"

FunctionsFramework.cloud_event "hello_pubsub" do |event|
  # The event parameter is a CloudEvents::Event::V1 object.
  # See https://cloudevents.github.io/sdk-ruby/latest/CloudEvents/Event/V1.html
  name = Base64.decode64 event.data["message"]["data"] rescue "World"

  # A cloud_event function does not return a response, but you can log messages
  # or cause side effects such as sending additional events.
  logger.info "Hello, #{name}!"
end

PHP


use CloudEvents\V1\CloudEventInterface;
use Google\CloudFunctions\FunctionsFramework;

// Register the function with Functions Framework.
// This enables omitting the `FUNCTIONS_SIGNATURE_TYPE=cloudevent` environment
// variable when deploying. The `FUNCTION_TARGET` environment variable should
// match the first parameter.
FunctionsFramework::cloudEvent('helloworldPubsub', 'helloworldPubsub');

function helloworldPubsub(CloudEventInterface $event): void
{
    $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb');

    $cloudEventData = $event->getData();
    $pubSubData = base64_decode($cloudEventData['message']['data']);

    $name = $pubSubData ? htmlspecialchars($pubSubData) : 'World';
    fwrite($log, "Hello, $name!" . PHP_EOL);
}

소스 코드 구성

Cloud Functions가 함수 정의를 찾으려면 각 런타임에 소스 코드에 대한 구조화 요구사항이 있어야 합니다. 일반적으로 대규모 다기능 코드베이스를 더 작은 함수별 코드베이스로 분할하여 코드 복잡성과 함수별 종속 항목 수를 줄이는 것이 좋습니다.

Node.js

Node.js 런타임의 경우 함수의 소스 코드는 Cloud Functions가 require() 호출로 로드하는 Node.js 모듈에서 내보내야 합니다. 로드할 모듈을 결정하기 위해 Cloud Functions는 package.json 파일의 main 필드를 사용합니다. main 필드가 지정되지 않은 경우 Cloud Functions는 index.js에서 코드를 로드합니다.

예를 들어 다음은 유효한 소스 코드 구성입니다.

  • 하나 이상의 함수를 내보내는 함수의 루트 디렉터리에 있는 단일 index.js입니다.

    .
    └── index.js
    
  • foo.js 파일에서 코드를 가져와서 하나 이상의 함수를 내보내는 index.js 파일:

    .
    ├── index.js
    └── foo.js
    
  • "main": "app.js"가 포함된 package.json 파일과 하나 이상의 함수를 내보내는 app.js 파일:

    .
    ├── app.js
    └── package.json
    

Python

Python 런타임의 경우 함수의 진입점이 함수의 루트 디렉터리에 있는 main.py라는 이름의 Python 소스 파일에 정의되어 있어야 합니다.

예를 들어 다음은 유효한 소스 코드 구성입니다.

  • 하나 이상의 함수를 정의하는 함수의 루트 디렉터리에 있는 단일 main.py 파일입니다.

    .
    └── main.py
    
  • main.py 파일과 종속 항목을 지정하는 requirements.txt 파일:

    .
    ├── main.py
    └── requirements.txt
    
  • 로컬 종속 항목에서 코드를 가져오는 main.py 파일:

    .
    ├── main.py
    └── mylocalpackage/
        ├── __init__.py
        └── myscript.py
    

Go

Go 런타임의 경우 함수는 프로젝트 루트의 Go 패키지에 있어야합니다. 함수는 package main에 있을 수 없습니다. 하위 패키지는 Go 모듈을 사용할 때만 지원됩니다.

예를 들어 다음은 유효한 소스 코드 구성입니다.

  • 하나 이상의 함수를 내보내는 프로젝트 루트의 패키지:

    .
    └── function.go
    
  • 프로젝트 루트에 있는 패키지가 하위 패키지의 코드를 가져오고 하나 이상의 함수를 내보냅니다.

    .
    ├── function.go
    ├── go.mod
    └── shared/
        └── shared.go
    
  • package main을 정의하는 하위 디렉터리가 있는 프로젝트 루트의 패키지:

    .
    ├── cmd/
    |   └── main.go
    └── function.go
    

자바

자바 런타임의 경우 src/main/java/ 하위 디렉터리와 pom.xml 파일이 포함된 최상위 함수 디렉터리를 만들어야 합니다. src/test/java/ 하위 디렉터리에 테스트를 배치하는 것이 좋습니다.

.
├── pom.xml
└── src/
    ├── main/
    |   └── java/
    |       └── MyFunction.java
    └── test
        └── java/
            └── MyFunctionTest.java

.java 파일에서 패키지(예: functions)를 선언하는 경우 디렉터리 계층 구조는 다음과 같습니다.

.
├── pom.xml
└── src/
    ├── main/
    |   └── java/
    |       └── functions/
    |               └── MyFunction.java
    └── test/
        └── java/
                └── functions/
                    └── MyFunctionTest.java

함수가 대부분의 자바 함수와 같이 특정 패키지에 정의된 경우 배포 시간--entry-point 값의 일부로 이를 지정해야 합니다.

여러 함수 그룹화

여러 함수를 단일 프로젝트로 그룹화하려는 경우 모든 함수가 동일한 종속 항목 집합을 공유하게 될 수 있습니다. 그러나 일부 함수에는 공유된 종속 항목 모두 필요하지 않을 수 있습니다.

위에서 언급한 바와 같이 각 함수를 자체 src/main/java 하위 디렉터리와 pom.xml 파일을 포함하는 자체 최상위 디렉터리에 배치하는 것이 좋습니다. 이렇게 하면 특정 함수에 필요한 종속 항목의 수를 최소화하여 함수에 필요한 메모리 양이 줄어듭니다.

또한 별도의 함수를 사용하면 함수 프레임워크를 통해 로컬에서 함수를 실행할 때 단일 함수를 더 쉽게 지정할 수 있습니다. 이는 로컬 개발 및 테스트에 유용할 수 있습니다.

C#

.NET 런타임의 경우 다른 .NET 소스 코드와 마찬가지로 프로젝트를 구성할 수 있습니다.

템플릿을 사용하여 C# 함수를 만들면 파일 시스템에서 동일한 수준으로 다음 파일이 생성됩니다.

  • Function.cs라는 이름의 함수 소스 파일
  • .csproj 확장자를 사용하는 프로젝트 파일

예를 들면 다음과 같습니다.

.
├── Function.cs
└── HelloHttp.csproj

템플릿을 사용하여 F# 및 Visual Basic 함수를 만들 때도 동일한 패턴이 적용됩니다. F#의 경우 파일 이름은 Function.fs이고 프로젝트 파일의 확장자는 .fsproj입니다. Visual Basic의 경우 파일 이름은 CloudFunction.vb이고 프로젝트 파일의 확장자는 .vbproj입니다.

그러나 이러한 패턴은 요구사항이 아닌 규칙일 뿐입니다. 여러 소스 파일, 리소스 등을 포함할 수 있는 다른 일반 C#, F# 또는 Visual Basic 프로젝트와 마찬가지로 코드를 구조화할 수 있습니다.

또한 프로젝트별로 함수를 하나만 구현하는 경우 함수 이름을 지정하지 않고도 해당 함수를 호스팅할 수 있어 로컬 개발과 디버깅이 더 간단해집니다.

Ruby

Ruby 런타임의 경우 함수의 진입점은 함수의 루트 디렉터리에 있는 app.rb라는 Ruby 소스 파일에 정의되어야 합니다. 또한 최소한 functions_framework gem을 포함한 종속 항목은 Gemfile에 나열되어야 하며 관련 Gemfile.lock 파일도 함수의 루트 디렉터리에 있어야 합니다. 함수는 루트 디렉터리 또는 하위 디렉터리에 추가 Ruby 파일을 포함할 수 있습니다.

예를 들어 다음은 유효한 소스 코드 구성입니다.

.
├── Gemfile
├── Gemfile.lock
└── app.rb

.
├── Gemfile
├── Gemfile.lock
├── app.rb
└── lib/
    └── my_class.rb

PHP

PHP 런타임의 경우 함수 구현이 포함된 index.php 파일이 있는 최상위 함수 디렉터리를 만들어야 합니다.

composer.json 파일은 종속 항목을 선언합니다. PHP의 종속 항목 지정에 설명된 대로 composer require google/cloud-functions-framework 명령어를 실행하면 종속 항목이 포함된 함수 코드 디렉터리에 vendor/ 디렉터리가 만들어집니다. composer.lock 파일은 함수의 종속 항목에 잠금을 설정하므로 함수는 설치된 종속 항목 버전의 정확한 기록을 유지합니다. 나중에 composer install을 다시 실행하면 composer.lock 파일에 지정된 종속 항목 버전이 사용됩니다.

test/ 디렉터리에 테스트를 배치하는 것이 좋습니다.

.
├── index.php
├── composer.json
├── composer.lock
├── vendor/
└── test/

종속 항목 지정

사용 중인 런타임에 따라 함수의 종속 항목을 관용적으로 지정합니다. 자세한 내용은 적절한 페이지를 참고하세요.

Cloud Functions 이름 지정

Cloud Functions에는 배포 시 설정되고 일단 설정되면 변경할 수 없는 '이름' 속성이 있습니다. 함수 이름은 식별자로 사용되며 한 리전 내에서 고유해야 합니다. 자세한 내용은 배포 문서를 참조하세요.

다음 단계

직접 사용해 보기

Google Cloud를 처음 사용하는 경우 계정을 만들어 실제 시나리오에서 Cloud Functions의 성능을 평가할 수 있습니다. 신규 고객에게는 워크로드를 실행, 테스트, 배포하는 데 사용할 수 있는 $300의 무료 크레딧이 제공됩니다.

Cloud Functions 무료로 사용해 보기