使用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控制台中查看。

Screen Shot 2021-07-27 at 17.56.11.png

在左侧菜单的图层中,可以查看创建的图层。

在Lambda控制台中,打开Lambda函数并通过Code -> Test进行执行(参数可以任意设置),但仍然会出错。这是因为没有访问RDS所在的VPC所需的权限。

向Lambda函数中添加VPC访问权限。

当打开 Lambda 函数的配置 -> 权限 并点击角色时,会打开 IAM 控制台。

Screen Shot 2021-07-27 at 18.10.56.png

在IAM的角色的权限选项卡中,点击”附加策略”,并添加AWSLambdaVPCAccessExecutionRole策略。
返回到Lambda函数页面并刷新,应该会发现在资源摘要的下拉菜单中显示的权限有所增加。

设置VPC访问权限

Screen Shot 2021-07-27 at 18.21.37.png
Screen_Shot_2021-07-27_at_18_22_42.png

在这里,当按下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,所以不必担心。

Screen Shot 2021-07-27 at 19.46.29.png

使用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数据库凭证”,然后输入用户名和密码,选择数据库并点击“下一步”继续,输入密钥名称,其他保持默认值后继续进行。

Screen Shot 2021-07-27 at 20.40.44.png

记下所创建的密钥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函数的控制台,从配置选项卡的左侧菜单打开数据库代理的页面,然后点击添加数据库代理。

Screen_Shot_2021-07-27_at_21_21_42-2.png

在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代理

Screen_Shot_2021-07-27_at_21_27_09-3.png
Screen_Shot_2021-07-27_at_21_30_49.png

JS 应用程序 -> GraphQL -> Lambda(JS)-> RDS 代理 -> RDS

以上、辛苦了。

广告
将在 10 秒后关闭
bannerAds