もふもふ技術部

IT技術系mofmofメディア

serverless frameworkでlambda関数からmysqlへの接続を試してみた

概要

serverless frameworkで作成するlambda関数から同じくserverless frameworkで作成したRDSのmysqlへ接続を試しました。

今回利用するRDSはこちらの記事で実装したものになります。 serverless framework でバブリックアクセス可能なmysqlを立ててみた | もふもふ技術部

※ 本記事ではRDSProxyに関しては触れていません

実装

流れ

  1. mysqlの準備
  2. mysqlに接続するlambda関数を作成
  3. 通常の方法でlambda関数をデプロイする
  4. lambda関数をVPCに配置する構成になるよう変更を加える

1. mysqlの準備

mysqlへアクセスし、データベースとテーブルを作成します。

$ mysql -h <endpoint> -u <user> -p
$ <password>
mysql> CREATE DATABASE mydb;
mysql> use mydb;
mysql> CREATE TABLE customers (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255));

2. mysqlに接続するlambda関数を作成

serverless-mysqlというライブラリを追加 これはserverless frameworkでmysqlを利用するのに特化したパッケージです。

yarn add serverless-mysql

lambda関数を作成

handler.js

import MySQL from "serverless-mysql";

// mysqlモジュールの初期化
// 関数外でモジュールを初期化することで実行間の接続で再利用が可能になる
const mysql = MySQL({
  // backoff: 'decorrelated',
  // base: 5,
  // cap: 200
});

// 接続設定オプションは初期化時でも後でも渡すことができる
// https://github.com/mysqljs/mysql#connection-options
mysql.config({
  database: process.env.MYSQL_DATABASE,
  user: process.env.MYSQL_USER,
  password: process.env.MYSQL_PASSWORD,
  port: process.env.MYSQL_PORT,
});

export const customerIndex = async (event) => {
  const results = await mysql.query("SELECT * FROM customers");

  await mysql.end();

  return results;
};

export const customerCreate = async (event) => {
  const body = JSON.parse(event.body);

  const query = `INSERT INTO customers (name) VALUES ('${body.name || ""}')`;

  const results = await mysql.query(query);

  await mysql.end();

  return results;
};

3. 通常の方法でlambda関数をデプロイする

serverless.ymlにfunctionsを追加する

functions:
  customerIndex:
    handler: handler.customerIndex
    events:
      - httpApi:
          path: /customers
          method: get
  customerCreate:
    handler: handler.customerCreate
    events:
      - httpApi:
          path: /customers/create
          method: post

この状態で一度デプロイを行い、curlにより関数を叩くと Task timed out after 6.01 seconds というエラーが起こります。 これはlambda関数からインターネットへのアクセスができないことで起こるエラーで、解決するにはRDSと同じVPC内に関数を置く必要があるとのことでした。

4. lambda関数をVPCに配置する構成になるよう変更を加える

providerにvpcに関する設定を追加することでlambda関数をvpcの内部に設定します。

provider:
  vpc:
    securityGroupIds:
      - !GetAtt SecurityGroup.GroupId
    subnetIds:
      - !GetAtt PublicSubnetA.SubnetId
      - !GetAtt PublicSubnetB.SubnetId

vpcの内部に設置したlambda関数からRDSへ接続するためのインバウンドルールを追加する。 lambda関数を設置したサブネットのcidrブロックをインバウンドルールに設定することで接続することができるようになります。

resources:
  Resources:
    ...
    SecurityGroupIngress:
      Type: AWS::EC2::SecurityGroupIngress
      Properties:
        IpProtocol: tcp
        CidrIp: ${self:custom.config.db.myip}
        GroupId: !GetAtt SecurityGroup.GroupId
        ToPort: 3306
        FromPort: 3306

    # 以下のを追加する ---

    SecurityGroupIngress2:
      Type: AWS::EC2::SecurityGroupIngress
      Properties:
        IpProtocol: tcp
        CidrIp: 10.0.64.0/20
        GroupId: !GetAtt SecurityGroup.GroupId
        ToPort: 3306
        FromPort: 3306

    SecurityGroupIngress3:
      Type: AWS::EC2::SecurityGroupIngress
      Properties:
        IpProtocol: tcp
        CidrIp: 10.0.80.0/20
        GroupId: !GetAtt SecurityGroup.GroupId
        ToPort: 3306
        FromPort: 3306

この状態でデプロイを行うとlambda関数からmysqlへの接続が成功します。

以上で実装は終了となります。

今回のコードはgithubに挙げていますので興味ありましたらご確認ください。

https://github.com/naok1207/serverless-rds-mysql

補足

今回の構成ではlambda関数毎にコネクションが作成され、運用に耐えうる構成とはなっていないので、運用を想定とする場合には、DBProxyでの構成を行うことをお勧めします。

まとめ

最初はlambda関数からパブリックアクセス可能なmysqlへの接続は簡単に行えると考えていましたが、実際にやってみると思ったより難しく、インバウンドルールの設定などで結構詰まってしまいました。最終的にはしっかりと接続ができる形で作成できてよかったです。コストが怖くて挑戦できなかったDBProxyもまたそのうち挑戦したいと思います。

会社の紹介

株式会社 mofmof では一緒に働いてくれるエンジニアを募集しています。 興味のある方は是非こちらのページよりお越しください! https://www.mof-mof.co.jp/ https://www.mof-mof.co.jp/recruit/