從 App Engine 工作佇列推送工作遷移至 Cloud Tasks (單元 8)

1. 總覽

無伺服器遷移站系列的程式碼研究室系列 (自助式實作教學課程) 和相關影片,旨在引導 Google Cloud 無伺服器開發人員透過一或多種遷移作業 (主要用於遷移舊版服務) 逐步翻新應用程式。這麼一來,您的應用程式就能更具可攜性,並提供更多選擇和使用彈性,進而整合及使用更多 Cloud 產品,也更容易升級至較新的語言版本。本系列課程一開始將著重在最早的 Cloud 使用者 (主要是 App Engine (標準環境) 開發人員),但涵蓋其他無伺服器平台,包括 Cloud FunctionsCloud Run 或其他無伺服器平台 (如適用)。

本程式碼研究室旨在向 Python 2 App Engine 開發人員說明如何從 App Engine 工作佇列 (推送工作) 遷移至 Cloud Tasks。系統也提供從 App Engine NDB 間接遷移Cloud NDB 的做法,讓可存取 Datastore (主要會列於單元 2)。

我們在單元 7 中新增了使用「推送」工作的方法,並在單元 8 將這類工作遷移至 Cloud Tasks,然後在單元 9 中繼續執行 Python 3 和 Cloud Datastore。使用工作佇列執行「提取」工作的使用者會遷移至 Cloud Pub/Sub,並應參照單元 18 至 19。

在接下來的研究室中

軟硬體需求

問卷調查

您會如何使用這個教學課程?

只能閱讀 閱讀並完成練習

您對 Python 的使用體驗有何評價?

新手 中級 還算容易

針對使用 Google Cloud 服務的經驗,您會給予什麼評價?

新手 中級 還算容易

2. 背景

App Engine 工作佇列支援推送和提取工作。為提升應用程式可攜性,Google Cloud 團隊建議從 Task Queue 等舊版套裝組合服務,遷移至其他 Cloud 獨立或第三方同等服務。

遷移模組 18 至 19 涵蓋提取工作遷移,而單元 7 至 9 則著重於推送工作遷移。為了從 App Engine 工作佇列推送工作遷移,我們在現有的 Python 2 App Engine 範例應用程式中,加入其使用行為,以註冊新網頁造訪,並顯示最近的造訪記錄。模組 7 程式碼研究室新增一項推送工作,可刪除最舊的造訪記錄 (不會再顯示),因此為何要在 Datastore 中佔用額外的儲存空間?本單元 8 程式碼研究室保留了相同功能,但會將基礎佇列機制從工作佇列推送工作遷移至 Cloud Tasks,並重複執行模組 2 從 App Engine NDB 遷移至 Cloud NDB 以存取 Datastore。

本教學課程包含下列步驟:

  1. 設定/事前作業
  2. 更新設定
  3. 修改應用程式程式碼

3. 設定/事前作業

本節說明如何:

  1. 設定 Cloud 專案
  2. 取得基準範例應用程式
  3. (重新) 部署及驗證基準應用程式
  4. 啟用新的 Google Cloud 服務/API

這些步驟可確保您從有效的程式碼開始著手,以及範例應用程式準備好將遷移至 Cloud 服務。

1. 設定專案

如果您已完成單元 7 程式碼研究室,請重複使用相同的專案和程式碼。或者,您可以建立新的專案,或是重複使用其他現有專案。請確認專案具備有效的帳單帳戶和已啟用的 App Engine 應用程式。在本程式碼研究室中,您需要先用到這組 ID 來尋找專案 ID,每次遇到 PROJECT_ID 變數時都會用到。

2. 取得基準範例應用程式

必要條件之一是正常運作的模組 7 App Engine 應用程式:完成模組 7 程式碼研究室 (建議) 或從存放區複製模組 7 應用程式。無論您使用自有或我們的應用程式,我們都會從單元 7 程式碼開始 (「START」)。本程式碼研究室將引導您逐步完成遷移作業,最後會產生與單元 8 存放區資料夾 (「FINISH」) 類似的程式碼。

