סביבת זמן הריצה של Node.js

סביבת זמן הריצה של Node.js היא מחסנית התוכנה שאחראית להתקנת קוד האפליקציה והתלות שלה, ולאחר מכן להפעלת האפליקציה בסביבה הגמישה.

גרסאות Node.js

‫Node.js 24 משתמש ב-buildpacks. מנוע Node.js שמוגדר כברירת מחדל משתמש בגרסת ה-LTS האחרונה. רשימה מלאה של גרסאות Node.js נתמכות וגרסאות Ubuntu התואמות שלהן זמינה בלוח הזמנים לתמיכה בזמן ריצה.

כדי להשתמש בגרסה נתמכת של Node.js, צריך:

  • מתקינים את gcloud CLI בגרסה 420.0.0 ואילך. אפשר לעדכן את כלי ה-CLI על ידי הרצת הפקודה gcloud components update. כדי לראות את הגרסה המותקנת, מריצים את הפקודה gcloud version.

  • כדי לציין מערכת הפעלה, צריך לכלול את ההגדרות runtime_config ו-operating_system בקובץ app.yaml.

  • אפשר גם לציין גרסה באמצעות:

    • מוסיפים את ההגדרה runtime_version לקובץ app.yaml. כברירת מחדל, נעשה שימוש בגרסה העדכנית ביותר של Node.js אם לא מציינים את ההגדרה runtime_version. לדוגמה:

      • כדי לציין Node.js 24 ב-Ubuntu 24:

          runtime: nodejs
          env: flex
        
          runtime_config:
              operating_system: "ubuntu24"
              runtime_version: "24"
        
      • כדי לציין את הגרסה העדכנית ביותר של Node.js שנתמכת ב-Ubuntu 24:

          runtime: nodejs
          env: flex
        
          runtime_config:
              operating_system: "ubuntu24"
        

        ההגדרה runtime_version תומכת ב-semver.

    • כוללים את הגרסה העדכנית ביותר של Node.js שנתמכת בקובץ package.json של האפליקציה באמצעות השדה engines. כשמשתמשים בשדה engines כדי לציין גרסה, ההגדרה runtime_version מקבלת עדיפות. כדי למנוע שיבושים בלתי צפויים, מומלץ לציין גרסת Node.js בשדה engines, יחד עם runtime_version. לדוגמה:

        {
          "engines": {
            "node": "24.x"
          }
        }
      

      המאפיין engines.node יכול להיות טווח semver. אם מציינים את המאפיין הזה, סביבת זמן הריצה מורידה ומתקינה את הגרסה האחרונה של Node.js שתואמת לטווח semver. אם לא נמצאת התאמה, פריסת האפליקציה תיכשל וסביבת זמן הריצה תחזיר שגיאה.

גרסאות קודמות של סביבת זמן הריצה

בזמן ריצה של Node.js בגרסה 16 ומגרסאות קודמות, מציינים גרסה בקובץ package.json של האפליקציה באמצעות השדה engines.

בדוגמה הבאה מוגדר זמן הריצה לשימוש בגרסה Node 9:

{
  "engines": {
    "node": "9.x"
  }
}

המאפיין engines.node יכול להיות טווח semver. אם מציינים את המאפיין הזה, סביבת זמן הריצה מורידה ומתקינה את הגרסה האחרונה של Node.js שתואמת לטווח semver. אם לא נמצאה התאמה, הפריסה של האפליקציה תיכשל וסביבת זמן הריצה תחזיר הודעת שגיאה.

תמיכה בסביבות זמן ריצה אחרות של Node.js

אם אתם צריכים להשתמש בגרסת Node.js שלא נתמכת, אתם יכולים ליצור סביבת ריצה בהתאמה אישית ולבחור אימג' בסיס תקין עם גרסת Node.js שאתם צריכים.

לגבי תמונות בסיס שסופקו על ידי Google או תמונות בסיס של Docker Node.js, אפשר לעיין במאמר בנושא יצירת סביבות ריצה בהתאמה אישית.

מערכות ניהול חבילות

