Heroku初心者がKotlinで作ったWEBサービスをデプロイした手順

Kotlinのソースをどこかにデプロイしたいんですけど
エックスサーバーでは Java のソースはデプロイできないので、いろいろ検討した結果、herokuにデプロイるすることにしました。 
このページでは、intellijで作成したKotlinのWEBサービスを、herokuにデプロイする手順を紹介します 

きっかけ

Web サービスという名目でのアフィリエイトサイトを作っています。
公開するためには、どこかにデプロイしないといけないんですが、自分が使っている XserverでJavaは実行できない。
そこで、ネットでよく見かけるHerokuにデプロイして見る事にしました。

自分のスキルなど

  • Heroku初めての超素人
  • WEBサービス開発は初心者
  • Java、Kotlinは実務経験者
  • intellijでローカル開発

やったこと要点

  • Herokuアカウントの作成
  • Heroku CLIをインストール
  • Herokuにアプリをpush

アカウント作成した後は、基本的に以下のページの流れで実施しています。

Getting Started with Gradle on Heroku

参考にした記事

Spring BootをHerokuにデプロイするのが劇的に簡単になっている件

GradleでビルドしたSpring BootアプリをHerokuで動かす

herokuにSpringBootアプリケーションをデプロイしてみる

Herokuアカウントの作成手順

herokuにアクセスして、無料アカウントを作成します。

https://jp.heroku.com/home

「無料で新規作成」をクリックして必要な情報を入力します。

必要な情報を入力すると登録したメールアドレスにメールが飛んで来るようです。

メールを開いてみると、 URL が添付されていてクリックを促されてます 

指定された URL をクリックすると、パスワード登録画面に飛んでいき自分で任意のパスワードを登録。 

パスワードの登録が終わったら、 アカウントの作成は完了です。 

ボタンを押すとHOME画面に飛びます。
アカウントの作成は完了です。

Heroku CLIをインストール&Herokuにログイン

https://devcenter.heroku.com/articles/getting-started-with-gradle-on-heroku#set-up

このページから、Heroku CLIを自分の環境に合わせてインストール。
僕はWin64bitでインストール

インストールが完了したらコマンドプロンプトでログインできるようになります。

> heroku login

コマンドを叩いたら、以下の文言が表示されるので、キーボードのキーをどれか叩きます。

heroku: Press any key to open up the browser to login or q to exit:

キーボードのどれかを入力すると、、以下の画面が自動的に表示されます。

画面中央の「LOG IN」ボタンを押しください。
ログインすると以下の画面になるので、ターミナルに戻ってください。
なお、ログインにはユーザー認証が必要という情報もありましたが、僕はボタンを押しただけでログインできました。

Herokuにアプリを作成&push

既に作成しているgit環境にcdして、以下のコマンドにて、herokuアプリを作成します。

$ heroku create
Creating app... done, sheltered-sea-52419
https://sheltered-sea-52419.herokuapp.com/ | https://git.heroku.com/sheltered-sea-52419.git

作成してあるWEBアプリをpushし様としたら、なんとエラー発生!!

$ git push heroku master
Enumerating objects: 179, done.
Counting objects: 100% (179/179), done.
Delta compression using up to 4 threads
Compressing objects: 100% (138/138), done.
Writing objects: 100% (179/179), 4.24 MiB | 1.82 MiB/s, done.
Total 179 (delta 51), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Gradle app detected
remote: -----> Installing JDK 1.8... done
remote: -----> Building Gradle app...
remote: -----> executing ./gradlew stage
remote:        Error: Could not find or load main class org.gradle.wrapper.GradleWrapperMain
remote:
remote:  !     ERROR: Failed to run Gradle!
remote:        It looks like you don't have a gradle-wrapper.jar file checked into your Git repo.
remote:        Heroku needs this JAR file in order to run Gradle.  Our Dev Center article on preparing
remote:        a Gradle application for Heroku describes how to fix this:
remote:        https://devcenter.heroku.com/articles/deploying-gradle-apps-on-heroku
remote:
remote:        If you're stilling having trouble, please submit a ticket so we can help:
remote:        https://help.heroku.com
remote:
remote:        Thanks,
remote:        Heroku
remote:
remote:  !     Push rejected, failed to compile Gradle app.
remote:
remote:  !     Push failed
remote: Verifying deploy...
remote:
remote: !       Push rejected to sheltered-sea-52419.
remote:
To https://git.heroku.com/sheltered-sea-52419.git
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'https://git.heroku.com/sheltered-sea-52419.git'

gradle wrapperを追加

