将本地数据库迁移到服务器
我将引导您完成将现有Go API转换为无服务器并将其部署到具有AWS Severless Application Model(SAM)的 AWS Lambda和API Gateway的过程。 整个过程应少于10分钟。 让我们开始吧!
我们的示例API使用HttpRouter
包,因此我们首先安装它。
$ go get github.com/julienschmidt/httprouter
我们定义了一个HTTP处理程序,它将返回带有ok
主体的200 HTTP响应。
# handlers.go
package main
import "net/http"
func HealthHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
}
我们进入应用程序的入口点,即main
功能,将HealthHandler
到/healthz
路由,并在端口8080
上侦听HTTP请求。
# main.go
package main
import (
"fmt"
"log"
"net/http"
"github.com/julienschmidt/httprouter"
)
const (
serverPort = 8000
)
func main() {
router := httprouter.New()
router.Handler("GET", "/healthz", http.HandlerFunc(goserverlessapi.HealthHandler))
fmt.Printf("Server listening on port: %d\n", serverPort)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", serverPort), router), nil)
}
让我们在本地构建并运行此程序以检查一切是否正常。
$ go build -o go-serverless-api && ./go-serverless-api
Server listening on port: 8080
为了部署到无服务器后端,我们需要能够处理来自AWS Lambda的请求。 Lambda函数与常规HTTP处理程序具有不同的签名。 试想一下,如果我们的应用程序中只有一个而不是数百个。 我们将不得不手动更新所有功能并重新编写测试,这样做将丧失我们部署到非无服务器后端的能力。
有一种解决方案可以避免上述所有问题。 我们将仅为AWS Lambda创建修改后的入口点。 使用gateway
包,我们将net/http
的ListenAndServe
gateway.ListenAndServe
,它将把AWS Lambda提供的有效负载转换为HTTP处理程序接受的*http.Request
类型。
为了实现第二个切入点,我们需要重新组织项目。 我们将为AWS Lambda创建一个目录,并将原始入口点也移至新文件夹。
# original entrypoint moved to new location
$ mkdir -p cmd/go-serverless-api
# new entrypoint for lambda
$ mkdir -p cmd/go-serverless-api-lambda
我们将main.go
文件复制到每个新目录中,然后将其从项目的根目录中删除。
$ cp main.go cmd/go-serverless-api
$ cp main.go cmd/go-serverless-api-lambda
$ rm main.go
我们项目根目录中的软件包将不再是main
软件包(Go用来运行您的应用程序)。 我们将其重命名为goserverlessapi
以便我们可以将其作为库导入到我们的新入口点中,这两个入口点都将成为main
包。
$ grep -l 'package main' *.go | xargs sed -i 's/package main/package goserverlessapi/g'
简单查找并替换为项目根目录中的Go文件后,您的handlers.go
应如下所示。
# handlers.go
package goserverlessapi
import "net/http"
func HealthHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
}
现在,我们需要更新原始入口点,以导入goserverlessapi
软件包,并HealthHandler
导出HealthHandler
函数。 请注意,您将需要修改goserverlessapi
软件包的导入路径,以匹配$GOPATH
项目根目录的位置。 本教程中使用的github上的示例应用程序的正确路径是github.com/techjacker/go-serverless-api
。
# cmd/go-serverless-api/main.go
package main
import (
"fmt"
"log"
"net/http"
"github.com/julienschmidt/httprouter"
"github.com/techjacker/go-serverless-api"
)
const (
serverPort = 8080
)
func main() {
router := httprouter.New()
router.Handler("GET", "/healthz", http.HandlerFunc(goserverlessapi.HealthHandler))
fmt.Printf("Server listening on port: %d\n", serverPort)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", serverPort), router), nil)
}
接下来,更新AWS Lambda入口点。 我们将使用gateway
包,该gateway
包在AWS Lambda和API网关上运行时可替代Go net / http,因此让我们先安装它。
$ go get github.com/apex/gateway
然后将AWS Lambda入口点文件更新为以下内容。
# cmd/go-serverless-api-lambda/main.go
package main
import (
"log"
"net/http"
"github.com/apex/gateway"
"github.com/julienschmidt/httprouter"
"github.com/techjacker/go-serverless-api"
)
func main() {
router := httprouter.New()
router.Handler("GET", "/healthz", http.HandlerFunc(goserverlessapi.HealthHandler))
log.Fatal(gateway.ListenAndServe("", router), nil)
}
我们已经为导入添加了gateway
,并将其替换为http.ListenAndServe
。 该端口值在Lambda上下文中是冗余的,并且gateway
程序包将其丢弃,因此我们可以安全地删除端口常量,并用空字符串替换它。 另外,我们从go-serverless-api
HealthHandler
go-serverless-api
程序包(项目根目录中的程序包,以前是我们的主程序包)中包含了我们的/healthz
作为/healthz
路径的处理程序。
让我们再次构建并运行原始的HTTP API。
$ go build \
-o go-serverless-api \
./cmd/go-serverless-api
$ ./go-serverless-api
Server listening on port: 8080
打开另一个终端窗口并进行查询。
$ curl -s http://localhost:8080/healthz ok
一切仍然有效!
让我们对AWS Lambda版本执行相同的操作。
$ go build \
-o go-serverless-api-lambda \
./cmd/go-serverless-api-lambda
$ ./go-serverless-api-lambda
再次,打开一个新终端并进行查询。
$ curl -s http://localhost:8080/healthz
http: error: ConnectionError: HTTPConnectionPool(host='localhost', port=8080): Max retries exceeded with url: /healthz (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 111] Connection refused',)) while doing GET request to URL: http://localhost:8080/healthz
不必担心会出现此错误,因为AWS Lambda版本不监听HTTP连接,而是期望被提供APIGatewayProxyRequest
类型。
构建适用于Linux的二进制文件,Linux是AWS Lambda使用的操作系统。
$ GOOS=linux go build \
-o go-serverless-api-lambda \
./cmd/go-serverless-api-lambda
AWS Lambda要求将功能代码捆绑到zip中,因此让我们继续压缩二进制文件。
$ zip go-serverless-api-lambda.zip go-serverless-api-lambda
在最后一步-部署中,我们将使用创建的go-serverless-api-lambda.zip
。
我看过一些教程,这些教程使用AWS CLI工具通过临时命令部署到AWS Lambda。 这绝对是错误的方法! 您应该像应用程序的其他所有方面一样自动化基础结构。 部署的行业标准是Terraform或AWS Cloudformation 。 两者都为您提供了一种声明性的方式来构建基础结构。 您可以将此配置保存到提交到存储库的YAML / JSON(Cloudformation)或HCL(Terraform)文件中。 这样处理的问题是您必须处理堆栈的所有低层详细信息。 如果我们能用不到20行代码而不是数百行代码来高层描述基础架构,那将是很好的。 输入AWS Severless Application Model(SAM) 。
SAM是由Amazon牵头的新标准,旨在使无服务器基础架构的部署更简单,更简洁。 SAM是一个开放源代码规范— 请参阅完整的参考指南 。 希望其他云供应商将来会采用此方法,并且有可能通过单一配置无缝部署到多个云。
将以下YAML文件添加到项目的根目录。 这是一个SAM模板,用于配置运行您的Go应用程序的AWS Lambda函数,并将其部署在AWS API Gateway提供的HTTP接口后面。
# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: 'Boilerplate Go API.'
Resources:
GoAPI:
Type: AWS::Serverless::Function
Properties:
Handler: go-serverless-api-lambda
Runtime: go1.x
CodeUri: ./go-serverless-api-lambda.zip
Events:
Request:
Type: Api
Properties:
Method: ANY
Path: /{proxy+}
Type: AWS::Serverless::Function
行Type: AWS::Serverless::Function
创建一个Lambda函数,该函数由我们之前构建的二进制文件处理( Handler: go-serverless-api-lambda
)。 此Lambda函数可以响应由其他AWS服务触发的任何事件,例如由AWS S3和Kinesis触发的事件。 该文档包含事件源的完整列表 。 在我们的例子中,我们希望它通过API网关响应HTTP请求,因此我们将事件设置为Type: Api
。 SAM为我们隐式创建一个API网关,作为其一部分,然后我们将方法设置为ANY
以配置为响应任何类型的HTTP请求。 我们通过添加Path: /{proxy+}
来告诉我们的API处理下面的所有路径,包括根。
我们仍然需要将包含Go二进制文件的zip上传到AWS S3。 确保您已创建一个S3存储桶,可以接收我们的邮政编码。 这是您要手动执行的一次性操作。
$ aws s3 mb s3://my-bucket
以下命令将上载zip并创建打包的SAM模板。
$ aws cloudformation package \
--template-file template.yaml \
--s3-bucket my-bucket \
--output-template-file packaged-template.yaml
现在,您应该具有一个指向上传的zip的packaged-template.yaml
文件。
# packaged-template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: 'Boilerplate Go API.'
Resources:
GoAPI:
Type: AWS::Serverless::Function
Properties:
Handler: go-serverless-api-lambda
Runtime: go1.x
CodeUri: s3://my-bucket/8982639e71e0d433cd99f9fa4207ecbe
Events:
Request:
Type: Api
Properties:
Method: ANY
Path: /{proxy+}
现在,让我们使用这个新的打包模板进行部署。
$ aws cloudformation deploy \
--template-file packaged-template.yaml \
--stack-name go-serverless-api-stack \
--capabilities CAPABILITY_IAM
--capabilities CAPABILITY_IAM
必须使用--capabilities CAPABILITY_IAM
标志才能为您创建堆栈,因为这将涉及修改IAM权限-AWS强制您将其明确设置为安全措施。 在内部,SAM模板被编译成一个常规的cloudformation模板,该模板长了数百行。 所有这些对用户都是完全隐藏的(尽管您可以随意检查编译的cloudformation模板)。
为了发现我们部署的API的端点,我们需要找出API Gateway REST id
。
$ aws apigateway get-rest-apis
{
"items": [
{
"id": "0qu18x8pyd",
"name": "go-serverless-api-stack",
"createdDate": 1523987269,
"version": "1.0",
"apiKeySource": "HEADER",
"endpointConfiguration": {
"types": [
"EDGE"
]
}
}
]
}
AWS API Gateway地址采用以下格式。
https://<api-rest-id>.execute-api.<your-aws-region>.amazonaws.com/<api-stage>
阶段是Amazon进行部署的术语。 SAM为您创建两个不同的阶段: Stage
和Prod
。 注意URL中也使用的标题大小写! 我认为,AWS忘了,大家都叫他们的测试环境Staging
没有Stage
,但没关系!
因此,SAM在以下位置为我们设置了端点。
https://0qu18x8pyd.execute-api.eu-west-1.amazonaws.com/Stage https://0qu18x8pyd.execute-api.eu-west-1.amazonaws.com/Prod
让我们调用我们的API。
$ curl -s https://0qu18x8pyd.execute-api.eu-west-1.amazonaws.com/Prod {"message":"Missing Authentication Token"}
不必惊慌! 当您向根资源请求但未为其定义处理程序时,这是标准的API网关错误 。 我们定义的唯一处理程序是/healthz
,所以让我们尝试一下。
$ curl -s https://0qu18x8pyd.execute-api.eu-west-1.amazonaws.com/Prod/healthz ok
瞧! 我们的API现在由无服务器后端提供支持。
本教程的完整代码可在github上找到 。
我将很快在Go和Serverless上发布更多文章。 跟随我在Twitter上获取通知
最初发布于 andrewgriffithsonline.com 。
翻译自: https://hackernoon.com/how-to-migrate-a-go-api-to-serverless-in-under-10-mins-e27b8a31202e
将本地数据库迁移到服务器