RailsアプリケーションをAWS Lambdaで動かす！Lambyを使った実践的なハンズオン

# RailsアプリケーションをAWS Lambdaで動かす！Lambyを使った実践的なハンズオン

## はじめに
自分でwebアプリケーションを作成して公開したい！となると昔のようにherokuで無料ではできなくなってしまいましたよね。また、AWSで公開したい！と思った時もRDSなど何かと高くつきます。
「じゃあAWS Lambdaでできればリクエストに応じた課金しかしなくていいのに笑」とおもったら本当にあった。

調べてみると[Lamby](https://lamby.cloud/)というのがあるんですね。しかし、Quick Start以上の情報が少なく、特に以下の点で苦労しましたのでまとめておきました。
- AWS上のMySQL（RDS）との接続設定
- System Managerのパラメーターストアからの環境変数の取得
- VPC、サブネット、セキュリティグループの設定

AWSの基本知識がないことから起きる問題ですがその辺りも丁寧に触れられればと思います。

本記事では、これらの課題を解決しながら、実際のアプリケーションをデプロイするまでの手順を詳しく解説します。

説明したりない部分や誤っている解釈があったらご指摘ください :bow:

## 目次

1. とりあえずQuick Start通りにローカル環境を立ち上げてみる
2. AWSにデプロイしてみる
3. AWSのRDSと接続してみる
   - VPCとサブネットの設定
   - セキュリティグループの設定
   - RDSの作成
4. 環境変数の管理
   - System Managerパラメーターストアの設定
5. マイグレーションをどうやるか？

## 1. Quick Start通りにローカル環境を立ち上げてみる

以下を参考にとりあえずローカル環境で`Yay! You’re on Rails!`までやってみます
https://lamby.cloud/docs/quick-start

```bash
cd YOUR_OWN_DIRECTORY
```

```bash
docker run \
  --rm \
  --interactive \
  --volume "${PWD}:/var/task" \
  ghcr.io/rails-lambda/lamby-cookiecutter \
  "gh:rails-lambda/lamby-cookiecutter"
```

github上のlambyのクッキーカッタープロジェクトからRailsのテンプレートを参照してdockerコンテナ上で実行しているんですね。

```bash
Unable to find image 'ghcr.io/rails-lambda/lamby-cookiecutter:latest' locally
latest: Pulling from rails-lambda/lamby-cookiecutter
579b34f0a95b: Pull complete 
00152206209d: Pull complete 
981d19de32f1: Pull complete 
f32a7c3c67ff: Pull complete 
d2b34077d5c8: Pull complete 
5ab76f42a493: Pull complete 
fd387620f47a: Pull complete 
Digest: sha256:c583410d17557f119a0ddb905fa191d9ca4f88e997f3eaef696a5aaab460d70c
Status: Downloaded newer image for ghcr.io/rails-lambda/lamby-cookiecutter:latest
```

プロジェクト名を決める
```bash
project_name [my_awesome_lambda]: YOUR_ONW_APP_NAME
```

vscode（Cursor）からdev containerのワークスペースを開く。
![スクリーンショット 2025-06-08 15.59.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427079/0f901bfe-14e6-4e40-89ac-eeff49daa504.png)

起動まで５分ほどかかる。

docker psでコンテナの起動を確認
APサーバーに加え、MySQLサーバーまで作ってくれるとは、、、。
```bash
docker ps

CONTAINER ID   IMAGE                   COMMAND                   CREATED         STATUS                   PORTS                 NAMES
357e95a57870   test_devcontainer-app   "/bin/sh -c 'echo Co…"   3 minutes ago   Up 2 minutes                                   test_devcontainer-app-1
933a7dee8d5e   mysql:8.0               "docker-entrypoint.s…"   3 minutes ago   Up 3 minutes (healthy)   3306/tcp, 33060/tcp   test_devcontainer-mysql-1
```

Lambyが用意してくれている初期設定プログラムを実行
```bash
./bin/setup
```

中を見るとこのようになっており
- 依存関係（gem）のインストール（bundle install）
- データベースの準備（./bin/rails db:prepare）
- 古いログや一時ファイルの削除（./bin/rails log:clear tmp:clear）
- アプリケーションサーバーの再起動（./bin/rails restart）

をひとまとめにしてくれている。
なのでgemを追加した時、dbスキーマに更新があったときはとりあえずこのコマンドを実行すればいいわけですね。

```sh
#!/bin/sh
set -e

echo '== Installing dependencies =='
bundle install

echo "== Preparing database =="
./bin/rails db:prepare

echo "== Removing old logs and tempfiles =="
./bin/rails log:clear tmp:clear

echo "== Restarting application server =="
./bin/rails restart

# ./bin/yarn
```

実際にrailsサーバーを起動
```bash
./bin/rails s
```

![スクリーンショット 2025-06-08 16.15.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427079/46f3a0dc-4914-42b7-874f-331a0b7d3cb7.png)
ここまで約5分。すごい。

## 2.AWSにデプロイしてみる
では実際にawsにLambdaサーバーをデプロイしてみる

以下のコマンドを実行してawsのアカウントとこのプロジェクトを紐づけます。
```bash
aws configure
```

deployを実行する
```bash
./bin/deploy
```

内容を要約すると
- 環境変数の設定
- ECR(Elastic Container Registry)にイメージがなければ作成する
- デプロイのためにbundle install
- アセットプリコンパイル
- 不要ファイルの削除
- SAM（Serverless Application Model）関係の処理
  - アプリケーションのビルド
  - ビルドしたアプリケーションをパッケージング（コンテナイメージとしてECRリポジトリに保存するため）
  - パッケージングしたアプリケーションをデプロイ（AWSリソースなどを作成するため）

ECRを作成、./.aws-sam/build/template.yamlをもとに内容をECRに保存、ECR上のイメージを利用してAWSリソースを作成、あるいは更新という流れですね。

<details><summary>./bin/deployの内容はこちら</summary>

```sh
#!/bin/sh
set -e

RAILS_ENV=${RAILS_ENV-production}

AWS_REGION=${AWS_REGION-$(aws configure get region || echo 'ap-northeast-1')}
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
IMAGE_REPOSITORY="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/esoteric-word"

if [ "$CI" != "true" ]; then
  echo "== Cleaning dev dependencies for local deploy. Run ./bin/setup again afterward! =="
  rm -rf ./.bundle \
         ./vendor/bundle
fi

echo '== Create ECR Repo if needed. =='
aws ecr describe-repositories \
  --repository-names "esoteric-word" \
  --region "$AWS_REGION" > /dev/null || \
aws ecr create-repository \
  --repository-name "esoteric-word" \
  --image-tag-mutability "MUTABLE" \
  --image-scanning-configuration "scanOnPush=true" \
  --region "$AWS_REGION" > /dev/null || true

echo '== Bundle For Deployment =='
bundle config --global silence_root_warning true
bundle config --local deployment true
bundle config --local without 'development test'
bundle config --local path './vendor/bundle'
bundle install --quiet --jobs 4

echo "== Asset Hosts & Precompiling =="
NODE_ENV='production' ./bin/rails assets:precompile

if [ "$CI" = "true" ]; then
  echo "== Cleanup Unused Files & Directories =="
  rm -rf \
    log \
    node_modules \
    test \
    tmp \
    vendor/bundle/ruby/*/cache
fi

echo "== SAM build =="
sam build \
  --parameter-overrides \
    RailsEnv="${RAILS_ENV}"

echo "== SAM package =="
sam package \
  --region "$AWS_REGION" \
  --template-file ./.aws-sam/build/template.yaml \
  --output-template-file ./.aws-sam/build/packaged.yaml \
  --image-repository "$IMAGE_REPOSITORY"

echo "== SAM deploy =="
sam deploy \
  --region "$AWS_REGION" \
  --template-file ./.aws-sam/build/packaged.yaml \
  --stack-name "esoteric-word-${RAILS_ENV}" \
  --image-repository "$IMAGE_REPOSITORY" \
  --capabilities "CAPABILITY_IAM" \
  --parameter-overrides \
    RailsEnv="${RAILS_ENV}"

if [ "$CI" != "true" ]; then
  echo "== Cleaning prod deploy dependencies from local. =="
  rm -rf ./.bundle \
         ./vendor/bundle \
         ./node_modules \
         ./public/assets
fi

```
</details>

デプロイ完了。簡単すぎる、、、。
```bash
CloudFormation outputs from deployed stack
-------------------------------------------------------------------------------------------
Outputs
-------------------------------------------------------------------------------------------
Key                 RailsLambdaUrl
Description         Lambda Function URL
Value               https://b4hsncwngvxg6rv67b64r545ly0jrwnk.lambda-url.us-east-1.on.aws/
-------------------------------------------------------------------------------------------

Successfully created/updated stack - new-service-production in us-east-1
```

ここまではすんなりできたのですがいくつかハマった点があるので順に見ていきます。
- AWSのRDSは使えるのか（環境変数の設定をSystem Managerでできるのか）

## 3. AWSのRDSと接続してみる

### VPCとサブネットの設定

Lambda関数からRDSにアクセスするために、VPCとサブネットの設定が必要なのでVPCとサブネット（2つ）を作成します。
VPCとサブネットを作成し、VPCの中に2つのサブネットが属している状態にします。
![スクリーンショット 2025-05-17 10.09.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427079/93a3bb4e-869b-40a5-9b9f-66a9c41daa8f.png)

### セキュリティグループの設定

#### LambdaのSG
LambdaがRDSにアクセスできるようにするため
![スクリーンショット 2025-05-17 10.16.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427079/47fec9d7-cde7-4980-b327-59dd56cdb1eb.png)
![スクリーンショット 2025-05-17 10.15.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427079/f5ea0f08-9cfc-447b-81f3-472e747687da.png)

#### RDSのSG
Lambdaからのアクセスのみを受け入れられるようにするため
インバウンドルールに上記で作成したSG（`lambda-rds-1`）を設定します。
![スクリーンショット 2025-05-17 10.16.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427079/b6f2c1d1-ef83-4f3a-93ad-a8de536a5836.png)
![スクリーンショット 2025-05-17 10.16.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427079/94bdeacf-ad2c-4ff5-af03-4fae6929e29d.png)


awsコンソールからRDSの接続をクリックして手動で作成したが、本当は自動でできるかも？

Lambdaのプロジェクトに対してVPCを設定するには`.aws-sam/build/template.yaml`を以下のように変更します。

```yaml
VpcConfig:
  SubnetIds:
    - subnet-xxxxxxxxxxxxxxxxx
    - subnet-xxxxxxxxxxxxxxxxx
  SecurityGroupIds:
    - sg-xxxxxxxxxxxxxxxxx # lambda-rds-1
```

### RDSの作成
RDSのサブネットグループに先ほど作成したサブネット二つを設定する
![スクリーンショット 2025-06-08 18.11.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427079/b7be1941-a01f-4c3c-bc70-620b96beb777.png)
このように設定できていればOK
![スクリーンショット 2025-05-17 10.06.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427079/62428742-04e4-49c4-9735-47ff07124f07.png)

config/database.ymlを変更
```yml
# config/database.yml

default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: <%= ENV["MYSQL_ROOT_PASSWORD"] %>
  host: <%= ENV.fetch("MYSQL_HOST") { "localhost" } %>

development:
  <<: *default
  database: esoteric_word_development

test:
  <<: *default
  database: esoteric_word_test

production:
  <<: *default
  database: esoteric_word_production
  url: <%= ENV["DATABASE_URL"] %>
  username: <%= ENV["MYSQL_ADMIN_USER"] %>
  password: <%= ENV["MYSQL_ADMIN_PASSWORD"] %>

```

## 4. 環境変数の管理

### System Managerパラメーターストアの設定

機密情報はSystem Managerパラメーターストアで管理します
![スクリーンショット 2025-05-17 10.19.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427079/b5e2336c-8aec-4c7e-bc27-f5cd7af67557.png)

System Managerの設定を`.aws-sam/build/template.yaml`が参照できるように書き換えます。
```yaml
Environment:
  Variables:
    MYSQL_HOST: !Sub '{{resolve:ssm:/esoteric/MYSQL_HOST}}'
    MYSQL_ADMIN_USER: !Sub '{{resolve:ssm:/esoteric/MYSQL_ADMIN_USER}}'
    MYSQL_ADMIN_PASSWORD: !Sub '{{resolve:ssm:/esoteric/MYSQL_ADMIN_PASSWORD}}'
```
ドキュメントの書き方だとなぜか参照できなかったので書き方を変更しました。

これでRDSと接続まではできるようになりました。

## 5.マイグレーションをどうやるか？
まず真っ先に思い浮かぶ方法が`./bin/deploy`を実行するスクリプトに含めること(`bundle install`やアセットプリコンパイルをしているから)ですが、これはうまくいきませんでした。
ほんとかどうかわかりませんがAI曰く「デプロイスクリプトの実行はインターネット環境からのアクセスであり、RDSはインターネット環境からのパブリックアクセスが許可されていないから」とのこと。

ここで公式ドキュメントをちゃんと読みます。
`lambda-console-cli`を使えとありますね。
自分の場合は`lambda-console-cli`を実行したら必要なパッケージが足りていないとメッセージが出ましたが、順番にインストールしたら実行できました。
以下の通り、rubyやrailsのバージョンも答えてくれましたのでマイグレーションを実行できました。
自分は`ridgepole`を使っていますがridgepoleができるのだからmigrateもできると思います。
![スクリーンショット 2025-04-27 17.49.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427079/b267c445-a389-4c2b-98d4-358f1684bcb7.png)

# これでLamby + RDSという環境を作成することができました！

## 課題の課題
- もっと節約するためにDynamoDBを利用する方式に変更したい（次回また記事書くかも）
- github actionのワークフローが失敗してしまう

## 参考リンク

- [Lamby公式ドキュメント](https://lamby.cloud)
- [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html)
- [AWS Systems Manager パラメーターストア](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html)