Hướng dẫn tạo Serverless RESTful API với NodeJS và AWS
Bài viết này mình xin hướng dẫn cho người mới bắt đầu về cách sử dụng AWS CloudFormation và Lambda để triển khai một API RESTful đơn giản (và có Serverless). Serverless là gì? Thuật ngữ Serverless (a.k.a. Chức năng-as-a-Service) mô tả loại kiến trúc cho phép mã code được triển khai và chạy ...
Bài viết này mình xin hướng dẫn cho người mới bắt đầu về cách sử dụng AWS CloudFormation và Lambda để triển khai một API RESTful đơn giản (và có Serverless).
Serverless là gì?
Thuật ngữ Serverless (a.k.a. Chức năng-as-a-Service) mô tả loại kiến trúc cho phép mã code được triển khai và chạy trên các vùng chứa tạm thời và không có trạng thái từ các nhà cung cấp bên thứ ba (ví dụ: Azure hoặc AWS).
Lợi ích của Serverless
Giảm quản lý hoạt động. Kiến trúc Serverless cho phép các nhà phát triển tập trung vào việc viết mã và không phải lo lắng về cấu hình và quản lý cơ sở hạ tầng mà mã của họ chạy. Dễ dàng mở rộng quy mô. Vì các chức năng "Không có Serverless" (a.k.a. các ứng dụng không có Serverless) là trạng thái không xác định và luôn được viện dẫn bởi một sự kiện (ví dụ: yêu cầu HTTP), bạn có thể chạy nhiều, hoặc ít, các chức năng theo nhu cầu của bạn. Tùy thuộc vào quy mô và hình dạng của lưu lượng truy cập của bạn, điều này rất hiệu quả về chi phí vì các chức năng của Serverless thường được tính cho mỗi lần gọi.
Nhược điểm của Serverless
Độ trễ cho các yêu cầu khi khởi tạo. Nếu chức năng Serverless không hoạt động (ví dụ như đã không được chạy trong một khoảng thời gian), thì việc xử lý lời gọi đầu tiên có thể cần thêm thời gian để hoàn thành vì phải khởi tạo (tức là phân bổ máy chủ lưu trữ, mã tải, v.v.). Thiếu kiểm soát hệ thống. Vì mã của bạn đang chạy trong môi trường được quản lý bởi nhà cung cấp, bạn sẽ không thể kiểm soát việc nâng cấp hệ thống hoặc phụ thuộc bên ngoài cơ sở mã của bạn.
CloudFormation là gì?
CloudFormation là một dịch vụ từ Amazon cho phép bạn xây dựng tài nguyên AWS sử dụng các mẫu (template). Mẫu là tệp cấu hình (YML hoặc JSON) để cung cấp tất cả các tài nguyên AWS của bạn như các trường hợp EC2, các bảng DynamoDB, vai trò và quyền IAM, hoặc bất cứ điều gì khác.
Trong hướng dẫn này, chúng ta sẽ tạo một API RESTful đơn giản với hai điểm cuối sau:
- POST /users/$${userId}/hello
- GET /users/$${userId}/hello
Có 2 tệp mà bạn cần cho hướng dẫn này: index.js (mã NodeJS cho hàm Lambda) và stack.yml (mẫu ngăn xếp CloudFormation)
// index.js "use strict"; var AWS = require('aws-sdk'); // Get "Hello" Dynamo table name. Replace DEFAULT_VALUE // with the actual table name from your stack. const helloDBArn = process.env['HELLO_DB'] || 'DEFAULT_VALUE'; //'Mark-HelloTable-1234567'; const helloDBArnArr = helloDBArn.split('/'); const helloTableName = helloDBArnArr[helloDBArnArr.length - 1]; // handleHttpRequest is the entry point for Lambda requests exports.handleHttpRequest = function(request, context, done) { try { const userId = request.pathParameters.userId; let response = { headers: {}, body: ', statusCode: 200 }; switch (request.httpMethod) { case 'GET': { console.log('GET'); let dynamo = new AWS.DynamoDB(); var params = { TableName: helloTableName, Key: { 'user_id' : { S: userId } }, ProjectionExpression: 'email' }; // Call DynamoDB to read the item from the table dynamo.getItem(params, function(err, data) { if (err) { console.log("Error", err); throw `Dynamo Get Error (${err})` } else { console.log("Success", data.Item.email); response.body = JSON.stringify(data.Item.email); done(null, response); } }); break; } case 'POST': { console.log('POST'); let bodyJSON = JSON.parse(request.body || '{}'); let dynamo = new AWS.DynamoDB(); let params = { TableName: helloTableName, Item: { 'user_id': { S: userId }, 'email': { S: bodyJSON['email'] } } }; dynamo.putItem(params, function(error, data) { if (error) throw `Dynamo Error (${error})`; else done(null, response); }) break; } } } catch (e) { done(e, null); } }
// stack.yaml --- AWSTemplateFormatVersion: 2010-09-09 Description: API Gateway, Lambda, and Dynamo. Resources: # Policy required for all lambda function roles. BaseLambdaExecutionPolicy: Type: AWS::IAM::ManagedPolicy Properties: Description: Base permissions needed by all lambda functions. PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents - ec2:CreateNetworkInterface - ec2:DescribeNetworkInterfaces - ec2:DeleteNetworkInterface Resource: "*" HelloTable: Type: AWS::DynamoDB::Table Properties: ProvisionedThroughput: ReadCapacityUnits: 5 WriteCapacityUnits: 5 AttributeDefinitions: - AttributeName: user_id AttributeType: S KeySchema: - AttributeName: user_id KeyType: HASH # FIXME How to hook up custom domain? MyApiGateway: Type: AWS::ApiGateway::RestApi Properties: Name: !Sub "${AWS::StackName}-MyApiGateway" Description: A description FailOnWarnings: true Body: swagger: 2.0 info: description: | The account API. version: 1.0 basePath: / schemes: - https consumes: - application/json produces: - application/json paths: /users/{userId}/hello: get: description: TBD x-amazon-apigateway-integration: uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HelloLambda.Arn}/invocations" credentials: !GetAtt MyApiGatewayRole.Arn passthroughBehavior: when_no_match httpMethod: POST type: aws_proxy operationId: getHello parameters: - name: userId in: path description: TBD required: true type: string format: uuid post: description: TBD x-amazon-apigateway-integration: uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HelloLambda.Arn}/invocations" credentials: !GetAtt MyApiGatewayRole.Arn passthroughBehavior: when_no_match httpMethod: POST type: aws_proxy operationId: postHello parameters: - name: userId in: path description: TBD required: true type: string format: uuid - name: body in: body description: TBD required: true schema: type: object required: - email properties: email: type: string MyApiGatewayDeployment: Type: AWS::ApiGateway::Deployment Properties: RestApiId: !Ref MyApiGateway StageName: prod MyApiGatewayRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: apigateway.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: InvokeLambda PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - lambda:InvokeFunction Resource: - !GetAtt HelloLambda.Arn HelloLambda: Type: AWS::Lambda::Function Properties: Role: !GetAtt HelloLambdaRole.Arn # TODO Handler: index.handleHttpRequest Runtime: nodejs6.10 Environment: Variables: HELLO_DB: !Sub "arn:aws:dynamodb:${AWS::Region}:*:table/${HelloTable}" Code: ZipFile: | exports.handlers = function(event, context) {} HelloLambdaRole: # -> AppAPIRole Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - !Ref BaseLambdaExecutionPolicy Policies: - PolicyName: getHello PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - dynamodb:GetItem Resource: !Sub "arn:aws:dynamodb:${AWS::Region}:*:table/${HelloTable}" - PolicyName: putHello PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - dynamodb:PutItem Resource: !Sub "arn:aws:dynamodb:${AWS::Region}:*:table/${HelloTable}"Bước 2. Trong tập tin cấu hình CloudFormation
Chú ý đến stack.yml, nó là tập tin cấu hình sẽ được sử dụng bởi CloudFormation để tạo ra tất cả mọi thứ ứng dụng của chúng ta sẽ yêu cầu.
Dưới đây là sơ đồ chi tiết về tất cả các tài nguyên AWS của chúng ta trong stack.yml sẽ cần phải tạo. Tên được sử dụng trong YML nằm trong các ô màu đỏ.
Bước 3. Tạo Cloudformation StackSau khi kiểm tra YML, hãy tới https://console.aws.amazon.com/cloudformation và nhấp vào nút Create Stack. Chọn Tải lên mẫu lên Amazon S3 và tải tệp stack.yml lên.
Trên màn hình tiếp theo, bạn sẽ được yêu cầu chọn một tên Stack (có thể là bất cứ thứ gì). Sau đó, nhấp vào Tiếp theo và chọn Tôi thừa nhận rằng AWS CloudFormation có thể tạo tài nguyên IAM và nhấp vào Tiếp theo một lần nữa.
Tại thời điểm này, stack của bạn đang được tạo ra. Đợi một phút trên trang Stacks cho đến khi trạng thái ngăn xếp của bạn trở thành CREATE_COMPLETE.
Bước 4. Sao chép và dán mã vào LambdaMột khi ngăn xếp của bạn đã hoàn tất, hãy đi tìm Lambda mới của stack ở đây: https://console.aws.amazon.com/lambda. Tên hàm Lambda của bạn phải giống với $${StackName} -HelloLambda-XXXX
Bước 5. Tìm API Gateway của bạn và kiểm tra nếu nó hoạt độngTìm API Gateway được tạo bởi mẫu CloudFormation của bạn tại đây: https://console.aws.amazon.com/apigateway. Tên Gateway API của bạn phải giống với $${StackName} -MyApiGateway.
Sau khi bạn tìm thấy Cổng API của mình, chúng ta có thể kiểm tra xem mọi thứ đã được nối bằng cách chọn tùy chọn POST bên dưới / người dùng và sau đó nhấp vào TEST.
Trên trang Test page, thiết lập UserId là 123, và thiết lập Request Body theo sau và nhấp vào Test. Nếu mọi thứ đã hoạt động, Trạng thái phải là 200 mà không có dữ liệu
Sau khi kiểm tra điểm cuối POST, bạn có thể kiểm tra xem liệu dữ liệu của bạn đã được lưu bằng cách vào /hello GET Test page và thử một yêu cầu (nhớ thiết lập userId là 123). Kết quả => xem phần trên.
Bước 6. Deploy API GatewayBây giờ bạn đã xác minh rằng API Gateway của bạn, Lambda và DynamoDB được kết nối, bạn có thể deploy API Gateway của mình để có thể truy cập nó từ internet.
Để triển khai API của bạn, hãy chọn Actions menu và chọn Deploy. Khi cửa sổ bật lên xác nhận xuất hiện, hãy thiết lập giai đoạn triển khai và sau đó nhấp vào Deploy.
Bước 7. Gửi yêu cầu tới API của bạnKhi bạn đã triển khai API của mình, bạn sẽ được chuyển tiếp tới trang Stages. Ở đây bạn sẽ tìm thấy tên miền cho API Gateway của mình trong khu vực đánh dấu màu xanh lam bên cạnh Invoke URL.
Sử dụng URL từ ảnh chụp màn hình ở trên, bạn có thể gửi yêu cầu GET /users/123/hello trong trình duyệt web của bạn như dưới đây.
Và đó là nó. Bây giờ bạn đã có một API RESTful Serverless có khả năng mở rộng, reliabe....