Physical Address

304 North Cardinal St.
Dorchester Center, MA 02124

GCP 筆記: Hosting a Web App on Google Cloud Using Compute Engine

設定預設的 Zone 並啟用 Compute Engine API。

gcloud config set compute/zone us-central1-f
gcloud services enable compute.googleapis.com

建立 Cloud Storage 貯體 (Bucket)

用來存放程式碼與起始指令 (startup scripts)。其中 $DEVSHELL_PROJECT_ID 是 Cloud Shell 中的環境變數,用來確保有唯一的名稱。

gsutil mb gs://fancy-store-$DEVSHELL_PROJECT_ID

從儲存庫複製原始碼

git clone https://github.com/googlecodelabs/monolith-to-microservices.git

## 安裝服務
cd ~/monolith-to-microservices
./setup.sh

## 開啟 npm 專案
cd microservices
npm start

開啟完專案後,可以在 Cloud Shell 的面板點擊 Web Preview 進行預覽。

此時 Products 跟 Orders 都會顯示 An error has occurred, please try reloading the page. 的錯誤訊息,因為這兩項功能還沒公開。

建立 Compute Engine 實體

建立起始指令碼

點擊 Cloud Shell 的 Open Editor,如果瀏覽器不支援第三方 Cookies 的話,系統會請你開啟新的頁面,點擊 Open in a new window。

前往 monolith-to-microservices 路徑,點擊 [File] > [New File] 建立 startup-script.sh 檔案,貼上下列指令後,將 [DEVSHELL_PROJECT_ID] 改成你的 Devshell Project ID (可以用 echo $DEVSHELL_PROJECT_ID 查詢):

#!/bin/bash


# Install logging monitor. The monitor will automatically pick up logs sent to
# syslog.
curl -s "https://storage.googleapis.com/signals-agents/logging/google-fluentd-install.sh" | bash
service google-fluentd restart &

# Install dependencies from apt
apt-get update
apt-get install -yq ca-certificates git build-essential supervisor psmisc

# Install nodejs
mkdir /opt/nodejs
curl https://nodejs.org/dist/v8.12.0/node-v8.12.0-linux-x64.tar.gz | tar xvzf - -C /opt/nodejs --strip-components=1
ln -s /opt/nodejs/bin/node /usr/bin/node
ln -s /opt/nodejs/bin/npm /usr/bin/npm

# Get the application source code from the Google Cloud Storage bucket.
mkdir /fancy-store
gsutil -m cp -r gs://fancy-store-[DEVSHELL_PROJECT_ID]/monolith-to-microservices/microservices/* /fancy-store/

# Install app dependencies.
cd /fancy-store/
npm install

# Create a nodeapp user. The application will run as this user.
useradd -m -d /home/nodeapp nodeapp
chown -R nodeapp:nodeapp /opt/app

# Configure supervisor to run the node app.
cat >/etc/supervisor/conf.d/node-app.conf << EOF
[program:nodeapp]
directory=/fancy-store
command=npm start
autostart=true
autorestart=true
user=nodeapp
environment=HOME="/home/nodeapp",USER="nodeapp",NODE_ENV="production"
stdout_logfile=syslog
stderr_logfile=syslog
EOF

supervisorctl reread
supervisorctl update

儲存並關閉檔案。

上述指令碼執行的工作:

  1. 安裝 Logging agent,從 syslog 蒐集紀錄。
  2. 安裝 Node.js 和 Supervisor。
  3. 從 Google Cloud Storage 儲存庫複製原始碼,並安裝相依套件。
  4. 調校 Supervisor,讓應用程式在未預期情況下結束時能重新啟動。同時將程式中的 stdoutstderr 傳送至 syslog 中。

利用 gsutil cp 將指令碼複製到貯體 (bucket) 中。

gsutil cp ~/monolith-to-microservices/startup-script.sh \
	gs://fancy-store-$DEVSHELL_PROJECT_ID

將其他程式碼也複製到貯體中。這個步驟會先將 node_modules 中的內容刪除,確保上傳效率。

