💡

Serverless環境での開発 ─ MOSHのテスト環境構築の取り組み

2025/02/28に公開

MOSHでソフトウェアエンジニアをしている masuyama です。

MOSHではバックエンドAPIの実行環境としてAWS Lambdaを利用しています。
いわゆるServerless環境は、サービス運用の手間を大幅に削減できる一方で、Cloud-nativeな環境のため、開発時に実際の環境と差異が生じ、動作確認が難しくなる場面があります。


開発しようとしていたもの

MOSHでは、それまで決済プロバイダーとして Stripe を使用していましたが、新たに Fincode も利用できるようにしようとしていました。
さらに、DynamoDBに保存していた決済関連のデータを、より厳密な型の制約を持たせられるRDSへ移行 することも、このタイミングで進めることにしました。[1]


その当時の問題点

この項目だけで3記事ほど書けるほどの複雑な経緯がありますが、要約すると以下のような問題がありました。

  • 認証や決済プロバイダーとの接続確認がローカル環境では難しい
  • AWS上の動作確認環境(以下staging)は、develop ブランチにマージするとデプロイされる仕組みだった[2]ため、開発途中のものを確認するには、マージ → 確認 → リバート という手順が必要だった
  • staging 環境は本番と同じ構成のため、新たに環境を作ろうとすると、大量のリソース(300以上のLambdaと関連リソース)が生成されてしまう
  • Lambdaの容量が限界に近い(250MB制限)

テスト環境の構築

上記の問題を回避しつつ、開発途中のものを動作確認できる環境の構築を目指しました。

  • staging のリソースを利用し、任意のブランチのAPIをテストできるようにする
  • APIのLambdaを1つに集約する
  • 新たにRDSとSQSを利用するため、それらの確認もできるようにする
  • 基本的に利用するのはAPIのみだが、設定変更によって他のイベント用Lambdaもデプロイ可能にする
  • 複数人で開発しているため、誰でもデプロイできる仕組も構築する

構成

完成イメージは以下のようになります。

テスト環境構成

  • API GatewayとLambdaを作成
  • staging 環境のリソースを共有
  • 記載していない細かいリソース(VPC、IAM、SMSなど)は staging 環境のものを再利用

実現するための課題の解決

1. 250MB制限の回避

AWS Lambdaには デプロイパッケージのサイズが250MB以内 という制限があります。もともとギリギリの容量でしたが、新たにRDSを利用するため、追加でRDBドライバー等の追加のパッケージのインストールが必要になり、250MB制限内に収めるのが困難になりました。
そこで、テスト環境では ZIPでのデプロイをやめ、コンテナイメージを用いる ことにしました。

参考: AWS Lambdaのコンテナイメージ

AWSが提供するベースイメージを活用し、以下のようなDockerfileを作成しました。[3]

FROM public.ecr.aws/lambda/python:3.10-x86_64

# コードをコピー
COPY . ${LAMBDA_TASK_ROOT}

# 必要なパッケージをインストール
RUN pip install -r requirements.txt

# ハンドラーを設定
CMD [ "foo/bar/test.handler" ]

2. Lambdaのハンドラー集約

通常、AWS LambdaではAPIエンドポイントごとにLambdaハンドラーを作成しますが、APIが増えるとLambdaの数も増え、管理が煩雑になります。
この問題を解決するために、Lambda-lith という1つのLambdaで複数のエンドポイントを処理する設計を採用しました。[4]

参考: Serverlessマイクロサービスの設計アプローチ

テスト環境では、独自のルーティング処理を導入し、簡易的に実現しました。

from foo.bar import user

function_map = {
    "GET /users": user.list_handler,
    "POST /users": user.create_handler,
    "PATCH /users": user.update_handler,
    ....
    ....
    ....
}

def handler(event, context):
    # デバッグ用ログ出力
    print("handler called", {"event": event, "context": context})  

    # リクエストを適切なハンドラーにルーティング
    key = f"{event['httpMethod']} {event['resource']}"
    if key in function_map:
        return function_map[key](event, context)

    # 未定義のエンドポイントに対するデフォルトレスポンス
    return {
        "statusCode": 404,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps({"event": event}),
    }

全エンドポイントを手動で記述するのは非効率だったため、設定ファイルをもとにコードを生成するスクリプトを作成して対応しました。

3. ChatOpsを活用したデプロイ

デプロイにはChatOpsを使う構成を採用しました。
当時のMOSHの環境では、開発リポジトリ外でGitHub Actionsを用いたデプロイが行われており[5]、構成が複雑でした。そのため、外部の仕組みとして構築しました。

デプロイにはBolt for Pythonを用いた簡易的なSlack Botを作成しています。
https://tools.slack.dev/bolt-python/ja-jp/getting-started/

Slack上で以下のようなコマンドを実行することで、任意のブランチをデプロイできる仕組みを構築しました。

@deploybot checkout_be [env] [branch]
@deploybot deploy_be [env]

実行すると、デプロイ用のシェルスクリプトが実行され、テスト環境にデプロイされる仕組みです。

開発のフロー

このテスト環境の導入により、開発のフローが以下のように改善されました。

導入前

  1. ローカルで開発
  2. 開発ブランチをGitHubに push
  3. develop に merge し、staging で確認
  4. revert branch を作成し revert
  5. revert branch をベースに追加開発
  6. (最初に戻る)

導入後

  1. ローカルで開発
  2. 開発ブランチをGitHubに push
  3. 開発ブランチをテスト環境にデプロイし、確認
  4. (最初に戻る)

この変更により、開発途中のコードを staging に影響を与えずにデプロイ・確認できるようになり、 develop ブランチへの不必要な merge も不要になりました。
その結果、繰り返しの確認が必要な大規模な変更も、よりスムーズに進められるようになりました。

現在の staging へのデプロイフロー

さらに、現在は 任意のブランチを staging にデプロイできるようになった ため、以下のフローで直接確認することも可能になりました。

  1. ローカルで開発
  2. 開発ブランチを GitHub に push して pull request を作成
  3. 開発ブランチを staging にデプロイし、確認
  4. (最初に戻る)

これにより、staging 環境を用いた動作確認の自由度が大幅に向上し、
リリース前の検証がより柔軟かつ効率的に行えるようになっています。

今後は

現在は、FastAPIとOpenAPIのコードジェネレーターを活用したLambda-lith化が進んでおり、暫定的に集約していたハンドラーは、近いうちにその役目を終える見込みです。
また、LambdaはすべてDockerイメージベースに移行済みのため、今後は環境の整理・集約がさらに進んでいくと考えています。

一方で、そもそもの課題であった ローカル環境での開発のしにくさ については、依然として解決されていません。この点についても、今後改善を進めていきたいと考えています。

こうした 開発に関連するあらゆる課題を一緒に解決し、より良い仕組みを作っていく仲間を募集 しています!
興味のある方はぜひ一度お話ししましょう!
https://careers.mosh.jp/

脚注
  1. このあたりの詳細については、別の記事で紹介できればと思います ↩︎

  2. 後述しますが現在はマージ前の任意のbranchをdeployできるようになりました ↩︎

  3. 現在は本番環境もコンテナイメージを利用しています ↩︎

  4. 現在、OpenAPI code generatorとFastAPIを用いたLambda-lith化に取り組んでいます ↩︎

  5. 現在は改善され、開発リポジトリ内のGitHub ActionsでCDが行われるようになりました ↩︎

MOSH

Discussion