全部产品

使用 Custom Runtime 打造 Golang Runtime 示例

更新时间:2020-03-31 14:38:53

本文以 Golang Runtime 为例,介绍了使用 Custom Runtime打造自定义 runtime 的示例。

前提条件

HTTP 函数

基于 Custom Runtime 的 HTTP 函数应用可以开发或快速移植一个已有的 Web 应用, 下文演示了开发一个简单的 Web 应用过程。

1.编辑文件,并将文件编译成可执行文件。

  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "net/http"
  6. "os"
  7. )
  8. func handler(w http.ResponseWriter, req *http.Request) {
  9. requestID := req.Header.Get("x-fc-request-id")
  10. fmt.Println(fmt.Sprintf("FC Invoke Start RequestId: %s", requestID))
  11. defer func() {
  12. fmt.Println(fmt.Sprintf("FC Invoke End RequestId: %s", requestID))
  13. }()
  14. // your logic
  15. b, err := ioutil.ReadAll(req.Body)
  16. if err != nil {
  17. panic(err)
  18. }
  19. info := fmt.Sprintf("method = %+v;\nheaders = %+v;\nbody = %+v", req.Method, req.Header, string(b))
  20. w.Write([]byte(fmt.Sprintf("Hello, golang http invoke! detail:\n %s", info)))
  21. }
  22. func main() {
  23. fmt.Println("FunctionCompute go runtime inited.")
  24. http.HandleFunc("/", handler)
  25. port := os.Getenv("FC_SERVER_PORT")
  26. if port == "" {
  27. port = "9000"
  28. }
  29. http.ListenAndServe(":" + port, nil)
  30. }

2.可执行文件命名为 bootstrap, 然后将 bootstrap 文件打包成 zip 文件,例如 code.zip

3.使用 fun 部署。

  1. rsong@iZbp1xxxxx:~# docker pull golang:1.12.9-stretch
  2. rsong@iZbp1xxxxx:~# docker run -it -v $(pwd):/tmp/code golang:1.12.9-stretch bash -c "go build -o /tmp/code/bootstrap /tmp/code/main.go"
  3. rsong@iZbp1xxxxx:~# zip code.zip bootstrap
  4. rsong@iZbp1xxxxx:~# fun deploy

template.yml 示例如下所示。

  1. ROSTemplateFormatVersion: '2015-09-01'
  2. Transform: 'Aliyun::Serverless-2018-04-03'
  3. Resources:
  4. CRService:
  5. Type: 'Aliyun::Serverless::Service'
  6. Properties:
  7. Description: 'custom runtime demo'
  8. hello:
  9. Type: 'Aliyun::Serverless::Function'
  10. Properties:
  11. Handler: index.handler
  12. CodeUri: ./code.zip
  13. Description: 'demo with custom runtime'
  14. Runtime: custom
  15. Events:
  16. http_t:
  17. Type: HTTP
  18. Properties:
  19. AuthType: ANONYMOUS
  20. Methods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD']
  21. goexample.abc.cn:
  22. Type: 'Aliyun::Serverless::CustomDomain'
  23. Properties:
  24. Protocol: HTTP
  25. RouteConfig:
  26. Routes:
  27. '/*':
  28. ServiceName: CRService
  29. FunctionName: hello

说明Handler 在此时没有实质意义, 填写任意的一个满足函数计算 Handler 字符集约束的字符串即可, 例如 index.handler。同时在没有设置自定义域名的情况下,直接使用函数计算 endpoint 的 URL 进行访问的时候, 路径格式为 /2016-08-15/proxy/$serviceName/$functionName/$yourRealPath

事件函数

事件函数调用也可以使用上文使用 Server 代码和逻辑绑定的方式去实现,但是每次编写函数都带上一堆 HTTP Server 的处理代码,这样代码出现冗余且不美观。下文示例基于一个简单的框架 Golang-Runtime 来编写函数,您只需要函数的 handler 和 initialize ,然后 main 函数里面将 handler 和 initialize 传入即可。

其中 handler 和 initialize 定义和官方其他 runtime 类似。

  1. func handler(ctx *FCContext, event []byte) ([]byte, error)
  2. func initialize(ctx *gr.FCContext) error

1.编辑文件,并将文件编译成可执行文件。

  1. package main
  2. import (
  3. "encoding/json"
  4. gr "github.com/awesome-fc/golang-runtime"
  5. )
  6. func initialize(ctx *gr.FCContext) error {
  7. fcLogger := gr.GetLogger().WithField("requestId", ctx.RequestID)
  8. fcLogger.Infoln("init golang!")
  9. return nil
  10. }
  11. func handler(ctx *gr.FCContext, event []byte) ([]byte, error) {
  12. fcLogger := gr.GetLogger().WithField("requestId", ctx.RequestID)
  13. _, err := json.Marshal(ctx)
  14. if err != nil {
  15. fcLogger.Error("error:", err)
  16. }
  17. fcLogger.Infof("hello golang!")
  18. return event, nil
  19. }
  20. func main() {
  21. gr.Start(handler, initialize)
  22. }

