הצגת תוכן דינמי ואירוח מיקרו-שירותים באמצעות Cloud Run

צריך להתאים את Cloud Run עם Firebase Hosting כדי ליצור ולהציג את תוכן דינמי או לפתח ממשקי API ל-REST כמיקרו-שירותים.

באמצעות Cloud Run אפשר לפרוס אפליקציה שארוזה בקובץ אימג' בקונטיינר. לאחר מכן, באמצעות Firebase Hosting, אפשר להפנות בקשות HTTPS שיפעילו את באפליקציה בקונטיינרים.

  • Cloud Run תומך במספר שפות (כולל Go,‏ Node.js,‏ Python ו-Java), כך שתוכלו להשתמש בשפת התכנות ובמסגרת התכנות שבחרתם.
  • Cloud Run קנה מידה אוטומטי ואופקי קובץ האימג' של הקונטיינר שיטפל בבקשות שהתקבלו, ואז המערכת תקטין את התמונה כאשר הביקושים יורדים.
  • אתם משלמים רק על השימוש במעבד, בזיכרון וברשתות במהלך הטיפול בבקשות.

לדוגמה, תרחישים לדוגמה ודוגמאות של Cloud Run שמשולבים עם Firebase Hosting, כדאי להיכנס אל סקירה כללית ללא שרת (serverless).


במדריך הזה מוסבר איך:

  1. כתיבת אפליקציה פשוטה של Hello World
  2. ארגון אפליקציה בקונטיינר והעלאה שלה אל Container Registry
  3. פריסת קובץ האימג' של הקונטיינר ב-Cloud Run
  4. הפניית בקשות Hosting לאפליקציה בקונטיינרים

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

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

לפני שמשתמשים ב-Cloud Run, צריך להשלים כמה משימות ראשוניות, כולל הגדרת חשבון Cloud Billing, הפעלת ה-API של Cloud Run והתקנת הכלי של שורת הפקודה gcloud.

הגדרת חיוב לפרויקט

Cloud Run מציע מכסת שימוש בחינם, אבל עדיין צריכה להיות לכם חשבון אחד (Cloud Billing) המשויך לפרויקט Firebase שלך כדי להשתמש בו או לנסות את Cloud Run.

הפעלת ה-API והתקנת ה-SDK

  1. מפעילים את ה-API של Cloud Run במסוף Google APIs:

    1. פותחים את דף API של Cloud Run במסוף Google APIs.

    2. כשמופיעה בקשה, בוחרים את הפרויקט ב-Firebase.

    3. לוחצים על Enable בדף ה-API Cloud Run.

  2. להתקין ולהפעיל את Cloud SDK.

  3. צריך לבדוק שהכלי gcloud מוגדר לפרויקט הנכון:

    gcloud config list

שלב 1: כתיבת האפליקציה לדוגמה

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

Go

  1. יוצרים ספרייה חדשה בשם helloworld-go ואז משנים את הספרייה ל זה:

    mkdir helloworld-go
    cd helloworld-go
  2. יוצרים קובץ חדש בשם helloworld.go ואז מוסיפים את הקוד הבא:

    package main
    
    import (
    	"fmt"
    	"log"
    	"net/http"
    	"os"
    )
    
    func handler(w http.ResponseWriter, r *http.Request) {
    	log.Print("helloworld: received a request")
    	target := os.Getenv("TARGET")
    	if target == "" {
    		target = "World"
    	}
    	fmt.Fprintf(w, "Hello %s!\n", target)
    }
    
    func main() {
    	log.Print("helloworld: starting server...")
    
    	http.HandleFunc("/", handler)
    
    	port := os.Getenv("PORT")
    	if port == "" {
    		port = "8080"
    	}
    
    	log.Printf("helloworld: listening on port %s", port)
    	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
    }
    

    הקוד הזה יוצר שרת אינטרנט בסיסי שמקשיב ביציאה שמוגדרת על ידי משתנה הסביבה PORT.

