300
312

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 5 years have passed since last update.

Heroku使い方メモ(Java)

Last updated at Posted at 2014-11-21

Heroku を使って Java の Web アプリを作ってみる。

#基本的な話
##Heroku とは
PaaS の1つ。
Web アプリケーションを動かすための土台(プラットフォーム)を提供してくれるクラウドサービス。

Google でいうと Google App Engine、 Amazon でいうと AWS Elastic Beanstalk とかが同じようなサービスに当たる。

最初は Ruby (Ruby on Rails)をサポートした PaaS だったけど、現在は様々な言語によるプラットフォームをサポートしている。
Java もサポートされている。

データベースは、 PostgreSQL や MySQL などを使える。
ただし、無償利用の場合はデータ量などに制限がある。

##全体像
heroku.JPG

  • アプリケーションのソースコードは Git を使って管理する。
  • Heroku 上の Git リポジトリに Push すると、アプリケーションをビルドし、必要なファイルをかき集めて Slug と呼ばれるパッケージが作成される。
  • Slug は Dyno と呼ばれる仮想の Unix コンテナにデプロイされ実行される。
  • 実行された Web アプリには、インターネット越しにブラウザなどからアクセスできるようになる。

##アプリケーション
Heroku 上でプログラムを動かすために必要なソースコードや設定ファイルなど一式のこと。

  • ソースコード
  • 依存ファイル(pom.xml, build.gradle, Gemfile などなど)
  • Procfile(Heroku 上で実行するコマンドを定義したファイル。詳細後述)

Heroku では、これらをまとめたものを「アプリケーション」と定義している。

##Slug
アプリケーションを Heroku 上で実行するために、ビルド、パッケージングして圧縮したもの。

  • ソースコードをビルドしてできた実行ファイル。
  • 依存するライブラリ一式。
  • 言語の実行環境。

これらをまとめたものを、 Slug と呼ぶ。

##Buildpacks
Slug を構築する方法を定義したスクリプト。

Heroku は標準で Ruby, Node.js, Clojure, Java など様々な Buidlpacks を用意している。
これらは全てオープンソースで、 GitHub 上で開発されている(Buildpacks | Heroku Dev Center)。

標準でサポートされていない言語やフレームワークでも、 Buildpacks を作成すれば Heroku 上で動かすことができる。

##Dyno
アプリケーションを実行するための実行環境。
仮想の Unix コンテナのようなもの。

Heroku のサービスでは、この Dyno を単位にして性能などが説明されている。
たとえば、 Dyno 1つだとメモリは 512 MBだとか、1つの Dyno が処理できるスレッドは 256 個まで、などなど。

デフォルトでは、1アプリケーションにつき1つの Dyno が割り当てられる。
複数の Dyno を割り当てることで性能を向上させることができるが、有償になる。

##無償利用する場合の制限
こちら に様々な制限事項が記載されている。

主要そうなのをいくつかピックアップする。

###Dyno の制限
####メモリ
1Dyno のメモリは 512MB。

####稼働時間
1アプリケーションあたり、1Dyno 月 750 時間までは無料で稼働させられる。

1ヶ月 = 31日とした場合、月合計は 744 時間なので、普通に使う限りでは無料で使用することができる。

しかし、1つのアプリケーションに 2 つ以上の Dyno を割り当てたり、バッチ処理などのプロセスを別途稼働させた場合、それらの時間も合計されることになり、 750 時間の制限をオーバーすることになる(有償になる)。

###Slug のサイズ制限
300MB が上限。

Heroku に git push すると、リポジトリの内容がパッケージング・圧縮され、 Slug と呼ばれるファイルが作成される。
このサイズの上限が 300MB。

Slug について

※翻訳が古いためか、最大サイズが 200MB となっているが、 2014/11/22 現在は 300MB が上限の模様。

Your slug size is displayed at the end of a successful compile. The maximum slug size is 300MB; most apps should be far below this limit.

###データベースの制限
PostgreSQL を無償で利用する場合は Hobby Tier というプランになり、以下のような制限がある。

  • 登録できるデータは、全部で 10,000 行。
  • 同時に開けるコネクションの数は、 20 まで。

詳細

#Hello World
Java の Getting Started を見ながら、 Java で Web アプリを動かすところまでやってみる。

##開発マシンの環境
###OS
Windows7 64bit SP1