色々調べて行くと、/gradle/wrapper/gradle-wrapper.jarが無いかららしい。

参考サイト

gradlewが謎のエラーで動かない件について
https://qiita.com/pg_mot/items/bf02f4f37382696f3898

jarファイルがあることは確認できたので、

$ git add gradle/wrapper/
$ git commit

なお、gradle-wrapper.jarだけ追加したら、gradle-wrapper.propertiesが無くてエラーになりました。ディレクトリごと追加するのが正しいようです。

Gradleのデフォルトtaskを指定

build.gradleに以下のソースコードを追加

// Herokuから実行されるタスク
task stage(dependsOn: ['clean', 'build'])
build.mustRunAfter clean

Procfileを記述

ProcfileとはHerokuでアプリを起動する際に参照されるファイルだそうです。
このファイルに記述されている内容が実行されます。

web: java -jar build\libs\appname-1.0-SNAPSHOT.jar

Main-Class属性を登録

ここで以下のコマンドでgradleでビルドして、作成されたjarファイルを直接実行するとエラーになりました。

> gradlew stage
> java -jar build\libs\appname-1.0-SNAPSHOT.jar
build\libs\appname-1.0-SNAPSHOT.jarにメイン・マニフェスト属性がありません

原因は、Main-Class属性が登録されていないかだそうです。

そこで、以下の記述をbuild.gradleに追加します。

jar {
    manifest {
        attributes 'Main-Class': "app.MainKt"
    }
}

参考:

ことりんと一緒 – 3. 実行可能 JAR
https://qiita.com/shinyay/items/1a000cd082bf2d670531

ここまで対応して、herokuにpush出来ました!

herokuにアップしたwebアプリが動かない

herokuにpush出来たので、実際にサーバーに見に行ってみると、Application errorとか出て表示されてないです。。。

ログを見てみると、次のメッセージが出てエラーになっていることだけはわかります。

ローカルだと動くんだけどな・・・・

$ heroku logs --tail
2020-04-21T03:56:56.925937+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/title" host=sheltered-sea-52419.herokuapp.com request_id=6ebc8d88-d599-49a2-afc8-291202db274c fwd="126.79.130.218" dyno= connect= service= status=503 bytes= protocol=https

リスタートすれば直ることもあるそうなのでやってみましたが、回復できず。

$ heroku restart -app

ローカル実行してみたところ、以下のエラーメッセージが。。

org.slf4j.impl.StaticLoggerBinderが無いというエラーなので、org.slf4jの依存関係を追加します。

$ heroku local web
20:41:36 web.1   |  SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
20:41:36 web.1   |  SLF4J: Defaulting to no-operation (NOP) logger implementation
20:41:36 web.1   |  SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[DONE] Killing all processes with signal  SIGINT
20:41:37 web.1   Exited with exit code null

build.gradleに次のコードを追加します。

dependencies {
    compile 'org.slf4j:slf4j-log4j12:1.7.21'
}

無事にローカル環境でエラーが無くなりました。

$ heroku local web
21:09:48 web.1   |  log4j:WARN No appenders could be found for logger (spark.staticfiles.StaticFilesConfiguration).
21:09:49 web.1   |  log4j:WARN Please initialize the log4j system properly.
21:09:49 web.1   |  log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

実際にlocalhost:5000にアクセスすると、文字化け。。。。
これは後で直すことにします。

サーバーにpushして動きを確認

$ git push heroku master

やっぱりエラー。。。
ローカルでは動くのに。。。厳しい戦いだなぁ。。。。

jarファイル内のテキストファイルを読み公務方法を変える

エラーになっている場所は大体わかっているんですが、どうもうまくいかない。
ファイルの指定するファイルパスと、読み込む方法を変えたら、ローカル環境では表示できるようになりました。

これで、動くようになった!と思ってpushしてみたけど、デプロイ環境では動かない。。。
なんでじゃーーー。

一応、ローカル環境で動くようになった方法を書いておきます。

読み込むファイルを、src/main/resources 配下に格納して、getClass().getResourceAsStream()を使用して読み込みます。読み込むファイルはルートディレクトリに格納されるので、getResourceAsStream()の引数はファイル名で読み込むことが可能です。

確認はローカル環境で動作させると良いと思います。

コンソールが見られるので、発生したexceptionのコールスタックなども見ることが可能です。逆にこれが無かったら、今でもまだ悩んでるかも。

heroku local web

参考:
JAVAのWEBフレームワークSPARKでプロパティファイルを読込たい
https://teratail.com/questions/24570

とりあえず、今のソースコードをherokuにアップデートするのは諦めました。