單元 1:從 App Engine web2 遷移至 Flask

1. 總覽

本系列的程式碼研究室 (自助教學和實作教學課程) 旨在引導 Google App Engine (Standard) 開發人員透過一系列遷移作業,協助他們翻新自家應用程式。最重要的步驟就是捨棄原始執行階段套裝組合服務,因為新一代執行階段更有彈性,為使用者提供更多種服務選項。轉換至新一代的執行階段可讓您更輕鬆地整合 Google Cloud 產品、使用更多元的支援服務,以及支援目前的語言版本。

這個初始教學課程說明在 App Engine 應用程式中翻新網路架構的第一步:從 webapp2 遷移至 Flask。在應用程式中,您可以使用任何處理轉送的網路架構,但在本教學課程中,我們使用的是社群廣泛採用的 Flask。

你將瞭解如何

  • 使用第三方程式庫 (內建或其他方式)
  • 更新設定檔
  • 將簡易應用程式從 webapp2 遷移至 Flask

軟硬體需求

問卷調查

您會如何使用本程式碼研究室?

只能閱讀 閱讀並完成練習

2. 背景

當 App Engine 在 2008 年首次推出 Python 2.5 時,隨附 webapp 架構。當年後,當 2.7 執行階段在 2013 年淘汰 2.5 時,該變數已由 webapp2 取代。

雖然 webapp2 (請參閱說明文件) 仍然存在,可在 App Engine 以外的地方使用,做為符合 WSGI 規定的網路架構,但其無法將使用者要求轉送至應用程式中的適當程式碼。相反地,它將依賴 App Engine、設定檔和開發人員來執行該網路流量,將網路流量轉送至對應的「處理常式」。此外,webapp2 的核心優勢與 App Engine 的套裝組合服務極為重要,即使 它可在 Python 3 中運作,也有效淘汰它 (另請參閱相關問題)。

本單元提供實際操作經驗,幫助使用者實際將簡易的 webapp2 應用程式遷移至 Flask。Flask 是一種由 App Engine 支援的架構,以及 Google Cloud 之外的許多服務,可讓應用程式更具可攜性。如果 Flask 不適合將「您的」應用程式移至目標架構,您可以選用其他架構,只要其有轉送即可。本程式碼研究室會向資訊技術決策者 (ITDM) 和開發人員說明遷移步驟,因此無論您實際遷移至哪個架構,都能熟悉這項程序。

以下是這項遷移作業的主要步驟:

  1. 設定/事前作業
  2. 新增 Flask 第三方程式庫
  3. 更新應用程式檔案
  4. 更新 HTML 範本檔案

3. 設定/事前作業

在開始教學課程的主要部分前,讓我們先設定專案、取得程式碼,然後 (以便) 熟悉 gcloud 指令並部署基準應用程式,這樣我們就能知道已開始使用有效的程式碼。

1. 設定專案

現有開發人員的 App Engine 資訊主頁可能已顯示您正在執行的服務。為符合本教學課程的目的,建議您建立新的專案,或在本教學課程中重複使用現有專案。請確認專案具備有效的帳單帳戶,且 App Engine (應用程式) 已啟用。

2. 下載基準範例應用程式

GAE 遷移存放區包含您需要的所有程式碼。複製或下載其 ZIP 檔案。在本教學課程中,您會從模組 0 資料夾 (START) 中的程式碼開始著手;完成教學課程後,您的程式碼應與模組 1 資料夾 (FINISH) 相符。如果不是,請查看兩者差異,以便繼續進入下一個研究室。

模組 0 資料夾中的檔案應如下所示,如 POSIX ls 指令所示:

$ ls
app.yaml        index.html      main.py

3. (請回到先前的步驟) 使用 gcloud 指令熟悉方法

如果電腦上還沒有 gcloud 指令,請安裝 Google Cloud SDK,並確保 gcloud 是做為執行路徑的一部分,並熟悉下列 gcloud 指令:

  1. gcloud components update - 更新 Google Cloud SDK
  2. gcloud auth login - 登入已認證的帳戶
  3. gcloud config list:列出 GCP 專案的配置設定
  4. gcloud config set project PROJECT_ID:設定 GCP 專案 ID
  5. gcloud app deploy:部署您的 App Engine 應用程式

