Apex/UpでAWSにサーバーレスアプリケーションをデプロイする

2017.12.29

「サーバーレスアーキテクチャ上で使い慣れたWebアプリケーションフレームワークを動かしたい」。こんな風に考えたことがある方は少なからずいらっしゃるかと思います。

awslabsでも、AWS Lambda + Amazon API GatewayでExpressSpringなどのJavaのフレームワークを使う動かすためのライブラリや実装例が公開されています。

既存のWebアプリケーションはそのままに、サーバーレスアーキテクチャのスケーラビリティやコストといったメリットを享受したい、、、今回はそんな希望をかなえてくれる、、、かもしれないツール、Upをご紹介します。

「Up」とは

以前「ApexでAWS Lambdaファンクションを管理する」という記事でAWS Lambdaファンクションのビルド/デプロイツールのApexを紹介しましたが、UpはこのApexの作者である@tjholowaychukさんが開発している、「AWSのサーバーレスアーキテクチャ上に(AWS Lambda + Amazon API Gatewayに)WebアプリケーションやAPIをデプロイするためのツール」です。最初のリリースは2017年8月のv0.1.0、現時点でv0.4.5までリリースされています。

サーバーレスアプリケーションのデプロイツールとしては Serverless FrameworkやAWS謹製のAWS SAMが有名かと思いますが、Upは"バニラな"WebアプリケーションやAPIをデプロイすることにフォーカスしている点が特徴です。

Upを使う場合、開発者がやることはAWS Lambdaファンクションではなく"バニラな"WebアプリケーションやAPIを開発してUpでデプロイするだけです。AWS LambdaやAmazon API Gatewayの設定はUpが抽象化してくれるため、開発者はその存在を意識する必要がありません(トラブルシュートの観点ではAWS LambdaやAmazon API Gatewayについて理解しておく必要があるかとは思います)。

公式ドキュメントに、

You can think of Up as self-hosted Heroku style user experience for a fraction of the price, with the security, flexibility, and scalability of AWS.

とあるように、AWS上に、シングルコマンドでデプロイ可能なスケーラブルかつ低コストなPaaS環境を構築するのがこのツールの機能です。

言葉だけではイメージしにくいかと思いますので、この後Github上に公開されているExamplesを使ってUpの具体的な使い方を紹介していきたいと思います。

ちなみに現時点でUpがサポートしているのはAWSのみですが、「platform-agnostic」を謳っており、将来的にはAWS以外もサポートする計画があるようです。

サポートしているランタイム

現時点でサポートしているランタイムは以下の5つです。基本的にはAWS Lambdaがサポートするランタイムに依存していますが、GolangやCrystalなど、ネイティブコードにコンパイル出来る言語でサポートされているものもあります。

  • Node.js
  • Golang
  • Python
  • Java
  • Crystal

この他に、静的サイトのデプロイもサポートしています。

OSS版とPro版

Up@tjholowaychukさんが設立したApex Softwareのプロダクトとしてリリースされており、OSS版とPro版(有償)があります。

Pro版は「$20/mo USD」です(※early-access特典で今なら50%OFFの「$10/mo USD」でサブスクライブできるようです)。現時点でPro版は「early-access alpha」のステータスで、Pro版の機能として利用できるのは「環境変数の暗号化」と「アラート通知」のみです。将来的にはCIツール連携、Proxyサポート、デスクトップアプリケーションなど機能が提供される予定のようです。

Upを使ってみる

前置きはこのぐらいにして、実際にUpを使ったWebアプリケーションのデプロイ方法を見ていきたいと思います。 作者が同じだけあって、全体的な使い勝手はApexのそれとほぼ同等です。

UpのGithubリポジトリ上には各ランタイム毎にExampleがいくつか公開されていますが、今回はこちらにある、GolangのWebアプリケーションフレームワークGinを使ったExmapleを動かしてみます。

動作確認環境

  • OS : macOS High Sierra v10.13.2
  • Up : v0.4.5
  • Golang : v1.9.2

インストール

以下を実行すると、/usr/local/binディレクトリ下にUpのバイナリファイルがインストールされます。

$ curl -sfL https://raw.githubusercontent.com/apex/up/master/install.sh | sh

動作確認のため、Upのバージョンを確認してみましょう。

$ up version
0.4.5

アップグレードは以下のコマンドで簡単に実行できます。

$ up upgrade

AWSのクレデンシャル

AWS LambdaやAmazon API Gatewayのリソース作成やAWS Lambdaファンクションのデプロイのために、AWSのクレデンシャル情報を設定します。これにはAWS CLIのそれと同じ方法が使えます。

今のところUpのデプロイコマンド実行時にプロファイルを指定する方法は無いようです。デフォルト以外のプロファイルを利用する場合は、~/.aws/credentialsにクレデンシャル情報を記載した上で、

  • 環境変数「AWS_PROFILE」でプロファイル名を指定(export AWS_PROFILE=myapp
  • Upのアプリケーション設定ファイル(up.json)で指定する

のいずれかの方法を使います(事故防止のため後者の方法が推奨されます)。

なお、up.jsonでは以下のようにプロファイルを指定します。

{
  "profile": "myapp"
}

サンプルアプリケーションの準備

Ginを使ったサンプルアプリケーションを準備します。

ディレクトリ構成

.
├── cmd
│   └── api
│       └── main.go
│
├── up.json
└── .upignore

main.go

package main

import (
    "github.com/gin-gonic/gin"
    "os"
)

func setupRouter() *gin.Engine {

    r := gin.Default()

    // Default Route
    r.GET("/", func(c *gin.Context) {
        c.String(200, "Hello world")
    })

    // Ping test
    r.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })

    // Get user value
    r.GET("/user/:name", func(c *gin.Context) {
        user := c.Params.ByName("name")
            c.JSON(200, gin.H{"user": user})
    })

    return r
}

