淺談Serverless Solution — 以GCP Cloud Function為例

Young Chen
宅男雜誌
Published in
13 min readApr 28, 2019

--

什麼是Serverless?

字面上意思很像是沒有Server,不過真正意義是你完全不要擔心維護Server這件事情。各家大廠也有對應的產品,例如Amazon AWS Lamda, Google Cloud Platform Cloud Function。由於工作上選用GCP,因此本篇將透過GCP Cloud Function嘗試總結自己在Serverless Solution的理解以及實用小技巧。

不管是Cloud Function或是AWS Lamda,在架構上都是以一個Function為單位,引導開發者往低耦合的方向開發應用程式,設計思維需要做點調整,首先要面臨的就是一個Function最小應該負責的事情是甚麼,Function之前互相應該如何溝通。

下文會依照軟體開發不同階段,提供Cloud Function應該注意的事項以及一些實作。此外,對於程式撰寫細節不會多加著墨,主要著重分享各階段的一些小小心得。

規劃(Plan)

為何選擇Cloud Function?

Cloud Function是一種輕量級的雲端應用方式,也是一種微服務(Micro Service)適合單一目的、獨立的功能。透過各種事件的觸發,讓開發者可以不用管理Server或控制執行環境。Cloud Function含以下特色:

  • 雲端環境執行程式碼最簡單的方法
  • 自動擴充,具備高可用性和容錯性
  • 無需佈建、管理、修補或更新伺服器
  • 執行程式碼時才需付費
  • 可連結和擴充雲端服務

計價模式是使用者付費,當Function有被執行時才有對應的費用,不用隨時開著Server並且管理。然而,在雲端環境開發服務,Cloud Function是一個簡單且便宜的解決方案,但是應當注意其限制以及使用情境,以下幾點供評估參考:

  • 每個Cloud Function有time out限制,預設1分鐘內,當超過限制後會拋出錯誤狀態且return。目前能夠容許的最大time out時間是9分鐘
  • 目前單一Function最大的可用Memory是2GB,如果執行期間程式超過其Memory,將會拋出錯誤中斷。就資料科學應用而言,這個情境將不適合應用大量資料的載入及運算相關功能。
  • Cloud Function執行環境目前支援Node.js、Python3.7、GO等,一些主流企業愛用程式語言如Java, C#等並未提供。
  • Cloud Function本身執行環境擁有唯讀的檔案系統,檔案系統的大小與Memory相依,位置在/tmp,適合用來暫存檔案。
  • 多個Cloud Function間並不共享Memory, Global Variable, File System。如果有資料共享的需求,可以使用GCP上的Storage Service

先前提到目前支援幾個相對新穎的程式語言,以下各間段範例將以Python為主作為範例。

建置(Build)

設計Cloud Function架構

既然是需要多個Function的設計模式,每個Function的參數/進入點就是一個必須要被優先思考的事情。以Cloud Function來說,是Event/Trigger的模式,因此在設計上需先釐清透過甚麼事件去驅動Cloud Function。

GCP環境中有許多雲端事件可以被收集當作Trigger的條件,例如上傳檔案至Storage或是一個pub/sub訊息(類似Kafka的Message Queue), 而這些雲端事件將Cloud Function被分為兩類:

Http Functions

(request)(Flask Request Object)作為參數,透過自動產生的url並使用該url(send request)觸發。

Backgroud Functions

(data, context)作為參數,是一種可以接收各種雲端服務事件的設計方式,以下引用官方說明:

data (dict): The dictionary with data specific to this type of event.
context (google.cloud.functions.Context): The Cloud Functions event
metadata.

舉例來說,一個Function在GCP雲端環境上監聽某個Storage,當檔案被上傳到該位置後發出事件通知,Cloud Function做出對應動作,例如解析檔案並且儲存於資料庫。我在建置時還會嘗試勾勒相關應用,產出架構圖,大致上概念如下:

繪製GCP各種服務的架構圖

定義/決定你應用情境(包含應該要什麼事件觸發)、繪製設計圖後,接著就可以著手進行開發。

初始化專案開發架構

Cloud Function概念是根據Event/Trigger觸發後執行程式邏輯,程式的進入點即是一個主程式 main.py,如果有其他相依副程式,可以包裝成package並且import至主程式中,依需求建立。概念上佈署到GCP的進入點就是main.py 以下分享一下我開發時的程式專案的架構:

  1. 建立專案資料夾架構

cloud function本機開發的參考專案架構如下:

myfunction/
├── main.py
├── requirements.txt
└── lib/
├── __init__.py
└── your_lib.py

是否需要lib資料夾依各人需求建立,你也可以將其他可能會用的module放在同一層直接import,分離資料夾的目的主要還是比較乾淨好管理,若是需要lib的話記得在資料夾底下建立一個無內容的__init__.py

2. 建立requirements.txt

cloud function服務要被啟動時才安裝對應的環境,requirements.txt這個描述檔目的就是在與GCP環境溝通在你的開發應用上究竟用了什麼延伸模組,也就是告訴GCP需要pip install什麼東西。

  • 格式: <package_name> == version
google-cloud-error-reporting==0.30.0
lib
sqlalchemy
pandas
numpy

值得注意的是若是你跟我一樣要分離一些lib到另外一個資料夾,記得也要到requirements .txt加入描述。而依照上面架構,如果你要import自己自定義的lib的話,可以在主程式這樣寫:

from lib.your_lib import xxx

更多細節我建議參考這裡

3. 撰寫Cloud Function

