接下來幾天會進行實測 Cloud Run 的內網的任務,如果你是從 AWS ECS 轉來 Cloud Run,會發現他們很不一樣,在 AWS ECS 上,一個 ECS 是可以對到多個服務的,且一個服務可以應用多個 Container。但是在 Cloud Run,一個 Cloud Run 只對應一個 Service。所以在一個名為 platform 的應用上,我必須分別建立一個前端跟一個後端的 Cloud Run。我自己訂出的 DoD 如下
[ ] 實作前端 Demo
[ ] 實作後端 Demo
[ ] 研究走內網方式1 - internal load balancer 並實作
[ ] 研究走內網方式2 - Private Google access 並實作
[ ] 進行比較,並決議採用哪種方式執行
首先是前端,配合公司的專案且要 SSR 的實踐,我會採用 使用 Next.js 來實作。後端因為不限,我就用 Golang。
以下為前端 Code
import { fetchData } from '../lib/api';
export async function getStaticProps() {
const data = await fetchData();
return {
props: {
data,
},
// Revalidate every hour
revalidate: 3600,
};
}
export default function Home({ data }: { data: any }) {
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
<h1 className="text-2xl font-bold mb-4">Platform</h1>
<div className="bg-gray-100 dark:bg-gray-800 p-4 rounded-lg overflow-auto max-w-full">
<pre className="text-sm">{JSON.stringify(data, null, 2)}</pre>
</div>
{/* Rest of the component remains the same */}
</main>
{/* Footer remains the same */}
</div>
);
}
前端未來要 Call 後端達到 SSR 的 code,這邊會使用 Async 來做到資料獲取。你可以注意到我先寫 localhost 方便本地開發
import axios from 'axios';
const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080',
});
export const fetchData = async () => {
try {
const response = await api.get('/api/data');
return response.data;
} catch (error) {
console.error('Error fetching data:', error);
return null;
}
};
至於後端 Code 則是
package main
import (
"encoding/json"
"log"
"net/http"
"os"
"time"
)
func main() {
http.HandleFunc("/api/data", handleDataRequest)
http.HandleFunc("/", handleDefault)
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Printf("Backend service listening on port %s", port)
log.Fatal(http.ListenAndServe(":"+port, corsMiddleware(logMiddleware(http.DefaultServeMux))))
}
func logMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("Received request: %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("Applying CORS headers for request: %s %s", r.Method, r.URL.Path)
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
func handleDataRequest(w http.ResponseWriter, r *http.Request) {
log.Printf("Handling data request")
response := map[string]interface{}{
"message": "Hello from platform backend service!",
"timestamp": time.Now().Format(time.RFC3339),
"data": map[string]interface{}{
"users": 100,
"active": 75,
"inactive": 25,
},
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(response); err != nil {
log.Printf("Error encoding response: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}
func handleDefault(w http.ResponseWriter, r *http.Request) {
log.Printf("Handling default request for path: %s", r.URL.Path)
if r.URL.Path != "/" {
log.Printf("Path not found: %s", r.URL.Path)
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "application/json")
response := map[string]string{
"message": "Welcome to platform Backend API",
}
if err := json.NewEncoder(w).Encode(response); err != nil {
log.Printf("Error encoding response: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}
在打包成 image 的時候,要特別注意如果你是在 MAC ARM 的環境打包,會造成 image 在 Cloud Run 上跑不起來,以下是踩雷 Code
│ Error: Error waiting to create Service: resource is in failed state "Ready:False", message: Revision '-platform-backend-alvin-00001-lwq' is not ready and cannot serve traffic. The user-provided container failed to start and listen on the port defined provided by the PORT=8080 environment variable. Logs for this revision might contain more information.
│
│ Logs URL: https://console.cloud.google.com/logs/viewer?project=shopeetw&resource=cloud_run_revision/service_name/-platform-backend-alvin/revision_name/-platform-backend-alvin-00001-lwq&advancedFilter=resource.type%3D%22cloud_run_revision%22%0Aresource.labels.service_name%3D%22-platform-backend-alvin%22%0Aresource.labels.revision_name%3D%22-platform-backend-alvin-00001-lwq%22
│ For more troubleshooting guidance, see https://cloud.google.com/run/docs/troubleshooting#container-failed-to-start
│
│ with module.cloud_run_backend.google_cloud_run_service.main,
│ on .terraform/modules/cloud_run_backend/main.tf line 22, in resource "google_cloud_run_service" "main":
│ 22: resource "google_cloud_run_service" "main" {
查看官方相關資訊,發現可以用 gcp cloud build
Verify that you can run your container image locally. If your container image cannot run locally, you need to diagnose and fix the issue locally first.
Check if your container is listening for requests on the expected port as documented in the container runtime contract. Your container must listen for incoming requests on the port that is defined by Cloud Run and provided in the PORT environment variable. See Configuring containers for instructions on how to specify the port.
Check if your container is listening on all network interfaces, commonly denoted as 0.0.0.0.
Verify that your container image is compiled for 64-t Linux as required by the container runtime contract.
Note: If you build your container image on a ARM based machine, then it might not work as expected when used with Cloud Run. To solve this issue, build your image using Cloud Build.
Use Cloud Logging to look for application errors in stdout or stderr logs. You can also look for crashes captured in Error Reporting.
You might need to update your code or your revision settings to fix errors or crashes. You can also troubleshoot your service locally.
這邊有個 Note 特別注意到了,如果是 ARM 要用特別的方式打包
Building using Cloud Build
You can build your image on Google Cloud by using Cloud Build:
Navigate to the folder containing your sources and Dockerfile.
Run the command:
gcloud builds submit --tag IMAGE_URL
Replace IMAGE_URL with a reference to the container image, for example, us-docker.pkg.dev/cloudrun/container/hello:latest. If you use Artifact Registry, the repository REPO_NAME must already be created. The URL has the shape LOCATION-docker.pkg.dev/PROJECT_ID/REPO_NAME/PATH:TAG .
For tips on improving build performance, see Speeding up your builds
詳細的 Dockerfile 跟 Script file 我等這個任務完成再補上 Github 連結。