האפליקציה מוכנה ואפשר להעלות אותה לקונטיינרים Container Registry

Node.js

  1. יוצרים ספרייה חדשה בשם helloworld-nodejs ואז משנים את הספרייה לתוכו:

    mkdir helloworld-nodejs
    cd helloworld-nodejs
  2. יוצרים קובץ package.json עם התוכן הבא:

    {
      "name": "knative-serving-helloworld",
      "version": "1.0.0",
      "description": "Simple hello world sample in Node",
      "main": "index.js",
      "scripts": {
        "start": "node index.js"
      },
      "author": "",
      "license": "Apache-2.0",
      "dependencies": {
        "express": "^4.20.0"
      }
    }
    
  3. יוצרים קובץ חדש בשם index.js ומוסיפים את הקוד הבא:

    const express = require('express');
    const app = express();
    
    app.get('/', (req, res) => {
      console.log('Hello world received a request.');
    
      const target = process.env.TARGET || 'World';
      res.send(`Hello ${target}!\n`);
    });
    
    const port = process.env.PORT || 8080;
    app.listen(port, () => {
      console.log('Hello world listening on port', port);
    });
    

    הקוד הזה יוצר שרת אינטרנט בסיסי שמאזינים ליציאה שהוגדרה על ידי משתנה הסביבה PORT.

האפליקציה מוכנה ואפשר להעלות אותה לקונטיינרים Container Registry

Python

  1. יוצרים ספרייה חדשה בשם helloworld-python ואז משנים את הספרייה לתוכו:

    mkdir helloworld-python
    cd helloworld-python
  2. יוצרים קובץ חדש בשם app.py ואז מוסיפים את הקוד הבא:

    import os
    
    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route('/')
    def hello_world():
        target = os.environ.get('TARGET', 'World')
        return 'Hello {}!\n'.format(target)
    
    if __name__ == "__main__":
        app.run(debug=True,host='0.0.0.0',port=int(os.environ.get('PORT', 8080)))
    

    הקוד הזה יוצר שרת אינטרנט בסיסי שמאזינים ליציאה שהוגדרה על ידי משתנה הסביבה PORT.

האפליקציה מוכנה ואפשר להעלות אותה לקונטיינרים Container Registry

Java

  1. התקנה Java SE 8 ואילך JDK ו-CURL.

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

  2. במסוף, יוצרים פרויקט אינטרנט ריק חדש באמצעות cURL, ואז פקודות של unzip:

    curl https://start.spring.io/starter.zip \
        -d dependencies=web \
        -d name=helloworld \
        -d artifactId=helloworld \
        -o helloworld.zip
    unzip helloworld.zip

    הפעולה הזו יוצרת פרויקט SpringBoot.

  3. עדכון הכיתה SpringBootApplication ב- src/main/java/com/example/helloworld/HelloworldApplication.java על ידי הוספת @RestController כדי לטפל במיפוי של / וגם להוסיף השדה @Value כדי לספק את משתנה הסביבה TARGET:

    package com.example.helloworld;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @SpringBootApplication
    public class HelloworldApplication {
    
      @Value("${TARGET:World}")
      String target;
    
      @RestController
      class HelloworldController {
        @GetMapping("/")
        String hello() {
          return "Hello " + target + "!";
        }
      }
    
      public static void main(String[] args) {
        SpringApplication.run(HelloworldApplication.class, args);
      }
    }
    

    הקוד הזה יוצר שרת אינטרנט בסיסי שמאזינים ליציאה שהוגדרה על ידי משתנה הסביבה PORT.

האפליקציה מוכנה והיא מוכנה ליצירת קונטיינרים והעלאתה אל Container Registry

