1. 程式人生 > >開發函式計算的正確姿勢 —— 使用 Fun Local 本地執行與除錯

開發函式計算的正確姿勢 —— 使用 Fun Local 本地執行與除錯

前言

首先介紹下在本文出現的幾個比較重要的概念:

函式計算(Function Compute): 函式計算是一個事件驅動的服務,通過函式計算,使用者無需管理伺服器等執行情況,只需編寫程式碼並上傳。函式計算準備計算資源,並以彈性伸縮的方式執行使用者程式碼,而使用者只需根據實際程式碼執行所消耗的資源進行付費。函式計算更多資訊 參考

Fun: Fun 是一個用於支援 Serverless 應用部署的工具,能幫助您便捷地管理函式計算、API 閘道器、日誌服務等資源。它通過一個資源配置檔案(template.yml),協助您進行開發、構建、部署操作。Fun 的更多文件 參考

2.0 版本的 Fun,在部署這一塊做了很多努力,並提供了比較完善的功能,能夠做到將雲資源方便、平滑地部署到雲端。但該版本,在本地開發上的體驗,還有較多的工作要做。於是,我們決定推出 Fun Local 彌補這一處短板。

Fun Local: Fun Local 作為 Fun 的一個子命令存在,只要 Fun 的版本大於等於 2.6.0,即可以直接通過 fun local 命令使用。Fun Local 工具可以將函式計算中的函式在本地完全模擬執行,並提供單步除錯的功能,旨在彌補函式計算相對於傳統應用開發體驗上的短板,併為使用者提供一種解決函式計算問題排查的新途徑。

《開發函式計算的正確姿勢》系列除本篇是為使用者介紹 fun local 的使用方法外,其他幾篇都會向用戶展示 Fun Local 對於函式計算開發所帶來的效率上的巨大提升。

Fun Local 命令格式

使用 fun local invoke -h

可以檢視 fun local invoke 的幫助資訊:

$ fun local invoke -h
  Usage: invoke [options] <[service/]function>

  Run your serverless application locally for quick development & testing.

  Options:

    -d, --debug-port <port>  used for local debugging
    -c, --config <ide>       print out ide debug configuration. Options are VSCode
    -e, --event <path>       event file containing event data passed to the function
    -h, --help               output usage information

本地執行函式

執行函式的命令格式為:

fun local invoke [options] <[service/]function>

其中 options、service 都是可以省略的。
從呼叫方式上,可以理解為,fun local invoke 支援通過 函式名 呼叫,或者 服務名/函式名 的方式呼叫,即

fun local inovke function
fun local inovke service/function

比如,如果要執行名為 php72 的函式,可以直接通過以下命令完成:

fun local invoke php72

呼叫結果為:

再比如,要執行名為 nodejs8 的函式,可以使用:

fun local invoke nodejs8

會得到如下結果:

如果 template.yml 中包含多個服務,而多個服務中包含相同名稱的函式時,通過函式名的方式呼叫 fun 只會執行第一個名稱匹配的函式

如果想要精準匹配,可以使用 服務名/函式名 的方式。

比如想要呼叫 localdemo 下的 php72,可以使用:

fun local invoke localdemo/php72

在本例中,會得到和 fun local invoke php72 一致的結果。

以下是一個執行 nodejs8 函式的演示:

本地執行 java 型別的函式

java 不同於解釋型的語言,在作為函式執行前,需要先編譯。在我們的例子中,可以進入到 demo 中的 java8 目錄,然後執行:

mvn package

可以看到 log:

[INFO] skip non existing resourceDirectory /Users/tan/code/fun/examples/local/java8/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ demo ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ demo ---
[INFO] No tests to run.
[INFO]
[INFO] --- maven-dependency-plugin:2.8:copy-dependencies (copy-dependencies) @ demo ---
[INFO] fc-java-core-1.0.0.jar already exists in destination.
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ demo ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.223 s
[INFO] Finished at: 2018-11-22T10:45:14+08:00
[INFO] Final Memory: 15M/309M
[INFO] ------------------------------------------------------------------------

該命令會在 java8/target 目錄下生成 demo-1.0-SNAPSHOT.jar 檔案。

由於我們在 template.yml 中配置的 CodeUri 為 java8/target/demo-1.0-SNAPSHOT.jar,因此不需要任何改動,可以直接通過以下命令執行:

fun local invoke java8

執行結果如下:

以下是一個執行 java8 函式的演示:

本地除錯

fun local invoke 支援 -d, --debug-port <port> 選項,可以對函式進行本地單步除錯。本文件只介紹如何配置除錯,並不涉及除錯技巧,更多文章,請參考

