Cloud Tasks를 사용하여 Cloud Functions 트리거

이 가이드에서는 Google App Engine 애플리케이션 내에서 Cloud Tasks를 사용하여 Cloud Functions를 트리거하고 예약된 이메일을 보내는 방법을 다룹니다.

목표

  • 각 구성 요소의 코드를 이해합니다.
  • SendGrid 계정을 만듭니다.
  • 소스 코드를 다운로드합니다.
  • Cloud Tasks 요청을 수신하고 SendGrid API를 통해 이메일을 보내려면 Cloud 함수를 배포합니다.
  • Cloud Tasks 큐를 만듭니다.
  • 서비스 계정을 만들어 Cloud Tasks 요청을 인증합니다.
  • 사용자가 이메일을 보낼 수 있는 클라이언트 코드를 배포합니다.

비용

Cloud Tasks, Cloud Functions, App Engine에는 무료 등급이 있으므로 특정 제품의 무료 등급 내에서 가이드를 실행하는 경우 추가 비용이 발생하지 않습니다. 자세한 내용은 가격을 참조하세요.

시작하기 전에

  1. GCP 프로젝트를 선택하거나 만듭니다.

    App Engine 페이지로 이동

  2. 프로젝트에서 App Engine 애플리케이션을 초기화하세요.

    1. App Engine 시작하기 페이지에서 애플리케이션 만들기를 클릭합니다.

    2. 애플리케이션의 리전을 선택합니다. 이 위치는 Cloud Tasks 요청의 LOCATION_ID 매개변수로 사용되므로 기록해두두세요. 두 위치는 App Engine 명령어에서는 europe-west 및 us-central로, Cloud Tasks 명령어에서는 europe-west1 및 us-central1로 각각 호출됩니다.

    3. 언어는 Node.js를 선택하고 환경은 표준을 선택합니다.

    4. 결제 사용 설정 팝업이 나타나면 결제 계정을 선택합니다. 현재 결제 계정이 없는 경우 결제 계정 만들기를 클릭하고 마법사의 안내에 따릅니다.

    5. 시작하기 페이지에서 다음을 클릭합니다. 이 문제는 나중에 처리합니다.

  3. Cloud Functions 및 Cloud Tasks API를 사용 설정합니다.

    API 사용 설정

  4. Cloud SDK를 설치 및 초기화합니다.

코드 이해하기

이 섹션에서는 앱 코드와 코드의 작동 방식을 살펴봅니다.

태스크 만들기

색인 페이지는 app.yaml의 핸들러를 사용하여 제공됩니다. 태스크 생성에 필요한 변수는 환경 변수로 전달됩니다.

runtime: nodejs10

env_variables:
  QUEUE_NAME: "my-queue"
  QUEUE_LOCATION: "us-central1"
  FUNCTION_URL: "https://<region>-<project_id>.cloudfunctions.net/sendEmail"
  SERVICE_ACCOUNT_EMAIL: "<member>@<project_id>.iam.gserviceaccount.com"

# Handlers for serving the index page.
handlers:
  - url: /static
    static_dir: static
  - url: /
    static_files: index.html
    upload: index.html

이 코드는 엔드포인트 /send-email을 생성합니다. 이 엔드포인트는 색인 페이지에서 양식 제출을 처리하고 해당 데이터를 태스크 생성 코드에 전달합니다.

app.post('/send-email', (req, res) => {
  // Set the task payload to the form submission.
  const {to_name, from_name, to_email, date} = req.body;
  const payload = {to_name, from_name, to_email};

  createHttpTaskWithToken(
    process.env.GOOGLE_CLOUD_PROJECT,
    QUEUE_NAME,
    QUEUE_LOCATION,
    FUNCTION_URL,
    SERVICE_ACCOUNT_EMAIL,
    payload,
    date
  );

  res.status(202).send('📫 Your postcard is in the mail! 💌');
});

이 코드는 실제로 태스크를 만들어 Cloud Tasks 큐로 보냅니다. 코드는 다음과 같이 태스크를 빌드합니다.

  • 대상 유형HTTP Request로 지정

  • 사용할 HTTP method와 대상의 URL 지정

  • 다운스트림 애플리케이션이 구조화된 페이로드를 파싱할 수 있도록 Content-Type 헤더를 application/json으로 설정

  • Cloud Tasks가 인증이 필요한 요청 대상에 사용자 인증 정보를 제공할 수 있도록 서비스 계정 이메일 추가 서비스 계정은 별도로 생성됩니다.

  • 날짜에 대한 사용자 입력이 최대 30일 이내인지 확인하고 이를 필드 scheduleTime으로 요청에 추가

const MAX_SCHEDULE_LIMIT = 30 * 60 * 60 * 24; // Represents 30 days in seconds.

