סביבת זמן הריצה של 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 במקום זאת.
  • רק בגרסה 18 ואילך של Node.js, אם קובץ 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. בקטע מנהל החבילות מוסבר איך סביבת זמן הריצה בוחרת את מנהל החבילות שבו היא תשתמש.

בנוסף, מידע נוסף על ניהול חבילות 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.');
});