動機
私は現在、TitterやLINEでbotを作り、特定の界隈向けに情報を発信しています。これらは既存のプラットフォームにのっかるだけなので比較的簡単に公開まで持って行くことができます。
しかし、これらの方法での発信では物足りなくなってきました。そこで、webサービスを作ろうと考えました。
完全に趣味としてやっているので、可能な限り無料でやろうということでHerokuに目を付けました。
というわけで今回はHeroku公式にあるチュートリアルをやっていこうと思います。
この記事はほぼチュートリアルの和訳みたいになってしまいましたがご了承ください。
準備
最初に見つけたのは**Getting Started on Heroku with Javaというページだったのですが、こちらはMavenを使用するようです。
このページの中で「MavenではなくGradleを使う人はこっちをみてね。」**とあったので、今回はそちらをやっていきます。
※私個人がGradleの方が馴染みがあるため。
環境
今回使用するPCの環境をざっくりと。
- OS Windows10 Home 64bit
- Java 11
- Git Bashは導入済み
チュートリアル目次
- Introduction
- [Set up](#Set up)
- [Prepare the app](#Prepare the app)
- [Deploy the app](#Deploy the app)
- View logs
- Define a Procfile
- Scale the app
- Declare app dependencies
- Run the app locally
- Push local changes
- Provision add-ons
- Start a one-off dyno
- Define config vars
- Use a detabase
- Next steps
Introduction
こちらには今回のチュートリアルの前提条件が記載してあります。
- Herokuのアカウントを持っていること
- Java8が導入されていること
Javaのバージョン指定が8となっていましたが、私の環境では11でも問題ありませんでした。
Set up
ここではHeroku Command Line Interface (CLI)を導入します。
(以前はHeroku Toolbeltという名前だったようです。)
CLIはアプリケーションの管理とスケーリング、アドオンのプロビジョニング、Herokuで実行されるアプリケーションのログの表示、アプリケーションのローカル実行に使用するようです。
自分の環境にあったインストーラを選択してダウンロードします。
インストールが終わるとheroku
コマンドが使えるようになります。heroku login
コマンドでHeroku CLIへログインしましょう。
$ heroku login
heroku: Press any key to open up the browser to login or q to exit
# ここでキーを押すとブラウザで画面が立ち上がる
# 画面にあるログインボタンを押すとログインできる
› Warning: If browser does not open, visit
› https://cli-auth.heroku.com/auth/browser/***
heroku: Waiting for login...
Logging in... done
Logged in as me@example.com
proxy設定ができるようですが、今回は必要ないので省略します。
参考:CLI Usage / Using an HTTP proxy
##Prepare the app
用意されているサンプル用プロジェクトを任意のディレクトリにクローンします。
$ git clone https://github.com/heroku/gradle-getting-started.git
$ cd gradle-getting-started
Deploy the app
先ほどクローンしたプロジェクトをデプロイしていきます。
まず、Herokuにアプリケーションを作成しHerokuでソースコードを受け取れるようにします。
$ heroku create
Creating app... done, arcane-inlet-19935
https://arcane-inlet-19935.herokuapp.com/ | https://git.heroku.com/arcane-inlet-19935.git
arcane-inlet-19935
というのはアプリケーション名でランダムに命名されたものです。アプリケーション名は指定することもできるようです。このアプリケーション名はURLの一部になります。今回はチュートリアルなので特に指定しませんでした。
heroku
というリポジトリへpushします。
$ git push heroku master
アプリケーションが動いていることを確認します。
$ heroku ps:scale web=1
Scaling dynos... done, now running web at 1:Free
下記コマンドをを打つとブラウザが立ち上がり、ページを開いてくれます。
$ heroku open
URLを確認すると、https://{アプリケーション名}.herokuapp.com
となっています。
View logs
Herokuのログはすべてのアカウント、Herokuコンポーネント共通で単一の出力ストリームに時系列順でログを出力しています。
$ heroku logs --tail
2019-12-15T06:22:53.476464+00:00 app[web.1]: 2019-12-15 06:22:53.476 INFO 4 --- [ main] o.s.b.a.w.s.WelcomePageHandlerMapping : Adding welcome page template: index
2019-12-15T06:22:54.441609+00:00 heroku[web.1]: State changed from starting to up
2019-12-15T06:22:54.408814+00:00 app[web.1]: 2019-12-15 06:22:54.405 INFO 4 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 37878 (http) with context path ''
2019-12-15T06:22:54.452349+00:00 app[web.1]: 2019-12-15 06:22:54.452 INFO 4 --- [ main] com.example.heroku.HerokuApplication : Started HerokuApplication in 12.317 seconds (JVM running for 14.742)
2019-12-15T06:32:15.384939+00:00 app[web.1]: 2019-12-15 06:32:15.382 INFO 4 --- [io-37878-exec-8] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-12-15T06:32:15.384963+00:00 app[web.1]: 2019-12-15 06:32:15.382 INFO 4 --- [io-37878-exec-8] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2019-12-15T06:32:15.412236+00:00 app[web.1]: 2019-12-15 06:32:15.412 INFO 4 --- [io-37878-exec-8] o.s.web.servlet.DispatcherServlet : Completed initialization in 29 ms
--tail
はリアルタイムで表示するというオプションです。起動確認時に訪れたページに再度アクセスすると、そのログが流れていく様子を見ることができます。
Ctrl + C
でリアルタイム表示を終了できます。
ログの出し方のオプションは他にもいろいろ用意されています。
ログの出し方:https://devcenter.heroku.com/articles/logging#view-logs
Define a Procfile
アプリケーションのルートディレクトリにあるProcfileでアプリケーションを起動するコマンドを明示的に宣言します。
サンプルプロジェクトのProcfileは次のようになっています。
web: java -jar build/libs/gradle-getting-started-1.0.jar
単一のプロセスタイプweb
とをれを実行するために必要なコマンドが宣言されています。ここではweb
という名前が重要な意味を持ちます。
プロセスタイプやProcfileについては下記を読んでください。
Procfileについて:https://devcenter.heroku.com/articles/procfile
Procfileに書かれたコマンドがそのままUNIX環境で実行されるので、Syntaxが重要になります。
Scale the app
現在、アプリケーションは単一のWeb dynoで実行されています。dynoというのはProcfileで指定されたコマンドを実行する小さなコンテナだと考えることができます。
psコマンドで実行されているdynoの数を確認できます。
$ heroku ps
Free dyno hours quota remaining this month: 949h 54m (94%)
Free dyno usage for this app: 0h 0m (0%)
For more information on dyno sleeping and how to upgrade, see:
https://devcenter.heroku.com/articles/dyno-sleeping
=== web (Free): java -jar build/libs/gradle-getting-started-1.0.jar (1)
web.1: up 2019/02/13 11:10:03 -0600 (~ 13s ago)
この例ではweb.1
が動いていることを確認できます。
この辺りは課金の話になりますが、dynoについてはこのように書かれています。
- デフォルトではFreeのdynoにデプロイされる。
- Freeのdynoは30分間操作しない(トラフィックを受信しない)とスリープ状態になる。
- スリープ時にリクエストを受けると起動のために数秒の遅延が起きる。
- アカウント単位で月ごとにFreeのdynoの起動時間の無料枠があり、それを消費しきるとFreeのdynoにデプロイされているアプリケーションは月末までスリープ状態のままになってしまう。
詳細はこちらに書いてあります。
Declare app dependencies
Herokuはルートディレクトリにgradlew
またはbuild.gradle
があることでGradleアプリであることを認識しています。
サンプルプロジェクトにはbuild.gradle
が入っています。
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
runtimeOnly 'org.postgresql:postgresql'
runtimeOnly 'org.webjars:jquery:3.3.1-1'
runtimeOnly 'org.webjars:jquery-ui:1.12.1'
runtimeOnly 'org.webjars:bootstrap:4.1.3'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
Javaのバージョンはルートディレクトリにあるsystem.properties
に記載します。
java.runtime.version=1.8
Introductionで書きましたが、このチュートリアルはJava8を前提としているため1.8となっています。
それではbuild
コマンドを実行しましょう。
$ ./gradlew build
Run the app locally
続いてローカル環境での実行です。
$ heroku local web
Heroku上で起動するのと同様にProcfileで実行するものを決定します。ポートの指定はsrc/main/resources/application.properties
に記載します。
server.port=${PORT:5000}
heroku local
コマンドはアプリケーションの実行だけでなくconfig vars
のセットもできます。(後述)
Push local changes
このセクションではローカルの変更をHerokuへ反映する手順を説明します。
例として以下のようにサンプルに変更を加えていきます。
compile "org.jscience:jscience:4.3.1"
// 19行目
import static javax.measure.unit.SI.KILOGRAM;
import javax.measure.quantity.Mass;
import org.jscience.physics.model.RelativisticModel;
import org.jscience.physics.amount.Amount;
// 中略
// 59行目
@RequestMapping("/hello")
String hello(Map<String, Object> model) {
RelativisticModel.select();
Amount<Mass> m = Amount.valueOf("12 GeV").to(KILOGRAM);
model.put("science", "E=mc^2: 12 GeV = " + m.toString());
return "hello";
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{fragments/layout :: layout (~{::body},'hello')}">
<body>
<div class="container">
<p th:text="${science}"/>
</div>
</body>
</html>
変更を終えたら再度ビルドし、アプリケーションを実行します。
$ ./gradlew build
...
BUILD SUCCESSFUL in 41s
5 actionable tasks: 5 executed
$ heroku local web
...
17:48:20 web.1 | 2019-12-15 17:48:20.865 INFO 14244 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 9409 ms
17:48:23 web.1 | 2019-12-15 17:48:23.462 INFO 14244 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
17:48:25 web.1 | 2019-12-15 17:48:25.429 INFO 14244 --- [ main] o.s.b.a.w.s.WelcomePageHandlerMapping : Adding welcome page template: index
17:48:26 web.1 | 2019-12-15 17:48:26.397 INFO 14244 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 5000 (http) with context path ''
起動したらhttp://localhost:5000/hello
にアクセスします。
E=mc^2: 12 GeV = (2.139194076302506E-26 ± 1.4E-42) kg
という何か物理に関する数式のようなものが出ればOKです。この計算をするためにdependencies
やimport
で追加したライブラリを使っているようです。
(jscienceなんてライブラリがあるんですね…)
確認出来たらデプロイしていきます。
$ git add .
$ git commit -m "Demo"
$ git push heroku master
デプロイできたことを確認します。
$ heroku open
デモのトップ画面が開いたらURLの末尾に/hello
を付けると、先ほどの数式のが表示されるはずです。
Provision add-ons
Herokuには様々なサードパーティアドオンがあります。今回はPapertrailというログ用アドオンを追加します。
Pepertrailを入れるとブラウザ上でログを確認することができます。
$ heroku addons:create papertrail
Creating papertrail on mysterious-wildwood-86066... !
! Please verify your account to install this add-on plan (please enter a
! credit card) For more information, see
! https://devcenter.heroku.com/categories/billing Verify now at
! https://heroku.com/verify
どうやらアドオンを追加するにはクレジットカードの登録が必要なようです。クレジットカードを登録するとFree dyno時間(いわゆる無料枠)が400h/月から1000h/月に拡大されるので私は登録しました。
では改めて
$ heroku addons:create papertrail
Creating papertrail on mysterious-wildwood-86066... free
Welcome to Papertrail. Questions and ideas are welcome (support@papertrailapp.com). Happy logging!
Created papertrail-curly-70913 as PAPERTRAIL_API_TOKEN
Use heroku addons:docs papertrail to view documentation
無事に追加できました。追加されているアドオンは以下のようにして確認することができます。
$ heroku addons
Add-on Plan Price State
─────────────────────────────────── ─────── ───── ───────
papertrail (papertrail-curly-70913) choklad free created
└─ as PAPERTRAIL
The table above shows add-ons and the attachments to the current app (mysterious-wildwood-86066) or other apps.
それでは下記のコマンドでログを見てみましょう。
$ heroku addons:open papertrail
ブラウザでログが表示されたページが立ち上がります。
Start a one-off dyno
one-off dynoというのはHeroku上でコマンドを一度限りで実行するもののようです。one-off dynoではheroku run
というコマンドを用います。
$ heroku run java -version
Running java -version on arcane-inlet-19935... up, run.6561 (Free)
openjdk version "1.8.0_201-heroku"
OpenJDK Runtime Environment (build 1.8.0_201-heroku-b09)
OpenJDK 64-Bit Server VM (build 25.201-b09, mixed mode)
ほかにもローカル端末に接続されたREPLプロセスを起動してアプリの環境で実験したり、アプリケーションと共にデプロイしたコードを起動したりすることもできます。
Define config vars
Herokuでは、暗号化キーや外部リソースアドレスなどのデータをconfig varsに保存して、構成を外部化できます。config varsは実行時に環境変数としてアプリケーションに公開されます。
例として先ほどサンプルに追加したHerokuApplication.java
のhello
メソッドがENERGY
環境変数からエネルギー値を取得するように変更してみます。
@RequestMapping("/hello")
String hello(Map<String, Object> model) {
RelativisticModel.select();
// ここから追加
String energy = System.getenv().get("ENERGY"); // ここで環境変数ENERGYを使用
if (energy == null) {
energy = "12 GeV";
}
// ここまで
Amount<Mass> m = Amount.valueOf(energy).to(KILOGRAM); // 変数energyを使用するように変更
model.put("science", "E=mc^2: " + energy + " = " + m.toString()); // 変数energyを使用するように変更
return "hello";
}
最上位ディレクトリの.env
ファイルに環境変数を記述します。このファイルはheroku local
コマンドの時に読み込まれるようです。
ENERGY=20 GeV
ビルドし直してlocalで起動後http://localhost:5000/hello
へアクセスすると数値が変わっていることが確認できます。
E=mc^2: 20 GeV = (3.5653234605041761E-26 ± 2.9E-42) kg
確認できたところでHerokuにも同じ環境変数を設定しましょう。
$ heroku config:set ENERGY="20 GeV"
'C:\Program' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。
???
とりあえずチュートリアルをさかのぼっていたらDefine a Procfileの項にこのように書いてありました。
When running locally under Windows, you may receive an error because the Unix way of passing in environment variables ($JAVA_OPTS) and of concatenating paths (:) is incompatible.
Unix環境では文字列結合の環境変数の渡し方とpathの連結の仕方に互換性がないことが原因のようです。
仕方がないのでHeroku上での確認はいったんここはスルーします。
Use a database
add-on maketplaceにはRedisやMongoDBからPostgres、MySQLまで数多くのデータストアがあります。まずはPostgreSQLアドオンの新しいインスタンスをアプリにアタッチしてみましょう。
$ heroku addons:create heroku-postgresql
Creating heroku-postgresql on mysterious-wildwood-86066... free
Database has been created and is available
! This database is empty. If upgrading, you can transfer
! data from another database with pg:copy
Created postgresql-amorphous-60925 as DATABASE_URL
Use heroku addons:docs heroku-postgresql to view documentation
heroku addons
コマンドで見てみましょう。
$ heroku addons
Add-on Plan Price State
────────────────────────────────────────────── ───────── ───── ───────
heroku-postgresql (postgresql-amorphous-60925) hobby-dev free created
└─ as DATABASE
papertrail (papertrail-curly-70913) choklad free created
└─ as PAPERTRAIL
The table above shows add-ons and the attachments to the current app (mysterious-wildwood-86066) or other apps.
heroku-postgresql
が追加されています。
続いて、環境変数を見てみましょう。
$ heroku config
=== arcane-inlet-19935 Config Vars
DATABASE_URL: postgres://pccigjsjmqxlvk:b3aedb1e099aa729d8037c0be6454e1028dc0c7c4dc16f8b366471486a8a7014@ec2-54-235-159-101.compute-1.amazonaws.com:5432/db2b5rodiinvv4
PAPERTRAIL_API_TOKEN: duMLEm2E5WHtf7mC163g
DATABASE_URL
という変数が追加されています。
heroku にはpg
というコマンドも用意されています。
$ heroku pg
=== DATABASE_URL
Plan: Hobby-dev
Status: Available
Connections: 10/20
PG Version: 11.6
Created: 2019-12-15 10:26 UTC
Data Size: 7.7 MB
Tables: 0
Rows: 0/10000 (In compliance) - refreshing
Fork/Follow: Unsupported
Rollback: Unsupported
Continuous Protection: Off
Add-on: postgresql-amorphous-60925
これをみるとHobby-dev(=無料プラン)
であり、PGのバージョンが11.6であることがわかりました。
これまでの手順でデプロイ済みのアプリケーションには既にデータベースの機能が含まれています。
https://{アプリケーション名}.herokuapp.com/db
にアクセスするとアクセスした日時が表示されます。(北米リージョンなので日本時間の9時間前)
@Value("${spring.datasource.url}")
private String dbUrl;
@Autowired
private DataSource dataSource;
@RequestMapping("/db")
String db(Map<String, Object> model) {
try (Connection connection = dataSource.getConnection()) {
Statement stmt = connection.createStatement();
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS ticks (tick timestamp)");
stmt.executeUpdate("INSERT INTO ticks VALUES (now())");
ResultSet rs = stmt.executeQuery("SELECT tick FROM ticks");
ArrayList<String> output = new ArrayList<String>();
while (rs.next()) {
output.add("Read from DB: " + rs.getTimestamp("tick"));
}
model.put("records", output);
return "db";
} catch (Exception e) {
model.put("message", e.getMessage());
return "error";
}
}
@Bean
public DataSource dataSource() throws SQLException {
if (dbUrl == null || dbUrl.isEmpty()) {
return new HikariDataSource();
} else {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(dbUrl);
return new HikariDataSource(config);
}
}
これにより、/db
にアクセスするたびにtick
テーブルに現在時刻のレコードが追加され、これまでのアクセス日時が一覧表示されます。
Next steps
ここまででデプロイの仕方、設定変更、ログチェック、アドオンの追加ができるようになりました。
次に学習するのにおすすめの記事がいくつか挙げられています。
終わりに
Herokuの基本的なところはこれで押さえられたかなと思います。Herokuはアクセスしなければ30分でスリープ状態になり無料枠を消費しなくなるので、お試しでいくつもアプリケーションを作った後にわざわざ消したりしなくてもよいのが地味にうれしいところかなと思います。