const createHttpTaskWithToken = async function (
  project = 'my-project-id', // Your GCP Project id
  queue = 'my-queue', // Name of your Queue
  location = 'us-central1', // The GCP region of your queue
  url = 'https://example.com/taskhandler', // The full url path that the request will be sent to
  email = '<member>@<project-id>.iam.gserviceaccount.com', // Cloud IAM service account
  payload = 'Hello, World!', // The task HTTP request body
  date = new Date() // Intended date to schedule task
) {
  // Imports the Google Cloud Tasks library.
  const {v2beta3} = require('@google-cloud/tasks');

  // Instantiates a client.
  const client = new v2beta3.CloudTasksClient();

  // Construct the fully qualified queue name.
  const parent = client.queuePath(project, location, queue);

  // Convert message to buffer.
  const convertedPayload = JSON.stringify(payload);
  const body = Buffer.from(convertedPayload).toString('base64');

  const task = {
    httpRequest: {
      httpMethod: 'POST',
      url,
      oidcToken: {
        serviceAccountEmail: email,
      },
      headers: {
        'Content-Type': 'application/json',
      },
      body,
    },
  };

  const convertedDate = new Date(date);
  const currentDate = new Date();

  // Schedule time can not be in the past.
  if (convertedDate < currentDate) {
    console.error('Scheduled date in the past.');
  } else if (convertedDate > currentDate) {
    const date_diff_in_seconds = (convertedDate - currentDate) / 1000;
    // Restrict schedule time to the 30 day maximum.
    if (date_diff_in_seconds > MAX_SCHEDULE_LIMIT) {
      console.error('Schedule time is over 30 day maximum.');
    }
    // Construct future date in Unix time.
    const date_in_seconds =
      Math.min(date_diff_in_seconds, MAX_SCHEDULE_LIMIT) + Date.now() / 1000;
    // Add schedule time to request in Unix time using Timestamp structure.
    // https://googleapis.dev/nodejs/tasks/latest/google.protobuf.html#.Timestamp
    task.scheduleTime = {
      seconds: date_in_seconds,
    };
  }

  try {
    // Send create task request.
    const [response] = await client.createTask({parent, task});
    console.log(`Created task ${response.name}`);
    return response.name;
  } catch (error) {
    // Construct error for Stackdriver Error Reporting
    console.error(Error(error.message));
  }
};

module.exports = createHttpTaskWithToken;

이메일 만들기

이 코드는 Cloud Task 요청의 대상인 Cloud 함수를 만듭니다. 요청 본문을 사용하여 이메일을 작성하고 SendGrid API를 통해 전송합니다.

const sendgrid = require('@sendgrid/mail');

/**
 * Responds to an HTTP request from Cloud Tasks and sends an email using data
 * from the request body.
 *
 * @param {object} req Cloud Function request context.
 * @param {object} req.body The request payload.
 * @param {string} req.body.to_email Email address of the recipient.
 * @param {string} req.body.to_name Name of the recipient.
 * @param {string} req.body.from_name Name of the sender.
 * @param {object} res Cloud Function response context.
 */
exports.sendEmail = async (req, res) => {
  // Get the SendGrid API key from the environment variable.
  const key = process.env.SENDGRID_API_KEY;
  if (!key) {
    const error = new Error(
      'SENDGRID_API_KEY was not provided as environment variable.'
    );
    error.code = 401;
    throw error;
  }
  sendgrid.setApiKey(key);

  // Get the body from the Cloud Task request.
  const {to_email, to_name, from_name} = req.body;
  if (!to_email) {
    const error = new Error('Email address not provided.');
    error.code = 400;
    throw error;
  } else if (!to_name) {
    const error = new Error('Recipient name not provided.');
    error.code = 400;
    throw error;
  } else if (!from_name) {
    const error = new Error('Sender name not provided.');
    error.code = 400;
    throw error;
  }

  // Construct the email request.
  const msg = {
    to: to_email,
    from: 'postcard@example.com',
    subject: 'A Postcard Just for You!',
    html: postcardHTML(to_name, from_name),
  };

  try {
    await sendgrid.send(msg);
    // Send OK to Cloud Task queue to delete task.
    res.status(200).send('Postcard Sent!');
  } catch (error) {
    // Any status code other than 2xx or 503 will trigger the task to retry.
    res.status(error.code).send(error.message);
  }
};

애플리케이션 준비

SendGrid 설정

  1. SendGrid 계정을 만듭니다.

  2. SendGrid API 키를 만듭니다.

    1. SendGrid 계정에 로그인합니다.

    2. 왼쪽 탐색 메뉴에서 설정을 열고 API 키를 클릭합니다.

    3. API 키 만들기를 클릭하고 제한된 액세스를 선택합니다. Mail Send 헤더에서 전체 액세스를 선택합니다.

    4. API 키가 표시되면 복사합니다. 이 키는 한 번만 표시되므로 나중에 사용할 수 있도록 어딘가에 붙여넣기 해 두세요.

소스 코드 다운로드

  1. 샘플 앱 저장소를 로컬 머신에 클론합니다.

    git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git
    
  2. 샘플 코드가 있는 디렉토리로 변경합니다.

    cd cloud-tasks/
    