無論使用哪個 Module 7 應用程式,資料夾應如下所示 (可能也包含 lib 資料夾):

$ ls
README.md               appengine_config.py     requirements.txt
app.yaml                main.py                 templates

3. (重新) 部署及驗證基準應用程式

執行下列步驟來部署模組 7 應用程式:

  1. 如有 lib 資料夾,請刪除該資料夾,然後執行 pip install -t lib -r requirements.txt 來重新填入 lib。如果您已在開發機器上安裝 Python 2 與 3,可能需要改用 pip2
  2. 確認您已安裝初始化 gcloud 指令列工具,並檢查其使用情況
  3. (選用) 如果您不想在發出的每個 gcloud 指令中輸入 PROJECT_ID,請使用 gcloud config set project PROJECT_ID 設定 Cloud 專案。
  4. 使用 gcloud app deploy 部署範例應用程式
  5. 確認應用程式運作正常,不會出現任何問題。如果您已完成單元 7 程式碼研究室,應用程式會顯示熱門訪客以及最近的造訪 (如下圖所示)。畫面底部會顯示舊有工作將遭到刪除。

4aa8a2cb5f527079.png

4. 啟用新的 Google Cloud 服務/API

舊版應用程式使用的是 App Engine 套裝組合服務,您不需要進行額外設定,但獨立的 Cloud 服務需另外設定。此外,更新後的應用程式將同時採用 Cloud Tasks 與 Cloud Datastore (透過 Cloud NDB 用戶端程式庫)。部分 Cloud 產品的定價為「一律免費」級別配額,包括 App EngineCloud DatastoreCloud Tasks。只要不超過這些限制,完成本教學課程時就不會產生費用。視您的偏好而定,您可以透過 Cloud 控制台或指令列啟用 Cloud API。

使用 Cloud 控制台

請前往 Cloud 控制台的 API Manager 的程式庫頁面 (需執行專案),然後使用頁面中間的搜尋列搜尋 Cloud Datastore 和 Cloud Tasks API:

c7a740304e9d35b.png

分別點選各個 API 的「啟用」按鈕,系統可能會提示您提供帳單資訊。這個範例含有 Cloud Pub/Sub API 程式庫頁面 (請勿為這個程式碼研究室啟用 Pub/Sub API,而是 Cloud Tasks 和 Datastore):

1b6c0a2a73124f6b.jpeg

透過指令列

從控制台啟用 API 可在視覺上取得豐富的資訊,但有些偏好使用指令列。發出 gcloud services enable cloudtasks.googleapis.com datastore.googleapis.com 指令,即可同時啟用這兩個 API:

$ gcloud services enable cloudtasks.googleapis.com datastore.googleapis.com
Operation "operations/acat.p2-aaa-bbb-ccc-ddd-eee-ffffff" finished successfully.

系統可能會提示您輸入帳單資訊。停用其他 Cloud API 並想瞭解這些 API 的「URI」只要前往各個 API 的「程式庫」頁面底部即可找到。舉例來說,您可以將 pubsub.googleapis.com 觀察為「服務名稱」。

完成步驟後,您的專案就能存取 API。現在請更新應用程式,以便使用這些 API。

4. 更新設定

因為使用 Cloud 用戶端程式庫,設定中的更新會明確生效。無論您使用何種程式庫,都必須對未使用「任何」Cloud 用戶端程式庫的應用程式進行相同變更。

requirements.txt

單元 8 讓模組 1 中的 App Engine NDB 和工作佇列能夠與 Cloud NDB 和 Cloud Tasks 交換使用。將 google-cloud-ndbgoogle-cloud-tasks 附加至 requirements.txt,以便彙整單元 7 的 flask

flask
google-cloud-ndb
google-cloud-tasks

這個 requirements.txt 檔案不提供任何版本號碼,表示已選取最新版本。如果發生任何不相容的問題,請為應用程式指定一個版本號碼,用於鎖定可運作的應用程式版本。

