4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

PLISEAdvent Calendar 2019

Day 23

Getting Started with Gradle on Heroku をやってみた

Last updated at Posted at 2019-12-22

動機

私は現在、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

こちらには今回のチュートリアルの前提条件が記載してあります。

  • Herokuのアカウントを持っていること
  • Java8が導入されていること

Javaのバージョン指定が8となっていましたが、私の環境では11でも問題ありませんでした。

Set up

ここではHeroku Command Line Interface (CLI)を導入します。
(以前はHeroku Toolbelt
という名前だったようです。)
CLIはアプリケーションの管理とスケーリング、アドオンのプロビジョニング、Herokuで実行されるアプリケーションのログの表示、アプリケーションのローカル実行に使用するようです。
自分の環境にあったインストーラを選択してダウンロードします。

インストールが終わるとherokuコマンドが使えるようになります。heroku loginコマンドでHeroku CLIへログインしましょう。

herokuへのログイン
$ 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は次のようになっています。

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が入っています。

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に記載します。

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に記載します。

application.properties
server.port=${PORT:5000}

heroku localコマンドはアプリケーションの実行だけでなくconfig varsのセットもできます。(後述)

Push local changes

このセクションではローカルの変更をHerokuへ反映する手順を説明します。
例として以下のようにサンプルに変更を加えていきます。

build.gradleの30行目
compile "org.jscience:jscience:4.3.1"
src/main/java/com/example/heroku/HerokuApplication.java
// 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";
  }
src/main/resources/templates/hello.htmlを作成
<!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です。この計算をするためにdependenciesimportで追加したライブラリを使っているようです。
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というコマンドを用います。

例)Javaのバージョンを確認する
$ 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.javahelloメソッドがENERGY環境変数からエネルギー値を取得するように変更してみます。

HerokuApplication.java
@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コマンドの時に読み込まれるようです。

.env
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時間前)

HerokuApplication.java(抜粋)
  @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分でスリープ状態になり無料枠を消費しなくなるので、お試しでいくつもアプリケーションを作った後にわざわざ消したりしなくてもよいのが地味にうれしいところかなと思います。

参考

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?