HaskellでもGoみたいにシングルバイナリでアプリケーションをデプロイしたい

[Haskell (その2) Advent Calendar 2017](https://qiita.com/advent-calendar/2017/haskell2)の(9日目)の記事です。

## モチベーション
業務でもHaskell使いたい!

## 前置き
業務ではGKEで主にScalaプログラムの開発を行なっています。
Kubernetes(Docker)を使う場合、imageサイズが大きいとデプロイに時間がかかり、リリースやCIが遅くなってしまいます。
プロジェクトでは、ベースにapline(14MB)を使い、JRE8/JDK8を入れると190MBくらいになり、アプリケーションを入れると350MBくらいになります。さらに4アプリケーションあるので全部で1.4GBくらいになります。
ScalaだとJVMが必要なのと、jarが大きくなりやすいのでimageサイズが大きくなりやすいです。

そうなってくると、Goのようにシングルバイナリだけで実行できるのはとても魅力的です。
最近はKubernetesとGoで開発するプロジェクトも社内で増えてきました。
Haskellでも同じことができれば、実務でも使えるのでは...

## webアプリケーションを作ってみる
最初は[この記事](https://www.ishiy.xyz/posts/2016-02-28-haskell-docker.html)をみてDockerをstack.yamlに指定して`stack build --ghc-options='-optl-static -optl-pthread'`を実行してみたのですが、うまくコンパイルできず、色々原因を調べていたのですが、ghc,gccあたりの知識が足らず、困っていました。
そしたら、@algas さんの[Haskell on Docker で Portable CLI を作ろう](https://qiita.com/algas/items/fde155abbc9d8ae3f8c9)で、pandocのDockerfileでやってるじゃないですか...

今回はWebアプリケーションとしてSpockの[tutorialのサンプル](https://www.spock.li/tutorials/getting-started)をそのまま使います。
まず、下記でstackアプリケーションを作成します。

```bash
$ stack new spock-app
```
そして、app/Main.hsを下に書き換えます。

```haskell
{-# LANGUAGE OverloadedStrings #-}
module Main where

import Web.Spock
import Web.Spock.Config

import Control.Monad.Trans
import Data.Monoid
import Data.IORef
import qualified Data.Text as T

data MySession = EmptySession
data MyAppState = DummyAppState (IORef Int)

main :: IO ()
main =
    do ref <- newIORef 0
       spockCfg <- defaultSpockCfg EmptySession PCNoDatabase (DummyAppState ref)
       runSpock 8080 (spock spockCfg app)

app :: SpockM () MySession MyAppState ()
app =
    do get root $
           text "Hello World!"
       get ("hello" <//> var) $ \name ->
           do (DummyAppState ref) <- getState
              visitorNumber <- liftIO $ atomicModifyIORef' ref $ \i -> (i+1, i+1)
              text ("Hello " <> name <> ", you are visitor number " <> T.pack (show visitorNumber))
```

Dockerfileとして下記を用意します。

```dockerfile
FROM fpco/stack-build:lts-9.14
WORKDIR /usr/lib/gcc/x86_64-linux-gnu/5.4.0
RUN cp crtbeginT.o crtbeginT.o.orig
RUN cp crtbeginS.o crtbeginT.o
ADD ./ /work
WORKDIR /work
RUN stack setup
RUN stack --system-ghc --local-bin-path /sbin build --ghc-options '-optl-static -fPIC -optc-Os'

FROM alpine:latest
RUN mkdir /work
COPY --from=0 /work/.stack-work/install/x86_64-linux/lts-9.14/8.0.2/bin/spock-app /sbin/
CMD ["/sbin/spock-app"]
```

最後にcabalファイルの依存関係を直します。
プロジェクト全体は[ここ](https://github.com/t10471/stack-app)にあります。

あとは下記コマンドでimageを作成します。

```
$ docker build --rm -t spock-app .
```

imageのサイズが`23.6MB`で結構小さくできました!

```
$ docker images
REPOSITORY TAG    IMAGE ID     CREATED             SIZE
spock-app  latest 814e8c86669d 12 minutes ago      23.6MB
```

試しに下記のコマンドで実行して、 `https://localhost:8080/` にアクセスするとちゃんとレスポンスが返ってくると思います。
```
docker run -d --name spock-app -d -p 8080:8080 spock-app
```

## 課題
* ~~ghcのダウンロードとフルコンパイルが毎回走るのでビルドがものすごく遅い...~~
  * コメントでもらった方法で大丈夫そう (初回のコンパイルはspockの依存が多いせいかちょっと遅いけど...)

## 【追記】 実は本当のシングルバイナリにはならないかも?
動いたから安心してすっかり忘れてたのですが、コンパイルログには下記のwarningが出てて、spockが依存しているwaiが依存しているnetworkにcのFFIがあり...
FFIがあるとそこはスタティックリンクにならないような...?
goだと[この記事のように](https://qiita.com/yugui/items/e71d3d0b3d654a110188)意識して頑張っている?

```
Linking .stack-work/dist/x86_64-linux/Cabal-1.24.2.0/build/spock-app/spock-app ...
/root/.stack/snapshots/x86_64-linux/lts-9.14/8.0.2/lib/x86_64-linux-ghc-8.0.2/network-2.6.3.2-IsLM4TXcLoRI0fmmBYVyQz/libHSnetwork-2.6.3.2-IsLM4TXcLoRI0fmmBYVyQz.a(HsNet.o):function hsnet_getaddrinfo: warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/opt/ghc/8.0.2/lib/ghc-8.0.2/rts/libHSrts_thr.a(Linker.thr_o):function internal_dlopen: warning: Using 'dlopen' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
```