app.yaml

使用 Cloud 用戶端程式庫時,Python 2 App Engine 執行階段需要特定的第三方套件,包括 grpciosetuptools。Python 2 使用者必須列出這類內建程式庫,以及 app.yaml 中的可用版本或「最新」。如果您還沒有 libraries 區段,請建立其中兩個程式庫,如下所示:

libraries:
- name: grpcio
  version: latest
- name: setuptools
  version: latest

遷移「您的應用程式」時,應用程式可能已「有」libraries 部分。如果是,而且缺少 grpciosetuptools,請將兩者新增至現有的 libraries 區段。更新後的 app.yaml 現在應如下所示:

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

libraries:
- name: grpcio
  version: latest
- name: setuptools
  version: latest

appengine_config.py

appengine_config.py 中的 google.appengine.ext.vendor.add() 呼叫,會將您所複製 (有時稱為「供應商」或「自備產品」) 的第三方程式庫lib連結至您的應用程式。在 app.yaml 的上層,我們新增了內建的第三方程式庫,而且這些程式庫需要setuptools.pkg_resources.working_set.add_entry()將您的應用程式與這些內建套件lib內建套件建立連結。以下是原始模組 1 appengine_config.py,且完成單元 8 更新後:

變更前:

from google.appengine.ext import vendor

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

變更後:

import pkg_resources
from google.appengine.ext import vendor

# Set PATH to your libraries folder.
PATH = 'lib'
# Add libraries installed in the PATH folder.
vendor.add(PATH)
# Add libraries to pkg_resources working set to find the distribution.
pkg_resources.working_set.add_entry(PATH)

您也可以在 App Engine 遷移說明文件中找到類似的說明。

5. 修改應用程式程式碼

本節提供的是主要應用程式檔案 main.py 的更新功能,同時會將 App Engine 工作佇列發送佇列替換為 Cloud Tasks。網路範本 (templates/index.html) 沒有任何變更,兩個應用程式應能以相同方式運作,顯示相同的資料。主要應用程式的修改內容分為下列四項「待辦事項」:

  1. 更新匯入和初始化作業
  2. 更新資料模型功能 (Cloud NDB)
  3. 遷移至 Cloud Tasks 和 Cloud NDB
  4. 更新 (推送) 工作處理常式

1. 更新匯入和初始化作業

  1. 將 App Engine NDB (google.appengine.ext.ndb) 和工作佇列 (google.appengine.api.taskqueue) 分別替換為 Cloud NDB (google.cloud.ndb) 和 Cloud Tasks (google.cloud.tasks)。
  2. 使用 Cloud 用戶端程式庫時,必須初始化並建立「API 用戶端」。並分別指派給 ds_clientts_client
  3. 工作佇列說明文件中指出:「App Engine 提供名為 default 的預設發送佇列,這個佇列已設定完成,可與預設設定搭配使用。」Cloud Tasks 不會提供 default 佇列 (因為這個佇列是獨立於 App Engine 的獨立 Cloud 產品),因此您必須使用新程式碼才能建立名為 default 的 Cloud Tasks 佇列。
  4. App Engine 工作佇列會使用應用程式的執行區域,因此不需要指定地區。不過,由於 Cloud Tasks 現已推出獨立產品,因此「確實」需要區域,且這個區域必須與應用程式執行的區域相符。必須提供區域名稱和 Cloud 專案 ID,才能建立「完整路徑名稱」做為佇列的專屬 ID

上述第三和第四個項目說明的更新是大部分的必要常數和初始化作業。請參閱以及「之後」並在 main.py 頂端進行這些變更。

變更前:

from datetime import datetime
import logging
import time
from flask import Flask, render_template, request
from google.appengine.api import taskqueue
from google.appengine.ext import ndb

app = Flask(__name__)

變更後:

from datetime import datetime
import json
import logging
import time
from flask import Flask, render_template, request
from google.cloud import ndb, tasks

