本篇文章紀錄,從現有的 Express Proejct,在搬上 AWS Lambda 時解決 MySQL 連接所造成的問題。
首先,預備上 Lambda 的專案有以下功能:
- 原本就自帶 Express
- 使用 mysql client 初始化長連接
- RDS (MySQL 8)
- Lambda Function
- API Gateway
- CloudFormation
- 如果 serverless-offline 出錯,那就代表你本身的程式碼有問題,必須注意 serverless 是無狀態的,所以是每次執行 api 都會從 main.js 執行,然後執行到 module.export 的那些 function。
- 如果 serverless-offline 正常,那表示可能是 VPC / Security Group 設定錯誤,這會在最下面討論。
Express 修正為 Serverless Express
const express = require("express"); const mysql = require('mysql'); const https = require('https'); const bodyParser = require('body-parser'); const cors = require("cors"); const fs = require('fs'); const path = require('path'); const morgan = require('morgan'); var dov = require('dotenv').config(); // AWS Lambda serveless const sls = require('serverless-http') // Use connection pool to handle each query // https://stackoverflow.com/questions/54888061/configuration-mysql-node-js-error-can-not-enqueue-query-after-fatal-error var db = mysql.createPool({ host: host, //RDS Endpoint port: port, //MySQL: 3306 user: "admin", password: passowrd, database: default_db_name }); /* var db = mysql.createConnection({ host: host, //RDS Endpoint port: port, //MySQL: 3306 user: "admin", password: passowrd, database: default_db_name }); */ // you're using connection pool, no need to connect on initialization // connect to database /*db.connect((err) => { if (err) { console.log(err) throw err; } console.log('Connected to database'); });*/ global.db = db; app = express(); app.use(morgan('dev')); app.use(cors({ origin:["*"], "methods": "GET,HEAD,PUT,PATCH,POST,DELETE", "preflightContinue": false, "allowedHeaders": ['Content-Type', 'Authorization'], "optionsSuccessStatus": 204 })); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); //Router app.use('/', require('./myRouter')); // no need to specific listen //app.listen(8080) // serveless export usage module.exports.server = sls(app)
exports.getXXXData = (req,res) => { //First to activate and get connection from pool db.getConnection(function(err, connection) { //Callback connection object can be use to query connection.query(`SELECT * FROM XXX`,(err, result) => { //When done by query, release connection immediately connection.release(); if (!!err){ res.send({setting: err.message}); return; } res.send({setting: result}); }); }); }; /*exports.getXXXData = (req,res) => { db.query(`SELECT * FROM XXX`, (err, result) => { if (!!err){ res.send({setting: err.message}); return; } res.send({setting: result}); }); };*/
新增 AWS Lambda Config 檔 (serverless.yml)
service: Your Service Name provider: name: aws runtime: nodejs12.x stage: dev # api gateway stage region: us-east-2 #Ohio memorySize: 512 functions: app: handler: app.server # app.js exported module: `module.exports.server = sls(app)`, name is `server` events: - http: path: / method: ANY cors: true - http: # all routes get proxied to the Express router path: /{proxy+} method: ANY cors: true - http: path: getXXXData method: post cors: true plugins: - serverless-offline
離線測試與佈署
需要安裝的工具:
npm install serverless --save
npm install serverless-http --savenpm install serverless-mysql --save
npm install serverless-offline --savesudo chmod -R 777 ./
然後,開啟離線測試指令
SLS_DEBUG=* sls offline start
此時就會看到離線測試的 API 清單:
確認正常後,使用 deploy 指令就可以佈署到雲端 AWS Lambda。
SLS_DEBUG=* sls deploy
Lambda Function 上雲後,連上 RDS 必要步驟
RDS 開放白名單或暫時公開全域
開放指定或全域的 Traffic 或 TCP ,才能讓外部連線進來,先到 RDS 選擇到 Security Group,並且把 Inbound, Outbound 設定 All Traffic / 0.0.0.0/0 以及 All Traffic / ::/0。
詳情說明,可以看 Stackoverflow 上其中一篇討論: AWS: can't connect to RDS database from my machine。
總之, RDS 的 Security Group 必須要開放 Inbound, Outbound Rule,而且完全對外的話,一定要有:
- 0.0.0.0/0
- ::/0
Lambda 開啟 VPC 權限及連線 Policy
對 Lambda 來說,Lambda 本身無法訪問 RDS ,是受到 VPC 跟 Policy 限制,因此,需要打開 Security Group Inbound, Outbound Rule 以及必須讓 Lambda 擁有 RDS 權限 (AWSLambdaVPCAccessExecutionRole)。 則有以下必須確認的步驟:
- Lambda 以及 RDS 必須共用同一個 VPC (需自行確認)
- Lambda Security Group 有開通 Inbound, Outbound Rule
- Lambda Policy 套用到 AWSLambdaVPCAccessExecutionRole
首先,到 Lambda 上,選到目標的 Application,然後到 Permissions 進入 Execution role:
然後,在 Policy 頁面增加 AWSLambdaVPCAccessExecutionRole 這個權限:
然後,確認 Lambda 的 VPC 名稱後,到 EC2/VPC 底下,更改 Inbound, Outbound Rule:
詳情說明,可以看 Stackoverflow 上其中一篇討論: Error: connect ETIMEDOUT rds lambda 。
完成後,到 API Gateway 就可以正常測試:
Reference:
https://stackoverflow.com/questions/35880022/error-connect-etimedout-rds-lambda
https://stackoverflow.com/questions/37212945/aws-cant-connect-to-rds-database-from-my-machine
https://docs.aws.amazon.com/lambda/latest/dg/configuration-database.html
https://medium.com/@hk_it_er/create-lambda-and-api-gateway-nodejs-aws-serverless-to-rds-mysql-6a75243e61cc
https://github.com/awsdocs/aws-lambda-developer-guide/blob/master/sample-apps/rds-mysql/dbadmin/index.js
https://www.youtube.com/watch?v=cGYknt3xIvM
https://redstapler.co/aws-lambda-tutorial-rds-mysql/
https://aws.amazon.com/blogs/compute/using-amazon-rds-proxy-with-aws-lambda/
https://docs.aws.amazon.com/zh_tw/lambda/latest/dg/configuration-vpc.html
沒有留言:
張貼留言