二次元看過來!基於 Serverless 的舞萌音遊查分器
前言
本文作者:遠哥製造
一、什麼是 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 新使用者禮包