app = Flask(__name__)
ds_client = ndb.Client()
ts_client = tasks.CloudTasksClient()

_, PROJECT_ID = google.auth.default()
REGION_ID = 'REGION_ID'    # replace w/your own
QUEUE_NAME = 'default'     # replace w/your own
QUEUE_PATH = ts_client.queue_path(PROJECT_ID, REGION_ID, QUEUE_NAME)

2. 更新資料模型功能 (Cloud NDB)

App Engine NDB 和 Cloud NDB 的運作方式幾乎完全相同。資料模型或 store_visit() 函式沒有任何重大變更。唯一的明顯差異是,在 store_visit() 中建立 Visit 實體現已封裝在 Python with 區塊中。Cloud NDB 需要在其結構定義管理工具中控管所有 Datastore 存取權,因此需要使用 with 陳述式。下列程式碼片段說明遷移至 Cloud NDB 時的這個細微差異。請導入這項變更。

變更前:

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)

def store_visit(remote_addr, user_agent):
    'create new Visit entity in Datastore'
    Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

變更後:

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)

def store_visit(remote_addr, user_agent):
    'create new Visit entity in Datastore'
    with ds_client.context():
        Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

3. 遷移至 Cloud Tasks 和 Cloud NDB

這次遷移作業中最重大的變更會更換基礎排入佇列基礎架構。這會在 fetch_visits() 函式中進行,其中建立 (推送) 刪除舊造訪工作,並將工作排入佇列來執行。不過,單元 7 的原始功能不會受到影響:

  1. 查詢最近造訪地點。
  2. 您不必立即傳回這些造訪記錄,只要儲存上次 Visit 的時間戳記 (就會顯示最舊的記錄),即可放心刪除超過這個時間的所有造訪記錄。
  3. 將時間戳記預告為浮點數和使用標準 Python 公用程式的字串,並用於各種容量,例如:顯示給使用者、新增至記錄、傳遞至處理常式等等。
  4. 建立發送工作,將這個時間戳記做為其酬載,並以 /trim 做為網址。
  5. 系統最終會透過 HTTP POST 呼叫該網址的工作處理常式。

此工作流程由「之前」程式碼片段:

變更前:

def fetch_visits(limit):
    'get most recent visits & add task to delete older visits'
    data = Visit.query().order(-Visit.timestamp).fetch(limit)
    oldest = time.mktime(data[-1].timestamp.timetuple())
    oldest_str = time.ctime(oldest)
    logging.info('Delete entities older than %s' % oldest_str)
    taskqueue.add(url='/trim', params={'oldest': oldest})
    return data, oldest_str

Cloud Tasks 的功能維持不變,但會成為執行平台。本次異動生效的更新內容包括:

  1. Visit 查詢納入 Python with 區塊 (重複模組 2 遷移至 Cloud NDB)
  2. 建立 Cloud Tasks 中繼資料,包含時間戳記酬載和網址等預期屬性,但請新增 MIME 類型和 JSON 編碼酬載。
  3. 使用 Cloud Tasks API 用戶端,建立含有佇列中繼資料和完整路徑名稱的工作。

以下說明 fetch_visits() 的異動內容:

變更後:

def fetch_visits(limit):
    'get most recent visits & add task to delete older visits'
    with ds_client.context():
        data = Visit.query().order(-Visit.timestamp).fetch(limit)
    oldest = time.mktime(data[-1].timestamp.timetuple())
    oldest_str = time.ctime(oldest)
    logging.info('Delete entities older than %s' % oldest_str)
    task = {
        'app_engine_http_request': {
            'relative_uri': '/trim',
            'body': json.dumps({'oldest': oldest}).encode(),
            'headers': {
                'Content-Type': 'application/json',
            },
        }
    }
    ts_client.create_task(parent=QUEUE_PATH, task=task)
    return data, oldest_str

4. 更新 (推送) 工作處理常式

