前言
本文作者:遠哥制造
一、什么是 Serverless Framework
Serverless Framework
是業界非常受歡迎的無服務器應用框架,開發者無需關心底層資源即可部署完整可用的Serverless
應用架構。Serverless Framework
具有資源編排、自動伸縮、事件驅動等能力,覆蓋編碼、調試、測試、部署等全生命周期,幫助開發者通過聯動雲資源,迅速構建Serverless
應用
沒錯,就像幾天前看到的《Serverless 之歌》里面所說 I'm gonna reduce your ops
,它能大幅度減輕運維壓力,那就開始動手吧!注意開發環境需 Node.js 10.0+
,一鍵全局安裝:npm install -g serverless
二、騰訊雲 Flask Serverless Component 簡介
騰訊雲
Flask Serverless Component
,支持Restful API
服務的部署
按照慣例首先來部署 demo
吧
- 本地
PyCharm
創建一個新的Flask
項目
-
手動創建內容為
Flask
的requirements.txt
-
按照配置文檔創建
serverless.yml
,例如本項目實際使用的完整內容,初次使用可自行酌情簡化 -
將密匙寫入
.env
(當然,部署的時候也可以選擇微信掃碼授權)
TENCENT_SECRET_ID=<rm>
TENCENT_SECRET_KEY=<rm>
這樣基於 Serverless
的 Flask Demo
就部署完成了,接下來繼續按照自己的方式寫剩下的代碼。
三、maimai_DX
maimai 是一款街機音游。
在這里放一張動圖自行體會一下,原始素材來自「外錄 maimai」QZKago Requiem Re:MASTER ALLPERFECT Player: Ruri*R
在國內,只能從微信公眾號中查看成績,而且每次進頁面都需要微信的授權登錄,並且里面存儲的記錄有條數限制,相冊
只存最新 10 條,游戲記錄
只存最新 50 條(就是一個隊列,先進先出的那種)。這就是本項目的初衷,自己打出來的每一次成績都應該保存好。
舞萌查分器
成果展示了,前端 Fomantic-UI
,后端 Flask
+MySQL
。gh
開源地址:https://github.com/yuangezhizao/maimai_DX_CN_probe,歡迎 watch
、star
、fork
& pr
!
目前實裝了如下功能:
- wechat_archive中包含
主頁
,游戲數據
,相冊
和游戲記錄
:對原始網頁進行了修改,並且添加了Highcharts
庫可視化曲線顯示變化 - record包含
記錄(分頁)
和差異(分頁)
:即自寫的快速預覽頁面,是查看歷史記錄和成績變化的非常實用的功能 - info包含
鋪面列表
:即全部鋪面基礎信息,輸出到一個頁面中,方便頁面內搜索
開發過程
接下來將按照時間的順序,描述一下開發過程中遇到的問題以及如何解決
1. Serverless Framework Component
配置文件
Serverless Framework
現在是 V2
版本,也就是說不能沿襲之前版本的 serverless.yml
配置文件,需要重新對照文檔修改。
a. 之前版本會根據 requirements.txt
自動下載第三方庫到項目目錄下的 .serverless
文件夾下的 requirements
文件夾以參加最終的依賴打包,壓縮成 zip
文件再最終上傳至雲函數運行環境
b. 最新版本不再自動下載,需要自行處理。官方示例的參考用法:hook
src:
# TODO: 安裝python項目依賴到項目當前目錄
hook: 'pip3 install -r requirements.txt -t ./requirements'
dist: ./
include:
- source: ./requirements
prefix: ../ # prefix, can make ./requirements files/dir to ./
exclude:
- .env
- 'requirements/**'
注釋寫的很清楚,使用 hook
去根據 requirements.txt
下載第三方庫到項目目錄下的 requirements
文件夾,避免第三方庫導致本地文件夾管理混亂。然后 include
中指定了項目目錄下的 requirements
文件夾在雲端的 prefix
,即對於雲端的雲函數運行環境,requirements
文件夾中的第三方庫和項目目錄是同級的,可以正常導入使用。當然了,本地運行使用的是全局的第三方庫,並未用到項目目錄下的 requirements
文件夾。
2. 層管理概述
前者(指 b)是一個很合理的設計,不過在實際環境中卻發現了新的問題。完全一致的配置文件
src:
hook: 'pip3 install -r ./src/requirements.txt -t ./src/requirements'
dist: ./src
include:
- source: ./requirements
prefix: ../
exclude:
- .env
在 macOS 下成功部署之后,雲端的雲函數編輯器中看到 requirements
文件夾不存在,第三方庫和項目目錄是同級的,的確沒問題。
不過在 Windows 下成功部署之后,雲端的雲函數編輯器中看到了 requirements
文件夾?也就是說第三方庫和項目目錄非同級,於是訪問就會出現 no module found
的導入報錯了……
反復嘗試修改 prefix
等配置項到最后也沒有調試成功,因此在這里提出兩種解決方法:
a. 修改配置文件如下,讓本地的第三方庫和項目目錄同級存在
src:
hook: 'pip3 install -r ./src/requirements.txt -t ./src'
dist: ./src
exclude:
- .env
不過隨着項目和第三方庫的擴大文件夾會越來越多,非常不便於管理
b. 使用雲函數提供的 層
雖然 sls deploy
部署的速度很快,但是如果可以在部署時只上傳項目代碼而不去處理依賴不就更好了嘛,這樣跨終協作端開發只需要關心項目代碼就 ok
了,再也不需要管理依賴!
並且還有一點,想在 SCF
控制台中在線編輯函數代碼需要將部署程序包保持在 10MB
以下,不要以為十兆很大,很快就用光也是可能的
具體如何操作呢?那就是要將第三方庫文件夾直接打包並創建為層,則在函數代碼中可直接通過 import
引用,畢竟有些特殊庫比如 Brotli
,Windows 下沒有 vc++
的話就只能去https://lfd.uci.edu/~gohlke/pythonlibs下載 wheel
安裝。
macOS 下正常安裝之后會得到 _brotli.cpython-39-darwin.so
,brotli.py
中再以 import _brotli
的形式導入,不過又出新問題了,雲端會導入報錯ModuleNotFoundError: No module named '_brotli'"
當前
SCF
的執行環境建立在以下基礎上:標准CentOS 7.2
為了解決問題嘗試在 linux 環境下打包,拿起手頭的 CentOS 8.2
雲主機開始操作
pip3 install -r requirements.txt -t ./layer --upgrade
zip -r layer.zip ./layer
然后就可以把打包的 layer.zip
下載到本地再傳上去了,暫時可以一勞永逸了。
對了,配置文件可以移除 hook
並添加 layers
了
src:
src: ./src
exclude:
- .env
- '__pycache__/**'
layers:
- name: maimai_DX_CN_probe
version: 3
已綁定層的函數被觸發運行,啟動並發實例時,將會解壓加載函數的運行代碼至
/var/user/
目錄下,同時會將層內容解壓加載至/opt
目錄下。若需使用或訪問的文件file
,放置在創建層時壓縮文件的根目錄下。則在解壓加載后,可直接通過目錄/opt/file
訪問到該文件。若在創建層時,通過文件夾進行壓縮dir/file
,則在函數運行時需通過/opt/dir/file
訪問具體文件
體驗更快的部署速度吧!因為第三方庫已經打包在“層”中了
但是奇怪的是,在雲端導入任意第三方庫均會報錯,於是調試着查看 path
for path in sys.path:
print(path)
/var/runtime/python3
/var/user
/opt
/var/lang/python3/lib/python36.zip
/var/lang/python3/lib/python3.6
/var/lang/python3/lib/python3.6/lib-dynload
/var/lang/python3/lib/python3.6/site-packages
/var/lang/python3/lib/python3.6/site-packages/pip-18.0-py3.6.egg
再查看 opt
import os
dirs = os.listdir('/opt')
for file in dirs:
print(file)
layer
這才恍然大悟,打包時需要在當前路徑直接打包。上傳之后“層”更新為版本 2
,但是 ModuleNotFoundError: No module named '_brotli'
報錯依舊,並且確認 _brotli.cpython-38-x86_64-linux-gnu.so
文件實際存在。
而在 CentOS
和 macOS
上本地導入均沒有問題,這可就犯難了,又想到很有可能是 python
版本的問題,於是去尋找現成 3.6
的環境,比如這里:
再再次上傳之后“層”更新為版本 3
,訪問成功!課題終於解決,原來是需要相同版本的 Python 3.6
運行環境
3.自定義入口文件
components源碼tencent-flask/src/_shims/中的文件每次都會被原封不動地重新打包上傳到雲端雲函數中,目前有兩個文件
a. severless_wsgi.py
,作用是 converts an AWS API Gateway proxied request to a WSGI request.
WSGI
的全稱是Python Web Server Gateway Interface
即Web 服務器網關接口
,它是為Python
語言定義的Web
服務器和Web
應用程序或框架之間的一種簡單而通用的接口
b. sl_handler.py
,就是默認的入口文件
import app # Replace with your actual application
import severless_wsgi
# If you need to send additional content types as text, add then directly
# to the whitelist:
#
# serverless_wsgi.TEXT_MIME_TYPES.append("application/custom+json")
def handler(event, context):
return severless_wsgi.handle_request(app.app, event, context)
針對於自己的項目,使用了 Flask
的 工廠函數
,為了避免每次都要在雲端雲函數編輯器中重新修改,最好的方法是自定義入口文件:
import severless_wsgi
from maimai_DX_CN_probe import create_app # Replace with your actual application
# If you need to send additional content types as text, add then directly
# to the whitelist:
#
# serverless_wsgi.TEXT_MIME_TYPES.append("application/custom+json")
def handler(event, context):
return severless_wsgi.handle_request(create_app(), event, context)
再指定 執行方法
為 serverless_handler.handler
,就 ok 了
4. url_for
輸出 http
而非 https
的 URL
在視圖函數中重定向到 url_for
所生成的鏈接都是 http
,而不是 https
……其實這個問題 Flask
的文檔 Standalone WSGI Containers有描述到
說到底這並不是 Flask
的問題,而是 WSGI
環境所導致的問題,推薦的方法是使用中間件,官方也給出了 ProxyFix
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)
但是是從X-Forwarded-Proto
中取的值,apigw
中其為http
,因此並不能直接使用這個ProxyFix
因為Flask
的社區還算完善,參考資料很多前人都鋪好了路,所以直接去Stack Overflow
搜解決方法,Flask url_for generating http URL instead of https
問題出現的原因如圖:Browser ----- HTTPS ----> Reverse proxy(apigw) ----- HTTP ----> Flask
因為自己在apigw
設置了前端類型
僅https
,也就是說Browser
端是不可能使用http
訪問到的,通過打印environ
可知
{
"CONTENT_LENGTH": "0",
"CONTENT_TYPE": "",
"PATH_INFO": "/",
"QUERY_STRING": "",
"REMOTE_ADDR": "",
"REMOTE_USER": "",
"REQUEST_METHOD": "GET",
"SCRIPT_NAME": "",
"SERVER_NAME": "maimai.yuangezhizao.cn",
"SERVER_PORT": "80",
"SERVER_PROTOCOL": "HTTP/1.1",
"wsgi.errors": <__main__.CustomIO object at 0x7feda2224630>,
"wsgi.input": <_io.BytesIO object at 0x7fed97093410>,
"wsgi.multiprocess": False,
"wsgi.multithread": False,
"wsgi.run_once": False,
"wsgi.url_scheme": "http",
"wsgi.version": (1, 0),
"serverless.authorizer": None,
"serverless.event": "<rm>",
"serverless.context": "<rm>",
"API_GATEWAY_AUTHORIZER": None,
"event": "<rm>",
"context": "<rm>",
"HTTP_ACCEPT": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"HTTP_ACCEPT_ENCODING": "gzip, deflate, br",
"HTTP_ACCEPT_LANGUAGE": "zh-CN,zh;q=0.9,en;q=0.8",
"HTTP_CONNECTION": "keep-alive",
"HTTP_COOKIE": "<rm>",
"HTTP_ENDPOINT_TIMEOUT": "15",
"HTTP_HOST": "maimai.yuangezhizao.cn",
"HTTP_SEC_FETCH_DEST": "document",
"HTTP_SEC_FETCH_MODE": "navigate",
"HTTP_SEC_FETCH_SITE": "none",
"HTTP_SEC_FETCH_USER": "?1",
"HTTP_UPGRADE_INSECURE_REQUESTS": "1",
"HTTP_USER_AGENT": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36",
"HTTP_X_ANONYMOUS_CONSUMER": "true",
"HTTP_X_API_REQUESTID": "5bcb29af2ca18c1e6d7b1ec5ff7b5427",
"HTTP_X_API_SCHEME": "https",
"HTTP_X_B3_TRACEID": "5bcb29af2ca18c1e6d7b1ec5ff7b5427",
"HTTP_X_QUALIFIER": "$LATEST"
}
HTTP_X_FORWARDED_PROTO
對應apigw
里的變量是HTTP_X_API_SCHEME
,故解決方法如下:app.wsgi_app = ReverseProxied(app.wsgi_app)
class ReverseProxied(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
scheme = environ.get('HTTP_X_FORWARDED_PROTO')
if scheme:
environ['wsgi.url_scheme'] = scheme
return self.app(environ, start_response)
app = Flask(__name__)
app.wsgi_app = ReverseProxied(app.wsgi_app)
5. 響應數據壓縮
不論是IIS
、Apache
還是Nginx
,都提供有壓縮功能。畢竟自己在用的雲主機外網上行只有1M
帶寬,壓縮后對於縮短首屏時間的效果提升極為顯著。對於Serverless
,響應數據是通過API Gateway
傳輸到客戶端,那么壓縮也應該是它所具備的能力(雖然外網速度大幅度提高,但是該壓縮還是得壓縮),然而並沒有找到……看到某些js
框架原生有提供壓縮功能,於是打算添加Flask
自行壓縮的功能。簡單來講,通過訂閱@app.after_request
信號並調用第三方庫brotli
的compress
方法即可(
在寫之前去gh
上看看有沒有現成的輪子拓展,果然有……剛開始用的是Flask-Zipper
,后來換成Flask-Compress
解決了問題
實測3.1 MB
的數據采用brotli
壓縮算法減至76.1 kB
6. apigw
三種環境不同路徑所產生的影響
默認的映射如下:
ID | 環境名 | 訪問路徑 |
---|---|---|
1 | 發布 | release |
2 | 預發布 | prepub |
3 | 測試 | test |
因為配置的static_url_path
為""
,即static
文件夾是映射到/
路徑下的,所以再加上release
、prepub
和test
訪問就自然404
了
因此綁定了自定義域名
,使用自定義路徑映射
,並將發布
環境的訪問路徑設置成/
,這樣再訪問發布
環境就沒有問題了
ID | 環境名 | 訪問路徑 |
---|---|---|
1 | 發布 | / |
2 | 預發布 | prepub |
3 | 測試 | test |
7. 同時訪問私有網絡
和外網
雲函數
中可以利用到的雲端數據庫有如下幾種
- 雲數據庫
CDB
,需要私有網絡
訪問,雖然可以通過外網訪問但是能走內網就不走外網 PostgreSQL for Serverless(ServerlessDB)
,這個是官方給Serverless
配的pg
數據庫- 雲開發
TCB
中的MongoDB
,沒記錯的話需要開通內測權限訪問
因為自己是從舊網站遷移過來的,數據暫時還沒有遷移,因此直接訪問原始雲數據庫CDB
,在雲函數
配置所屬網絡
和所屬子網
即可。但是此時會無法訪問外網,一種解決方法是開啟公網訪問
和公網固定IP
,就可以同時訪問內網和外網資源了。關於配置文件,本項目是單實例應用
也就是說項目中只引入一個組件,部署時只生成一個組件實例
。但是如果想引入數據庫的話,就得新增組件了,目前在Flask Components
中並沒有提供數據庫相關的配置項,因此需要項目中引入多個組件,部署時生成多個組件實例
。也很簡單,創建一個含有serverless.yml
的新文件夾,用來配置postgresql
component: postgresql # (必填) 組件名稱,此處為 postgresql
name: maimai_DX_CN_probe # (必選) 組件實例名稱.
org: yuangezhizao # (可選) 用於記錄組織信息,默認值為您的騰訊雲賬戶 appid,必須為字符串
app: yuangezhizao # (可選) 用於記錄組織信息. 默認與name相同,必須為字符串
stage: dev # (可選) 用於區分環境信息,默認值是 dev
inputs:
region: ap-beijing # 可選 ap-guangzhou, ap-shanghai, ap-beijing
zone: ap-beijing-3 # 可選 ap-guangzhou-2, ap-shanghai-2, ap-beijing-3
dBInstanceName: maimai_DX_CN_probe
# projectId: 0
dBVersion: 10.4
dBCharset: UTF8
vpcConfig:
vpcId: vpc-mrg5ak88
subnetId: subnet-hqwa51dh
extranetAccess: false
然后在終端cd
到這個目錄再執行sls deploy
即可成功部署postgresql
yum install python3-devel postgresql-devel
pip install psycopg2
結果
import psycopg2
File "/opt/psycopg2/__init__.py", line 51, in <module>
from psycopg2._psycopg import ( # noqa
ImportError: libpython3.6m.so.1.0: cannot open shared object file: No such file or directory
下列問題處於解決之中:
http
強制跳轉https
- 測試環境推送至生產環境
至此,本文就結束了,歡迎交流!
One More Thing
立即體驗騰訊雲 Serverless Demo,領取 Serverless 新用戶禮包 👉 serverless/start
歡迎訪問:Serverless 中文網!