2
4

More than 3 years have passed since last update.

Rails on Lambdaの環境構築

Last updated at Posted at 2020-04-07

はじめに

Rails+Lambdaの記事が少なかったので投稿します。
この記事はRails+Lambda(+APIGateway)で GET /me が実行できるところを目指します。
Railsはある程度分かるけど、Docker使ったことない、サーバレスやりたい人向けです。
今回SAMは使わずある泥臭いやり方で行います。
mysqlは今回コード上では使いませんが、デプロイの関係で構築は行っています。もしRDSやDyanamo使いたい場合は適時調べて行ってください。

環境

Docker for Windows
Ruby 2.7
Rails 6
MySQL 5.x

ディレクトリ構成


rails_api
├── Dockerfile
├── docker-compose.yml
├── Gemfile
├── Gemfile.lock

各種ファイル

Dockerfile

native extension を含む gem を lambci/lambdaの docker イメージでビルドしなおしをするため、イメージはLambdaのものを使用します。


FROM lambci/lambda:build-ruby2.7

# install required libraries
RUN yum -y install mysql-devel

# install bundler
RUN gem install bundler

WORKDIR /tmp
ADD Gemfile Gemfile
ADD Gemfile.lock Gemfile.lock
RUN bundle install

WORKDIR /app
COPY . /app

docker-compose.yml

version: '3'
services:
  mysql:
    image: mysql:5.7
    command: mysqld --character-set-server=utf8 --collation-server=utf8_general_ci
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql
    environment:
      MYSQL_DATABASE: app_development
      MYSQL_USER: root
      MYSQL_PASSWORD: password
      MYSQL_ROOT_PASSWORD: password
  app:
    tty: true
    stdin_open: true
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app
    ports:
      - "3000:3000"
    depends_on:
      - mysql
volumes:
  mysql-data:
    driver: local

Gemfile

railsは最新版をとりあえず。

すでにローカル環境でRailsを動かせている人は不要です。

source 'https://rubygems.org'
gem 'rails', '~> 6.0.2', '>= 6.0.2.1'

Gemfile.lock

空ファイルで作成
すでにローカル環境でRailsを動かせている人は不要です。

Rails作成(Rails new + Rails Server)

ネット環境の良い場所で行いましょう。そこそこ時間がかかります。

すでにローカル環境でRailsを動かせている人は不要です。


$ docker-compose run app bundle exec rails new . --force --database=mysql
Starting rails_api_mysql_1 ... done               exist
       force  README.md
   identical  Rakefile
      create  .ruby-version
      create  config.ru
      create  .gitignore
       force  Gemfile
以下省略

ここでGemfileが新しく生成されるので不要なgemを削除します。(必要な場合は入れてください。)

## 以下削除
gem 'sass-rails', '>= 6'
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 4.0'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.7'

docker build & railsサーバ起動

Railsサーバ起動