Cloud 함수 배포

  1. function/ 디렉터리로 이동합니다.

    cd function/
    
  2. 함수를 배포합니다. 여기에서 sendgrid_api_key를 내 키로 바꿉니다.

    gcloud functions deploy sendEmail --runtime nodejs8 --trigger-http --set-env-vars SENDGRID_API_KEY=sendgrid_api_key
    

    이 명령어는 다음 플래그를 사용합니다.

    • --trigger-http를 사용하여 Cloud Functions 트리거 유형 지정

    • --set-env-var를 사용하여 SendGrid 사용자 인증 정보 설정

  3. 인증된 사용자만 허용하도록 함수의 액세스 제어를 설정합니다.

    1. Cloud Functions UI에서 sendEmail 함를 선택합니다.

    2. sendEmail에 대한 권한 정보가 표시되지 않으면 오른쪽 상단의 정보 패널 표시를 클릭합니다.

    3. Cloud Functions 호출자 헤더 아래에서 회원 allUsers를 삭제합니다.

    4. 위의 구성원 추가 버튼을 클릭합니다.

    5. 새 구성원allAuthenticatedUsers로 설정합니다.

    6. 역할Cloud Function Invoker로 설정합니다.

    7. 저장을 클릭합니다.

Cloud Tasks 큐 만들기

  1. 다음 gcloud 명령어를 사용하여 큐를 만듭니다.

    gcloud tasks queues create my-queue
    
  2. 성공적으로 생성되었는지 확인합니다.

    gcloud tasks queues describe my-queue
    

서비스 계정 만들기

Cloud Tasks 요청은 Cloud Functions이 요청을 인증할 수 있도록 Authorization 헤더에 사용자 인증 정보를 제공해야 합니다. 이 서비스 계정을 사용하면 Cloud Tasks에서 이러한 목적으로 OIDC 토큰을 만들고 추가할 수 있습니다.

  1. IAM 및 관리자 | 서비스 계정 UI에서 + 서비스 계정 만들기를 클릭합니다.

  2. 서비스 계정 이름(알기 쉬운 표시 이름)을 추가하고 만들기를 선택합니다.

  3. Cloud Functions 호출자 역할을 선택하고 계속을 클릭합니다.

  4. 완료를 선택합니다.

엔드포인트 및 태스크 생성자를 App Engine에 배포

  1. app/ 디렉터리로 이동합니다.

    cd ../app/
    
  2. app.yaml의 변수를 다음 값으로 업데이트합니다.

    env_variables:
      QUEUE_NAME: "my-queue"
      QUEUE_LOCATION: "us-central1"
      FUNCTION_URL: "https://<region>-<project_id>.cloudfunctions.net/sendEmail"
      SERVICE_ACCOUNT_EMAIL: "<member>@<project_id>.iam.gserviceaccount.com"

    큐 위치를 찾으려면 다음 명령어를 사용합니다.

    gcloud tasks queues describe my-queue
    

    함수 URL을 찾으려면 다음 명령어를 사용합니다.

    gcloud functions describe sendEmail
    
  3. 다음 명령어를 사용하여 애플리케이션을 App Engine 표준 환경에 배포합니다.

    gcloud app deploy
    
  4. 애플리케이션을 열어 이메일로 엽서를 보냅니다.

    gcloud app browse
    

삭제

가이드를 완료한 후에는 Google Cloud에서 만든 리소스를 삭제하여 할당량을 차지하지 않고 향후 요금이 청구되지 않도록 할 수 있습니다. 다음 섹션에서는 리소스를 삭제하거나 사용 중지하는 방법을 설명합니다.

리소스 삭제

GCP에서 만든 리소스를 정리하여 할당량을 차지하지 않고 향후 요금이 청구되지 않도록 할 수 있습니다. 다음 섹션에서는 이러한 리소스를 삭제 또는 해제하는 방법을 설명합니다.

Cloud 함수 삭제

  1. Cloud Console에서 Cloud Functions 페이지로 이동합니다.

    Cloud Functions 페이지로 이동.

  2. 함수 옆에 있는 체크박스를 클릭합니다.

  3. 페이지 상단의 삭제 버튼을 클릭하고 삭제를 확인합니다.

Cloud Task 큐 삭제

  1. 콘솔에서 Cloud Tasks 큐 페이지를 엽니다.

    Cloud Tasks 큐 페이지로 이동

  2. 삭제할 대기열의 이름을 선택하고 대기열 삭제를 클릭합니다.

  3. 작업을 확인합니다.

프로젝트 삭제

비용이 청구되지 않도록 하는 가장 쉬운 방법은 가이드에서 만든 프로젝트를 삭제하는 것입니다.

프로젝트를 삭제하는 방법은 다음과 같습니다.

  1. Cloud Console에서 리소스 관리 페이지로 이동합니다.

    리소스 관리 페이지로 이동

  2. 프로젝트 목록에서 삭제할 프로젝트를 선택하고 삭제 를 클릭합니다.
  3. 대화상자에서 프로젝트 ID를 입력한 다음 종료를 클릭하여 프로젝트를 삭제합니다.

다음 단계