Cloud Tasks を使用した Cloud Functions のトリガー

このチュートリアルでは、Google App Engine アプリケーション内で Cloud Tasks を使用して Cloud Function をトリガーし、スケジュールされたメールを送信する方法について説明します。

目標

  • 各コンポーネントのコードを理解する。
  • SendGrid アカウントを作成する。
  • ソースコードをダウンロードする。
  • Cloud Tasks リクエストを受信し、SendGrid API を介してメールを送信する Cloud Function をデプロイする。
  • 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 と呼ばれる 2 つのロケーションは、Cloud Tasks コマンドではそれぞれ europe-west1 および us-central1 と呼ばれます。

    3. 言語として [Node.js] を選択し、環境として [Standard] を選択します。

    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 Function を作成します。リクエストの本文を使用してメールを作成し、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. [Create API Key] をクリックし、制限付きアクセスを選択します。[メール送信] ヘッダーで、[フルアクセス] を選択します。

    4. 表示された API キーをコピーします(このキーは 1 回だけ表示されます。後で使用できるように別の場所に貼り付けてください)。

ソースコードのダウンロード

  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 UIsendEmail 関数を選択します。

    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 Function がリクエストを認証するための認証情報を Authorization ヘッダーで指定する必要があります。このサービス アカウントで、その目的のために Cloud Tasks で OIDC トークンを作成して追加できます。

  1. [IAM と管理] の [サービス アカウント] で、[+ サービス アカウントを作成] をクリックします。

  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 Tasks キューを削除する

  1. Console で Cloud Tasks キューのページを開きます。

    Cloud Tasks キューのページに移動

  2. 削除するキューの名前を選択し、[キューの削除] をクリックします。

  3. 処理を確認します。

プロジェクトの削除

課金をなくす最も簡単な方法は、チュートリアル用に作成したプロジェクトを削除することです。

プロジェクトを削除するには:

  1. Cloud Console で [リソースの管理] ページに移動します。

    [リソースの管理] ページに移動

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。

次のステップ