備註:Fun Local 涉及到的 debugging 技術全部都基於各個語言通用的除錯協議實現的,因此無論什麼語言的開發者,即使不喜歡用 VSCode,只要使用對應語言的 remote debugging 方法都可以進行除錯。

本地除錯 nodejs、python 型別的函式

對於 nodejs6、nodejs8、python2.7、python3、java8 型別的函式,除錯方法基本一致。下面拿 nodejs8 舉例。

我們上面演示了可以通過 fun local invoke nodejs8 來執行名稱為 nodejs8 的函式,如果想對該函式進行除錯,只需要使用 -d 引數,並配置相應的埠號即可。

比如我們以除錯方式執行函式,並將除錯埠設定在 3000,可以通過下面的命令:

fun local invoke -d 3000 nodejs8

另外,推薦新增 --config 引數,在除錯的同時,可以輸出用來除錯的 IDE 的配置資訊:

fun local invoke -d 3000 --config VSCode nodejs8

命令執行結果如下:

skip pulling images ...
you can paste these config to .vscode/launch.json, and then attach to your running function
///////////////// config begin /////////////////
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "fc/localdemo/nodejs8",
            "type": "node",
            "request": "attach",
            "address": "localhost",
            "port": 3000,
            "localRoot": "/Users/tan/code/fun/examples/local/nodejs8",
            "remoteRoot": "/code",
            "protocol": "inspect",
            "stopOnEntry": false
        }
    ]
}
///////////////// config end /////////////////
Debugger listening on ws://0.0.0.0:3000/b65c288b-bd6a-4791-849b-b03e0d16b0ce
For help see https://nodejs.org/en/docs/inspector

程式會阻塞在這裡,並不會繼續往下執行。只有 IDE 的連線上來後,程式才會繼續執行。接下來,我們針對 VSCode 配置、VSCode 除錯兩個方面分別進行講解。

其中 VSCode 配置只有在第一次對函式進行除錯時才需要,如果已經配置過,則不需要再次配置。

VSCode 配置

  1. 建立 vscode launch.json 檔案

    ![](https://tan-blog.oss-cn-hangzhou.aliyuncs.com/img/20181116202330.png)
    
  2. 複製日誌中的 config begin 與 config end 之間的配置到 launch.json 中。

    ![](https://tan-blog.oss-cn-hangzhou.aliyuncs.com/img/20181118174828.png)
    
  3. 完成上面配置後,在 Debug 檢視可以看到配置的函式列表。

    ![](https://tan-blog.oss-cn-hangzhou.aliyuncs.com/img/20181118204006.png)
    

至此,VSCode 配置完成。VSCode 更多配置知識可以參考官方文件

VSCode 除錯

VSCode 配置成功後,只需要在 VSCode 編輯器側邊欄單擊設定斷點,然後點選“開始除錯”按鈕,即可開始除錯。

以下是一個 nodejs8 函式本地單步除錯的流程例子:

本地除錯 java 型別的函式

除錯 java 函式的過程和 nodejs、python 是類似的。但由於 java 程式設計師通常喜歡用 IDEA、Eclipse 這樣的 IDE,所以我們單獨拿出來說一下。

使用 VSCode 除錯 java

使用 VSCode 除錯 java 時,需要安裝兩個外掛:Language Support for Java(TM) by Red HatDebugger for Java。利用 VSCode 的外掛市場安裝外掛很簡單,可以 參考

以下是一個使用 VSCode 除錯 java 型別函式的例子:

使用 IDEA 除錯 java

IDEA 配置

IDEA 配置 remote debugging 還是比較簡單的,首先在選單欄依次點選 Run -> Edit Configurations...

然後新建一個 Remote Debugging:

然後我們隨意輸出一個名字,並配置埠號為 3000.

以下是一個配置 IDEA remote debugging 的完整流程演示:

使用 IDEA 開始除錯

首先將 java 函式以 debug 的方式執行起來:

fun local invoke -d 3000 java8

可以看到函式卡在這裡了,接著我們使用 IDEA 連線並開始除錯。可以通過選單欄上的 Run -> Debug... 或者工具欄直接點選 Debug 按鈕,即可開始除錯。

以下是一個用 IDEA 進行 remote debugging 的完整流程演示:

本地除錯 php 型別的函式

php 的除錯與其他型別的函式除錯在流程上有一些不同。

首先,php 的執行通過 fun local invoke php72 命令完成,這與其他型別的函式一致。除錯時,也像其他型別的函式一樣,通過 -d 引數以除錯模式啟動函式:

fun local invoke -d 3000 --config VSCode php72

但不同的是,以 debug 方式執行 php 函式後,php 函式並沒有阻塞等待 vscode 偵錯程式的連線,而是直接執行結束。

skip pulling images ...
you can paste these config to .vscode/launch.json, and then attach to your running function
///////////////// config begin /////////////////
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "fc/localdemo/php72",
            "type": "php",
            "request": "launch",
            "port": 3000,
            "stopOnEntry": false,
            "pathMappings": {
                "/code": "/Users/tan/code/fun/examples/local/php7.2"
            },
            "ignore": [
                "/var/fc/runtime/**"
            ]
        }
    ]
}
///////////////// config end /////////////////
FunctionCompute php7.2 runtime inited.
FC Invoke Start RequestId: 6e8f7ed7-653d-4a6a-94cc-1ef0d028e4b4
FC Invoke End RequestId: 6e8f7ed7-653d-4a6a-94cc-1ef0d028e4b4
hello world