במהלך הפריסה, סביבת זמן הריצה משתמשת במנהל החבילות npm,‏ yarn או Pnpm כדי להתקין יחסי תלות ולהפעיל את האפליקציה. מנהל החבילות מוגדר עם הלוגיקה הבאה:

  • מנהל החבילות שמוגדר כברירת מחדל הוא npm.
  • אם קובץ yarn.lock נמצא בספריית הבסיס של האפליקציה, סביבת זמן הריצה משתמשת במנהל החבילות yarn במקום זאת.
  • רק ב-Node.js בגרסה 18 ואילך, אם קובץ pnpm-lock.yaml נמצא בספריית הבסיס של האפליקציה, סביבת זמן הריצה משתמשת במנהל החבילות Pnpm.
  • אם קיימים גם package-lock.json וגם yarn.lock או pnpm-lock.yaml, הפריסה תיכשל ותוצג שגיאה. אם אתם צריכים את הקובץ package-lock.json, אתם צריכים לציין את הקבצים האחרים של מנהל החבילות בקטע skip_files של הקובץ app.yaml כדי להגדיר באיזה מנהל חבילות להשתמש.

גרסת כלי לניהול חבילות

תמונת זמן הריצה מבוססת על הגרסה האחרונה של yarn ועל הגרסה של npm שזמינה בגרסת ה-LTS האחרונה של Node.js.

אתם יכולים לציין גרסה אחרת של מנהל חבילות לשימוש בקובץ package.json של האפליקציה באמצעות השדה engines. במקרה כזה, סביבת זמן הריצה מוודאת שלמנהל החבילות שמשמש לפריסה יש גרסה שתואמת למפרט שמופיע בשדה engines.

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

בדוגמה הבאה מוגדרת סביבת זמן ריצה לשימוש בגרסה מותאמת אישית של npm:

{
  "engines": {
    "npm": "5.x"
  }
}

בדוגמה הבאה מוגדרת סביבת זמן ריצה לשימוש בגרסה מותאמת אישית של yarn:

{
  "engines": {
    "yarn": ">=1.0.0 <2.0.0"
  }
}

המאפיינים engines.npm ו-engines.yarn יכולים להיות טווח semver.

תלויות

במהלך הפריסה, סביבת זמן הריצה תשתמש במנהל החבילות npm או yarn כדי להתקין יחסי תלות על ידי הפעלת npm install או yarn install. מידע נוסף על האופן שבו זמן הריצה בוחר את מנהל החבילות לשימוש זמין בקטע Package Manager.

בנוסף, מידע נוסף על ניהול חבילות Node.js ב-Google App Engine זמין במאמר שימוש בספריות Node.js.

כדי לאפשר שימוש בחבילות Node.js שנדרשות להן תוספים מקוריים, חבילות Ubuntu הבאות מותקנות מראש בקובץ אימג' של Docker.

  • build-essential
  • ca-certificates
  • curl
  • git
  • imagemagick
  • libkrb5-dev
  • netbase
  • python

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

סקריפט build של NPM

בסביבת זמן הריצה של Node.js גרסה 18 ואילך, סביבת זמן הריצה מפעילה את npm run build אם מזוהה סקריפט build ב-package.json כברירת מחדל. אם אתם צריכים שליטה נוספת בשלבי הבנייה לפני הפעלת האפליקציה, אתם יכולים לספק שלב בנייה בהתאמה אישית על ידי הוספת סקריפט gcp-build לקובץ package.json.

כדי למנוע את הרצת הסקריפט npm run build ב-build, צריך:

  • מוסיפים סקריפט gcp-build עם ערך ריק לקובץ package.json: "gcp-build":"".
  • מוסיפים את משתנה הסביבה של ה-build‏ GOOGLE_NODE_RUN_SCRIPTS עם ערך ריק לקובץ app.yaml.

    build_env_variables:
      GOOGLE_NODE_RUN_SCRIPTS: ''
    
פרטים על הגדרת סביבת build מופיעים בקטע build_env_variables בקובץ app.yaml.

הפעלת האפליקציה

סביבת זמן הריצה מפעילה את האפליקציה באמצעות npm start, שמשתמשת בפקודה שצוינה ב-package.json. לדוגמה:

"scripts": {
  "start": "node app.js"
}

סקריפט ההפעלה צריך להפעיל שרת אינטרנט שמגיב לבקשות HTTP ביציאה שצוינה במשתנה הסביבה PORT, בדרך כלל 8080.