###Git
Git for Windows 1.9.4

###SSH
Cygwin でインストール。
%CYGWIN_HOME%\bin にパスを通して、 sshssh-keygen が使える状態にしておく。

####Git から使用する SSH を指定する
環境変数 GIT_SSH に Cygwin と一緒にインストールした ssh.exe のパスを設定しておく。

###Maven
Maven 3.2.3

※Maven3 が必要みたいです。

##Heroku のアカウントを作成する
公式サイト から Sign Up して、アカウントを作成する。
必要なのはメールアドレスだけで、無償で作成可能。

##Heroku の CLI ツールをインストールする
こちらDownload Heroku Toolbelt for Windows をクリックしてインストーラをダウンロード。

フルインストールすると Git と SSH もインストールされるみたいだが、自分の環境にはすでに Git と SSH はインストールしているので、 Custom Installation を選択して Heroku ClientForeman だけをインスト―ルした。

インストールが完了したら、コマンドラインを開いて以下コマンドを実行。

> heroku version
heroku/toolbelt/3.12.1 (i386-mingw32) ruby/1.9.3

##Heroku Toolbelt で Heroku にログインする

> heroku login
Enter your Heroku credentials.
Email: <Heroku アカウントのメールアドレス>
Password (typing will be hidden):<パスワード>

メールアドレスとパスワードを聞かれるので、先ほど作成したアカウントの情報を入力する。

続いて SSH の鍵を作成するか尋ねられる。
Windows 環境だと、当然 ~/.ssh/id_rsa.pub なんてパスは無効なので、 Y を選択してもエラーになって終了する。

なので、別途 ssh-keygen で鍵を作る。

##SSH の鍵を作成して登録する
任意のフォルダで以下のコマンドを実行。

鍵の生成
> ssh-keygen.exe -t rsa

質問は全て「未入力 Enter」でOK。
<Cygwin インストールフォルダ>/home/<ユーザー名>/.ssh の下に、秘密鍵(id_rsa)と公開鍵(id_rsa.pub)が生成される。

鍵を登録する
> heroku keys:add <先ほど作成した id_rsa.pub のパス>
Uploading SSH public key id_rsa.pub... done

これで鍵が登録される。
登録されているかどうかは heroku keys コマンドで確認できる。

##アプリケーションを作成する

> heroku create
Creating shielded-retreat-8659... done, stack is cedar-14
https://shielded-retreat-8659.herokuapp.com/ | git@heroku.com:shielded-retreat-8659.git

上記コマンドでアプリケーションが新規に作成される。
作成が完了すると、アプリケーションの URL と Git リポジトリの URL が表示される。

試しにアプリケーションの URL にアクセスしてみる。

heroku.JPG

###アプリケーション作成時に名前を指定する

> heroku create <アプリケーション名>
  • create 時に任意の名前を指定することができる。
  • 未指定の場合はランダムな文字列が使用される。

###アプリケーションの名前を変更する

> heroku apps:rename <変更後の名前> --app <変更前の名前>

変更が完了すると、変更後の Web アプリと Git リポジトリの URL が出力される。
なお、変更後の名前が既に使われている場合は、 Name is already taken というエラーメッセージが表示される。

##サンプルプロジェクトを取得する
Java のサンプルプロジェクトが Git リポジトリとして公開されているので、それをクローンしてくる。

> git clone https://github.com/heroku/java-getting-started.git

##プロジェクトを Heroku にアップする
先ほどクローンしてきた Git リポジトリのルートに移動して、リモートリポジトリを追加する。

リモートリポジトリの追加
> cd java-getting-started

> git remote add heroku <git リポジトリのパス>

heroku という名前でアプリの Git リポジトリを登録したので、次はこのリポジトリに push する。

push
> git push heroku master

なにやら Maven の出力が大量に出力されたのち、 BUILD SUCCESS と出て終了する。

さっそくブラウザから Web アプリにアクセスしてみる。
URL を直接ブラウザで指定してもいいけど、以下のコマンドを打つことで Web ブラウザを起動することもできる。

> heroku open

heroku.JPG

パスに /db を追加すると、データベース(PostgreSQL)を使用したサンプルページが表示される。

heroku.JPG

アクセスするたびに時刻が記録され、結果が画面に表示される。

##各ファイルの意味とか
サンプルプロジェクトは、以下のようなファイル構成になっている。