RequestId: 6e8f7ed7-653d-4a6a-94cc-1ef0d028e4b4          Billed Duration: 48 ms          Memory Size: 1998 MB        Max Memory Used: 58 MB

這是因為,對於 php 程式,需要首先啟動 vscode 的偵錯程式。

php 型別的函式啟動 VSCode 偵錯程式的流程與其他型別的函式一致:複製上面日誌中的 vscode 配置到 launch.json,單擊“開始除錯”即可。

然後在終端重新以除錯模式啟動 php 函式即可開始除錯:

fun local invoke -d 3000 php72

Event 事件源

函式計算提供了豐富的觸發器,包括但不侷限於物件儲存觸發器、日誌服務觸發器、CDN 事件觸發器等。在本地無論是執行還是除錯函式時,為了能夠完全模擬線上環境,通常需要構造觸發事件。

觸發事件可以是一段可讀的 json 配置,也可以是一段非可讀的二進位制資料。這裡我們拿 json 舉例,假設觸發事件內容為:

{
    "testKey": "testValue"
}

想要將這段事件內容傳給函式,可以通過以下三種途徑:

  1. 管道: echo '{"testKey": "testValue"}' | fun local invoke nodejs8
  2. 檔案: 將的 json 內容寫入到檔案,檔名隨意,比如 event.json。然後通過 -e 指定檔名:fun local invoke -e event.json nodejs8
  3. 重定向: fun local invoke nodejs8 < event.json 或者 fun local invoke nodejs8 <<< '{"testKey": "testValue"}' 等等。更多資訊可以參考這篇文章

環境變數

在 template.yml 中配置的 EnvironmentVariables 會與線上行為一致,當函式執行時,可以通過程式碼獲取到。更多資訊參考

在本地執行函式時,除了 EnvironmentVariables 配置的環境變數,fun 還會額外提供一個 local=true 的環境變數,用來標識這是一個本地執行的函式。

通過這個環境變數,使用者可以區分是本地執行還是線上執行,以便於進行一些特定的邏輯處理。

Initializer

在 template.yml 中配置的 Initializer 屬性會與線上行為一致,當函式執行時,會首先執行 Initializer 指定的方法。Initializer 更多資訊 參考

Credentials

使用者可以通過 Credentials 中儲存的 ak 資訊訪問阿里雲的其他服務。Fun local 在本地執行函式時,會按照與 fun deploy 相同的 策略 尋找 ak 資訊。

關於函式計算 Credentials 的描述,可以參考

以下是一個根據本地、線上環境的不同,利用函式提供的 Credentials 配置 oss client 的例子:

local = bool(os.getenv('local', ""))
if (local):
    print 'thank you for running function in local!!!!!!'
    auth = oss2.Auth(creds.access_key_id,
                     creds.access_key_secret)
else:
    auth = oss2.StsAuth(creds.access_key_id,
                        creds.access_key_secret,
                        creds.security_token)

附錄

程式碼

本文講解涉及到的 demo 程式碼,託管在 github 上。專案目錄結構如下:

.
├── java8
│   ├── pom.xml
│   ├── src
│   │   └── main
│   │       └── java
│   │           └── example
│   │               └── App.java
│   └── target
│       └── demo-1.0-SNAPSHOT.jar
├── nodejs6
│   └── index.js
├── nodejs8
│   └── index.js
├── php7.2
│   └── index.php
├── python2.7
│   └── index.py
├── python3
│   └── index.py
└── template.yml

template.yml 定義了函式計算模型,其中定義了一個名為 localdemo 的服務,並在該服務下,定義了 6 個函式,名稱分別是 nodejs6、nodejs8、php72、python27、python3、java8。它們對應的程式碼目錄由 template 中的 CodeUri 定義,分別位於 nodejs6、nodejs8、php7.2、python2.7、python3、java8 目錄。

更多參考

  1. Fun Repo
  2. Fun specs
  3. Fun examples
  4. Fun 釋出 2.0 新版本啦