如果您最近未曾使用 gcloud 進行 App Engine 開發,請先執行前四個指令 (#1-#4) 完成設定,再繼續進行後續步驟。以下是這些指令的快速總覽。

首先,gcloud components update 可確保您擁有最新版本的 Cloud SDK。執行這個指令應會顯示如下的輸出內容:

$ gcloud components update

Your current Cloud SDK version is: 317.0.0
You will be upgraded to version: 318.0.0

┌──────────────────────────────────────────────────┐
│        These components will be updated.         │
├──────────────────────────┬────────────┬──────────┤
│           Name           │  Version   │   Size   │
├──────────────────────────┼────────────┼──────────┤
│ Cloud SDK Core Libraries │ 2020.11.06 │ 15.5 MiB │
│ gcloud cli dependencies  │ 2020.11.06 │ 10.6 MiB │
└──────────────────────────┴────────────┴──────────┘

The following release notes are new in this upgrade.
Please read carefully for information about new features, breaking changes,
and bugs fixed.  The latest full release notes can be viewed at:
  https://cloud.google.com/sdk/release_notes

318.0.0 (2020-11-10)

      . . .
      (release notes)
      . . .

    Subscribe to these release notes at
    https://groups.google.com/forum/#!forum/google-cloud-sdk-announce.

Do you want to continue (Y/n)?

╔════════════════════════════════════════════════════════════╗
╠═ Creating update staging area                             ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Uninstalling: Cloud SDK Core Libraries                   ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Uninstalling: gcloud cli dependencies                    ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: Cloud SDK Core Libraries                     ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Installing: gcloud cli dependencies                      ═╣
╠════════════════════════════════════════════════════════════╣
╠═ Creating backup and activating new installation          ═╣
╚════════════════════════════════════════════════════════════╝

Performing post processing steps...done.

Update done!

To revert your SDK to the previously installed version, you may run:
  $ gcloud components update --version 317.0.0

接下來,請使用 gcloud auth login 驗證您即將發出的 gcloud 指令:

$ gcloud auth login
Your browser has been opened to visit:

    https://accounts.google.com/o/oauth2/auth?response_type=code&client_id= . . .

You are now logged in as [YOUR_EMAIL].
Your current project is [PROJECT_ID].  You can change this setting by running:
  $ gcloud config set project PROJECT_ID

使用「gcloud config list」查看目前的專案設定:

$ gcloud config list
[core]
account = YOUR_EMAIL
disable_usage_reporting = False
project = PROJECT_ID

Your active configuration is: [default]

上述指令應引導您建立新專案或選取現有專案。如果 gcloud config list 的輸出內容與您要用於本教學課程的所選專案不符,請執行 gcloud config set project PROJECT_ID 來設定專案 ID。接著再次執行 gcloud config list,確認已設定正確的專案 ID。

$ gcloud config set project PROJECT_ID
Updated property [core/project].

如果您偏好使用 Cloud 控制台,可以按照使用者介面上的指示建立新專案,或是直接使用現有的專案。您專案資訊主頁應會顯示專案資訊卡,當中包含專案 ID (以及專案名稱和編號):

專案資訊卡

最後一個指令 (#5) gcloud app deploy 是將應用程式部署至 App Engine。由於我們才剛啟動,現在可以選擇是否要執行,但我們也不鼓勵部署模組 0 的程式碼來確認能否正常運作。執行完成後,請選取執行應用程式所在的地理區域 (通常是您的所在地)。不過,代碼設定後即無法變更。然後觀看其餘的部署作業資訊。完成後,我們會通知您應用程式提供服務的網址。下圖為您可能看到的摘要版本:

$ gcloud app deploy
Services to deploy:

descriptor:      [/private/tmp/mod0-baseline/app.yaml]
source:          [/private/tmp/mod0-baseline]
target project:  [PROJECT_ID]
target service:  [default]
target version:  [20201116t220827]
target url:      [https://PROJECT_ID.REG_ABBR.r.appspot.com]


Do you want to continue (Y/n)?

Beginning deployment of service [default]...
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 1 file to Google Cloud Storage                 ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [default]...done.
Setting traffic split for service [default]...done.
Deployed service [default] to [https://PROJECT_ID.REG_ABBR.r.appspot.com]

You can stream logs from the command line by running:
  $ gcloud app logs tail -s default

To view your application in the web browser run:
  $ gcloud app browse

如果您有一段時間未使用 App Engine,可能會發現原始部署的 appcfg.py update 指令已由 gcloud app deploy 取代。如要進一步瞭解 gcloud app deploy,請參閱說明文件頁面

另一項近期異動是已部署的應用程式URL,已從 http://PROJECT_ID.appspot.com 改為 http://PROJECT_ID.REG_ABBR.r.appspot.com。最後,大多數應用程式都會轉換成新格式。如要進一步瞭解網址格式,請參閱要求和轉送說明文件

應用程式部署完成後,請重新整理瀏覽器 (可能執行幾次),以查看最近的造訪記錄:

Visitme 應用程式

如果是全新應用程式,則只會看到一或幾次造訪。

4. 新增 Flask 第三方程式庫

Python 2 App Engine 執行階段提供了一組「內建」您需要在 app.yaml 檔案中指定要使用的第三方程式庫。雖然這種遷移作業不需使用,但將會是在下個遷移教學課程 (單元 2 的說明) 中操作。

「不是」內建內建程式庫的第三方程式庫必須在名為 requirements.txt 的檔案中指定,然後安裝在本機的 lib 資料夾中,所有內容會上傳至 App Engine。詳情請參閱合併第三方程式庫的說明文件

如果您複製了 Flask 等程式庫,就必須要求 App Engine 使用 appengine_config.py 設定檔在 lib 資料夾中尋找這些程式庫。appengine_config.py 設定檔位於與 requirements.txtlib 相同的頂層應用程式資料夾中。在這個部分,您將進行以下作業:

  • 建立 requirements.txt (指定已複製 [非內建] 的第三方程式庫)
  • 建立 appengine_config.py (識別第三方程式庫)
  • 安裝 (第三方) 套件和依附元件

1. 建立「requirements.txt

建立 requirements.txt 檔案以指定套件。在本範例中,Flask 是所需的第三方程式庫。撰寫本文時,最新版本為 1.1.2 版,因此請按照下列這一行建立 requirements.txt

Flask==1.1.2

如要進一步瞭解系統接受的格式,請參閱 requirements.txt 說明文件

2. 建立「appengine_config.py

下一步是讓 App Engine 識別外部第三方程式庫。建立名為 appengine_config.py 的檔案,並在當中加入下列內容:

from google.appengine.ext import vendor

# Set PATH to your libraries folder.
PATH = 'lib'
# Add libraries installed in the PATH folder.
vendor.add(PATH)

這個程式碼會完全符合我們先前指定的內容,也就是將 App Engine 指向要複製的程式庫的 lib 資料夾。

3. 安裝套件和依附元件

接著執行 pip install 指令來建立 lib 資料夾,然後在該資料夾安裝 Flask 及其依附元件:

$ pip install -t lib -r requirements.txt

無論您使用的是 pip 還是 pip2,套件安裝完成後都應有一個 lib 資料夾,內含類似以下的內容:

$ ls lib
bin/
click/
click-7.1.2.dist-info/
flask/
Flask-1.1.2.dist-info/
itsdangerous/
itsdangerous-1.1.0.dist-info/
jinja2/
Jinja2-2.11.2.dist-info/
markupsafe/
MarkupSafe-1.1.1.dist-info/
werkzeug/
Werkzeug-1.0.1.dist-info/

5. 更新應用程式檔案

現在讓我們更新應用程式檔案 main.py

1. 匯入

匯入作業會首先跟所有 Python 檔案一樣。webapp2 架構匯入後面接有 ndb Datastore 程式庫,最後是處理 Django 變種範本的 App Engine 擴充功能。畫面應顯示如下:

  • 更新前:
import webapp2
from google.appengine.ext import ndb
from google.appengine.ext.webapp import template

移至 Flask 時,您必須同時匯入 Flask 和範本轉譯器項目。請刪除一對 webapp2 相關匯入項目,並依照下列方式替換 (保留 ndb 匯入內容):

  • 更新後:
from flask import Flask, render_template, request
from google.appengine.ext import ndb

2. 啟動

使用 webapp2 的應用程式需要單一陣列 (Python 清單),當中會列出任何 Python 檔案中的所有路徑和處理常式 (可能還有其他陣列):

  • 更新前:
app = webapp2.WSGIApplication([
    ('/', MainHandler),
], debug=True)

請注意,app.yaml 會執行較高層級的轉送作業,且可能會呼叫不同的處理常式。範例應用程式非常簡單,所有路徑都會傳送至 main.py 處理常式。

Flask 不會使用這類路徑資料表,因此請在 main.py刪除這些行。Flask 也需要初始化,因此請在匯入作業下方,在 main.py頂端 新增下列程式碼:

  • 更新後:
app = Flask(__name__)

在 Flask 中將架構初始化,然後使用裝飾器定義路徑。此外,路徑會與函式配對,而非類別或方法。

這個程式碼研究室涵蓋 Flask 教學課程,但這個做法不在涵蓋範圍內,因此請花點時間瀏覽 Flask 教學課程,並參閱 Flask 說明文件,進一步瞭解這個架構。

3. 資料模型

這裡沒有任何變更。Datastore 是下一個程式碼研究室的重點。

4. 處理常式

無論您使用哪種架構 (webapp2 或 Flask),應用程式會執行以下 3 件事:

  1. 處理根路徑 (/) GET 要求
  2. 註冊網頁「造訪」(建立/儲存 Visit 物件)
  3. 顯示最近 10 次的造訪 (使用預先定義的範本:index.html)

webapp2 架構採用類別式執行模型,其中每個支援的 HTTP 方法都會建立處理常式。在這個簡易案例中,我們只有 GET,因此定義了 get() 方法:

  • 更新前:
class MainHandler(webapp2.RequestHandler):
    def get(self):
        store_visit(self.request.remote_addr, self.request.user_agent)
        visits = fetch_visits(10) or ()  # empty sequence if None
        tmpl = os.path.join(os.path.dirname(__file__), 'index.html')
        self.response.out.write(template.render(tmpl, {'visits': visits}))

如上所述,Flask 會執行自身的轉送路徑。您不是處理常式類別,而是可以編寫函式,並以應呼叫的路徑加以裝飾。使用者可以在裝飾器呼叫中指定 HTTP 方法,例如@app.route('/app/', methods=['GET', 'POST'])。由於預設值為 GET (且默示為 HEAD),因此可以保持關閉。

遷移至 Flask 時,請將 MainHandler 類別及其 get() 方法替換為以下 Flask 轉送函式:

  • 更新後:
@app.route('/')
def root():
    store_visit(request.remote_addr, request.user_agent)
    visits = fetch_visits(10) or ()  # empty sequence if None
    return render_template('index.html', visits=visits)

當然,這並不能代表您的應用程式,絕對比這個範例更複雜。這些教學課程的主要目標之一,就是協助您踏出第一步,建立一些「肌肉記憶」以及如何在 App Engine 專屬程式碼中進行變更。為了確保這項變更正確,請比較您的與單元 1 main.py

5. 輔助檔案

.gcloudignore 檔案沒有任何變更。其目的是指定要部署及執行應用程式不部署至 App Engine 的檔案,包括但不限於輔助 Python、原始碼控制、存放區樣板和其他檔案。我們的 .gcloudignore 看起來會像這樣 (為求簡潔,已移除留言):

.gcloudignore
.git
.gitignore
.hgignore
.hg/
*.pyc
*.pyo
__pycache__/
/setup.cfg
README.md

6. 更新 HTML 範本檔案

1. 移動範本檔案

在基準存放區資料夾 (模組 0) 中,index.html 範本檔案與應用程式檔案位於相同的資料夾中。Flask 需要放在 templates 資料夾中的 HTML 檔案,因此您必須建立該資料夾 (mkdir templates),並將 index.html 移入該資料夾。在支援 POSIX 的系統 (例如 Linux 或 Mac OS X) 中,指令應如下所示:

mkdir templates
mv index.html templates

2. 更新範本檔案

index.html 移至 templates 後,即可進行必要的小幅編輯。讓我們看看原始範本檔案的完整內容:

<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
<body>

<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
    <li>{{ visit.timestamp.ctime }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>

</body>
</html>

webapp2 使用 Django 範本執行 visit.timestamp.ctime 等呼叫項目,其中「不含」括號 ( ),Jinja2 則需要明確定義這些範本。這聽起來有點小幅調整,但 Jinja 範本是立即可用的功能,因為您可以在呼叫中傳遞引數。

在 Django 中,您必須建立「範本標記」也可以編寫篩選器如已瞭解,請在 visit.timestamp.ctime 呼叫中加入一對括號來更新 index.html

  • 更新前:
<li>{{ visit.timestamp.ctime }} from {{ visit.visitor }}</li>
  • 更新後:
<li>{{ visit.timestamp.ctime() }} from {{ visit.visitor }}</li>

這是唯一必要的變更。所有其餘的遷移程式碼研究室都不需要對 index.html 進行其他變更。

7. 摘要/清除

部署應用程式

完成本教學課程中的所有變更後,應用程式資料夾中的檔案應與模組 1 存放區資料夾中的檔案相同 (或幾乎如此)。現在部署並確定模組 1 Flask 應用程式的執行方式與模組 0 webapp2 版本相同。

使用 gcloud app deploy 指令,方法與先前部署原始模組 0 程式碼時相同。透過網路瀏覽器、curlwget 指令存取 PROJECT_ID.appspot.com 中的應用程式,確認應用程式可正常運作。

如果發生伺服器錯誤,通常意味著 Python 程式碼出現某種錯誤。請查看您的應用程式記錄檔進行調查。此外,也請比較您的檔案與模組 1 存放區 (上方連結) 中的檔案。

選用:清除

在準備進入下一個遷移程式碼研究室之前,您該如何清除所用資源,以免產生費用?身為現有開發人員,您可能已經能快速瞭解 App Engine 的定價資訊

選用:停用應用程式

如果尚未準備好進行下一個教學課程,請停用應用程式,以免產生費用。當您準備好前往下一個程式碼研究室時,即可重新啟用。停用的應用程式不會獲得任何流量,不過如果用量超過免費配額,我們就會向您收取 Datastore 用量的費用,因此刪除費用就會低於配額上限。

另一方面,如果不想繼續進行遷移作業,想要完全刪除所有項目,請關閉專案

後續步驟

遷移模組有兩個從完成的單元 1 程式碼、單元 2 和 7 開始:

  • 模組 2 (使用 Datastore 時為必要)
    • 從 App Engine ndb 遷移至 Cloud NDB
    • 切換至 Cloud NDB 後,還有許多其他選項可用
      • 將應用程式容器化,以便在 Cloud Run 中執行
      • 將應用程式進一步遷移至 Cloud Datastore 用戶端程式庫
      • 將應用程式遷移至 Cloud Firestore,以便存取 Firebase 功能
  • 模組 7 (使用 [push] 工作佇列時為必要)
    • 新增 App Engine (推送) taskqueue 用量
    • 準備模組 1 應用程式,以便遷移至單元 8 中的 Cloud Tasks

8. 其他資源

App Engine 遷移模組程式碼研究室問題/意見回饋

如果您在本程式碼研究室中發現任何問題,請先搜尋您的問題再提出申請。搜尋及建立新問題的連結:

遷移資源

下表提供模組 0 (START) 和單元 1 (FINISH) 的存放區資料夾連結。您也可以透過所有 App Engine 遷移作業的存放區存取這些檔案,複製或下載 ZIP 檔案。

Codelab

Python 2

Python 3

單元 0

程式碼

(不適用)

Module 1

程式碼

(不適用)

App Engine 資源

以下是這項特定遷移作業的其他資源: