Herokuで2つの異なる言語で構成されたアプリケーションを1つのdynoで動かす
Rails等で作ったちょっとしたWebアプリケーションを動かす時に、
Herokuにデプロイするとお手軽で便利ですが。
近頃はちょっとしたアプリケーションでも、
バックエンド(API)と、
フロントエンドのServerSideRenderingなどを組み合わせることも多くなってきました。
小さなアプリケーションを試す場合に、
バックエンドとフロントエンドで個々にデプロイするのは面倒だと思います。
そこで、このエントリでは、
バックエンドとフロントエンドが異なる言語で構成されたアプリケーションを
1つのdynoで動かす手順について記載します。
実現方法の整理
まず、Multiple Buildpacksを使うと1つのdynoで異なる言語を動かすことが可能になります。
Using Multiple Buildpacks for an App | Heroku Dev Center
https://devcenter.heroku.com/articles/using-multiple-buildpacks-for-an-app
次に、以下の方法を用いると1つのdynoで複数のプロセスを実行することが出来ます。
How do I run multiple processes on a dyno? | Heroku Help
https://help.heroku.com/CTFS2TJK/how-do-i-run-multiple-processes-on-a-dyno
これらを組み合わせると、
バックエンドとフロントエンドが異なる言語で構成されたアプリケーションを
1つのdynoで動かせることがわかります。
環境を作る① - Railsアプリ(API)の作成
それでは実際に環境を作っていきます。
まずはバックエンド部分としてRailsでAPIを作成します。
$ rails new multi-language-single-dyno-sample --api --skip-active-record
$ cd multi-language-single-dyno-sample
ここでは、 以下のように現在時刻を返却するAPIを実装します。
app/controllers/sample_controller.rb
class SampleController < ApplicationController
def current_time
render json: { 'current_time': Time.now() }
end
end
config/routes.rb
Rails.application.routes.draw do
get '/current_time', to: 'sample#current_time'
end
サーバを起動して動作を確認します。
$ bundle exec rails s
http://localhost:3000/current_time
にアクセスして、
現在時刻がJSONで返却されればOKです。
環境を作る② - Express(Node)アプリの作成
次にフロントエンド部分としてExpress(Node)のアプリを作成します。
Railsアプリのディレクトリと同じ場所で、
npmを初期化して、express、 express-http-proxyを追加します。
express-http-proxyは、APIをプロキシするために使います。
$ npm init
$ npm add express
$ npm add ejs
$ npm add express-http-proxy
expressを起動するindex.jsを用意します。
index.js
const proxy = require('express-http-proxy');
const express = require('express')
const path = require('path')
const PORT = process.env.PORT || 5000
express()
.use(express.static(path.join(__dirname, 'public')))
.set('views', path.join(__dirname, 'views'))
.set('view engine', 'ejs')
.get('/', (req, res) => res.render('pages/index'))
.use('/api', proxy('127.0.0.1:3000'))
.listen(PORT, () => console.log(`Listening on ${ PORT }`))
出力するHTMLファイルを作ります。
$ mkdir -p views/pages
views/pages/index.ejs
<html>
<head>
<title>hello express</title>
</head>
<body>
<script>
var request = new XMLHttpRequest();
var hostname = location.host;
request.open('GET', '//' + hostname + '/api/current_time', true);
request.onload = function () {
var data = eval("(" + this.response + ")");
document.write(data['current_time']);
}
request.send();
</script>
</body>
</html>
Herokuで実行出来るようにProcfileを用意します。
Procfile
web: node index.js
Heroku localで動かしてみます。
$ heroku local
以上で、http://localhost:5000/
にアクセスすると、
APIから現在時刻を取得して表示します。
環境を作る③ - Rails/ExpressをHeroku Localで実行する
一旦、RailsとExpressを終了して、
Procfileで両方あわせて起動できるように設定します。
Rails(puma)とexpressを別ポートで起動するように、
pumaの設定で参照するPORTの環境変数を変更しておきます。
config/puma.rb
修正前: port ENV.fetch("PORT") { 3000 }
修正後: port ENV.fetch("PORT_API") { 3000 }
両方あわせて起動できるように、Procfileを変更します。
Procfile
web: PORT_API=3000 puma -C config/puma.rb & node index.js & wait -n
Heroku localで動かしてみます。
$ heroku local
http://localhost:5000/
へのアクセスで同じ結果が表示されます。
環境を作る④ - Herokuにデプロイ
以上でローカルで動く環境が構築出来たので、Herokuにデプロイしてみます。
Gitにcommitします。
$ echo "node_modules/*" >> .gitignore
$ git add .
$ git commit -a -m initial-commit
Herokuにアプリを作り、ruby, nodejsのbuildpackを追加します。
その後、モジュールをpushするとHerokuにデプロイできます。
$ heroku create --buildpack heroku/ruby
$ heroku buildpacks:add --index 1 heroku/nodejs
$ git push heroku master
表示されたURLにアクセスするとローカルと同じ結果が表示されるはずです。
以上の流れで、
2つの異なる言語で構成されたアプリケーションを1つのdynoで動かす事が出来ました。
この手順を、APIとフロントが分かれている小さなアプリを実験する時などに、
役立てて頂ければと思います。