會建議針對Google本身提供的sample code去改寫,框架上明確許多,跟著大神範例修改準沒錯:

git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git

clone下來後可以去參考以下路徑的程式碼:

python-docs-samples/functions

code內要做什麼就看需求了,重點還是要搞清楚你要開發的是http function還是接收各種不同事件的background function。就我經驗而言拿範例來修改真的比自己邊摸索邊寫簡單太多,滿滿血淚史,路別白走了。

以下擷取兩段範例分別是background function/http function的helloworld

# [START functions_tips_terminate]
# [START functions_helloworld_get]
def hello_get(request):
return 'Hello World!'# [END functions_helloworld_get]
# [START functions_helloworld_background]
def hello_background(data, context):

if data and 'name' in data:
name = data['name']
else:
name = 'World'
return 'Hello {}!'.format(name)
# [END functions_helloworld_background]
# [END functions_tips_terminate]

http function還能夠直接解析request夾帶的資訊,這在以http cloud function作為各種cloud service接口時很實用:

# [START functions_helloworld_http]
def hello_http(request):

request_json = request.get_json(silent=True)
request_args = request.args
if request_json and 'name' in request_json:
name = request_json['name']
elif request_args and 'name' in request_args:
name = request_args['name']
else:
name = 'World'
return 'Hello {}!'.format(escape(name))
# [END functions_helloworld_http]

佈署(Deploy)

佈署方法百百種,剛開始你可能會選擇透過GCP的圖形化介面上傳zip或者是將程式碼直接貼上去佈署,不過這裡我推薦可以從本機直接透過gcloud commnad line 將本機寫完也測試好的程式碼上傳到GCP。以Mac為例,你可以在安裝好google cloud sdk後利用一下指令佈署你的cloud function:

gcloud functions deploy NAME --runtime RUNTIME TRIGGER [FLAGS...]

關於該指令相關參數說明,可以參考這裡

然而,透過gcp command line tool最棒的是可以做一定程度的自動化,減少自己在個個上傳zip檔時可能發生的低級錯誤,例如漏傳等。以下分享段落我如何自己寫shell將大量cloud function佈署至GCP環境:

  1. 用純文字編輯程式開啟,開檔按存成.sh
  2. 撰寫以下內容啟用大量佈署,格式如下:
#!/bin/bash#define your own sdk path
gcp_sdk_path="<your_gcp_sdk_path>"
#change to your cloud function folder
cd <your_cloud_function_folder>
#deploy your cloud function
$gcp_sdk_path/gcloud functions deploy <your_cloud_function> --entry-point xxx --runtime python37 --trigger-resource xxx &

3. 開啟terminal執行你的.sh

以上只是大概.sh架構,如果多個function只要從撰寫script重複轉換資料夾>佈署就好。分享一下小技巧,由於是透過Mac佈署,在佈署command後加上&可以讓job持續在後端跑(應該是所有unix家族都可以這樣),接著就會直接執行下個job,全部你想佈署的cloud function會一次被上傳。又不延遲!

測試(Test)

一般而言對於特定某事件例如檔案上傳至某個bucket觸發,可以直接上傳檔案並且另外開個視窗監控:

在Stackdriver可以在事件觸發後協助檢查

以上方式大概是background function比較簡易的觀察觸發/結果的方式。而http function在佈署後會產生url,測試時可以透過curl觸發:

curl -X POST "https://YOUR_REGION-YOUR_PROJECT_ID.cloudfunctions.net/FUNCTION_NAME" -H "Content-Type:application/json" --data '{"name":"Keyboard Cat"}'

除此之外,其實範例中也有提供利用測試程式

functions/helloworld/sample_http_test.py

from unittest.mock import Mock

import main


def test_print_name():
name = 'test'
data = {'name': name}
req = Mock(get_json=Mock(return_value=data), args=data)

# Call tested function
assert main.hello_http(req) == 'Hello {}!'.format(name)


def test_print_hello_world():
data = {}
req = Mock(get_json=Mock(return_value=data), args=data)

# Call tested function
assert main.hello_http(req) == 'Hello World!'

用以下指令測試你的http cloud function

pytest sample_http_test.py

更多測試相關資訊,可以參考這裡

小結

以上大略是以懶人包的方式記敘了一些注意事項以及我筆記的資訊、重要參考來源。這段開發過程挑戰解耦合這件事情比想像中困難一些,心得整理以下幾點:

  • 架構時設計可能是挑戰,但是設計的夠好理論上低耦合度可以在改版時變更風險變小。架構設計可以善用pub/sub服務,有相依性的subsriber只要訊息格式一樣,發出topic的程式碼更動核心邏輯也不會對有相依性的其他程式造成過大影響。
  • Cloud Function其實不是非常好管理的,尤其同一project可能散落好幾個Cloud Function列成同一清單只會眼花繚亂。因此,善用自訂的命名原則以及都多利用tag是一個比較推薦的方式,幫助你快速找到你想找的目標。
  • 最後我認為最大的優點是:便宜,比起開一台Compute Engine,在多數情況下(非密集存取以及耗用大量網路資源),Cloud Function所產稱的費用一個月十分低廉(常常估算一個月存取約20000次只需要5美金以下),無怪乎我很在意的難以分類管理這件事情這麼多人願意接受。

--

--

Young Chen
宅男雜誌

曾經是全端工程師,目前在資料科學團隊中主要負責雲端架構相關工作,透過自學正在資料科學領域相關知識耕耘中。mail: chiyoung0307@gmail.com