│  README.md
│  LICENSE
│  .gitignore
│  Procfile
│  system.properties
│  pom.xml
└─src
    └─main
        └─java
                Main.java
ファイルの種類 ファイル名 説明
Heroku 関係 Procfile foreman の設定ファイル。
system.properties Heroku で動かす時の環境設定などを記載したファイル。
Java プログラム関係 src/main/java/Main.java ソースコード。組み込みの Jetty を起動させている。
pom.xml 依存関係を定義した普通の pom.xml。
GitHub 関係 .gitignore
LICENSE
README.md

###Procfile
foreman という Ruby のプログラム用の設定ファイル。
Heroku 上で実行するコマンドをここに記載する。

Procfile
web:    java -cp target/classes:target/dependency/* Main

Procfile は <プロセスタイプ>: <実行するコマンド> という書式で記述する。
コマンドは複数記述することができ、その場合 foreman は各コマンドを別プロセスで起動する。

サンプルアプリでは、 web というコマンドが定義され、 Maven のビルド結果を使用して java のコマンドを実行している。
Heroku では、このように web という名前で定義されたコマンドが存在するとアプリケーションサーバーが起動するようになっている。

###system.properties

system.properties
java.runtime.version=1.7

内容を見れば何となく想像できるが、 Java の実行環境のバージョンを指定している。

Heroku は、デフォルトでは Java 1.6 (1.6.0_27)が使用される。
このサンプルでは、 Java 1.7 (1.7.0_55)を使用するように指定している。

ちなみに、 Java 8 (1.8.0_20)もサポートされている。

※マイナーバージョンは 2014/11/13 現在ドキュメントに記載されている もの。

#サンプルをローカルで動かす
##データベースを用意する
###PostgreSQL をインストールする。
とりあえずローカルに PostgreSQL をインストールする。
手順は割愛(インストーラ落として叩くだけ)。

###データベース・ユーザーの作成
test という名前でデータベースを作成し、 test という名前のユーザーで書き込みできるようにしておく。
pgAdmin で GUI で簡単に作れるので方法は割愛。

###環境変数 DATABASE_URL の設定
環境変数 DATABASE_URL に以下のようにデータベースの情報を設定する。

> set DATABASE_URL=postgres://test:test@localhost:5432/test

何故この環境変数を設定するのかというと、サンプルアプリの実装を見ると分かる。

Main.java
  private Connection getConnection() throws URISyntaxException, SQLException {
    URI dbUri = new URI(System.getenv("DATABASE_URL"));

    String username = dbUri.getUserInfo().split(":")[0];
    String password = dbUri.getUserInfo().split(":")[1];
    String dbUrl = "jdbc:postgresql://" + dbUri.getHost() + dbUri.getPath();

    return DriverManager.getConnection(dbUrl, username, password);
  }

System.getenv("DATABASE_URL") でデータベースの接続情報を取得してコネクションを生成している。
Heroku 上で動かすと、 Heroku 上の PostgreSQL の URL 情報をこの DATABASE_URL 環境変数から取得できる。

URL のフォーマットは postgres://<username>:<password>@<hostname>:<port>/<path> なので、それに合わせてローカルの設定をしている。

##Procfile を修正する
あたりまえだけど、 Procfile は Heroku(Linux) 上で動かすことが前提の記述になっており、 Windows 上で動かそうとするとエラーになる。

Procfile
- web:    java -cp target/classes:target/dependency/* Main
+ web:    java -cp target\classes;target\dependency\* Main

スラッシュをバックスラッシュに、コロンをセミコロンに変更する。

##ビルドする

> mvn install

##起動する

> foreman start web

##確認する
Web ブラウザを開いて http://localhost:5000/ にアクセスする。

#ログの出力を確認する
ローカルで以下のコマンドを打つことで、 Heroku 上の Web アプリが出力しているログを確認することができる。

> heroku logs

また、 --tail オプションを追加することで監視も可能。

> heroku logs --tail

#SSH で接続する
Heroku 上のアプリケーションが動いているサーバーに SSH で接続できる。

> heroku run bash
...

~ $ pwd
pwd
/app

~ $ ls
ls
Procfile  build  build.gradle  gradle  gradlew  gradlew.bat  src

#Gradle を使用する
Heroku では、 Gradle も使えるらしい。

前述の Maven でのサンプルを Gradle に切り替えて Heroku で動かしてみる。

##フォルダ構成

|-.gitignore
|-build.gradle
|-gradle/
|-gradlew
|-gradlew.bat
|-Procfile
|-system.properties
`-src/main/java
  `-Main.java

##変更点
###build.gradle

build.gradle
apply plugin: 'application'

sourceCompatibility = '1.7'
targetCompatibility = '1.7'

mainClassName = 'Main'
applicationName = 'app'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.eclipse.jetty:jetty-servlet:7.6.0.v20120127'
    compile 'javax.servlet:servlet-api:2.5'
    compile 'postgresql:postgresql:9.0-801.jdbc4'
}

task stage(dependsOn: ['clean', 'installApp'])

task wrapper(type: Wrapper) {
    gradleVersion = '2.1'
}
  • 依存関係は pom.xml にあったのをそのまま持ってきた。
  • application プラグインを有効にして、ビルド後の起動ファイルの名前などを設定している。
  • Gradle Wrapper を有効にしている。
  • stage という名前のタスクを用意している。
    • stage というタスクは、 Heroku の git リポジトリに push したときに自動的に実行される特別なタスク。
    • このタスクで、プロジェクトをビルドし、コマンドラインから起動ができる状態にしておく必要がある。

###Procfile

Procfile
- web:    java -cp target/classes:target/dependency/* Main
+ web:    ./build/install/app/bin/app
  • installApp タスクで生成される起動スクリプトを実行するように修正。

###.gitignore

.gitignore
- target
+ /build/
+ /.gradle/
  • Maven ではなく、 Gradle 用に無視するファイルを修正。

##Heroku にデプロイする
以下コマンドを、プロジェクトのルートで実行する。

> git init

> heroku create

> heroku addons:add heroku-postgresql

> git push heroku master

フォルダを Git リポジトリ化した後、 Heroku に新規アプリケーションを作成、 PostgreSQL のアドオンを有効にし、 Heroku の Git にプッシュしている。

heroku という名前の remote は、 heroku create した時に自動で登録されている。

「Gradle はまだベータ版です」みたいなメッセージが出るけど、一応ビルドは正常に完了する。

##動作確認

> heroku open

ブラウザが立ち上がり、 Maven の時と同じようにアプリが動作するのが確認できる。

#タイムゾーンを変更する

> heroku config:add TZ=Asia/Tokyo

#アドオンを使う
Heroku では、アドオンという形で追加機能を利用することができる。

アドオンには、条件によっては無料で使えるものもある。
ただし、無料であっても使用するにはクレジットカード番号の登録が必要になる。

##アドオンの追加方法
こちら にアドオンの一覧がある。

追加したいアドオンのページを開き、プランと対象のアプリケーションを選択して追加する。

追加されたアドオンは、ダッシュボードのアプリごとの画面で確認することができる。

##無料で使えるアドオンの例
###ロギング
アドオンなしの場合、 heroku logs でしかログを見ることができない。
この場合、参照できるログが最新 1500 行分までなど制約が大きい。

アドオンを入れることで、それより以前のログを参照したり、アーカイブ化したログをダウンロードしたりできるようになる。

###PostgreSQL のバックアップ

PostgreSQL のバックアップを1日1回取得することができる。

####コマンド

  • 以下のコマンドは、アプリケーションを作成した(heroku create した)ディレクトリで実行する。
  • そうでないディレクトリで実行する場合は、 --app <アプリケーション名> オプションを末尾につけて、対象のアプリを指定する必要がある。

#####バックアップを確認する

> heroku pgbackups
ID    Backup Time                Status                                Size   Database
----  -------------------------  ------------------------------------  -----  ------------
a001  2014/11/21 07:56.55 +0000  Finished @ 2014/11/21 07:56.56 +0000  2.0KB  DATABASE_URL

#####手動でバックアップを取得する

> heroku pgbackups:capture

※無料版の場合、手動バックアップは2個まで作成可能。

#####バックアップファイルをダウンロードする

> heroku pgbackups:url <ID>
"https://https://s3.amazonaws.com/hkpgbackups/app31504551@heroku.com/a001.dump?(後略)"
  • ID には、ダウンロードしたいバックアップの ID を取得する。
    • バックアップの一覧表示をしたときに、一番左に表示される項目(a001 など)。
  • URL が出力されるので、ブラウザなどを使ってアクセスすると dump ファイルをダウンロードできる。

#####リストアする

> heroku pgbackups:restore DATABSE_URL <ID>
  • 指定した ID のバックアップをリストアする。
  • 試してないけど、 ID の代わりに URL を指定できるらしい。つまり、手動で取得したバックアップを何処かネットワーク上の見える場所に置いて、 URL を指定すれば任意のバックアップからリストアできる?

#####バックアップを削除する

> heroku pgbackups:destroy <ID>
  • 指定した ID のバックアップを削除する。

###メール送信
SendGrid

月200通までなら無料で使用できるメール送信用アドオン。

####Java でメール送信を実装

フォルダ構成
|-build.gradle
|-system.properties
|-Procfile
|-gradlew
|-gradlew.bat
|-gradle/
`-src/main/java/sample/heroku/mail/
   `-Main.java
build.gradle
apply plugin: 'application'

sourceCompatibility = '1.7'
targetCompatibility = '1.7'

mainClassName = 'sample.heroku.mail.Main'
applicationName = 'mail'

repositories {
    mavenCentral()
}

dependencies {
    compile 'javax.activation:activation:1.1'
    compile 'javax.mail:mail:1.4'
    compile 'org.eclipse.jetty:jetty-servlet:7.6.0.v20120127'
    compile 'javax.servlet:servlet-api:2.5'
}

task stage(dependsOn: ['clean', 'installApp'])

task wrapper(type: Wrapper) {
    gradleVersion = '2.1'
}
Procfile
web:    ./build/install/mail/bin/mail
system.properties
java.runtime.version=1.7
Main.java
package sample.heroku.mail;

import java.io.IOException;
import java.util.Properties;

import javax.mail.Authenticator;
import javax.mail.BodyPart;
import javax.mail.Message;
import javax.mail.Multipart;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;

public class Main extends HttpServlet {
    private static final long serialVersionUID = 1L;

    private static final String SMTP_HOST_NAME = "smtp.sendgrid.net";
    private static final String SMTP_AUTH_USER = System.getenv("SENDGRID_USERNAME");
    private static final String SMTP_AUTH_PWD  = System.getenv("SENDGRID_PASSWORD");

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        if (req.getRequestURI().endsWith("/mail")) {
            try {
                sendMail(req,resp);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            showHome(req,resp);
        }
    }

    private void sendMail(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        Properties props = new Properties();
        props.put("mail.transport.protocol", "smtp");
        props.put("mail.smtp.host", SMTP_HOST_NAME);
        props.put("mail.smtp.port", 587);
        props.put("mail.smtp.auth", "true");

        Authenticator auth = new SMTPAuthenticator();
        Session mailSession = Session.getDefaultInstance(props, auth);
        
        Transport transport = mailSession.getTransport();

        MimeMessage message = new MimeMessage(mailSession);

        Multipart multipart = new MimeMultipart("alternative");

        BodyPart part1 = new MimeBodyPart();
        part1.setText("This is multipart mail and u read part1......");

        BodyPart part2 = new MimeBodyPart();
        part2.setContent("<b>This is multipart mail and u read part2......</b>", "text/html");

        multipart.addBodyPart(part1);
        multipart.addBodyPart(part2);

        message.setContent(multipart);
        message.setFrom(new InternetAddress("me@myhost.com"));
        message.setSubject("This is the subject");
        message.addRecipient(Message.RecipientType.TO, new InternetAddress("to@myhost.com"));

        transport.connect();
        transport.sendMessage(message, message.getRecipients(Message.RecipientType.TO));
        transport.close();
    }

    private class SMTPAuthenticator extends javax.mail.Authenticator {
        public PasswordAuthentication getPasswordAuthentication() {
            String username = SMTP_AUTH_USER;
            String password = SMTP_AUTH_PWD;
            return new PasswordAuthentication(username, password);
        }
    }
    
    private void showHome(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().print("Hello from Java!");
    }

    public static void main(String[] args) throws Exception{
        Server server = new Server(Integer.valueOf(System.getenv("PORT")));
        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
        context.setContextPath("/");
        server.setHandler(context);
        context.addServlet(new ServletHolder(new Main()),"/*");
        server.start();
        server.join();
    }
}

メール送信の実装は こちら を丸コピ。

Heroku にデプロイして、 /mail パスにアクセスすれば、メールが送信される。

#参考

300
312
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
300
312

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?