cd ~
rm -rf monolith-to-microservices/*/node_modules
gsutil -m cp -r monolith-to-microservices gs://fancy-store-$DEVSHELL_PROJECT_ID/

部署後端實體

在此案例中,後端實體的目的用來支援 Products 和 Orders。

實際運作中會建議將 Products 與 Orders 再分開成獨立的實體,但範例中先放一起。

建立 backend 實體,並執行貯體中的起始指令。

gcloud compute instances create backend \
    --machine-type=n1-standard-1 \
    --tags=backend \
   --metadata=startup-script-url=https://storage.googleapis.com/fancy-store-$DEVSHELL_PROJECT_ID/startup-script.sh

檢查目前有的實體。

gcloud compute instances list

## 結果
NAME     ZONE           MACHINE_TYPE   PREEMPTIBLE  INTERNAL_IP  EXTERNAL_IP    STATUS
backend  us-central1-f  n1-standard-1               10.128.0.2   xxx.xxx.xxx.xx RUNNING

複製 EXTERNAL_IP,到 Cloud Shell Explorer (Open Editor 開啟的視窗) 中前往 monolith-to-microservices > react-app 路徑。點選選單中的 View > Toggle Hidden Files 檢視隱藏檔案。

編輯 .env 檔,用 EXTERNAL_IP 取代 localhost。

REACT_APP_ORDERS_URL=http://EXTERNAL_IP:8081/api/orders
REACT_APP_PRODUCTS_URL=http://EXTERNAL_IP:8082/api/products

接著重新建立一次 NPM 專案。

cd ~/monolith-to-microservices/react-app
npm install && npm run-script build

接著再將原始碼複製到 Cloud Storage 的貯體中。

cd ~
rm -rf monolith-to-microservices/*/node_modules
gsutil -m cp -r monolith-to-microservices gs://fancy-store-$DEVSHELL_PROJECT_ID/

部署前端實體

這裡建立 frontend 的標籤,主要目的是設定防火牆規則用。

gcloud compute instances create frontend \
    --machine-type=n1-standard-1 \
    --tags=frontend \
    --metadata=startup-script-url=https://storage.googleapis.com/fancy-store-$DEVSHELL_PROJECT_ID/startup-script.sh

設定網路 (Network)

建立前端與後端的防火牆規則。

gcloud compute firewall-rules create fw-fe \
    --allow tcp:8080 \
    --target-tags=frontend

gcloud compute firewall-rules create fw-be \
    --allow tcp:8081-8082 \
    --target-tags=backend

此時應該已經可以正常存取網站了。用 watch 指令來監看狀態。

watch -n 2 curl http://[FRONTEND_ADDRESS]:8080

此時再透過 frontend 的 EXTERNAL_IP 存取,就能夠看到功能完整的應用程式了。

建立受管控的實體群組 (Managed Instance Groups)

接下來要透過前面建立的 frontend 與 backend 實體,建立實體範本。首先,先停止這兩個實體的服務。

gcloud compute instances stop frontend
gcloud compute instances stop backend

利用 gcloud compute instance-templates 指令建立實體範本。藉由 --source-instance 選項指定作為範本的實體。

gcloud compute instance-templates create fancy-fe \
    --source-instance=frontend
gcloud compute instance-templates create fancy-be \
    --source-instance=backend
# 檢視範本清單
gcloud compute instance-templates list

刪除原本的 backend 實體,釋放一些資源。

gcloud compute instances delete backend

建立 MIG

gcloud compute instance-groups managed create fancy-fe-mig \
    --base-instance-name fancy-fe \
    --size 2 \
    --template fancy-fe
gcloud compute instance-groups managed create fancy-be-mig \
    --base-instance-name fancy-be \
    --size 2 \
    --template fancy-be

base-instance-name 的選項是在命名實體名稱時使用,實體名稱就會是 base-instance-name + 隨機字串。

設定存取服務的連接埠,前端是 8080,後端 Orders 是 8081 與 Products 的 8082。使用 --named-ports 的選項是為了後續做負載平衡用。

gcloud compute instance-groups set-named-ports fancy-fe-mig \
    --named-ports frontend:8080
gcloud compute instance-groups set-named-ports fancy-be-mig \
    --named-ports orders:8081,products:8082

調校自動修復 (Autohealing) 政策

如果連續遇到三次 Unhealthy 時,將啟動自動修復。

gcloud compute health-checks create http fancy-fe-hc \
    --port 8080 \
    --check-interval 30s \
    --healthy-threshold 1 \
    --timeout 10s \
    --unhealthy-threshold 3
gcloud compute health-checks create http fancy-be-hc \
    --port 8081 \
    --request-path=/api/orders \
    --check-interval 30s \
    --healthy-threshold 1 \
    --timeout 10s \
    --unhealthy-threshold 3

接著要設定防火牆規則,允許 Health Check 進行檢查。

gcloud compute firewall-rules create allow-health-check \
    --allow tcp:8080-8081 \
    --source-ranges 130.211.0.0/22,35.191.0.0/16 \
    --network default

將 Health Check 檢查套用到實體群組中。

gcloud compute instance-groups managed update fancy-fe-mig \
    --health-check fancy-fe-hc \
    --initial-delay 300
gcloud compute instance-groups managed update fancy-be-mig \
    --health-check fancy-be-hc \
    --initial-delay 300

建立負載平衡

HTTP(s) 負載平衡的架構

  1. 將要求透過轉送規則導向目標的 HTTP Proxy。
  2. 目標 HTTP Proxy 透過 URL 對應表檢查要求,決定適合的後端服務。
  3. 後端服務將要求,根據實體的狀態、地區與健康狀況,導向適合的後端實體。健康狀況檢查是透過 HTTP 來進行檢查,如果後端服務使用 HTTPS 或 HTTP/2 協定進行檢查,則 HTTP 要求會在後端服務往後端實體的過程中加密。
  4. 在負載平衡器與實體間的工作階段可以透過 HTTP、HTTPS 或 HTTP/2 的方式傳遞。如果使用的是 HTTPS 或 HTTP/2,則後端服務的每個實體都需要有 SSL 憑證。

建立 Health Check 規則

gcloud compute http-health-checks create fancy-fe-frontend-hc \
  --request-path / \
  --port 8080
gcloud compute http-health-checks create fancy-be-orders-hc \
  --request-path /api/orders \
  --port 8081
gcloud compute http-health-checks create fancy-be-products-hc \
  --request-path /api/products \
  --port 8082

建立後端服務

GCP 的後端服務是用來定義如何分配負載平衡流量。其組態包含要使用的連接埠、健康狀態檢查、以及逾時設定等。

gcloud compute backend-services create fancy-fe-frontend \
  --http-health-checks fancy-fe-frontend-hc \
  --port-name frontend \
  --global
gcloud compute backend-services create fancy-be-orders \
  --http-health-checks fancy-be-orders-hc \
  --port-name orders \
  --global
gcloud compute backend-services create fancy-be-products \
  --http-health-checks fancy-be-products-hc \
  --port-name products \
  --global

將後端服務加入實體群組

gcloud compute backend-services add-backend fancy-fe-frontend \
  --instance-group fancy-fe-mig \
  --instance-group-zone us-central1-f \
  --global
gcloud compute backend-services add-backend fancy-be-orders \
  --instance-group fancy-be-mig \
  --instance-group-zone us-central1-f \
  --global
gcloud compute backend-services add-backend fancy-be-products \
  --instance-group fancy-be-mig \
  --instance-group-zone us-central1-f \
  --global

建立 URL 對應

gcloud compute url-maps create fancy-map \
  --default-service fancy-fe-frontend

新增路徑的比對規則到 fancy-map 中。

gcloud compute url-maps add-path-matcher fancy-map \
   --default-service fancy-fe-frontend \
   --path-matcher-name orders \
   --path-rules "/api/orders=fancy-be-orders,/api/products=fancy-be-products"

針對 fancy-map 建立 URL 對應的 Proxy

gcloud compute target-http-proxies create fancy-proxy \
  --url-map fancy-map

設定轉址規則

gcloud compute forwarding-rules create fancy-http-rule \
  --global \
  --target-http-proxy fancy-proxy \
  --ports 80

更新專案組態

利用 gcloud compute forwarding-rules list --global 檢視負載平衡器的外部 IP。

重新編輯 .env 檔案,將 IP 改為外部 IP。

REACT_APP_ORDERS_URL=http://[LB_IP]/api/orders
REACT_APP_PRODUCTS_URL=http://[LB_IP]/api/products

重新建立 NPM 專案,並更新到 Cloud Storage 上。