$ docker-compose build
$ docker-compose up
※問題があればここでエラーします。
app_1    | => Booting Puma
app_1    | => Rails 6.0.2.1 application starting in development
app_1    | => Run `rails server --help` for more startup options
app_1    | /var/runtime/gems/actionpack-6.0.2.1/lib/action_dispatch/middleware/stack.rb:37: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
app_1    | /var/runtime/gems/actionpack-6.0.2.1/lib/action_dispatch/middleware/static.rb:110: warning: The called method `initialize' is defined here
app_1    | Puma starting in single mode...
app_1    | * Version 4.3.3 (ruby 2.7.0-p0), codename: Mysterious Traveller
app_1    | * Min threads: 5, max threads: 5
app_1    | * Environment: development
app_1    | * Listening on tcp://0.0.0.0:3000
app_1    | Use Ctrl-C to stop

とりあえず、localhost:3000 で確認

Mysql2::Error::ConnectionErrorが発生しているので、すこし修正します。

database.ymlの修正

将来のことも考え、productionも記載します。
productionには環境変数で設定しているため、Lambdaの環境変数を使ってユーザ名、パスワードなどを設定します。

default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000
  username: root
  password: password
  host: mysql

development:
  <<: *default
  database: app_development

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: app_test

production:
  <<: *default
  username: <%= ENV['MYSQL_USER'] %>
  password: <%= ENV['MYSQL_ROOT_PASSWORD'] %>
  database: <%= ENV['MYSQL_DATABASE'] %>
  host: <%= ENV['MYSQL_HOST'] %>
  socket: <%= ENV['MYSQL_SOCKET'] %>

再度Railsサーバ起動+localhost:3000 確認

$ docker-compose up

ここでエラーがでなければ、一応初期セットアップ完了、APIサーバとして動かすため、事前に/meを作成しておきます。

APIの作成

コントローラーとRouteのみ記載しlocalhostで確認します。

  • app/controller/me_controller.rb
class MeController < ApplicationController
  def index
    render json: { 'me' => 'me' }
  end
end
  • config/routes.rb
Rails.application.routes.draw do
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
  resources :me, only: [:index]
end

GET /me の確認
念のためdockerコンテナを再起動して、APIの確認を行います。

$ docker-compose up
$ curl localhost:3000/me
{"me":"me"}

Lambda(+ API Gateway)化

Lambda 用の Rack ブリッジサンプルをダウンロード

下記のサンプルソースからLambda用のブリッジサンプルを入手し、
https://github.com/aws-samples/serverless-sinatra-sample/blob/master/lambda.rb

中身のパスを1箇所だけ修正します。

- $app ||= Rack::Builder.parse_file("#{__dir__}/app/config.ru").first
+ $app ||= Rack::Builder.parse_file("#{__dir__}/config.ru").first

Native Extensions のローカルへのリビルド

dockerの中に入って作業します。この作業を行わないとNative Extensions系のライブラリが動作しないです。Postgresqlを使用する場合は別途コピー元のディレクトリを変更します。

dockerの中に入るコマンド

$ docker-compose ps
※ここでdockerの名前を確認
$ docker exec -it docker名 bash
bash-4.2# 

リビルド

bash-4.2# bundle install --path /bundle
bash-4.2# mv /bundle /app/vendor/
bash-4.2# sed -e 's|/bundle|vendor/bundle|' -i .bundle/config
bash-4.2# cat .bundle/config
---
BUNDLE_PATH: "vendor/bundle"

# 下記作業はmysqlを使う場合
bash-4.2# /sbin/ldconfig -p | grep mysql | cut -d\> -f2
bash-4.2# cp /usr/lib64/mysql/libmysqlclient.so.18 lib/

動くか確認します。

$ docker-compose stop
$ docker-compose up
$ curl http://localhost:3000/me

ファイルをzipで固めてlambdaへデプロイします。今回はdockerの中でやっています。

$ zip -r lambda_function.zip .bundle app config Gemfile* config.ru lambda_function.rb vendor lib public

Lambdaの作成

※細かい部分は省略。

デプロイするときzip容量が大きいのでS3からアップロード。
Lambda作成時の環境変数は以下のとおり。

Name Value
BOOTSNAP_CACHE_DIR /tmp/cache
BUNDLE_APP_CONFIG /var/task/.bundle
PASSWORD password
RAILS_ENV production
RAILS_LOG_TO_STDOUT true
RAILS_SERVE_STATIC_FILES true
USER root

ポイントはbootsnap_cache_dirで、bootsnap は ${WORKING_DIR}/tmp/cache にキャッシュを生成することになっており、このままLambdaを使うと、/var/task/tmp 自体は読み込み専用ディレクトリなっているためエラーが発生します。

テストイベントの作成

新しいテストイベントの作成 > イベントテンプレート > Amazon API Gateway AWS Proxy を選択
path httpMethod pathParameters を下記のように/me用に変更する

"path": "/me"
"httpMethod": "GET"
"pathParameters": {
    "proxy": "/me"
  }

テストを実行して下記レスポンスが返ってくるか確認する

{
  "statusCode": 200,
  "headers": {
    "X-Frame-Options": "SAMEORIGIN",
    "X-XSS-Protection": "1; mode=block",
    "X-Content-Type-Options": "nosniff",
    "X-Download-Options": "noopen",
    "X-Permitted-Cross-Domain-Policies": "none",
    "Referrer-Policy": "strict-origin-when-cross-origin",
    "Content-Type": "application/json; charset=utf-8",
    "ETag": "W/\"hogehoge\"",
    "Cache-Control": "max-age=0, private, must-revalidate",
    "X-Request-Id": "hoge-hoge-hoge-hoge-hoge",
    "X-Runtime": "0.004623"
  },
  "body": "{\"me\":\"me\"}"
}

API Gatewayの作成+Lambda連携

※細かい部分は省略。
proxyとして動作。
apigateway.PNG

ステージからAPIGatewayのデプロイを行い、APIの実行を行う

$ curl https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/hoge/me
{"me":"me"}

おわり

あとはRoute53やAPI Gatewayのカスタムドメインの設定を行えば本番運用可能です。またAPIGatewayの代わりにALBを使用することも可能です。その場合Lambdaのコードは修正不要です。

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4