使用GraphQL放大器来使用RDS
概述
目标概述:使用GraphQL和Amplify创建一个可以获取RDS数据的环境。
JS应用程序 -> GraphQL -> Lambda (JS) -> RDS代理 -> RDS
JavaScript应用程序使用GraphQL与Lambda函数进行通信,Lambda函数又通过RDS代理访问RDS。
由于几乎所有的东西都是第一次接触,所以可能会有一些奇怪的地方,但总之作为备忘录还是可以的。
余談:现在Lambda访问RDS已不再是反模式。
在线研讨会《RDS+Lambda 着手之际:过去的反模式将如何改变》所提供的资料,清楚地解释了之前被称为反模式的原因以及AWS所进行的改进。
没有安装RDS proxy,仍然可以访问RDS,但这正是反模式的一种解决方案,更详细的信息请参考上述链接。
前提是指一个事件或问题的基本条件或前置条件。
aws CLIコマンド使えるくらいの知識はある。
aws CLIコマンド ver.2 インストール済み
アクセスできるRDS (AuroraだがServerlessではない) がある。
フロントエンドアプリもLambdaもJS(ちなみに私はJS信者ではない)
概述步骤
-
- Amplifyの準備
-
- AmplifyでLambda Layerを作成
-
- AmplifyでLambda関数を作成
-
- Lambda関数にVPCアクセス権限を追加
-
- VPCアクセスを設定する
-
- GraphQL APIを作成する
-
- LambdaでGraphQLからのパラメータを使用する
-
- フロントエンドアプリからGraphQL APIを呼び出す
-
- RDS proxyを導入する
シークレットを作成
シークレットを使用するポリシーを作成
シークレットを使用するロールを作成
RDS proxyを作成する
RDS proxyを使用する
步骤
扩大 Amplify 的准备
建议在使用Amplify之前,对项目进行git管理以便于回滚源代码树或了解发生了什么。可以通过在执行amplify init命令之前对项目进行git管理来实现这一点。
首先,您可以通过本地的官方教程来安装Amplify的CLI,并创建应用程序的代码基础。在教程中,会演示如何创建项目,但如果您想在现有项目中使用Amplify,只需运行amplify init即可。
我先在这里提交一下。
在Amplify中创建Lambda层。
Lambda Layer是一块可由多个Lambda共享的代码块,在JavaScript中,类似于node_modules的库文件集合可以作为与Lambda函数不同的Lambda Layer创建。虽然可以将其包含在Lambda函数中,但若太大,Lambda控制台的编辑器将无法使用,因此最好将其分开以避免问题。
本次将在此层面中加入用于访问RDS的库,例如mysql2。
选择amplify add function命令,然后根据提示的问题逐步回答,创建Lambda layer的配置文件。
MacBook-Pro.local:amplify-js-app% amplify add function
? Select which capability you want to add: Lambda layer (shared code & resource used across functions)
? Provide a name for your Lambda layer: layer9da0e99d
? Choose the runtime that you want to use: NodeJS
? The current AWS account will always have access to this layer.
Optionally, configure who else can access this layer. (Hit <Enter> to skip)
✅ Lambda layer folders & files created:
amplify/backend/function/amplifyjsapplayer9da0e99d
Next steps:
Move your libraries to the following folder:
[NodeJS]: amplify/backend/function/amplifyjsapplayer9da0e99d/lib/nodejs
...
只需根据CLI输出的最后一行所示,在amplify/backend/function/amplifyjsapplayer9da0e99d/lib/nodejs中放置node_modules即可,然后在该目录下创建package.json并安装包。
在下面的例子中,还包含了用于测量RDS访问性能的AWS XRay软件包。如果不需要的话,不必安装 aws-xray-sdk-*。
{
"name": "rds-xray-js-lib",
"description": "NPM dependencies for RDS access with XRAY support",
"version": "1.0.0",
"private": true,
"dependencies": {
"aws-xray-sdk-core": "2.4.0",
"aws-xray-sdk-mysql": "2.4.0",
"mysql2": "2.1.0"
},
"scripts": {}
}
在包含package.json文件的目录中运行npm install命令以下载包。
在Amplify中创建一个Lambda函数。
这次,使用“amplify add function”命令来选择Lambda函数并创建Lambda函数的配置文件。
在这个时候,请指定先前设置的Lambda层。请注意要使用空格键进行选择。
此外,你还可以将用于访问RDS的数据库主机名、数据库名、用户名和密码作为环境变量进行注册。(虽然也可以使用IAM或系统管理器的密钥,但在此我们省略。似乎可以在此向导中的”Do you want to configure secret values this function can access?”选项中设置密钥。)
MacBook-Pro.local:amplify-js-app% amplify add function
? Select which capability you want to add: Lambda function (serverless function)
? Provide an AWS Lambda function name: amplifyjsapp92a6207c
? Choose the runtime that you want to use: NodeJS
? Choose the function template that you want to use: Hello World
Available advanced settings:
- Resource access permissions
- Scheduled recurring invocation
- Lambda layers configuration
- Environment variables configuration
- Secret values configuration
? Do you want to configure advanced settings? Yes
? Do you want to access other resources in this project from your Lambda function? No
? Do you want to invoke this function on a recurring schedule? No
? Do you want to enable Lambda layers for this function? Yes
? Provide existing layers or select layers in this project to access from this function (pick up to 5): amplifyjsapplayer9da0e99d
? Do you want to configure environment variables for this function? Yes
? Enter the environment variable name: db_host
? Enter the environment variable value: database.cluster-xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com
? Select what you want to do with environment variables: Add new environment variable
? Enter the environment variable name: db_schema
? Enter the environment variable value: my_database
? Select what you want to do with environment variables: Add new environment variable
? Enter the environment variable name: db_username
? Enter the environment variable value: my_user
? Select what you want to do with environment variables: Add new environment variable
? Enter the environment variable name: db_password
? Enter the environment variable value: my_password
? Select what you want to do with environment variables: I'm done
? Do you want to configure secret values this function can access? No
? Do you want to edit the local lambda function now? Yes
Successfully added resource amplifyjsapp92a6207c locally.
利用Lambda函数的源文件现在在amplify/backend/function/amplifyjsapp92a6207c/src/index.js中创建成功。
将其修改如下所示。
const AWSXRay = require('aws-xray-sdk-core')
const captureMySQL = require('aws-xray-sdk-mysql')
const mysql = captureMySQL(require('mysql2/promise'))
exports.handler = async (event) => {
const connection = await mysql.createConnection({
host : process.env.db_host,
database : process.env.db_schema,
user : process.env.db_username,
password : process.env.db_password
})
const [rows, fields] = await connection.execute('select * from users')
console.log(rows)
await connection.end()
return rows[0].name
}
如果不需要X射线,将前三行替换为 const mysql = require(’mysql2/promise’);,并根据实际表更改’select * from users’和rows[0].name。
执行amplify push。需要几分钟,所以去泡杯咖啡。
MacBook-Pro.local:amplify-js-app% amplify push
✔ Successfully pulled backend environment dev from the cloud.
Current Environment: dev
| Category | Resource name | Operation | Provider plugin |
| -------- | ------------------------- | --------- | ----------------- |
| Function | amplifyjsapplayer9da0e99d | Create | awscloudformation |
| Function | amplifyjsapp92a6207c | Create | awscloudformation |
? Are you sure you want to continue? Yes
Suggested configuration for new layer versions:
amplifyjsapplayer9da0e99d
- Description: Updated layer version 2021-07-27T10:49:26.931Z
? Accept the suggested layer version configurations? Yes
⠇ Updating resources in the cloud. This may take a few minutes...
...
✔ All resources are updated in the cloud
现在Lambda已经创建,并且可以在Lambda控制台中查看。
在左侧菜单的图层中,可以查看创建的图层。
在Lambda控制台中,打开Lambda函数并通过Code -> Test进行执行(参数可以任意设置),但仍然会出错。这是因为没有访问RDS所在的VPC所需的权限。
向Lambda函数中添加VPC访问权限。
当打开 Lambda 函数的配置 -> 权限 并点击角色时,会打开 IAM 控制台。
在IAM的角色的权限选项卡中,点击”附加策略”,并添加AWSLambdaVPCAccessExecutionRole策略。
返回到Lambda函数页面并刷新,应该会发现在资源摘要的下拉菜单中显示的权限有所增加。
设置VPC访问权限
在这里,当按下Lambda函数控制台上的测试按钮时,可以从数据库中读取数据。
2021/07/31注:
由于在控制台创建的内容即使使用 amplify pull 也无法在本地的 Amplify 管理文件中找到,因此在单独创建环境时会再次需要手动执行。为了防止这种情况发生,建议在 Amplify 生成并提交的 CloudFormation 文件中(amplify/backend/function/myFunction/myFunction-cloudformation-template.json)添加 VPC 访问权限和 VPC 访问设置,这样可以放心。但是,请注意,如果使用不同的环境使用不同的 VPC,就需要根据每个环境进行更改。我认为可以将其作为参数,并自动在每个环境中进行切换,但目前还无法实现。
"Resources": {
"LambdaFunction": {
"Type": "AWS::Lambda::Function",
"Metadata": {
"aws:asset:path": "./src",
"aws:asset:property": "Code"
},
"Properties": {
...
"VpcConfig": { // これ追加
"SecurityGroupIds": [
"sg-xxxxxxxxxxxxxxxxx"
],
"SubnetIds": [
"subnet-xxxxxxxx",
"subnet-yyyyyyyy",
"subnet-zzzzzzzz"
]
},
...
},
},
"LambdaExecutionRole": {
"Type": "AWS::IAM::Role",
"Properties": {
...
"ManagedPolicyArns": [ // ここ追加
"arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
],
...
}
},
...
},
创建GraphQL API
回到项目根目录并执行“ ”以定义GraphQL的API。
MacBook-Pro.local:amplify-js-app% amplify add api
? Please select from one of the below mentioned services: GraphQL
? Provide API name: amplifyjsapp
? Choose the default authorization type for the API API key # ①
? Enter a description for the API key: amplifyjsapp
? After how many days from now the API key should expire (1-365): 7
? Do you want to configure advanced settings for the GraphQL API No, I am done.
? Do you have an annotated GraphQL schema? No
? Choose a schema template: Single object with fields (e.g., “Todo” with ID, name, description) # ②
...
① 使用一個最簡單且不費負擔的API金鑰進行驗證。由於Amplify會將其值寫入設定文件中並自動使用,所以不需要擔心。
② 暫時使用了Todo模板,但可在下列內容中進行修改。
可以在 amplify-js-app/amplify/backend/api/amplifyjsapp/schema.graphql 文件中定义 schema,然后在这里定义使用 Lambda 的解析器。
type Query {
queryRds(query: String): String @function(name: "amplifyjsapp92a6207c-${env}")
}
然后,使用amplify push将其应用于环境。
MacBook-Pro.local:amplify-js-app% amplify push
✔ Successfully pulled backend environment dev from the cloud.
Current Environment: dev
| Category | Resource name | Operation | Provider plugin |
| -------- | ------------------------- | --------- | ----------------- |
| Api | amplifyjsapp | Create | awscloudformation |
| Function | amplifyjsapp92a6207c | Update | awscloudformation |
| Function | amplifyjsapplayer9da0e99d | No Change | awscloudformation |
? Are you sure you want to continue? Yes
GraphQL schema compiled successfully.
Edit your schema at /Users/kanji/development/aws/amplify-js-app/amplify/backend/api/amplifyjsapp/schema.graphql or place .graphql files in a directory at /Users/kanji/development/aws/amplify-js-app/amplify/backend/api/amplifyjsapp/schema
? Do you want to generate code for your newly created GraphQL API Yes
? Choose the code generation language target javascript
? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes
? Enter maximum statement depth [increase from default if your schema is deeply nested] 2
⠹ Updating resources in the cloud. This may take a few minutes...
...
✔ Generated GraphQL operations successfully and saved at src/graphql
✔ All resources are updated in the cloud
GraphQL endpoint: https://xxxxxxxxxxxxxxxxxxxxx.appsync-api.ap-northeast-1.amazonaws.com/graphql
GraphQL API KEY: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
由于Amplify会自动为应用程序应用API KEY,所以不必担心。
使用Lambda函数来使用GraphQL参数
由於在上述的queryRds中傳遞了query,所以需要在Lambda代碼中將其使用。
...
exports.handler = async (event) => {
...
const [rows, fields] = await connection.execute(event.arguments.query)
...
}
从前端应用程序调用GraphQL API
首先,通过npm i aws-amplify安装所需的软件包。
接下来,在src/app.js文件中编写前端应用程序的代码,并从那里访问GraphQL API。
import Amplify, { API, graphqlOperation } from "aws-amplify";
import awsconfig from "./aws-exports";
import { queryRds } from "./graphql/queries";
Amplify.configure(awsconfig);
const button = document.getElementById("my_button");
button.addEventListener("click", (evt) => {
API.graphql(graphqlOperation(queryRds, { query: 'select * from internal_users' }))
.then((evt) => {
console.log(evt.data.queryRds)
});
});
终于,前端应用可以通过GraphQL访问RDS的数据了。
引入RDS代理
如果继续这样下去,Lambda 将会向 RDS 发送请求并在单位时间内创建过多的连接,很快就会达到上限并引发错误。
因此使用RDS代理。它负责连接池的功能。
创建秘密
首先,在AWS Secrets Manager中创建用于RDS Proxy访问RDS的数据库凭证。
在Aurora的情况下,选择“RDS数据库凭证”,然后输入用户名和密码,选择数据库并点击“下一步”继续,输入密钥名称,其他保持默认值后继续进行。
记下所创建的密钥ARN。
创建使用密钥的策略
接下来,在IAM控制台的“创建策略”页面中,打开JSON选项卡,并粘贴以下JSON内容。参考文档。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:GetResourcePolicy",
"secretsmanager:DescribeSecret",
"secretsmanager:ListSecretVersionIds"
],
"Resource": [
"arn:aws:secretsmanager:{region}:{account_id}:{secret_name}"
]
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "kms:Decrypt",
"Resource": "arn:aws:kms:{region}:{account_id}:key/{key_id}",
"Condition": {
"StringEquals": {
"kms:ViaService": "secretsmanager.{region}.amazonaws.com"
}
}
}
]
}
上面的”Resource”是将先前记录的秘密的ARN放入。
{account_id}和{region}根据需要进行替换。将{key_id}替换为可以在KMS控制台中找到的”aws/secretsmanager”键ID。
创建一个使用秘密的角色
在IAM控制台上,创建一个应用了之前创建的策略的角色。
-
- AWS service -> RDS -> “RDS – Add Role to Database” -> Next:Permissions
-
- Select permission policyで、上で作成したポリシーを選択
-
- 名前をつけて保存
- ロールの Trust relationship タブから Edit trust relationship をクリックしてTrust policyを下記に変更する。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "rds.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
创建RDS代理
回到Lambda函数的控制台,从配置选项卡的左侧菜单打开数据库代理的页面,然后点击添加数据库代理。
在Secret和IAM角色上指定之前创建的内容,并添加。
2021/07/31补记
在Lambda控制台上设置的内容,在提交本地未提交的内容后,使用amplify pull命令进行下载时,
我认为会在amplify/backend/function/myFunction/myFunction-cloudformation-template.json中添加下面所示的策略。如果进行了提交,在重新构建时就无需手动操作。
"Resources": {
...
"RdsProxyAccessPolicy": { // これ追加
"DependsOn": [
"LambdaExecutionRole"
],
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyName": "rds-proxy-access-policy",
"Roles": [
{
"Ref": "LambdaExecutionRole"
}
],
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "rds-db:connect",
"Resource": "arn:aws:rds-db:ap-northeast-1:xxxxxxxxxxx:dbuser:prx-yyyyyyyyyyyyyyy/*"
}
]
}
}
},
},
使用RDS代理
JS 应用程序 -> GraphQL -> Lambda(JS)-> RDS 代理 -> RDS
以上、辛苦了。