cd ~/monolith-to-microservices/react-app
npm install && npm run-script build
cd ~
rm -rf monolith-to-microservices/*/node_modules
gsutil -m cp -r monolith-to-microservices gs://fancy-store-$DEVSHELL_PROJECT_ID/

利用 rolling-action 來更新前端的原始碼。

gcloud compute instance-groups managed rolling-action replace fancy-fe-mig \
    --max-unavailable 100%

測試部署狀況

監控前端受控管實體群組 (fancy-fe-mig) 的狀況。

watch -n 2 gcloud compute instance-groups list-instances fancy-fe-mig

監管前端受控管實體群組 (fancy-fe-mig) 後端服務的健康狀況。

watch -n 2 gcloud compute backend-services get-health fancy-fe-frontend --global

等到都顯示 HEALTHY 即代表完成。

縮放 Compute Engine

前面的步驟建置了靜態的架構,無法根據流量大小調整。因此要設定縮放政策 set-autoscaling,根據動態情況來縮放實體。

gcloud compute instance-groups managed set-autoscaling \
  fancy-fe-mig \
  --max-num-replicas 2 \
  --target-load-balancing-utilization 0.60

gcloud compute instance-groups managed set-autoscaling \
  fancy-be-mig \
  --max-num-replicas 2 \
  --target-load-balancing-utilization 0.60

上述的指令中,當負載平衡達到 60% 的效能時,會再建立新的實體,當低於 60% 時,便會移除實體。

啟用 CDN

利用 gcloud compute backend-services update 的方式,更新後端服務的選項設定。加入 --enable-cdn 來啟用 CDN 功能。

gcloud compute backend-services update fancy-fe-frontend \
    --enable-cdn --global

更新網站

變更主機類型

已經建立的實體範本 (Instance Template) 不能修改。

如果要修改範本內的設定,需要建立新的範本,並改用新的範本。

利用 set-machine-type 將主機的類型調整為客製化的 4 vCPU + 3840 Mb 的 RAM。

gcloud compute instances set-machine-type frontend --machine-type custom-4-3840

建立新的實體範本。

gcloud compute instance-templates create fancy-fe-new \
    --source-instance=frontend \
    --source-instance-zone=us-central1-f

利用 rolling-action start-update 將實體更新為新的範本 fancy-fe-new

gcloud compute instance-groups managed rolling-action start-update fancy-fe-mig \
    --version template=fancy-fe-new

監看實體群組的狀態。

watch -n 2 gcloud compute instance-groups managed list-instances fancy-fe-mig

## 結果
NAME           ZONE           STATUS   HEALTH_STATE  ACTION     INSTANCE_TEMPLATE  VERSION_NAME  LAST_ERROR
fancy-fe-gzll  us-central1-f  RUNNING  HEALTHY       VERIFYING  fancy-fe-new
fancy-fe-x2xr  us-central1-f  RUNNING  TIMEOUT       VERIFYING  fancy-fe-new

複製其中一個實體的名稱,並用 describe 來檢查實體的主機類型是否已經變更。

gcloud compute instances describe fancy-fe-x2xr | grep machineTyp

# 結果
machineType: https://www.googleapis.com/compute/v1/projects/qwiklabs-gcp-02-d1a7a4979118/zones/us-central1-f/machineTypes/custom-4-3840

更新網站內容

Lab 中已經準備 index.js.new 檔案,用這個檔案取代原本的 index.js 檔。

cd ~/monolith-to-microservices/react-app/src/pages/Home
mv index.js.new index.js

重新建立 NPM 程式。

cd ~/monolith-to-microservices/react-app
npm install && npm run-script build

整理檔案,並上傳至 Cloud Storage 的貯體中。

cd ~
rm -rf monolith-to-microservices/*/node_modules
gsutil -m cp -r monolith-to-microservices gs://fancy-store-$DEVSHELL_PROJECT_ID/

利用 rolling-action replace 更新實體內容。

gcloud compute instance-groups managed rolling-action replace fancy-fe-mig \
    --max-unavailable=100%

模擬失敗情境

先列出群組中的實體名稱,並建立 SSH 連線。

gcloud compute instance-groups list-instances fancy-fe-mig
gcloud compute ssh [INSTANCE_NAME]

關閉 nodeapp 的服務,並結束 SSH 連線。

sudo supervisorctl stop nodeapp; sudo killall node
exit

監看 gcloud compute operation 執行修復。

watch -n 2 gcloud compute operations list \
--filter='operationType~compute.instances.repair.*'
Eric Chuang
Eric Chuang

正職是廣告行銷人員,因為 Google Tag Manager 的關係開始踏入網站製作的領域,進一步把 WordPress 當成 PHP + HTML + CSS + JavaScript 的學習教材。此外,因為工作的關係,曾經用 Automattic 的 Underscores (_s) 替客戶與公司官網進行全客製化佈景主題開發。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料