はじめに
AWS lambdaでWEBアプリを動かしたいけど、API Gatewayの仕様が窮屈で諦めた。
ステートフルなWEBアプリをステートレスに改修するのは面倒。
そのような方に、API Gatewayを使わない構成をご紹介します
AWS lambdaでWEBアプリは無理?
AWS lambdaの主な特徴
- サーバの面倒をみなくてよい
- 使わないと料金が発生しない
- 急なリクエスト増にも標準で対応できる
この最初の2つの特徴を知って、多くの人が驚き、喜び、そして、
サーバーの面倒みなくていいし、使わない時間の料金は請求されなくて安いし、
月に2, 3回数人が数分使うだけの社内の簡易WEBアプリを動かすのに最適じゃない?
と、ごくごく自然に考えます。
じゃ、さっそく試そうとすると、
AWS lambdaをHTTPSで使うには、図のようにAPI Gatewayを前段に使う必要があることがわかり、
じゃ、API Gatewayを使おうとすると、
- API Gatewayには最大30秒のタイムアウト制約があり、AWS lambdaを30秒しか動かせない
- API Gatewayが、常に同じlambdaインスタンスを使ってくれる保証がない
- API Gatewayがヘッダーを書き換えてしまうので、WEBアプリに実装されているBASIC認証が使えない。API Gateway専用のlambdaを作る必要がある
- websocketが使えない
と、いろいろ面倒(仕様上の制約)な壁にぶつかります。
WEBアプリを改修すればAWS lambda + API Gatewayで動かせますが、
そもそも利用頻度も低く手間をかけたくないという動機なので、
わざわざWEBアプリをステートレスに改修する手間はかけたくありません。
できないじゃん、、、残念
と私も諦めていました。この方法を発見するまでは。
この方法でAWS lambdaのWEBアプリにHTTPSアクセスできる
Ngrok(エングロック)というサービスをご存知でしょうか。
URLのエンドポイントを提供してくれる、開発者に人気のリバースプロキシ―サービスです。
PC上で開発中のアプリを起動し、Ngrokで一時的なURLを取得してインターネットに公開。
インターネットからのアクセスを開発中アプリに流すことができます。
ある時思い立って、AWS lambdaからNgrokに接続しやると、
Ngrokで取得したURLで、ステートフルなWEBアプリが普通に使えるではありませんか!!
この構成であれば、
- 30秒のタイムアウト制約はありません。300秒AWS lambdaの実行時間最大まで使えます
- 同じインスタンスがリクエストを処理してくれるので、ステートフルWEBアプリがそのまま動きます
- WEBアプリに実装されているBASIC認証がそのまま使えます
- websocketも使えます
今まで、WEBアプリをlambdaで動かすには、次の記事のように
- [Node-REDをAWS API Gateway + lambda + S3で動かす方法]
(https://qiita.com/sakazuki/items/9d55ac14432e73524e04)
AWS Serverless Expressなどを使って改修して動かすことができましたが、
この構成では、Ngrokに接続するラッパーだけ書けばいいのでとても簡単です。
サンプル
この構成のサンプルアプリ(Node.js + express)をGithubに登録しましたので試してみてください。
'use strict';
const app = require('./app');
const PORT = 3000;
const ngrok = require('ngrok');
const EventEmitter = require('events').EventEmitter;
const ev = new EventEmitter();
const slack_url = process.env.SLACK_URL;
const https = require('https');
const url = require('url');
const postSlack = (msg) => {
const opts = url.parse(slack_url);
opts.method = 'POST';
opts.headers = {'Content-Type': 'application/json'};
const req = https.request(opts, (res) => {
console.log("Send a slack message: " + res.statusCode);
});
req.on('error', (err) => {console.log(err)});
req.write(JSON.stringify({text: msg}));
req.end();
}
module.exports.server = (event, context, callback) => {
let URL;
// (1)
const server = app.start(PORT, context, (req, res) => { // (4)
ngrok.kill();
console.log('Receive Bye request');
postSlack('Closed ' + URL);
ev.emit('kill');
})
// (2)
ngrok.connect(PORT, (err, url) => {
if (err) {
callback(err, null);
return;
}
URL = url;
ev.once('kill', () => {
const response = {
statusCode: 200,
body: JSON.stringify({ message: 'Good bye' }),
};
server.close();
console.log('App closed.');
callback(null, response);
})
postSlack('Go ' + URL); //(3)
console.log('Connect to %s', URL);
})
};
簡単に解説すると
- アプリを
Port 3000
で起動する - Ngrokで
Port 3000
を公開して、一時URLを取得する - 一時URLをSLACKで通知する
- 特定のリクエストがきたら終了する
- 4の終了処理をきれいにやるためにコードが少し多くなっていますが、lambdaのタイムアウト終了でよしと割り切ればシンプルにできます
おわりに
AWS lambdaの中からリバースプロキシ(Ngrokなど)との間に接続を張り、リバースプロキシ経由でWEBアプリを利用する。これでAPI Gatewayの仕様上の制約から解放されて、AWS lambda上でステートフルなWEBアプリを使うことができました。
AWS lambdaの最大実行時間300秒の制約の範囲内でですが、
社内のちょっとした処理など一回2,3分アプリが動けば十分なケースで使える構成かと思います。試してみてください。
補足
- この記事はServerless Meetup Tokyo #8のLTで話した内容です
- 参考 LT資料@SlideShare
- 他の言語や他のWEBサーバーでも同様のアプローチでWEBアプリを使う事ができるようになります
- Ngrokはセキュリティーが気になるという場合は、自前でlocaltunnelサーバーを建てるか、SSHポート転送を使う事でも問題を解決できます。