שלב 2: הכנסת אפליקציה לקונטיינר והעלאה שלה אל Container Registry

  1. ליצור קונטיינרים של האפליקציה לדוגמה על ידי יצירת קובץ חדש בשם Dockerfile לאותה ספרייה כמו קובצי המקור. מעתיקים את התוכן הבא לקובץ.

    Go

    # Use the official Golang image to create a build artifact.
    # This is based on Debian and sets the GOPATH to /go.
    FROM golang:latest as builder
    
    ARG TARGETOS
    ARG TARGETARCH
    
    # Create and change to the app directory.
    WORKDIR /app
    
    # Retrieve application dependencies using go modules.
    # Allows container builds to reuse downloaded dependencies.
    COPY go.* ./
    RUN go mod download
    
    # Copy local code to the container image.
    COPY . ./
    
    # Build the binary.
    # -mod=readonly ensures immutable go.mod and go.sum in container builds.
    RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -mod=readonly -v -o server
    
    # Use the official Alpine image for a lean production container.
    # https://hub.docker.com/_/alpine
    # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
    FROM alpine:3
    RUN apk add --no-cache ca-certificates
    
    # Copy the binary to the production image from the builder stage.
    COPY --from=builder /app/server /server
    
    # Run the web service on container startup.
    CMD ["/server"]
    

    Node.js

    # Use the official lightweight Node.js 12 image.
    # https://hub.docker.com/_/node
    FROM node:12-slim
    
    # Create and change to the app directory.
    WORKDIR /usr/src/app
    
    # Copy application dependency manifests to the container image.
    # A wildcard is used to ensure both package.json AND package-lock.json are copied.
    # Copying this separately prevents re-running npm install on every code change.
    COPY package*.json ./
    
    # Install production dependencies.
    RUN npm install --only=production
    
    # Copy local code to the container image.
    COPY . ./
    
    # Run the web service on container startup.
    CMD [ "npm", "start" ]
    

    Python

    # Use the official lightweight Python image.
    # https://hub.docker.com/_/python
    FROM python:3.7-slim
    
    # Allow statements and log messages to immediately appear in the Knative logs
    ENV PYTHONUNBUFFERED True
    
    # Copy local code to the container image.
    ENV APP_HOME /app
    WORKDIR $APP_HOME
    COPY . ./
    
    # Install production dependencies.
    RUN pip install Flask gunicorn
    
    # Run the web service on container startup. Here we use the gunicorn
    # webserver, with one worker process and 8 threads.
    # For environments with multiple CPU cores, increase the number of workers
    # to be equal to the cores available.
    CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 app:app
    

    Java

    # Use the official maven/Java 8 image to create a build artifact: https://hub.docker.com/_/maven
    FROM maven:3.5-jdk-8-alpine as builder
    
    # Copy local code to the container image.
    WORKDIR /app
    COPY pom.xml .
    COPY src ./src
    
    # Build a release artifact.
    RUN mvn package -DskipTests
    
    # Use the Official OpenJDK image for a lean production stage of our multi-stage build.
    # https://hub.docker.com/_/openjdk
    # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
    FROM openjdk:8-jre-alpine
    
    # Copy the jar to the production image from the builder stage.
    COPY --from=builder /app/target/helloworld-*.jar /helloworld.jar
    
    # Run the web service on container startup.
    CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/helloworld.jar"]
    

  2. צריך ליצור קובץ אימג' בקונטיינר באמצעות Cloud Build על ידי הרצת הפקודה הבאה מהספרייה שמכילה את קובץ ה-Docker:

    gcloud builds submit --tag gcr.io/PROJECT_ID/helloworld

    אחרי השלמת הפעולה, תופיע הודעת 'הצלחה' שמכילה את שם התמונה
    (gcr.io/PROJECT_ID/helloworld).

תמונת הקונטיינר מאוחסנת עכשיו ב-Container Registry ואפשר להשתמש בה שוב אם הרצויה.

לתשומת ליבך: במקום Cloud Build, אפשר להשתמש בגרסה מותקנת באופן מקומי מ-Docker מפתחים את הקונטיינר באופן מקומי.

שלב 3: פורסים את קובץ האימג' של הקונטיינר ב-Cloud Run

  1. לפרוס באמצעות הפקודה הבאה:

    gcloud run deploy --image gcr.io/PROJECT_ID/helloworld

  2. כשמוצגת ההודעה:

כדי לקבל את הביצועים הטובים ביותר, צריך לקשר את שירות Cloud Run עם Hosting באזורים הבאים:

  • us-west1
  • us-central1
  • us-east1
  • europe-west1
  • asia-east1

אפשר לכתוב מחדש ב-Cloud Run מ-Hosting באזורים הבאים:

  • asia-east1
  • asia-east2
  • asia-northeast1
  • asia-northeast2
  • asia-northeast3
  • asia-south1
  • asia-south2
  • asia-southeast1
  • asia-southeast2
  • australia-southeast1
  • australia-southeast2
  • europe-central2
  • europe-north1
  • europe-southwest1
  • europe-west1
  • europe-west12
  • europe-west2
  • europe-west3
  • europe-west4
  • europe-west6
  • europe-west8
  • europe-west9
  • me-central1
  • me-west1
  • northamerica-northeast1
  • northamerica-northeast2
  • southamerica-east1
  • southamerica-west1
  • us-central1
  • us-east1
  • us-east4
  • us-east5
  • us-south1
  • us-west1
  • us-west2
  • us-west3
  • us-west4
  • us-west1
  • us-central1
  • us-east1
  • europe-west1
  • asia-east1
  1. ממתינים כמה רגעים עד שהפריסה תסתיים. לאחר הצלחה, שורת הפקודה מציג את כתובת ה-URL של השירות. לדוגמה: https://helloworld-RANDOM_HASH-us-central1.a.run.app

  2. כדי להיכנס למאגר התגים שנפרס, פותחים את כתובת ה-URL של השירות בדפדפן אינטרנט.

השלב הבא ינחה אותך איך לגשת לאפליקציה הזו בקונטיינר מ בכתובת URL אחת (Firebase Hosting), כדי שהיא תוכל ליצור תוכן דינמי אתר שמתארח ב-Firebase.

שלב 4: הפניית בקשות אירוח לאפליקציה בקונטיינרים

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

בדוגמה הבאה מוסבר איך להפנות את כל הבקשות מהדף /helloworld באתר Hosting כדי להפעיל את המכונה של הקונטיינר helloworld ולהריץ אותה.

  1. מוודאים שהתנאים הבאים מתקיימים:

    לקבלת הוראות מפורטות להתקנת ה-CLI ולאתחול Hosting, מומלץ לעיין מדריך לתחילת העבודה עבור Hosting.

  2. פותחים את קובץ firebase.json.

  3. מוסיפים את ההגדרות הבאות של rewrite בקטע hosting:

    "hosting": {
      // ...
    
      // Add the "rewrites" attribute within "hosting"
      "rewrites": [ {
        "source": "/helloworld",
        "run": {
          "serviceId": "helloworld",  // "service name" (from when you deployed the container image)
          "region": "us-central1",    // optional (if omitted, default is us-central1)
          "pinTag": true              // optional (see note below)
        }
      } ]
    }
  4. פורסים את תצורת האירוח באתר באמצעות הפקודה הבאה מהרמה הבסיסית (root) של ספריית הפרויקט:

    firebase deploy --only hosting

ניתן להגיע למאגר התגים דרך כתובות ה-URL הבאות:

  • תת-הדומיינים שלך ב-Firebase:
    PROJECT_ID.web.app/ והקבוצה PROJECT_ID.firebaseapp.com/

  • כל דומיין מותאם אישית מקושר:
    CUSTOM_DOMAIN/

בדף ההגדרות של Hosting תוכלו לקרוא פרטים נוספים על כללי הכתיבה מחדש. אפשר ללמוד גם על סדר העדיפות של התשובות לתצורות שונות של Hosting.

בדיקה מקומית

במהלך הפיתוח, תוכלו להריץ ולבדוק את קובץ האימג' של הקונטיינר באופן מקומי. עבור להוראות מפורטות, מסמכי תיעוד של Cloud Run.

השלבים הבאים