{{ selected_gold.title }}

  • ¥ {{ selected_gold.original_price }}
  • ¥ {{ selected_gold.price }}
  • {{ selected_gold.number_of_order }} 人订阅
  • {{ selected_gold.total_likers_count }}
    由 {{ selected_gold.creator_name }} 编辑

    {{ title }}

    请登录购买({{ selected_gold.price }}元),即可解锁剩余教程
    点击购买

    • Air
    • 2020年11月12日

    AWS Lambda服务的最佳实践

    AWS Lambda是一款计算服务。有了这个款服务研发人员就无需准备以及管理服务器就可以运行代码。研发人员使用Lambda服务创建Lambda function,并为之选择运行时环境,然后将写好的代码上传到S3中供Lambda function使用。Lambda function只有需要的时候才会被调用。Lambda服务会启动一个Lambda Container来加载并执行Lambda Function。每当请求的数量变多之后,Lambda服务会启动多个Lambda Container来运行和执行Lambda Function。只有Lambda function运行的时候才会产生费用,费用的计算是根据运行的时间运行的内存来计算的。

    接下来本章内容将以Go语言为例子来说明如何高效使用Lambda服务。本章内容将分解成以下章节:

    1. 准备Go环境、AWS CLI和AWS 账号
    2. 创建和使用Lambda Function
    3. 设计ServerLess服务
    4. 为ServerLess服务添加数据库
    5. CICD上的ServerLess服务
    6. 测试、监控以及调试ServerLess服务
    7. 从成本角度设计性价比高的ServerLess服务
    8. 用CloudFormation来驱动ServerLess服务

    准备Go环境、AWS CLI和AWS 账号

    AWS Lambda支持Go语言编写的程序。Go语言是支持跨平台的,历史包袱小,是Google专门为大规模、分布式的应用准备的。除此之外,它在异步IO方面做的很好,非常适合服务器端的高并发服务应用。在使用Lambda function之前,首先需要在本机上准备开发环境。本节内容将演示如何准备Go环境、如何安装AWS的CLI以及为其配置AWS账号之类的步骤。

    1. 这里安装Go环境
    2. 安装Python 3.x,并键入以下指令来安装AWS CLI
    pip install awscli
    

    在本地安装Go环境是为了能够编译和执行go程序;在本地安装AWS CLI是为了能够在命令行里创建AWS资源。

    当Go环境安装好之后,键入以下命令:

    go version
    

    会得到以下结果:

    go version go1.13.4 windows/amd64
    

    当AWS CLI安装好之后,键入以下命令:

    aws --version
    

    会得到以下结果:

    aws-cli/1.16.296 Python/3.6.5 Windows/10 botocore/1.13.32
    

    为了能够使用AWS CLI,首先要为其配置一个AWS账号。这个账号一般会根据使用者的角色来限制其权限,比如这章内容主要涉及Lambda服务,因此这个账号应该只有与Lambda服务相关的权限。接下来登录AWS root账号,使用IAM服务创建一个用户:digoldslambda,并为其添加AWSLambdaFullAccess策略,最后生成密钥信息。这一步主要是生成了一个用户digoldslambda,该用户能够使用Lambda服务,AWS CLI可以使用该用户的账号密码来使用Lambda服务。

    • 使用IAM服务创建用户:

    • 为该用户添加使用Lambda服务的权限:

    • 检查生成用户的信息:

    • 查看密钥信息:

    有了密钥信息(Acess Key ID和Secret access key)就可以键入以下命令来进行以下配置:

    aws configure
    

    根据提示输入密钥信息:

    AWS Access Key ID [None]: ******
    AWS Secret Access Key [None]: ******
    Default region name [None]: ap-southeast-1
    Default output format [None]:
    

    键入以下命令测试是否能操作Lambda服务:

    aws lambda list-functions
    

    返回的结果如下:

    {
        "Functions": []
    }
    

    以上结果说明AWS CLI中配置的密钥是有效的。在之后的操作中关于Lambda服务的操作都会在AWS CLI中完成。配置完成之后,接下来让我们编写一个Go文件,并且将其应用到Lambda Function上。

    创建和使用Lambda Function

    Lambda function是Lambda服务的基础单元。我们要在本地写一个Go程序,编译、运行,测试这个程序,然后使用AWS CLI创建Lambda Function并将该程序上传。

    为了能够高效地编写Lambda程序,需要在本地安装AWS提供的Lambda框架(GO语言版本)以及选择合适的代码编辑器,这里我选择VSCODE。将开发环境准备好之后,接下来用Go编写一个Lambda程序,并创建对应的Lambda function。

    安装Go语言版的Lambda框架:

    go get github.com/aws/aws-lambda-go/lambda
    

    打开VSCode,并编写以下程序代码:

    get_all_movies.go

    package main
    
    import (
        "encoding/json"
    
        "github.com/aws/aws-lambda-go/events"
        "github.com/aws/aws-lambda-go/lambda"
    )
    
    var movies = []struct {
        ID   int    `json:"id"`
        Name string `json:"name"`
    }{
        {
            ID:   1,
            Name: "Avengers",
        },
        {
            ID:   2,
            Name: "Ant-Man",
        },
        {
            ID:   3,
            Name: "Thor",
        },
        {
            ID:   4,
            Name: "Hulk",
        }, {
            ID:   5,
            Name: "Doctor Strange",
        },
    }
    
    func findAll() (events.APIGatewayProxyResponse, error) {
        response, err := json.Marshal(movies)
        if err != nil {
            return events.APIGatewayProxyResponse{}, err
        }
    
        return events.APIGatewayProxyResponse{
            StatusCode: 200,
            Headers: map[string]string{
                "Content-Type": "application/json",
            },
            Body: string(response),
        }, nil
    }
    
    func main() {
        lambda.Start(findAll)
    }
    

    运行一下命令创建一个只能被Lambda服务使用的角色(Role)Log2cloudwatchFromLambdaRole

    aws iam create-role --role-name Log2cloudwatchFromLambdaRole --assume-role-policy-document file://LambdaPolicy.json
    

    成功运行之后会返回以下结果:

    {
        "Role": {
            "Path": "/",
            "RoleName": "Log2cloudwatchFromLambdaRole",
            "RoleId": "AROA3GVZIQ5BR2VHM2DH7",
            "Arn": "arn:aws:iam::770261419843:role/Log2cloudwatchFromLambdaRole",
            "CreateDate": "2019-12-05T08:12:07Z",
            "AssumeRolePolicyDocument": {
                "Version": "2012-10-17",
                "Statement": [
                    {
                        "Effect": "Allow",
                        "Principal": {
                            "Service": "lambda.amazonaws.com"
                        },
                        "Action": "sts:AssumeRole"
                    }
                ]
            }
        }
    }
    

    接着执行以下命令为这个角色添加操作CloudWatch的行为:

    aws iam put-role-policy --role-name Log2cloudwatchFromLambdaRole --policy-name LogToCloudWatchPolicy --policy-document file://Log2CloudWatchPolicy.json
    

    到目前为止,我们创建了一个Role,名字叫Log2cloudwatchFromLambdaRole。通过LambdaPolicy.json文件,我们规定这个角色只能由Lambda 服务使用;通过Log2CloudWatchPolicy.json,我们规定这个角色只能对CloudWatch进行以下3种操作:

    "logs:CreateLogStream",
    "logs:CreateLogGroup",
    "logs:PutLogEvents"
    

    角色创建完之后接下来就要创建Lambda function,这个Lambda function需要依赖前面创建的角色Log2cloudwatchFromLambdaRole。运行以下指令创建Lambda function并把压缩好的可执行文件上传到Lambda function:

    aws lambda create-function --function-name GetAllMovies --zip-file fileb://get_all_movies.zip --runtime go1.x --handler get_all_movies --role arn:aws:iam::770261419843:role/Log2cloudwatchFromLambdaRole --region ap-southeast-1
    

    运行结束后返回的结果如下:

    {
        "FunctionName": "GetAllMovies",
        "FunctionArn": "arn:aws:lambda:ap-southeast-1:770261419843:function:GetAllMovies",
        "Runtime": "go1.x",
        "Role": "arn:aws:iam::770261419843:role/Log2cloudwatchFromLambdaRole",
        "Handler": "get_all_movies",
        "CodeSize": 5081020,
        "Description": "",
        "Timeout": 3,
        "MemorySize": 128,
        "LastModified": "2019-12-05T08:36:04.790+0000",
        "CodeSha256": "OYZFYVLUVvVg4rbKPJAfTsa+bmqxxh5Lo029YZTapFw=",
        "Version": "$LATEST",
        "TracingConfig": {
            "Mode": "PassThrough"
        },
        "RevisionId": "15353281-cd23-49e5-a7c7-281a055ed7f3",
        "State": "Active",
        "LastUpdateStatus": "Successful"
    }
    

    执行以下命令调用Lambda function:

    aws lambda invoke --function-name GetAllMovies result.json
    

    调用结束返回以下结果:

    {
        "StatusCode": 200,
        "ExecutedVersion": "$LATEST"
    }
    

    文件result.json的内容如下:

    {
        "statusCode": 200,
        "headers": {
            "Content-Type": "application/json"
        },
        "multiValueHeaders": null,
        "body": "[{\"id\":1,\"name\":\"Avengers\"},{\"id\":2,\"name\":\"Ant-Man\"},{\"id\":3,\"name\":\"Thor\"},{\"id\":4,\"name\":\"Hulk\"},{\"id\":5,\"name\":\"Doctor Strange\"}]"
    }
    

    运行一下命令创建非关系型数据库movies

    aws dynamodb create-table --table-name movies --attribute-definitions AttributeName=ID,AttributeType=S --key-schema AttributeName=ID,KeyType=HASH --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5
    

    运行成功的输出结果如下:

    {
        "TableDescription": {
            "AttributeDefinitions": [
                {
                    "AttributeName": "ID",                "AttributeType": "S"
                }        ],
            "TableName": "movies",
            "KeySchema": [
                {
                    "AttributeName": "ID",
                    "KeyType": "HASH"
                }
            ],
            "TableStatus": "CREATING",
            "CreationDateTime": 1575602497.644,
            "ProvisionedThroughput": {
                "NumberOfDecreasesToday": 0,
                "ReadCapacityUnits": 5,
                "WriteCapacityUnits": 5
            },
            "TableSizeBytes": 0,
            "ItemCount": 0,
            "TableArn": "arn:aws:dynamodb:ap-southeast-1:770261419843:table/movies",
            "TableId": "77757eb9-9c69-4481-891a-2cab520963c0"
        }
    }
    

    到目前为止,我们已经创建了一个非关系型数据库movies,为了能让Lambda Function GetAllMovies访问这个数据库,需要向之前的角色Log2cloudwatchFromLambdaRole追加Scan movies的权限。接下来复制以下代码到get_all_movies.go并编译更新到之前创建的Lambda function。最后向Lambda function GetAllMovies添加一个环境变量(TABLE_NAME,movies),因为更新的代码需要用到这个环境变量。

    package main
    
    import (
        "context"
        "encoding/json"
        "net/http"
        "os"
    
        "github.com/aws/aws-lambda-go/events"
        "github.com/aws/aws-lambda-go/lambda"
        "github.com/aws/aws-sdk-go-v2/aws"
        "github.com/aws/aws-sdk-go-v2/aws/external"
        "github.com/aws/aws-sdk-go-v2/service/dynamodb"
    )
    
    type Movie struct {
        ID   string `json:"id"`
        Name string `json:"name"`
    }
    
    func findAll() (events.APIGatewayProxyResponse, error) {
        cfg, err := external.LoadDefaultAWSConfig()
        if err != nil {
            return events.APIGatewayProxyResponse{
                StatusCode: http.StatusInternalServerError,
                Body:       "Error while retrieving AWS credentials",
            }, nil
        }
    
        svc := dynamodb.New(cfg)
        req := svc.ScanRequest(&dynamodb.ScanInput{
            TableName: aws.String(os.Getenv("TABLE_NAME")),
        })
        res, err := req.Send(context.Background())
        if err != nil {
            return events.APIGatewayProxyResponse{
                StatusCode: http.StatusInternalServerError,
                Body:       "Error while scanning DynamoDB",
            }, nil
        }
    
        movies := make([]Movie, 0)
        for _, item := range res.Items {
            movies = append(movies, Movie{
                ID:   *item["ID"].S,
                Name: *item["Name"].S,
            })
        }
    
        response, err := json.Marshal(movies)
        if err != nil {
            return events.APIGatewayProxyResponse{
                StatusCode: http.StatusInternalServerError,
                Body:       "Error while decoding to string value",
            }, nil
        }
    
        return events.APIGatewayProxyResponse{
            StatusCode: 200,
            Headers: map[string]string{
                "Content-Type": "application/json",
            },
            Body: string(response),
        }, nil
    }
    
    func main() {
        lambda.Start(findAll)
    }
    

    测试、监控以及调试ServerLess服务

    在本地测试Lambda function(Unit test,集成测试(SAM),Load test)。