Fn Project ことはじめ - Fn ServerとUIの投入

# はじめに

Publickeyの「[Java対応のサーバレスプラットフォーム「Fn Project」、オラクルがオープンソースで公開](http://www.publickey1.jp/blog/17/javafn_projectjavaone_2017.html)」によると、先日、米国でのJavaOneでFn Projectが発表されたようです。

すでにこのQiitaで記事を書かれた方もいらっしゃいますが、GitHubの「[Fn Project](https://github.com/fnproject)」を参照にして、Fn Projectを導入してみましょう。

以下、作業手順です。
なお、「#」とあるのはrootユーザー、「$」とあるのはdockerユーザーで実行するコマンドです。

# CentOS 7 64ビットの仮想マシン作成

1. Windows 64ビットで、Oracle VM VirtualBoxでCentOSの仮想マシンを作成します
2. 作成した仮想マシンでCentOSをインストールします
3. インストール時にGUIでネットワーク設定できるので、IPアドレスを決めます（今回は「192.168.1.10」）

# Docker投入

Fn ProjectはDockerを使う前提で開発されたものです。なので、まず、Dockerを投入します。
「rpm -qa」コマンドで仮想マシンに古いDockerがないか確認します。

```shell-session
# rpm -qa | grep docker
```

docker-common、docker-client、docker等が出力された場合、「rpm -e」コマンドで削除します。

```shell-session
# rpm -e docker-common docker-client docker
```

Dockerを投入します。

```shell-session
# yum install -y yum-utils
# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# yum makecache fast
# yum -y install docker-ce
```

Dockerサービスを登録し、有効化します。
これにより、仮想マシンの起動時に、Dockerも起動されるようになります。

```shell-session
# systemctl start docker
# systemctl enable docker.service
```

Dockerサービスの状況を確認します。

```shell-session
# ls /usr/lib/systemd/system | grep docker
# systemctl status docker.service
```

# Fn Server投入

次に「GitHubの[Fn Serverリポジトリ](https://github.com/fnproject/fn)」のREADME.mdを参考にして、Fn Serverを投入します。
curlコマンドで一発です。

```shell-session
# curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh
```

「fn start」コマンドで、Fn Serverを起動します。

```
$ (fn start > /dev/null 2>&1 &)
```

ここで、psコマンドで確認すると、Dockerコンテナがキックされていることとがわかります。

```shell-session
docker 15285 0.0 0.5 123704 11260 pts/0 Sl 00:34 0:00 docker run --rm -i --name functions -v /home/docker/data:/app/data -v /var/run/docker.sock:/var/run/docker.sock -p 8080:8080 fnproject/functions
```

また、「docker ps -a」コマンドを実行すると、Dockerコンテナの状態を確認できます。
Dockerコンテナの名前は「functions」であり、ポート番号8080でサービスを提供していることがわかります。

```shell-session
CONTAINER ID IMAGE               COMMAND                CREATED STATUS               PORTS                            NAMES
2dd93c867a93 fnproject/functions "preentry.sh ./fun..." 12 seconds ago Up 11 seconds 2375/tcp, 0.0.0.0:8080->8080/tcp functions
```

Fn Serverにはサンプルがあり、ブラウザから「http://<仮想マシンのIPアドレス>:8080」でアクセスし、次が帰ってくれば成功です。

```
{"goto":"https://github.com/fnproject/fn","hello":"world!"}
```

# function作成

Fn Serverには複数のアプリを登録可能であり、また、各アプリごとに複数のfunctionをデプロイできます。
functionを「関数」と訳すには違和感があるので、ここではそのままfunctionと書きます。

functionを作成するために、ディレクトリを作成しそこに移動します。

```shell-session
$ mkdir -p ~/fn/hello
$ cd ~/fn/hello
```

「fn init」コマンドでfunctionプロジェクト（開発環境）のひな形を作成してくれます。
どの言語で開発するかを「--runtime」オプションで指定します。

```shell-session
$ fn init -runtime java
```

すると、次のファイルが生成されます。

```
├ func.yaml
├ pom.xml
└ src/
  ├ main/java/com/example/fn/HelloFunction.java
  └ test/java/com/example/fn/HelloFunctionTest.java
```

サンプルは、お決まりのHello worldです。
修正しなくてもすぐ動作するので、さっそく「fn run」でビルドおよび実行させます。

```shell-session
$ fn run
```

「fn run」でビルドし、成功すればローカルでfunctionを実行します。
もちろん、お決まりの「Hello, world」が出力されます。

通常のJavaアプリと違い、Docker上でビルドするため、仮想マシンにMaven等を入れる必要がない点が特徴です。
しかし、ビルドするたびにDockerイメージを作成するので、放置するとすぐディスク容量を圧迫します。
業務でFn Projectを使う場合、その辺をどうするか調査する必要がありそうです。

# functionデプロイ

ビルドに成功したら、Fn Serverが起動されている状態であることを確認し、functionをデプロイします。

デプロイには「fn deploy」コマンドを使うわけですが、デフォルトではビルドの生産物であるDockerイメージを自動的にDocker Hubへpushします。しかし、開発段階ではDocker Hubへのpushは不要なので、「--local」オプションを指定します。また、「--app」にはアプリ名を指定します。

```shell-session
$ API_URL=http://192.168.1.10:8080 fn deploy --local --app myapp
```

API_URLを指定しないとデフォルトは「http://localhost:8080 」となりDockerイメージの外部からアクセスできません。したがって、Fn Serverを投入したマシンのIPアドレスを指定します。

以上でmyappというアプリがFn Serverにデプロイされるわけですが、function名はどうなるかというと、親ディレクトリの名前で決まります。今回は「~/fn/hello/」ディレクトリ上で「fn deploy」コマンドを実行したので、function名がhelloとなりました。

なお、DockerHubにpushさせる場合は、次のようにあらかじめDocker Hubにログインしておいてから、「fn deploy」コマンドを「--local」オプション無しで実行します。

```shell-session
$ docker login
$ export FN_REGISTRY=<Docker Hubのアカウント名>
$ API_URL=http://192.168.1.10:8080 fn deploy --app myapp
```

functionをデプロイしたら、「fn routes」コマンドでfunctionのルーティングを確認できます。

```shell-session
$ API_URL=http://192.168.1.10:8080 fn routes list myapp
path   image       endpoint
/hello hello:0.0.5 192.168.1.10:8080/r/myapp/hello
```

上のようにpath、imageおよびendpointが出力されますが、先述のとおり「fn run」を実行するたびにバージョンがどんどん上がり、すでに0.0.5となっています。
pathとendpointの違いですが、functionをFn Server投入マシン上でCLIで実行させる場合には次のようにpathを指定し、ブラウザから実行させる場合にはendpointの「http://192.168.1.10:8080/r/myapp/hello 」へGETを投げます。両方ともfunction実行後に「Hello, world!」が返ってきます。


```shell-session
$ fn call myapp /hello
Hello, world!
```

# Fn User Interface投入

Fn Serverやfunctionの稼働状況をWebブラウザで確認するには、別途Fn User Interface（以降、FnUI）を投入する必要があります。
FnUIもDockerコンテナとして実行するので、次のようにコマンドを実行します。最初にこのコマンドを実行する場合はDocker Hubから「fnproject/ui」のDockerイメージを取得してから、FnUIが起動することになります。こういう手軽さはDockerの特長ですね。

```shell-session
$ docker run --name fnui --rm -it -d --link functions:api -p 4000:4000 -e "API_URL=http://api:8080" fnproject/ui
```

FnUI起動後、ブラウザから「http://192.168.1.10:4000/ 」へGETすれば、次の画面が表示されます。

![FnUI](https://raw.githubusercontent.com/fnproject/ui/master/docs/screenshots/apps.png "FnUI")

# 終了処理

先述のとおり、Fn ServerもFnUIもDockerコンテナなので、終了させるにはdocker stopコマンドを用います。

```shell-session
$ docker stop fnui
$ docker stop functions
```

# 宿題

Fn Server本体にはフロー機能が備わっているわけではなく、別途、Fn Flowを投入する必要があるのですが、Fn Projectの魅力はこのフロー機能に懸かっていると見ています。

「[Serverless Sagas with Fn Flow](https://medium.com/fnproject/serverless-sagas-with-fn-flow-d8199b608b12)」を参考にすれば、フロー機能を実現できそうです。この記事にある通り、「[チュートリアルのGitHubリポジトリ](https://github.com/fnproject/tutorials)」の「FlowSaga」がFn Flowのサンプルです。また、「[scripts](https://github.com/fnproject/tutorials/tree/master/FlowSaga/scripts)」ディレクトリにあるスクリプトが参考になります。

# まとめ

以上、ざっとFn Projectを触ってきた感想です。

- Fn Projectの魅力は、Fn FlowとFn Flow UI等のフロー機能に懸かっている
- クラウドでFn Projectを展開する際、課金の自動計算方法を調べる必要あり
- 「fn xxx」コマンドは簡易コマンドの位置付けであり、コマンド群の強化も課題になりそう
  - 特にFn Serverにデプロイしたfunctionを削除するコマンドが見当たらない
  - functionをビルドするたびにDockerイメージを作成するので、別のビルド方法やデプロイ方法等を検討する必要あり
  - Fn UIは3秒位の短い間隔でfunctionの実行状況をチェックする。チェック間隔を調整する方法等を知りたい