הפעלת פונקציות Cloud Run באמצעות Cloud Tasks

במדריך הזה נסביר איך להשתמש ב-Cloud Tasks באפליקציית App Engine כדי להפעיל פונקציה ב-Cloud Run ולשלוח הודעת אימייל מתוזמנת.

מטרות

  • להבין את הקוד בכל אחד מהרכיבים.
  • יוצרים חשבון SendGrid.
  • מורידים את קוד המקור.
  • פורסים פונקציית Cloud Run כדי לקבל בקשות Cloud Tasks ולשלוח אימייל באמצעות SendGrid API.
  • יוצרים תור של Cloud Tasks.
  • יוצרים חשבון שירות כדי לאמת את הבקשות של Cloud Tasks.
  • פורסים את קוד הלקוח שמאפשר למשתמש לשלוח אימייל.

עלויות

ל-Cloud Tasks, לפונקציות Cloud Run ול-App Engine יש תוכנית בחינם, כך שאם תפעילו את ההדרכה במסגרת התוכנית בחינם של המוצרים האלה, לא תהיה לכם עלות נוספת. מידע נוסף על תמחור

לפני שמתחילים

  1. בוחרים או יוצרים Google Cloud פרויקט.

    כניסה לדף App Engine

  2. מאתחלים אפליקציית App Engine בפרויקט:

    1. בדף Welcome to App Engine, לוחצים על Create Application.

    2. בוחרים אזור בשביל הבקשה. המיקום הזה ישמש כפרמטר LOCATION_ID בבקשות שלכם ל-Cloud Tasks, לכן חשוב לרשום אותו. שימו לב: שני מיקומים, שנקראים europe-west ו-us-central בפקודות של App Engine, נקראים europe-west1 ו-us-central1 בפקודות של Cloud Tasks.

    3. בוחרים באפשרות Node.js בשדה Language (שפה) ובאפשרות Standard בשדה Environment (סביבה).

    4. אם מופיע חלון קופץ עם האפשרות הפעלת החיוב, בוחרים את החשבון לחיוב. אם אין לכם חשבון לחיוב, לוחצים על יצירת חשבון לחיוב ופועלים לפי ההוראות באשף.

    5. בדף Get started (תחילת העבודה), לוחצים על Next (הבא). תטפל בזה מאוחר יותר.

  3. מפעילים את ממשקי ה-API של פונקציות Cloud Run ו-Cloud Tasks.

    הפעלת ממשקי ה-API

  4. מתקינים ומפעילים את ה-CLI של gcloud.

הסבר על הקוד

בקטע הזה נסביר איך הקוד של האפליקציה פועל.

יצירת המשימה

דף האינדקס מוצג באמצעות פונקציות handler ב-app.yaml. המשתנים שנדרשים ליצירת המשימה מועברים כמשתני סביבה.

runtime: nodejs16

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,
        audience: url,
      },
      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 Run שהיא היעד של הבקשה של Cloud Tasks. הוא משתמש בגוף הבקשה כדי ליצור אימייל ולשלוח אותו באמצעות 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. יוצרים מפתח API של SendGrid:

    1. נכנסים אל חשבון SendGrid.

    2. בתפריט הניווט הימני, פותחים את הגדרות ולוחצים על מפתחות API.

    3. לוחצים על Create API Key ובוחרים באפשרות 'גישה מוגבלת'. בכותרת Mail Send (שליחת אימייל), בוחרים באפשרות Full Access (גישה מלאה).

    4. מעתיקים את מפתח ה-API כשהוא מוצג (הוא יוצג רק פעם אחת, לכן חשוב להדביק אותו במקום כלשהו כדי שאפשר יהיה להשתמש בו בהמשך).

הורדת קוד המקור

  1. משכפלים את מאגר האפליקציה לדוגמה ומעבירים אותו למכונה המקומית:

    git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git
    
  2. עוברים לספרייה שמכילה את הקוד לדוגמה:

    cd cloud-tasks/
    