הארכת זמן הריצה

אתם יכולים להשתמש בסביבות ריצה בהתאמה אישית כדי להוסיף פונקציונליות נוספת לאפליקציית Node.js שפועלת בסביבה הגמישה של App Engine. כדי להגדיר סביבת ריצה בהתאמה אישית, מחליפים את השורה הבאה בקובץ app.yaml:

runtime: nodejs

עם השורה הזו:

runtime: custom

צריך גם להוסיף קובצי Dockerfile ו-.dockerignore באותה ספרייה שמכילה את קובץ app.yaml.

במסמכי התיעוד בנושא סביבות זמן ריצה בהתאמה אישית מוסבר איך להגדיר Dockerfile בסביבת זמן ריצה בהתאמה אישית.

פרוטוקול HTTPS ושרתי proxy להעברה

‫App Engine מפסיק את חיבור ה-HTTPS במאזן העומסים ומעביר את הבקשה לאפליקציה. באפליקציות מסוימות צריך לקבוע את כתובת ה-IP והפרוטוקול של הבקשה המקורית. כתובת ה-IP של המשתמש זמינה בכותרת הרגילה X-Forwarded-For. באפליקציות שדורשות את המידע הזה, צריך להגדיר את מסגרת האינטרנט כך שתיתן אמון ב-proxy.

ב-Express.js, משתמשים בהגדרה trust proxy:

app.set('trust proxy', true);

מידע על אכיפת חיבורי HTTPS זמין במאמר איך בקשות מטופלות.

משתני סביבה

משתני הסביבה הבאים מוגדרים על ידי סביבת זמן הריצה:

משתנה הסביבה תיאור
GAE_INSTANCE השם של המופע הנוכחי.
GAE_MEMORY_MB נפח הזיכרון שזמין לתהליך האפליקציה.
GAE_SERVICE שם השירות שצוין בקובץ app.yaml של האפליקציה, או אם לא צוין שם שירות, הוא מוגדר כ-default.
GAE_VERSION תווית הגרסה של האפליקציה הנוכחית.
GOOGLE_CLOUD_PROJECT מזהה הפרויקט שמשויך לאפליקציה, שמופיע במסוף Google Cloud
NODE_ENV כשהאפליקציה שלכם נפרסת, הערך הוא production.
PORT היציאה שתקבל בקשות HTTP. ההגדרה היא 8080.

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

שרת מטא-נתונים

כל מופע של האפליקציה יכול להשתמש בשרת המטא-נתונים של Compute Engine כדי לשלוח שאילתות לגבי המופע, כולל שם המארח, כתובת ה-IP החיצונית, מזהה המופע, מטא-נתונים מותאמים אישית ופרטי חשבון השירות. ב-App Engine אי אפשר להגדיר מטא-נתונים מותאמים אישית לכל מופע, אבל אפשר להגדיר מטא-נתונים מותאמים אישית ברמת הפרויקט ולקרוא אותם מהמופעים של App Engine ו-Compute Engine.

הפונקציה בדוגמה הזו משתמשת בשרת המטא-נתונים כדי לקבל את כתובת ה-IP החיצונית של המופע.

import express from 'express';
import fetch from 'node-fetch';

const app = express();
app.enable('trust proxy');

const METADATA_NETWORK_INTERFACE_URL =
  'http://metadata/computeMetadata/v1/' +
  '/instance/network-interfaces/0/access-configs/0/external-ip';

const getExternalIp = async () => {
  const options = {
    headers: {
      'Metadata-Flavor': 'Google',
    },
    json: true,
  };

  try {
    const response = await fetch(METADATA_NETWORK_INTERFACE_URL, options);
    const ip = await response.json();
    return ip;
  } catch (err) {
    console.log('Error while talking to metadata server, assuming localhost');
    return 'localhost';
  }
};

app.get('/', async (req, res, next) => {
  try {
    const externalIp = await getExternalIp();
    res.status(200).send(`External IP: ${externalIp}`).end();
  } catch (err) {
    next(err);
  }
});

const PORT = parseInt(process.env.PORT) || 8080;
app.listen(PORT, () => {
  console.log(`App listening on port ${PORT}`);
  console.log('Press Ctrl+C to quit.');
});