BASEプロダクトチームブログ

ネットショップ作成サービス「BASE ( https://thebase.in )」、ショッピングアプリ「BASE ( https://thebase.in/sp )」のプロダクトチームによるブログです。

S3 + API gateway + Lambda (+ Aurora) による Serverless 申請フォームの構築

はじめに

はじめまして、CSE (Corporate Solution Engineering1)の上野です。 今回は BASE Partners という事業で使用していた Google フォームを S3 + API gateway + Lambda (+ Aurora) を使用した Serverless 構成のフォームに移行するというプロジェクトについてお話します。

変更前の構成図と構築した構成図としては以下のようになります。

変更前

変更後

BASE Partners について

BASE では新規のショップオーナー様を紹介・支援いただくオフィシャルパートナーを募集するパートナープログラムを運営しています。 それらの申請には初期的には Move fast に行うため、Google フォームと Google スプレッドシートが使用されていましたが、ありがたいことにパートナー様やご紹介いただいたショップ様は順調に増加し、それに比例して、誤申請とそれによるやり取りが増加するなど、業務負荷が高まっていました。 既存の仕組みが Google フォーム → Google スプレッドシート → DB へバッチ取り込み という流れのため、申請者がリアルタイムに申請した内容が正しいかどうかがわからないというのが誤申請の主な原因だったため、フォームから直接 DB を参照できるよう、フォームを移行することに決定しました。

技術選定

移行するにあたって、まずはどの技術を使用するかというところから検討をはじめました。今回はサーバーを管理・運用する人的リソースがない、などの理由から少しでも運用が軽減できるよう S3 の静的サイトホスティングと API gateway、Lambda を用いた Serverless の構成とするように決定しました。言語については、BASE では PHP がよく用いられていますが、Lambda では PHP は公式ではサポートされていないため、CSE で Lambda を使用する際によく用いる Python を選択しました。 また、Serverless アプリケーションの管理は、CSE で使用実績のあった Servewrless Application Model (SAM)を使用することにしました。

SAMによる実装

ネットワークの設定

Lambda から既存の DB にアクセスする必要があるため、VPC、Subnet、Security Group の設定が必要になります。 今回の実装では Terraform でネットワーク等を管理している別リポジトリがあるため、実装としてはありませんが、下記のような構成となるようにそれぞれ設定しました。

VPC、Subnet、Security Group の設定後、SAM の template.yaml の VpcConfig 設定で Lambda に Subnet と Security Group の設定を付与します。

Resources:
  SomeFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: functions/
      Handler: some_function.lambda_handler
      Runtime: python3.9
      Architectures:
        - x86_64
      VpcConfig:
        SecurityGroupIds:
        - <SecurityGroupId> # 構築したセキュリティグループIDを設定
        SubnetIds:
        - <SubnetId>  # 構築したサブネットIDを設定

CORS の設定

S3の静的サイトホスティングのドメインと、API gateway のドメインが異なるため、Cross-Origin Resource Sharing (CORS) の設定が必要になります。 API gateway と Lambda の両方に設定が必要になるため、以下のような形で実装をします。

template.yaml の実装

下記のサンプルでは1つの関数しかありませんが、実際には複数の関数が存在するため、Globals 内で api に Cors プロパティを設定します。 今回実装した API は POST メソッドがメインになりますが、CORS の設定をする場合は OPTIONS メソッドで preflight リクエストを送信するため、OPTIONS メソッドの設定も必要になります。

Globals:
  Api:
    Cors:
      AllowOrigin: "'<origin>'"
      AllowCredentials: false
      AllowMethods: "'POST,OPTIONS'"
      AllowHeaders: "'<headers>'"

Resources:
  SomeFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: functions/
      Handler: some_function.lambda_handler
      Runtime: python3.9
      Architectures:
        - x86_64
      VpcConfig:
        SecurityGroupIds:
        - <SecurityGroupId>
        SubnetIds:
        - <SecurityGroupId>
      Events:
        SomeApi:
          Type: Api
          Properties:
            Path: /path
            Method: post

Lambda の実装

API gateway の Lambda プロキシ統合の場合、バックエンドの Lambda 関数でも Access-Control-Allow-Origin および Access-Control-Allow-Headers を返す必要があるため、以下のように返り値の headers に記載します。2

def lambda_handler(event, context):
    ...
    return {
        "statusCode": 200,
        "headers": {
            'Access-Control-Allow-Headers': 'Content-Type',
            'Access-Control-Allow-Origin': '<ORIGIN>',
            'Access-Control-Allow-Methods': 'OPTIONS,POST'
        },
        "body": json.dumps({
            ...
        }),
    }

デプロイの自動化

続いてデプロイを自動化する仕組みについてです。 今回は大きなアプリケーションでは無いので GitHub Actions で SAM の deploy コマンドを実行する簡易的なものを使用しました。 また、デプロイに使用する認証は、初期的には GitHub Secrets を使用しようと思っていましたが、OpenID Connect であればクレデンシャル情報の漏洩などのリスクがなく、適切に使用すればよりセキュアであるということを知り、OpenID Connect を使用することにしました。

OpenID Connect の IAM Role 設定

ID プロバイダの追加

まずは以下の設定で GitHub を ID プロバイダとして AWS に追加します。 Provider URL : https://token.actions.githubusercontent.com Audience : sts.amazonaws.com

IAM Role の作成

次に認証後に使用する IAM Role を作成します。 まず Policy で先程設定した GitHub の ID プロバイダを Principal に設定し、Action で sts:AssumeRoleWithWebIdentity を指定します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect" : "Allow",
            "Principal" : { "Federated": ["arn:aws:iam::<account_id>:oidc-provider/token.actions.githubusercontent.com"]
            },
            "Action" : "sts:AssumeRoleWithWebIdentity",
            "Condition" : {
                "StringLike":{
                    "token.actions.githubusercontent.com:sub":["repo:<repository>:*"]
                }
            }
        }
    ]
}

