RailsアプリケーションをAWS Lambdaで動かす!Lambyを使った実践的なハンズオン
はじめに
自分でwebアプリケーションを作成して公開したい!となると昔のようにherokuで無料ではできなくなってしまいましたよね。また、AWSで公開したい!と思った時もRDSなど何かと高くつきます。
「じゃあAWS Lambdaでできればリクエストに応じた課金しかしなくていいのに笑」とおもったら本当にあった。
調べてみるとLambyというのがあるんですね。しかし、Quick Start以上の情報が少なく、特に以下の点で苦労しましたのでまとめておきました。
- AWS上のMySQL(RDS)との接続設定
- System Managerのパラメーターストアからの環境変数の取得
- VPC、サブネット、セキュリティグループの設定
AWSの基本知識がないことから起きる問題ですがその辺りも丁寧に触れられればと思います。
本記事では、これらの課題を解決しながら、実際のアプリケーションをデプロイするまでの手順を詳しく解説します。
説明したりない部分や誤っている解釈があったらご指摘ください
目次
- とりあえずQuick Start通りにローカル環境を立ち上げてみる
- AWSにデプロイしてみる
- AWSのRDSと接続してみる
- VPCとサブネットの設定
- セキュリティグループの設定
- RDSの作成
- 環境変数の管理
- System Managerパラメーターストアの設定
- マイグレーションをどうやるか?
1. Quick Start通りにローカル環境を立ち上げてみる
以下を参考にとりあえずローカル環境でYay! You’re on Rails!
までやってみます
https://lamby.cloud/docs/quick-start
cd YOUR_OWN_DIRECTORY
docker run \
--rm \
--interactive \
--volume "${PWD}:/var/task" \
ghcr.io/rails-lambda/lamby-cookiecutter \
"gh:rails-lambda/lamby-cookiecutter"
github上のlambyのクッキーカッタープロジェクトからRailsのテンプレートを参照してdockerコンテナ上で実行しているんですね。
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
プロジェクト名を決める
project_name [my_awesome_lambda]: YOUR_ONW_APP_NAME
vscode(Cursor)からdev containerのワークスペースを開く。
起動まで5分ほどかかる。
docker psでコンテナの起動を確認
APサーバーに加え、MySQLサーバーまで作ってくれるとは、、、。
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が用意してくれている初期設定プログラムを実行
./bin/setup
中を見るとこのようになっており
- 依存関係(gem)のインストール(bundle install)
- データベースの準備(./bin/rails db:prepare)
- 古いログや一時ファイルの削除(./bin/rails log:clear tmp:clear)
- アプリケーションサーバーの再起動(./bin/rails restart)
をひとまとめにしてくれている。
なのでgemを追加した時、dbスキーマに更新があったときはとりあえずこのコマンドを実行すればいいわけですね。
#!/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サーバーを起動
./bin/rails s
2.AWSにデプロイしてみる
では実際にawsにLambdaサーバーをデプロイしてみる
以下のコマンドを実行してawsのアカウントとこのプロジェクトを紐づけます。
aws configure
deployを実行する
./bin/deploy
内容を要約すると
- 環境変数の設定
- ECR(Elastic Container Registry)にイメージがなければ作成する
- デプロイのためにbundle install
- アセットプリコンパイル
- 不要ファイルの削除
- SAM(Serverless Application Model)関係の処理
- アプリケーションのビルド
- ビルドしたアプリケーションをパッケージング(コンテナイメージとしてECRリポジトリに保存するため)
- パッケージングしたアプリケーションをデプロイ(AWSリソースなどを作成するため)
ECRを作成、./.aws-sam/build/template.yamlをもとに内容をECRに保存、ECR上のイメージを利用してAWSリソースを作成、あるいは更新という流れですね。
./bin/deployの内容はこちら
#!/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
デプロイ完了。簡単すぎる、、、。
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つのサブネットが属している状態にします。
セキュリティグループの設定
LambdaのSG
RDSのSG
Lambdaからのアクセスのみを受け入れられるようにするため
インバウンドルールに上記で作成したSG(lambda-rds-1
)を設定します。
awsコンソールからRDSの接続をクリックして手動で作成したが、本当は自動でできるかも?
Lambdaのプロジェクトに対してVPCを設定するには.aws-sam/build/template.yaml
を以下のように変更します。
VpcConfig:
SubnetIds:
- subnet-xxxxxxxxxxxxxxxxx
- subnet-xxxxxxxxxxxxxxxxx
SecurityGroupIds:
- sg-xxxxxxxxxxxxxxxxx # lambda-rds-1
RDSの作成
RDSのサブネットグループに先ほど作成したサブネット二つを設定する
このように設定できていればOK
config/database.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パラメーターストアで管理します
System Managerの設定を.aws-sam/build/template.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もできると思います。
これでLamby + RDSという環境を作成することができました!
課題の課題
- もっと節約するためにDynamoDBを利用する方式に変更したい(次回また記事書くかも)
- github actionのワークフローが失敗してしまう