פריסת הפונקציה של Cloud Run

  1. מנווטים לספרייה function/:

    cd function/
    
  2. פורסים את הפונקציה:

    gcloud functions deploy sendEmail --runtime nodejs14 --trigger-http \
      --no-allow-unauthenticated \
      --set-env-vars SENDGRID_API_KEY=SENDGRID_API_KEY \

    מחליפים את הערך SENDGRID_API_KEY במפתח ה-API שלכם.

    בפקודה הזו נעשה שימוש בדגלים:

    • --trigger-http כדי לציין את סוג הטריגר של פונקציות Cloud Run.

    • --no-allow-unauthenticated כדי לציין שהפעלת הפונקציה מחייבת אימות.

    • --set-env-var כדי להגדיר את פרטי הכניסה שלכם ל-SendGrid

  3. מגדירים את בקרת הגישה לפונקציה כך שרק משתמשים מאומתים יוכלו לגשת אליה.

    1. בוחרים את הפונקציה sendEmail בממשק המשתמש של Cloud Run functions.

    2. אם לא רואים את פרטי ההרשאות של sendEmail, לוחצים על SHOW INFO PANEL בפינה השמאלית העליונה.

    3. לוחצים על הלחצן Add principals (הוספת ישויות) שלמעלה.

    4. מגדירים את New principals לערך allAuthenticatedUsers.

    5. מגדירים את התפקיד.

      • פונקציות דור ראשון: מגדירים את התפקיד ל-Cloud Function Invoker
      • פונקציות דור שני: מגדירים את התפקיד ל-Cloud Run Invoker
    6. לוחצים על SAVE.

יצירת תור ב-Cloud Tasks

  1. יוצרים תור באמצעות הפקודה gcloud הבאה:

    gcloud tasks queues create my-queue --location=LOCATION

    מחליפים את LOCATION במיקום המועדף של התור, לדוגמה, us-west2. אם לא מציינים את המיקום, ה-CLI של gcloud בוחר את ברירת המחדל.

  2. מוודאים שהחשבון נוצר בהצלחה:

    gcloud tasks queues describe my-queue --location=LOCATION

    מחליפים את LOCATION במיקום של התור.

יצירת חשבון שירות

כדי שהפונקציה של Cloud Run תוכל לאמת את הבקשה, הבקשה של Cloud Tasks צריכה לספק פרטי כניסה בכותרת Authorization. חשבון השירות הזה מאפשר ל-Cloud Tasks ליצור ולהוסיף אסימון OIDC למטרה הזו.

  1. בממשק המשתמש של חשבונות השירות, לוחצים על +יצירת חשבון שירות.

  2. מוסיפים שם לחשבון השירות(שם מוצג ידידותי) ולוחצים על יצירה.

  3. מגדירים את התפקיד ולוחצים על המשך.

    • פונקציות דור ראשון: מגדירים את התפקיד ל-Cloud Function Invoker
    • פונקציות דור שני: מגדירים את התפקיד ל-Cloud Run Invoker
  4. לוחצים על סיום.

פריסת נקודת הקצה (endpoint) וכלי ליצירת משימות ב-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 --location=LOCATION

    מחליפים את LOCATION במיקום של התור.

    כדי למצוא את כתובת ה-URL של הפונקציה, משתמשים בפקודה הבאה:

    gcloud functions describe sendEmail
  3. פורסים את האפליקציה בסביבה הרגילה של App Engine באמצעות הפקודה הבאה:

    gcloud app deploy
  4. פותחים את האפליקציה כדי לשלוח גלויה באימייל:

    gcloud app browse

הסרת המשאבים

אחרי שמסיימים את המדריך, אפשר למחוק את המשאבים שנוצרו, כדי שהם יפסיקו להשתמש במכסה ולצבור חיובים. בסעיפים הבאים מוסבר איך למחוק או להשבית את המשאבים האלו.

מחיקת משאבים

אפשר להסיר את המשאבים שיצרתם ב- Google Cloud כדי שלא יתפסו מכסת שימוש ולא תחויבו עליהם בעתיד. בסעיפים הבאים מוסבר איך למחוק או להשבית את המשאבים האלו.

מחיקת פונקציית Cloud Run

  1. נכנסים לדף Cloud Run functions במסוף Google Cloud .

    כניסה לדף Cloud Run functions.

  2. לוחצים על תיבות הסימון לצד הפונקציות.

  3. לוחצים על הלחצן מחיקה בחלק העליון של הדף ומאשרים את המחיקה.

מחיקת התור ב-Cloud Tasks

  1. פותחים את הדף Cloud Tasks queues במסוף.

    כניסה לדף התורים של Cloud Tasks

  2. בוחרים את שם התור שרוצים למחוק ולוחצים על מחיקת התור.

  3. מאשרים את הפעולה.

מחיקת הפרויקט

הדרך הקלה ביותר לבטל את החיוב היא למחוק את הפרויקט שיצרתם בשביל המדריך.

כדי למחוק את הפרויקט:

  1. במסוף Google Cloud , נכנסים לדף Manage resources.

    כניסה לדף Manage resources

  2. ברשימת הפרויקטים, בוחרים את הפרויקט שרוצים למחוק ולוחצים על Delete.
  3. כדי למחוק את הפרויקט, כותבים את מזהה הפרויקט בתיבת הדו-שיח ולוחצים על Shut down.

המאמרים הבאים