2.可执行文件命名为 bootstrap, 然后将 bootstrap 文件打包成 zip 文件,例如 code.zip

3.使用 fun 部署。

  1. rsong@iZbp1xxxxx:~# docker pull golang:1.12.9-stretch
  2. rsong@iZbp1xxxxx:~# docker run -it -v $(pwd):/tmp/code golang:1.12.9-stretch bash -c "go get github.com/awesome-fc/golang-runtime;go build -o /tmp/code/bootstrap /tmp/code/main.go"
  3. rsong@iZbp1xxxxx:~# zip code.zip bootstrap
  4. rsong@iZbp1xxxxx:~# fun deploy

template.yml 示例如下所示。

  1. ROSTemplateFormatVersion: '2015-09-01'
  2. Transform: 'Aliyun::Serverless-2018-04-03'
  3. Resources:
  4. CRService:
  5. Type: 'Aliyun::Serverless::Service'
  6. Properties:
  7. Description: 'custom runtime demo'
  8. hello:
  9. Type: 'Aliyun::Serverless::Function'
  10. Properties:
  11. Handler: index.handler
  12. Initializer: index.initializer
  13. CodeUri: ./code.zip
  14. Description: 'demo with custom runtime'
  15. Runtime: custom

注意

  • HandlerInitializer 在此时没有实质意义, 填写任意的一个满足函数计算 handlerInitializer 字符集约束的字符串即可, 例如 index.handlerindex.initializer

  • 如果不需要 initialize, 则不需要编写 initialize 函数,同时 main 函数里面的逻辑修改为下文的内容。同时创建函数的时候, 没有 initialize 参数。

    1. gr.Start(handler, nil)

Custom Runtime 的实现

x-fc-control-path 区分是 handler 还是 initialize

  1. controlPath := req.Header.Get(fcControlPath)
  2. if controlPath == "/initialize" {
  3. initializeHandler(w, req)
  4. } else {
  5. invokeHandler(w, req)
  6. }

construct param context and event

  1. ctx := &FCContext{
  2. RequestID: req.Header.Get(fcRequestID),
  3. Credentials: Credentials{
  4. AccessKeyID: req.Header.Get(fcAccessKeyID),
  5. AccessKeySecret: req.Header.Get(fcAccessKeySecret),
  6. SecurityToken: req.Header.Get(fcSecurityToken),
  7. },
  8. Function: FunctionMeta{
  9. Name: req.Header.Get(fcFunctionName),
  10. Handler: req.Header.Get(fcFunctionHandler),
  11. Memory: m,
  12. Timeout: t,
  13. Initializer: req.Header.Get(fcFunctionInitializer),
  14. InitializationTimeout: it,
  15. },
  16. Service: ServiceMeta{
  17. ServiceName: req.Header.Get(fcServiceName),
  18. LogProject: req.Header.Get(fcServiceLogProject),
  19. LogStore: req.Header.Get(fcServiceLogstore),
  20. Qualifier: req.Header.Get(fcQualifier),
  21. VersionID: req.Header.Get(fcVersionID),
  22. },
  23. Region: req.Header.Get(fcRegion),
  24. AccountID: req.Header.Get(fcAccountID),
  25. }
  1. event, err := ioutil.ReadAll(req.Body)
  2. if err != nil {
  3. panic(err)
  4. }

x-fc-status 和日志格式

  1. func invokeHandler(w http.ResponseWriter, req *http.Request) {
  2. requestID := req.Header.Get(fcRequestID)
  3. fmt.Println(fmt.Sprintf(fcLogTailStartPrefix, requestID))
  4. defer func() {
  5. if r := recover(); r != nil {
  6. w.Header().Set(fcStatus, "404")
  7. w.Write([]byte(fmt.Sprintf("Error: %+v;\nStack: %s", r, string(debug.Stack()))))
  8. }
  9. fmt.Println(fmt.Sprintf(fcLogTailEndPrefix, requestID))
  10. }()
  11. ...
  12. w.Write([]byte(resp))
  13. }
  • x-fc-status 用于 Custom Runtime 捕获函数代码逻辑异常。

    通过 404 告知函数计算系统函数执行发生错误, 这样函数计算最后会告知调用函数客户端一个 UnHandled Error, 和官方 runtime 行为一致。

  • 支持函数调用返回的响应携带函数执行日志。支持调用函数的时候,请求的头部携带 x-fc-log-type = “Tail”,那么函数的执行日志能通过响应的 x-fc-log-result 头部返回。

    说明FC Invoke Start RequestId: ${RequestId}FC Invoke End RequestId: ${RequestId} 这两条日志在每一个请求的开始和结束是必须要有的。

  • 推荐的日志格式, 带有 UTC 时间和 requestId。

  1. log = &logrus.Logger{
  2. Out: os.Stderr,
  3. Level: logrus.InfoLevel,
  4. Formatter: &UTCFormatter{
  5. TimestampFormat: "2006-01-02T15:04:05.999Z",
  6. LogFormat: "%time%: %requestId% [%lvl%] %msg%\n",
  7. },
  8. },
  9. }