func main() {
    r := setupRouter()
    port := ":" + os.Getenv("PORT")
    r.Run(port)
}

GinのGithubリポジトリに公開されているExampleも参考にしつつ、多少手を加えました。

up.json

{
  "name": "up-sample-golang-gin",
  "profile": "up-sample"
  "regions": [
    "ap-northeast-1"
  ],
  "lambda": {
    "memory": 128
  },
  "hooks": {
    "build": "GOOS=linux GOARCH=amd64 go build -o server cmd/api/main.go",
    "clean": "rm server"
  }
}

リージョン(ap-northeast-1)、プロファイル(up-sample)、AWS Lambdaファンクションのメモリサイズ(128)を指定しています。最低限必要なのはこれだけです。

今回のサンプルはGolangのプログラムなので、hooksで、デプロイ前にプログラムをビルド(cmd/api/main.goをコンパイル)し、デプロイ後にバイナリ(server)を削除する設定を入れています。

up.jsonの設定の詳細については公式ドキュメントを参照ください。

.upignore

*
!server

.upignoreファイルで、デプロイ対象外とするファイルやディレクトリを指定できます。今回のサンプルアプリケーションではデプロイ対象はGolangのバイナリ「server」のみなので、「server」以外をデプロイ対象外にする設定を入れています。

依存パッケージのインストール

depで依存パッケージをインストールします。

$ dep init

デプロイ

ここまででサンプルアプリケーションの準備が整ったので、Upでデプロイしてみます。デプロイはupコマンドを実行するだけです。

$ up

build: 5 files, 8.9 MB (1.066s)
deploy: version 1 (18.068s)
stack: complete (1m34.16s)

AWS LambdaとAPI Gatewayのリソースが作成されるため、初回デプロイには少し時間がかかります。

upコマンドを実行するとAmazon API Gatewayでdevelopmentstagingproductionの3つのステージが作成されます。各ステージ単位でデプロイを実行したい場合はup deploy [<stage>]コマンドを使います(stageを指定しなかった場合、デフォルトはdevelopmentに対してデプロイが実行されます)。

動作確認

up url --open [<stage>]コマンドを実行すると、ブラウザが起動しデプロイしたWebアプリケーションにアクセスします(stageを指定しなかった場合には、デフォルトのdevelopmentにアクセスします)。up url --copyでURLをクリップボードにコピーすることもできます。

UpにはRoute53を利用して独自ドメインを設定する機能もあります(up domains buyコマンドでドメインの購入からできたりします)。

今回は独自ドメインは設定していないので、Amazon API Gatewayで払い出されたドメイン名が利用されます。

アプリケーションの削除

up stack deleteコマンドを実行すると、AWS LambdaとAmazon API Gatewayのリソース含めてアプリケーションが削除されます。

AWSリソースの設定

実施にAWS LambdaとAmazon API Gatewayがどのように設定されているかを覗いてみます。

Amazon API Gateway

  • 先ほど触れたように、developmentstagingproductionの3つのステージが作成されています。

  • パス変数のキャッチオールが設定されています。またステージ変数を利用して各ステージとAWS Lambdaファンクションのエイリアスがマッピングされている様子がうかがえます。

AWS Lambda

  • エイリアスが設定されています。

  • Golangのアプリケーションをデプロイした場合、AWS LambdaファンクションのランタイムはNode.jsになります。これはApexと同様です(Node.jsからGolangのバイナリをspawnで呼び出す)。

アプリケーションバンドルの中身

up buildコマンドを実行すると、AWS Lambdaファンクションのアプリケーションバンドル(zipファイル)をローカルに出力できます(out.zip)。

今回のサンプルアプリケーションのバンドルの中身は以下のようになっていました。

.
├── _proxy.js
├── byline.js
├── main
├── server
└── up.json
  • バンドルにはmainserverの2つのバイナリが含まれています。
  • mainがAmazon API Gateway <-> AWS Lambda間のリクエスト/レスポンス(JSON)とhttp.Requesthttp.ResponseWriterをコンバートする役割を担っているようです。

まとめ

Upを使ってシングルコマンドでWebアプリケーションをAWS Lambda + Amazon API Gatewayにデプロイする様子をご紹介しました。 今回ご紹介した設定の他に、

  • カスタムエラーページ
  • ヘッダーインジェクション
  • リダイレクト/URLリライト

などもup.jsonで設定できるようなので、別の記事でご紹介したいと思っています。

ブラックボックス化されるとはいえ実体はAWS Labmbdaファンクションなので、「Upを使えば既存のWebアプリケーションが何でもかんでもサーバーレスで動く!」、というわけでもちろんありません。もう少し本格的なアプリケーションを動かしつつ、諸々の制約やハマりどころなどを見極めて行きたいと思います。

ところで、サーバーレスアプリケーションのデプロイ先としてはAWS Fargateも今後有力な選択肢になってくるかと思います。Dockernizeできてしまえば基本的には何でも動くので、フレームワーク選択の自由度はAWS Fargateが圧倒的に有利かと思います。コンテナのメンテが不要である点やコスト面ではUp(AWS Lambda + Amazon API Gateway)の方がメリットがありそうです。

繰り返しになりますがGithub上に各ランタイムのExampleが多数公開されています。皆さんもぜひお好みのランタイムでUpをお試しください。