Ruby 製サーバーレスフレームワークの Jets を検証してみたら、Rails ライクに使えていい感じだった

## はじめに 

2018年11月29日、[AWS Lambda で Ruby が正式にサポート](https://aws.amazon.com/jp/blogs/compute/announcing-ruby-support-for-aws-lambda/) されましたね。サーバーレスアプリケーションを試そうとしている Rubyist の方々には、念願のアップデートだったのではないでしょうか。

しかし、Lambda で Ruby がサポートされたとはいえ、どこから手をつけていいかわからない！という方が多いと思います。僕自身もそうでした。。

そんな中、Ruby 製のサーバーレスフレームワーク、[Jets](https://github.com/tongueroo/jets) という gem を見つけました。どんな形で使えるか検証したところ、簡単にサーバーレスアプリケーションに入門できそうな印象を持ちました。

この記事では、そんな Jets について、簡単な概要と、とっかかりとなるサンプルアプリケーションの作成・デプロイの検証結果についてまとめてみました。

## Jets とは

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/71cac4ba-aa96-d7b6-e590-d84be44c9bc3.png)

[Jets](https://github.com/tongueroo/jets) は、Ruby 製のサーバーレスアプリケーションフレームワークです。Ruby でサーバーレスアプリケーションを実装することができます。

また Jets にはアプリケーションをビルドし、AWS Lambda にデプロイする機能が含まれているため、コマンドライン上で手間をかけずビルド・デプロイすることが可能です。

## Jets の思想

Jets の利用方法をこの後書いていきますが、Jets で使うコマンドやコードは、[Ruby on Rails](https://github.com/rails/rails) とよく似ているところが多いです。

コマンド・コードを抜粋してみましたが、非常に Rails と似ていることがわかるのではないでしょうか。

```bash
jets new
jets generate scaffold
jets db:migrate
jets server
jets console
jets deploy
jets routes
jets call
jets status
jets url
jets delete
```

```ruby
class PostsController < ApplicationController
  def index
    # renders Lambda Proxy structure compatiable with API Gateway
    render json: {hello: "world", action: "index"}
  end

  def show
    id = params[:id] # params available
    # puts goes to the lambda logs
    puts event # raw lambda event available
    render json: {action: "show", id: id}
  end
end
```

(参考：http://rubyonjets.com/#page-top)


Jets の作者の Tung Nguyen さんは、 [Jets の紹介ブログポスト](https://blog.boltops.com/2018/08/18/introducing-jets-a-ruby-serverless-framework) で以下のようにコメントしています。

> I love working with Rails, Ruby and AWS; and wanted to work with something similar in the serverless world. So I built Jets.
>
> (意訳) 私は Rails、Ruby、AWS を使うことが好きで、サーバーレスの世界でも似たようなものを使いたいと思っていました。だから Jets を作りました。

このコメントからも、Jets が Rails の思想に影響を受けていることがわかりますね。

Rails をよく使っている方々でしたら、Rails の設計や考え方を流用して理解できるところが多く、使い方のキャッチアップもしやすいですし、設計で悩む時間も短縮できそうです。

## Jets アーキテクチャ概要

[Jets の README](https://github.com/tongueroo/jets#what-is-jets) によると、Jets を利用する上でキーとなっているのが、AWS Lambda と API Gateway のようです。以下にそれぞれの役割を簡単にまとめます。

AWSサービス|役割
:--|:--
AWS Lambda| Fuction as a Service として扱う。Jets では AWS のインフラの詳細を気にせず Lambda Function をアップロード・実行することが可能
API Gateway| Lambda Function へのルーティングを担う。RESTful な URL エンドポイントを、Lambda Function に紐付ける

イメージ図を [Jets 公式ドキュメント](http://rubyonjets.com/)から引用します。API Gateway がそれぞれの Lambda Function に対し、RESTful な URL を提供していることがざっくりつかめるのではないでしょうか。

![jets-web-architecture.png](http://rubyonjets.com/img/home/jets-web-architecture.png)

(画像引用元：http://rubyonjets.com/#page-top)

## Quick start で Jets アプリケーションを動かす

それでは実際に Jets アプリケーションを動かしてみたいと思います。[Jets 公式ドキュメントの Quick Start](http://rubyonjets.com/quick-start/) を試していきますが、以下2パターンで試していきます。

1. ローカル環境で動かす
2. AWS 環境で動かす

### 環境情報

* Mac OS X 10.13.6
* ruby 2.5.1
* mysql Ver 8.0.12 for osx10.13 on x86_64 (Homebrew)

### 1. ローカル環境で Jets を動かす

まず AWS の環境にデプロイする前に、ローカル環境で Jets アプリケーションを使えるようにします。

#### Jets インストール

まず gem をインストールします。コマンド一発ですね。

```bash
gem install jets
```

#### jets new：アプリケーション作成の下準備

次に、Jets アプリケーションの土台を作ります。`rails new` っぽいですね。

```bash
jets new jets-sandbox
cd jets-sandbox
```

実行時の出力を適宜省略しながら記載します。ファイル作成や gem install といった諸々の処理を実行しています。

```bash
# ファイル作成
Creating new project called jets-sandbox.
      create  jets-sandbox
      create  jets-sandbox/.env.development
      create  jets-sandbox/.env.test
      create  jets-sandbox/.env
      create  jets-sandbox/.gitignore
      create  jets-sandbox/.jetskeep
      create  jets-sandbox/.rspec
      create  jets-sandbox/Gemfile
      create  jets-sandbox/Procfile
      create  jets-sandbox/README.md
      create  jets-sandbox/Rakefile
      create  jets-sandbox/app/controllers/application_controller.rb
      create  jets-sandbox/app/helpers/application_helper.rb

(中略)

# gem インストール
Fetching gem metadata from https://rubygems.org/........
Resolving dependencies...
Fetching rake 12.3.2
Installing rake 12.3.2
Fetching concurrent-ruby 1.1.4
Installing concurrent-ruby 1.1.4


# yarn で JavaScript 関連ライブラリ インストール
Installing all JavaScript dependencies
         run  yarn add @rails/webpacker coffeescript@1.12.7 from "."

(中略)

# git commit
Initialized empty Git repository in 
 (中略)
 49 files changed, 7094 insertions(+)

Congrats 🎉 You have successfully created a Jets project.

Cd into the project directory:
  cd jets-sandbox

To start a server and test locally:
  jets server # localhost:8888 should have the Jets welcome page

Scaffold example:
  jets generate scaffold Post title:string body:text published:boolean

To deploy to AWS Lambda, edit your .env.development.remote and add a DATABASE_URL endpoint.
Then run:

  jets deploy
```

#### jets generate scaffold：アプリケーションの雛形を作る

Jets アプリケーションの雛形を作ります。こちらも `rails generate scaffold` とよく似ていますね。

```bash
jets generate scaffold Post title:string

     invoke  active_record
      create    db/migrate/20180925233914_create_posts.rb
      create    app/models/post.rb
      invoke  resource_route
       route    resources :posts
      invoke  scaffold_controller
      create    app/controllers/posts_controller.rb
      invoke    erb
      create      app/views/posts
      create      app/views/posts/index.html.erb
      create      app/views/posts/edit.html.erb
      create      app/views/posts/show.html.erb
      create      app/views/posts/new.html.erb
      create      app/views/posts/_form.html.erb
      invoke    helper
      create      app/helpers/posts_helper.rb
```

#### jets db:create db:migrate：DB のマイグレーション

DB のマイグレーションを行います。こちらも`rails db:create db:migrate` とほとんど同じです。

```bash
jets db:create db:migrate

Created database 'jets-sandbox_development'
Created database 'jets-sandbox_test'
== 20181230024506 CreatePosts: migrating ======================================
-- create_table(:posts) -> 0.0079s 
== 20181230024506CreatePosts: migrated (0.0080s)=============================
```

##### 補足： DBマイグレーションでエラーが起きた場合は

もし以下のエラーが出た場合は、MySQL 5系→MySQL 8系にアップグレードしたことが原因の可能性が高いです。

```bash
ERROR 1449 (HY000): The user specified as a definer ('mysql.infoschema'@'localhost') does not exist
```

以下コマンドを実行し、MySQL のシステム情報をアップデートしましょう。

```bash
mysql_upgrade -u root
```

(参考：https://bugs.mysql.com/bug.php?id=91432)

#### jets server：アプリケーション起動

いよいよ Jets アプリケーションを起動します。

```bash
jets server

=> bundle exec shotgun --port 8888 --host 127.0.0.1
Jets booting up in development mode!
== Shotgun/WEBrick on http://127.0.0.1:8888/
[2018-12-30 12:11:15] INFO  WEBrick 1.4.2
[2018-12-30 12:11:15] INFO  ruby 2.5.1 (2018-03-29) [x86_64-darwin17]
[2018-12-30 12:11:15] INFO  WEBrick::HTTPServer#start: pid=14592 port=8888
```

`http://localhost:8888` にアクセスすると、以下のような画面が表示されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/1805e803-83eb-2dfd-ed48-4908f9f01fcd.png)


そして scaffold で作成した Post モデルの CRUD 操作は、`http://localhost:8888/posts` にアクセスして行います。

アクセスの前に、`webpack` でJavaScript のライブラリをバンドルしてからアクセスしましょう。

```
webpack -w
```

それぞれ URL にアクセスすると、遷移が確認できると思います。

##### index: `http://localhost:8888/posts`

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/604d9b89-77b0-ef3a-ec30-1a451a950a20.png)

##### new: `http://localhost:8888/posts/new`

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/c3068192-d7e3-ba38-1102-e52e74cf0c74.png)

##### show: `http://localhost:8888/posts/1`

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/4eaa08de-63fa-c40e-0bad-5f68a77a550e.png)

### 2. AWS Lambda にデプロイ

ローカル環境での確認ができたので、AWS 環境に Jets アプリケーションをデプロイしましょう。

#### AWS シークレットキー設定

まず、Jets アプリケーションをデプロイする AWS アカウントのシークレットキーを設定します。

今回は検証のため、Administrator 権限の IAM ユーザーのシークレットキーを使いましたが、本番環境で使う場合などは、適切な IAM 権限で絞って使う必要がありそうですね。

```
vim ~/.aws/credentials

aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```

#### jets deploy：アプリケーションを AWS 環境にデプロイ

それではデプロイを実行してみましょう。以下コマンド一発でOKです。

```bash
jets deploy


(中略)

Time took for stack deployment: 4min 41s.
Prewarming application.
API Gateway Endpoint: https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/
```

コマンドを実行すると様々な処理が走ります。

その後、出力の最後に表示される API Gateway のURL `https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/` にアクセスし、アプリケーションの画面が表示されるか確認してください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/1805e803-83eb-2dfd-ed48-4908f9f01fcd.png)

`jets deploy` コマンドでは、アプリケーションのビルドからデプロイまで自動で実行してくれます。

* Lambda 用にアプリケーションの zip ファイルを作成
* S3 に zip ファイルをアップロード
* API Gateway の設定、Lambda Function を CloudFormation 経由で作成実行

AWS のコンソールを確認すると、それぞれ AWS リソースが作成されていることがわかると思います。

##### S3

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/5d45ee51-1bfd-567b-3b2a-3b253e84739f.png)

##### API Gateway

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/e5b4db18-2be4-4ea4-ed47-ccde50de1ab6.png)

##### Lambda

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/bb02fb0a-c340-f0e4-3fa8-527a58520ee6.png)

##### CloudFormation

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/fbc88062-2597-b45a-047e-5a59a4127530.png)

#### RDS MySQL データベース作成

これでアプリケーションのデプロイ自体はできましたが、 `Post` モデルの CRUD 画面の確認はまだできない状態です。

そのため、AWS 環境用のデータベースとして、RDS で MySQL データベースを作成・設定することで、`Post` モデルの CRUD 画面も確認できるようにします。

**※ Post モデルの CRUD 画面確認は不要で、デプロイ確認のみで十分な場合は、以降の手順は飛ばしてもらってOKです。**

データベース作成手順は [AWS の RDS ユーザーガイド](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/CHAP_GettingStarted.CreatingConnecting.MySQL.html)が詳しいのでそちらをご参照ください。

設定内容はほとんどデフォルトにしましたが、最低限修正したところは以下の通りです。

項目|設定内容
:--|:--
MySQLバージョン| MySQL 8.0.11
インスタンスサイズ|db.t2.micro
インスタンス名|jets-sandbox-development-instance
データベース名|jets_sandbox_development
ユーザー名|jetssandboxuser
パスワード|任意
パブリック接続|可能

検証において考慮する点を減らすため、Lambda Function はパブリック通信で RDS データベースに接続することを想定します。(同一プライベート VPC 内に Lambda、RDS を含めればできそうですが、今回は検証対象外とします)

そこで、 RDS データベース作成後、Lambda Function からパブリック通信経由で RDS データベースにアクセスできるようにするため、セキュリティグループの設定を修正します。

RDS に設定された セキュリティグループのインバウンドを、任意の IP (0.0.0.0/0) に設定し直してください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/c02914fb-cadd-102c-ce48-e2f2b14e7670.png)


#### Jets アプリケーションの環境変数ファイル修正

Jets アプリケーションが RDS データベース接続情報を認識できるよう、データベース設定を修正します。

まず `.env.development.remote` というファイルを作成します。

```bash
cp .env.development .env.development.remote
```

ファイル内で、RDSデータベース情報を以下のように設定します。

```.env.development.remote
ENV_DEVELOPMENT_KEY=example1
DB_NAME=jets_sandbox_development
DB_USER=jetssandboxuser
DB_PASS=EXAMPLEPASSWORD
DB_HOST=jets-sandbox-development-instance.XXXXXXXXXXXX.us-east-1.rds.amazonaws.com
```

#### RDSデータベースに対しマイグレーション実行

RDSデータベースに対し、マイグレーションを実行します。

以下2つの環境変数の指定しつつコマンドを実行することで、`.env.development.remote` を参照するようになります。

* `JETS_ENV`: 環境を指定
* `JETS_ENV_REMOTE`: リモートの環境かどうかを指定

```
JETS_ENV=development JETS_ENV_REMOTE=1 jets db:create db:migrate
```

#### jets console: コンソールでアプリケーションのデバッグ

もし接続がうまくいかないよう場合は、`jets console` を使ってデバッグ確認してみましょう。

`rails console` のような形で、`Post.all`　のようにアプリのモデルの状態を確認できます。

```
JETS_ENV=development JETS_ENV_REMOTE=1 jets console


Jets booting up in development mode!
irb(main):001:0> Post.all
=> #<ActiveRecord::Relation [#<Post id: 1, title: "test", created_at: "2018-12-30 05:24:18", updated_at: "2018-12-30 05:24:18">]>
irb(main):002:0>
``````

#### 再度デプロイ・画面確認

最後に再度デプロイします。

```bash
jets deploy
```

デプロイ後、`https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/posts` にアクセスできるか確認してください。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/222030/604d9b89-77b0-ef3a-ec30-1a451a950a20.png)


### jets delete︰サンプルアプリケーションを削除

検証で使ったアプリケーションを最後に削除したい！という場合は、`jets delete`コマンドで関連する AWS リソースを一括削除できます。

RDS データベースは、直接 RDS コンソールからインスタンスを削除してください。

```bash
jets delete

Deleting project...
Are you sure you want to want to delete the jets-sandbox-dev project? (y/N)
y
First, deleting objects in s3 bucket jets-sandbox-dev-s3bucket-uio563jvqwra
Deleting jets-sandbox-dev...
03:27:22PM DELETE_IN_PROGRESS AWS::CloudFormation::Stack jets-sandbox-dev User Initiated
03:27:23PM DELETE_IN_PROGRESS AWS::CloudFormation::Stack JetsPreheatJob
03:27:23PM DELETE_IN_PROGRESS AWS::CloudFormation::Stack ApiDeployment20181230142056
03:27:34PM DELETE_COMPLETE AWS::CloudFormation::Stack ApiDeployment20181230142056
03:27:35PM DELETE_IN_PROGRESS AWS::CloudFormation::Stack PostsController
03:27:35PM DELETE_IN_PROGRESS AWS::CloudFormation::Stack JetsPublicController
03:27:58PM DELETE_COMPLETE AWS::CloudFormation::Stack JetsPublicController
03:28:10PM DELETE_COMPLETE AWS::CloudFormation::Stack PostsController
03:28:11PM DELETE_IN_PROGRESS AWS::CloudFormation::Stack ApiGateway
03:28:22PM DELETE_COMPLETE AWS::CloudFormation::Stack ApiGateway
03:28:41PM DELETE_COMPLETE AWS::CloudFormation::Stack JetsPreheatJob
03:28:42PM DELETE_IN_PROGRESS AWS::IAM::Role IamRole
03:28:42PM DELETE_IN_PROGRESS AWS::Lambda::LayerVersion GemLayer
03:28:43PM DELETE_COMPLETE AWS::IAM::Role IamRole
Stack jets-sandbox-dev deleted.
Time took for deletion: 1m 46s.
Deleting CloudWatch logs
Project jets-sandbox-dev deleted!
```

こちらで検証は以上です。お疲れ様でした！

## 終わりに

だいぶ長くなってしまいましたが、Jets を触ってみた所感としては、Rails のコンテキストを踏まえて簡単にアプリケーションを作成・デプロイできるので、割とすんなり動かせたなと思いました。

また、コマンドラインから AWS リソースをあまり意識せずにビルド・デプロイできるのも、習得コストが少なくていい感じです。

ただ、IAM の権限制御や実際のアプリケーションのパフォーマンスといった、本番環境で実運用するための考慮点はまだまだありそうなので、また別途検証をしてみようと思います！

## 参考URL 

* [週刊Railsウォッチ（20181225）Rails 6新機能第2弾「Action Mailbox」、url_forは慣れが要る、Ruby製サーバーレスフレームワークJetsほか](https://techracho.bpsinc.jp/hachi8833/2018\_12_25/67290)
* [Jets Ruby Serverless Framework](http://rubyonjets.com/)
* [Introducing Jets: A Ruby Serverless Framework on AWS Lambda](https://blog.boltops.com/2018/08/18/introducing-jets-a-ruby-serverless-framework)