最近F#を触り始め、HerokuにWebアプリをデプロイしてみた。
色々調べたので手順をまとめておく。
完成物
環境
OSX Mojave
.NET Core 3.0
Heroku CLI 7.33.3
.NET Coreのインストール
インストーラで簡単に導入できる。
dotnetコマンドも自動で入る。
ソリューション・プロジェクト作成
ソリューション作成
dotnet new sln -o heroku-suave-getting-started
プロジェクト作成
cd heroku-suave-getting-started
dotnet new console -lang F#
dotnet sln add heroku-suave-getting-started.fsproj
Hello World
dotnet run
Hello World from F#!
Suave導入
SuaveはF#における (たぶん) デファクトのWEBフレームワーク。
Expressのようなマイクロフレームワーク。
パッケージインストール
サードパーティのパッケージマネージャなど不要で、dotnetコマンドでパッケージインストールが行える。
dotnet add package Suave --version 2.5.6
サーバを起動してみる
先程のHello Worldの記述がされているProgram.fsというファイルがあるので下のように書き換える。
open Suave
[<EntryPoint>]
let main _argv =
startWebServer defaultConfig (Successful.OK "Hello World!")
0
dotnet run
でビルドと実行を行える。
dotnet run
[20:00:56 INF] Smooth! Suave listener started in 45.809ms with binding 127.0.0.1:8080
この状態でブラウザを開き http://localhost:8080/ を見ると 「Hello World!」が確認できる。
Herokuで動作するコードに変更
Herokuの仕様でポートは環境変数 $PORT
から読み取るようにしなければいけない。
そのため下記のように変更する。
open System
open Suave
open Suave.Sockets
open YoLo
[<EntryPoint>]
let main _argv =
let portByEnv =
match Env.var ("PORT") with
| Some(x) -> x
| None -> "8080"
let ip : Net.IPAddress = Net.IPAddress.Parse("0.0.0.0")
let port : Port = uint16 portByEnv
let socketBinding : Sockets.SocketBinding =
{ ip = ip
port = port }
let httpBinding : Http.HttpBinding =
{ scheme = Protocol.HTTP
socketBinding = socketBinding }
let conf = { defaultConfig with bindings = [ httpBinding ] }
startWebServer conf (Successful.OK "Hello World")
0
ずらずら書いているが環境変数 $PORT
を読み取ってそれを起動ポートとしているだけ。
$PORT
変数がない場合は8080となる。
実行ファイルを生成して実行 (Mac OSX)
.NET Core3.0にはスタンドアロンな実行ファイルを生成するという機能がある。
dotnet publish コマンドは、プラットフォーム固有の単一ファイルの実行可能ファイルにアプリをパッケージ化することをサポートします。 実行可能ファイルは自己展開型であり、アプリの実行に必要なすべての依存関係 (ネイティブを含む) が含まれます。
自己完結型アプリには、コードの実行に必要なものがすべて含まれるので、ホスト コンピューターに .NET をインストールする必要はありません。
スタンドアロンというだけあってデプロイ先の本番環境に.NETがなくても動作する。
ではまずはローカル (OSX) で試す。
ビルド時にどのOS向けかを指定する必要がある。
RIDカタログによるとOSXはosx-x64となるので -r
オプションの引数に指定。
dotnet publish -c Release -r osx-x64 /p:PublishSingleFile=true /p:PublishTrimmed=true
少し待つとビルドが終わりbin/Release/netcoreapp3.0/osx-x64/publishの下にheroku-suave-getting-startedとheroku-suave-getting-started.pdbというファイルができる。
本番にはこの2つのファイルを設置しheroku-suave-getting-startedを実行すれば動作する。
実行してみる。
./bin/Release/netcoreapp3.0/osx-x64/publish/heroku-suave-getting-started
[20:09:07 INF] Smooth! Suave listener started in 52.388ms with binding 127.0.0.1:8080
このようにサーバ起動できた。
デプロイ準備
Herokuでは.NETの公式ビルドパックがないため、
- サードパーティのビルドパックを使う
- Dockerデプロイする
かのどちらかになる。
今回は後者でいく。
最も、上述のシングル実行ファイル生成機能のおかげで環境構築は非常に簡単。
デプロイ用バイナリを生成 (Alpine Linux)
RIDカタログを参照。
Alpineなので linux-musl-x64
を指定。
dotnet publish -c Release -r linux-musl-x64 /p:PublishSingleFile=true /p:PublishTrimmed=true
Dockerfile
ソリューション直下にDockerfileを用意する。
動かすのはスタンドアロンな実行ファイルなので、.NET入りのDockerイメージを用意する必要はなく、素のAlpineで構わない。
しかし多少の依存はあるらしく、最低限以下のパッケージを入れれば動くようだ。
apk add gcc libintl icu
Dockerfileの内容は下記。
FROM alpine:3.10
RUN apk update && \
apk add gcc libintl icu
ADD ./bin/Release/netcoreapp3.0/linux-musl-x64/publish /app
CMD /app/heroku-suave-getting-started
Herokuデプロイ
CLIインストール
brew tap heroku/brew && brew install heroku
で導入できる。
Herokuログイン
heroku login
heroku container:login
Herokuアプリ作成
heroku create heroku-suave-getting-started
heroku-suave-getting-startedは任意のアプリ名。
デプロイ
heroku container:push web -a heroku-suave-getting-started
heroku container:release web -a heroku-suave-getting-started
アプリをブラウザで開く
heroku open
「Hello World!」が出ていればOK。
参考
https://docs.microsoft.com/ja-jp/dotnet/core/tools/dotnet-new?tabs=netcore22
http://maxfie1d.hatenablog.com/entry/2018/01/23/141755
https://docs.microsoft.com/ja-jp/dotnet/core/rid-catalog
https://dev.to/pluralsight/creating-trimmed-self-contained-executables-in-net-core-4m08
https://hub.docker.com/_/microsoft-dotnet-core
https://docs.microsoft.com/en-us/dotnet/core/deploying/deploy-with-cli