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

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

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

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


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

  1. כתיבת אפליקציה פשוטה מסוג Hello World
  2. ארגון אפליקציה בקונטיינר והעלאה שלה אל Artifact 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.

האפליקציה מוכנה להעלאה ל-Artifact 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.21.1"
      }
    }
    
  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.

האפליקציה מוכנה להעלאה ל-Artifact 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.

האפליקציה מוכנה להעלאה ל-Artifact Registry בקונטיינר.

Java

  1. מתקינים את JDK של Java SE 8 ואילך ואת CURL.

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

  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.

האפליקציה מוכנה להעלאה ל-Artifact Registry בקונטיינר.

שלב 2: הכנסת אפליקציה לקונטיינר והעלאה שלה אל Artifact 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
    
    # Copy local code to the container image.
    COPY . ./
    
    # Install dependencies and tidy up the go.mod and go.sum files.
    RUN go mod tidy
    
    # 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, מריצים את הפקודה הבאה מהספרייה שמכילה את קובץ Dockerfile:

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

    אם הפעולה תתבצע בהצלחה, תוצג הודעה על הצלחה עם שם התמונה
    (gcr.io/PROJECT_ID/helloworld).

קובץ האימג' של הקונטיינר נשמר עכשיו ב-Artifact Registry וניתן לעשות בו שימוש חוזר אם רוצים.

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

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

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

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

  2. כשמופיעה בקשה:

  3. ממתינים כמה רגעים עד שהפריסה תושלם. אם הפעולה מסתיימת בהצלחה, כתובת ה-URL של השירות תוצג בשורת הפקודה. לדוגמה: https://helloworld-RANDOM_HASH-us-central1.a.run.app

  4. כדי להיכנס לקונטיינר שנפרס, פותחים את כתובת ה-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. כדי לפרוס את הגדרות האירוח באתר, מריצים את הפקודה הבאה מהשורש של תיקיית הפרויקט:

    firebase deploy --only hosting

עכשיו אפשר לגשת לקונטיינר דרך כתובות ה-URL הבאות:

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

בדיקה מקומית

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

השלבים הבאים