はじめに
serverless deployする機会があったので、学んだところなども踏まえて簡単にまとめてみます
ディレクトリ構成
sample-project
┣ backend(railsプロジェクト)
┣ Dockerfile
┣ serverless.yml
┣ entrypoint.sh
┣ docker-compose.yml
ファイル準備
Dockerfileは開発環境用と本番環境用に分けています
# 開発用
ARG DOCKER_TAG
ARG NODE_TAG
ARG RUBY_TAG
FROM public.ecr.aws/docker/library/node:${NODE_TAG} as node
FROM public.ecr.aws/docker/library/docker:${DOCKER_TAG} as docker
FROM public.ecr.aws/lambda/ruby:${RUBY_TAG}
WORKDIR /app
RUN yum update -y && yum -y groupinstall "Development tools" --setopt=group_package_types=mandatory,default,optional && \
yum install -y less tzdata unzip python3 && \
cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
unzip awscliv2.zip && \
./aws/install && \
yum clean all
ARG YARN_VER
COPY --from=node /opt/yarn-${YARN_VER} /opt/yarn
COPY --from=node /usr/local/bin/node /usr/local/bin/
COPY --from=node /usr/local/lib/node_modules/ /usr/local/lib/node_modules/
RUN ln -s /opt/yarn/bin/yarn /usr/local/bin/yarn \
&& ln -s /opt/yarn/bin/yarn /usr/local/bin/yarnpkg \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs \
&& ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
&& ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npx
# Docker binary copy
COPY --from=docker /usr/local/bin/docker /usr/local/bin/
ARG SLS_VER
RUN yarn global add serverless@${SLS_VER} aws-sso-credentials-getter @vue/cli @vue/cli-service \
&& yarn cache clean
ARG BUNDLE_VER
ENV BUNDLE_VER ${BUNDLE_VER}
ARG RUBY_TAG
ENV RUBY_TAG ${RUBY_TAG}
RUN gem install bundler -v ${BUNDLE_VER}
RUN bundle config path /usr/local/bundle
ENTRYPOINT ["./entrypoint.sh"]
# 本番用
ARG RUBY_TAG
FROM public.ecr.aws/lambda/ruby:${RUBY_TAG}
ENV LANG C.UTF-8
RUN yum update -y && yum groupinstall -y "Development Tools"
WORKDIR /var/task
ARG BUNDLE_VER
RUN gem install bundler -v ${BUNDLE_VER}
COPY lambda.rb /var/task
COPY ./Gemfile /var/task
COPY ./Gemfile.lock /var/task
ENV GEM_HOME=/var/task
RUN bundle install
ADD . /var/task/
CMD ["lambda.App::Handler.process"]
- lambda関数の実体は/var/taskに存在しているようなので、ハンドラーのlambda.rb(後述)やGemfileなどをマウントしています
- CMDでイメージ実行時にハンドラー(後述)が実行されるようにしています
services:
qiita:
build:
context: .
args:
DOCKER_TAG: "20.10"
NODE_TAG: "17.1.0"
RUBY_TAG: "2.7.2022.02.01.09"
YARN_VER: "v1.22.15"
SLS_VER: "3.23.0"
BUNDLE_VER: "2.3.5"
tty: true
init: true
ports:
- 8080:8080
volumes:
- ./:/app
- ~/.aws:/root/.aws
- /var/run/docker.sock:/var/run/docker.sock
- node_modules:/app/node_modules
- bundle:/usr/local/bundle
container_name: qiita
volumes:
node_modules:
bundle:
bundle _${BUNDLE_VER}_ install --gemfile backend/Gemfile && yarn install && sh
serverless deployを実行する際の設定をserverless.ymlに記載します
service: qiita-api
provider:
name: aws
runtime: ruby2.7
timeout: 300
region: ap-northeast-1
deploymentBucket:
name: qiita-rails-api-dev-slsdeploymentbucket
ecr:
scanOnPush: true
images:
lambda_docker_qiita:
path: ./backend
buildArgs:
BUNDLE_VER: ${env:BUNDLE_VER, '2.3.5'}
RUBY_TAG: ${env:RUBY_TAG, '2.7'}
custom:
defaultStage: dev
deploymentBucket:
# s3バケットオブジェクトへのパブリックアクセスをすべてブロック
blockPublicAccess: true
functions:
api:
timeout: 25
image:
name: lambda_docker_qiita
events:
- http:
path: /
method: ANY
- http:
path: /{proxy+}
method: ANY
plugins:
- serverless-deployment-bucket
-
serverless deploy時に自動で作成されるs3バケットがパブリックアクセスオンの状態なので、
serverless-deployment-bucketというプラグインを用いてパブリックアクセスをオフにしています -
ecrにpushされたイメージを元にlambdaが関数を作ってくれます
-
eventsでエンドポイントを2つ(rootとroot以下全て)指定しています
/{proxy+}がワイルドカードのようなふるまいをしてくれているようです -
このeventsがトリガーとなってlambda関数が実行されます。
エンドポイントにアクセスする度に本番環境用のDockerイメージが作成され、CMDで指定したハンドラーが実行されます
プラグインはsls deploy時に使用するので、開発環境用のコンテナに入ってインストールします
docker exec -it qiita sh
yarn add serverless-deployment-bucket
成功するとsample-project/pacage.jsonが以下のようになると思います
{
"dependencies": {
"serverless-deployment-bucket": "^1.6.0"
}
}
次に動作確認用として以下のファイルを作成します
class TestController < ApplicationController
def index
render json: {status: :ok, message: "hello! world!"}
end
end
Rails.application.routes.draw do
resources :test
end
# This file is used by Rack-based servers to start the application.
require_relative 'config/environment'
run Rails.application
require 'json'
require 'rack'
require 'base64'
$app ||= Rack::Builder.parse_file("#{__dir__}/config.ru").first
module App
class Handler
def self.process(event:, context:)
body = if event['isBase64Encoded']
Base64.decode64 event['body']
else
event['body']
end || ''
puts body if body.present? && body.exclude?('password')
headers = event.fetch 'headers', {}
env = {
'REQUEST_METHOD' => event.fetch('httpMethod'),
'SCRIPT_NAME' => '',
'PATH_INFO' => event.fetch('path', ''),
'QUERY_STRING' => Rack::Utils.build_query(event['queryStringParameters'] || {}),
'SERVER_NAME' => headers.fetch('Host', 'localhost'),
'SERVER_PORT' => headers.fetch('X-Forwarded-Port', 443).to_s,
'rack.version' => Rack::VERSION,
'rack.url_scheme' => headers.fetch('CloudFront-Forwarded-Proto') { headers.fetch('X-Forwarded-Proto', 'https') },
'rack.input' => StringIO.new(body),
'rack.errors' => $stderr,
}
headers.each_pair do |key, value|
name = key.upcase.gsub '-', '_'
header = case name
when 'CONTENT_TYPE', 'CONTENT_LENGTH'
name
else
"HTTP_#{name}"
end
env[header] = value.to_s
end
begin
status, headers, body = $app.call env
headers.merge!({
'Access-Control-Allow-Credentials': true,
'Access-Control-Expose-Headers': 'Content-Disposition,X-Suggested-Filename',
'Content-Security-Policy': "default-src 'self'"
})
body_content = ""
body.each do |item|
body_content += item.to_s
end
response = {
'statusCode' => status,
'headers' => headers,
'body' => body_content
}
rescue Exception => exception
response = {
'statusCode' => 500,
'body' => exception.message
}
end
response
end
end
end
-
API GatewayがLambdaにeventを渡した後、LambdaがRailsを実行するためのHandlerとしてlambda.rbが必要になります
-
ECR接続などでaws認証が必要になったので、今回はコマンドから設定しておきます
# docker exec -it qiita sh
serverless config credentials --provider aws --key hogehoge --secret fugafuga
動作確認
デプロイを実行します
# docker exec -it qiita sh
sls deploy
デプロイに成功すると以下の表示がされると思います
ここでは伏せていますが、endpointsやfunctionsなども表示されていると思います
動作確認用に設定した/testにアクセスして、hello worldが画面に表示されることを確認します
(//○○.ap-northeast-1.amazonaws.com/dev/test)
上手くいきました!
おわりに
とりあえずdeployできるところまで行きたかったので駆け足になってしまった部分があります。
これからAPI GatewayとLambdaの詳しい動きやRack部分の理解を深めていきます。