(push) 工作處理常式函式不需要重大更新。只需要執行這適用於工作佇列或 Cloud Tasks。「代碼是程式碼」他們就會知道不過,有一些微幅異動:

  1. 時間戳記酬載已一字不差地傳遞至工作佇列,但它是對 Cloud Tasks 採用 JSON 編碼的,因此必須在抵達時進行 JSON 剖析。
  2. 利用 Task Queue 對 /trim 發出的 HTTP POST 呼叫的隱含 MIME 類型為 application/x-www-form-urlencoded,但在 Cloud Tasks 中,該 MIME 類型明確指定為 application/json,因此在擷取酬載的方式會略有不同。
  3. 使用 Cloud NDB API 用戶端結構定義管理員 (模組 2 遷移至 Cloud NDB)。

以下是變更工作處理常式 trim() 前後的程式碼片段:

變更前:

@app.route('/trim', methods=['POST'])
def trim():
    '(push) task queue handler to delete oldest visits'
    oldest = request.form.get('oldest', type=float)
    keys = Visit.query(
            Visit.timestamp < datetime.fromtimestamp(oldest)
    ).fetch(keys_only=True)
    nkeys = len(keys)
    if nkeys:
        logging.info('Deleting %d entities: %s' % (
                nkeys, ', '.join(str(k.id()) for k in keys)))
        ndb.delete_multi(keys)
    else:
        logging.info('No entities older than: %s' % time.ctime(oldest))
    return ''   # need to return SOME string w/200

變更後:

@app.route('/trim', methods=['POST'])
def trim():
    '(push) task queue handler to delete oldest visits'
    oldest = float(request.get_json().get('oldest'))
    with ds_client.context():
        keys = Visit.query(
                Visit.timestamp < datetime.fromtimestamp(oldest)
        ).fetch(keys_only=True)
        nkeys = len(keys)
        if nkeys:
            logging.info('Deleting %d entities: %s' % (
                    nkeys, ', '.join(str(k.id()) for k in keys)))
            ndb.delete_multi(keys)
        else:
            logging.info(
                    'No entities older than: %s' % time.ctime(oldest))
    return ''   # need to return SOME string w/200

主要應用程式處理常式 root() 和網路範本 templates/index.html 沒有任何更新。

6. 摘要/清除

本節總結此程式碼研究室的內容,做法是部署應用程式,確認應用程式是否正常運作,以及任何反映的輸出內容。驗證應用程式後,請執行所有清理作業,並考慮後續步驟。

部署及驗證應用程式

使用 gcloud app deploy 部署應用程式。輸出內容應與模組 7 應用程式相同,但您發現您已改用完全不同的發送佇列產品,因此應用程式具備更高的可攜性!

4aa8a2cb5f527079.png

清除所用資源

一般

如果您現階段已完成設定,建議您停用 App Engine 應用程式,以免產生帳單費用。不過,如果您想測試或進行其他測試,App Engine 平台提供免費配額,而且只要不超出用量限制,就不需支付任何費用。這適用於運算,但相關 App Engine 服務可能也會產生費用,詳情請參閱定價頁面。如果這項遷移作業涉及其他 Cloud 服務,我們會另外計費。無論採用哪種情況,請參閱「本程式碼研究室的專屬」以下章節。

如要完整揭露,部署至 Google Cloud 無伺服器運算平台 (如 App Engine) 會產生少許建構和儲存空間費用Cloud Build 提供的免費配額與 Cloud Storage 相同。該映像檔的儲存空間會佔用部分配額。不過,您可能居住的區域沒有這類免費方案,因此請留意儲存空間用量,盡可能降低潛在費用。特定 Cloud Storage「資料夾」請務必查看:

  • console.cloud.google.com/storage/browser/LOC.artifacts.PROJECT_ID.appspot.com/containers/images
  • console.cloud.google.com/storage/browser/staging.PROJECT_ID.appspot.com
  • 上方的儲存空間連結取決於您的 PROJECT_ID 和 *LOC*,例如「us」如果應用程式是由美國代管

另一方面,如果您不打算繼續使用這個應用程式或其他相關的遷移程式碼研究室,且想要徹底刪除所有項目,請關閉專案

本程式碼研究室的專屬功能

下列服務專屬於本程式碼研究室。詳情請參閱每項產品的說明文件:

後續步驟

以上就是從 App Engine 工作佇列推送工作遷移至 Cloud Tasks 的作業。如果您想繼續將應用程式轉移至 Python 3,並進一步從 Cloud NDB 遷移至 Cloud Datastore,請考慮使用模組 9

Cloud NDB 專供 Python 2 App Engine 開發人員使用,提供幾乎與 App Engine 開發人員相同的使用者體驗,但 Cloud Datastore 擁有專為非 App Engine 使用者或新 (Python 3) App Engine 使用者所打造的原生用戶端程式庫。不過,由於 Cloud NDB 適用於 Python 2「和」3,則不需要遷移至 Cloud Datastore。

Cloud NDB 和 Cloud Datastore 都會透過不同方式存取 Datastore,因此若您已有其他應用程式 (特別是非 App Engine 應用程式),使用 Cloud Datastore 並希望對單一 Datastore 用戶端程式庫進行標準化,才是考慮遷移至 Cloud Datastore 的唯一理由。這項選擇性地從 Cloud NDB 遷移至 Cloud Datastore 的程序,也會在模組 3 中自行說明 (不含工作佇列或 Cloud Tasks)。

除了單元 3、8 和 9 之外,其他遷移模組則著重於停用 App Engine 舊版套裝組合服務,建議一併納入考量:

  • 模組 2:從 App Engine NDB 遷移至 Cloud NDB
  • 模組 12-13:從 App Engine Memcache 遷移至 Cloud Memorystore
  • 模組 15 至 16:從 App Engine Blob 遷移至 Cloud Storage
  • 模組 18-19:App Engine 工作佇列 (提取工作) 至 Cloud Pub/Sub

App Engine 不再是 Google Cloud 中唯一的無伺服器平台。如果您有小型 App Engine 應用程式,或功能有限,但想將其轉換為獨立微服務,或想將單體式應用程式拆解成多個可重複使用的元件,請考慮改用 Cloud Functions。如果容器化已成為應用程式開發工作流程的一部分,特別是在含有 CI/CD (持續整合/持續推送軟體更新或部署) 管道的情況下,請考慮遷移至 Cloud Run。以下單元將介紹這些情境:

  • 從 App Engine 遷移至 Cloud Functions:請參閱單元 11
  • 從 App Engine 遷移至 Cloud Run:請參閱模組 4,使用 Docker 將應用程式容器化;或參閱模組 5,瞭解如何在沒有容器、Docker 或 Dockerfile 的情況下執行應用程式

您可以選擇是否要改用其他無伺服器平台,在進行任何變更前,建議您考量應用程式和用途的最佳選擇。

無論接下來選擇使用哪個遷移模組,所有無伺服器遷移站內容 (程式碼研究室、影片、原始碼 [如有]) 都可以透過其開放原始碼存放區存取。存放區的 README 還針對應考慮遷移的項目和任何相關的「訂單」提供指引接下來介紹遷移模組

7. 其他資源

以下列出開發人員可進一步探索這個或相關的遷移模組和相關產品。包括提供這項內容意見回饋的地方、程式碼連結,以及各種實用的說明文件。

程式碼研究室問題/意見回饋

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

遷移資源

下表提供單元 7 (START) 和單元 8 (FINISH) 的存放區資料夾連結。

Codelab

Python 2

Python 3

Module 7

程式碼

code (並未出現在本教學課程中)

單元 8 (本程式碼研究室)

程式碼

(不適用)

線上資源

以下為可能與本教學課程相關的線上資源:

App Engine 工作佇列和 Cloud Tasks

App Engine NDB 和 Cloud NDB (Datastore)

App Engine 平台

其他 Cloud 資訊

影片

授權

這項內容採用的是創用 CC 姓名標示 2.0 通用授權。