今回は SAM のデプロイをするため、以下のポリシーをアタッチします。 - IAMFullAccess - AmazonS3FullAccess - AmazonAPIGatewayAdministrator - AWSCloudFormationFullAccess - AWSLambda_FullAcces

GitHub Actions の実装

先に設定した IAM Role を使用して、自動デプロイを実行する GitHub Actions を実装します。

name: 'deploy'

on:
  push:
    paths:
      - 'template.yaml'
      - '.github/workflows/deploy.yml'
      - 'functions/**'
    branches:
      - 'branch'

env:
  AWS_ROLE_ARN: 'arn:aws:iam::<aws_account_id>:role/<OIDC_ROLE_NAME>'

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-python@v2
      - uses: aws-actions/setup-sam@v1
      - uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ${{ env.AWS_ROLE_ARN }}
      - run: sam build --use-container
      - run: sam deploy --config-env <env> --no-confirm-changeset

まとめ

本記事では、業務改善を目的とした API gateway と Lambda による Serverless なフォームの構築についてお話しました。 業務改善の観点としては、移行して間もないためどのくらいの改善ができたかという定量的な指標はないものの、最初に記載していた誤申請での問い合わせは削減することができました。 また、API gateway、Lambda での CORS の設定の記事や、OIDC についての記事など、各論の記事は多々あるものの、全体の実装をまとめたものが少ないと思ったので、参考になれば幸いです。

感想

AWS に触れるのは BASE に入社してからだったのですが、経験が少ない中でも挑戦させてもらえてとてもいい経験ができました。以前公開された若菜の記事にも 手を挙げることでやりたいことに積極的にチャレンジさせてもらえる という社風がある とありますが、たしかにそういった社風があるというのを体感することができました。 また、コーポレートエンジニアというとベンダーコントロールがメインというイメージや、レガシーなシステムの保守がメインだというイメージがある方もいらっしゃるかと思いますが、BASE では実装や技術選定など様々なフェーズやAWS、Serverless などモダンな技術も経験ができる、ということを知っていただければ幸いです。

最後に

BASE では積極的にチャレンジがしたいエンジニアを募集しています。カジュアル面談も実施していますので、興味を持っていただけた方はぜひお気軽にご応募ください。 https://herp.careers/v1/base


  1. CSE についてはこちらの記事を参照ください
  2. AWS 公式ドキュメント