前言
首先介绍下在本文出现的几个比较重要的概念:
函数计算(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 现已集成到 VSCode、IDEA、Pycharm 等 IDE 的图形化插件中,相较于命令行,这些插件往往通过图形化的方式带来更好的使用体验,因此:
- 如果您是 VSCode 用户,我们强烈推荐您直接使用 VSCode 插件。
- 如果您是 IDEA 或 Pycharm 用户,我们强烈推荐您直接使用 Cloud Toolkit 插件。
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 invoke function
fun local invoke 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 不同于解释型的语言,在作为函数运行前,需要先编译。在我们的例子中,可以直接使用 fun build 函数名
的方式,在我们的例子中,函数名为 java8
,因此编译命令如下:
fun build java8
可以看到 log:
using template: template.yml
start building function dependencies without docker
building localdemo/java8
running task flow MavenTaskFlow
running task: MavenCompileTask
running task: MavenCopyDependencies
running task: CopyMavenArtifacts
Build Success
Built artifacts: .fun/build/artifacts
Built template: .fun/build/artifacts/template.yml
Tips for next step
======================
* Invoke Event Function: fun local invoke
* Invoke Http Function: fun local start
* Deploy Resources: fun deploy
编译成功后,可以直接使用以下命令运行函数:
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 配置
- 创建 vscode launch.json 文件
- 复制日志中的 config begin 与 config end 之间的配置到 launch.json 中。
- 完成上面配置后,在 Debug 视图可以看到配置的函数列表。
至此,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 Hat
、Debugger 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"
}
想要将这段事件内容传给函数,可以通过以下三种途径:
- 管道:
echo '{"testKey": "testValue"}' | fun local invoke nodejs8
- 文件: 将的 json 内容写入到文件,文件名随意,比如 event.json。然后通过 -e 指定文件名:
fun local invoke -e event.json nodejs8
- 重定向:
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 目录。