Fn Project - Fn Server構築 (Oracle Linux7.6)

　Oracle社が開発したFnは、サーバー上でサーバレス処理が実行できる、軽量なDockerベースのプラットフォームです。
　サーバレスと言いつつもFn Serverは必要となります。Fn Projectについて[こちら](https://dev.classmethod.jp/server-side/faas-first-step-fn/)の記事が非常にわかりやすくまとめてくださっています。
　AWS LambdaやCloud Functionsではリソースの管理はクラウド側でしますが、Fn はオープンソースであり、Fn Server側で自分達でリソース管理する必要があります。その分細かい制御、設定ができるというのが特徴です。 


　今回Oracle Linux7.6にFn Serverを構築します。Fn ServerはDockerとFn cliのインストールが必要です。
またFnサーバーは最初からPrometheusメトリクスを内蔵しているため、ファンクションの動きを監視できるよう、Prometheus+Grafanaをインストールします。

## 参考
[oracle-functions-labs](https://github.com/shaunsmith/oracle-functions-labs)
[Announcing Prometheus Metrics from Fn](https://medium.com/fnproject/announcing-prometheus-metrics-from-fn-2d0f9ddf0f09)

## 前提
Oracle Linux7.6でFn Serverを構築
selinux無効化
8080,9090,3000ポートを開放していること

※rootユーザでの作業です。

## dockerインストール
```
リポジトリ設定
# vi /etc/yum.repos.d/docker.repo
以下記載

[dockerrepo]
name=Docker Repository
baseurl=https://yum.dockerproject.org/repo/main/oraclelinux/7
enabled=1
gpgcheck=1
gpgkey=https://yum.dockerproject.org/gpg

インストール
# yum install docker-engine

確認
# dockerd --version
Docker version 18.09.1-ol, build e32a1bd

起動、自動起動設定
# systemctl start docker.service
# systemctl enable docker.service
```

## FN CLIインストール・設定
### インストールからスタートまで
```
インストール
# curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh

※以下表示されたら成功です。
fn version 0.5.84

        ______
       / ____/___
      / /_  / __ \
     / __/ / / / /
    /_/   /_/ /_/`

Fnサーバスタート
# (fn start > /dev/null 2>&1 &)

確認
# fn version
Client version is latest version: 0.5.84
Server version:  0.3.727
※Server version: が「?」と表示されると正しくインストールされていません。

docker ps
※ポート番号8080でfnseverのコンテナが稼働していること。

ブラウザ表示確認
http://FnサーバのIPアドレス:8080
※以下表示されること。
{"goto":"https://github.com/fnproject/fn","hello":"world!"}

※8080ポートを他のアプリで使用して違うポートを利用したい場合
　スタート時以下で指定し、環境変数を設定します(8081番にしたい場合)。
# (fn start -p 8081 > /dev/null 2>&1 &)
# export  FN_API_URL=http://127.0.0.1:8081
```

###　FN CLI設定
続いて現在のコンテキストとしてdefaultを選択し、リモートまたはローカルのDockerで使用するためのレジストリ値を設定する必要があります。

```
コンテキストの確認
# fn list context

defaultを設定
# fn use context default

レジストリ設定
# fn update context registry fnuser

確認
# fn list contexts
※defaultコンテキストに「*」が付き、REGISTRYにfnuserと表示されること。
```
## PrometheusとGrafanaインストール
PrometheusとGrafanaを使ってFn実行時メトリックを表示できるようにします。

#### FnサーバーのPrometheusメトリクスについて
Fnは3つのメトリックを提供しています。
１、機能数：現在キューに入れられているか実行されている機能の数、およびサーバーが最後に始動されてから正常に完了または失敗した機能の数。
２、操作期間：まざまな操作を実行するのにかかる時間を表します。関数の実行にかかる時間を取得するだけでなく、実行されているdockerコンテナの起動にかかる時間など、より詳細なデータを取得することもできます。。
３、Docker統計メトリクス：コンテナからCPUやメモリ使用量などのさまざまな統計情報を取得し、それらをPrometheusメトリクスとして利用できるようにします。

以下でメトリクスを表示できます。
http://FnサーバのIP:8080/metrics

### Grafana/Prometheus環境導入
```
Goインストール
# yum install golang-bin

確認
# go version

ディレクトリ作成
# mkdir grafana

移動
# cd grafana

GOPATHを通す
# GOPATH=`pwd` ; export GOPATH

goでfnインストール
# go get github.com/fnproject/fn
※↓のディレクトリが作成されます。
$GOPATH/src/github.com/fnproject/fn

Prometheusインストール
# docker run --name=prometheus -d -p 9090:9090 -v ${GOPATH}/src/github.com/fnproject/fn/examples/grafana/prometheus.yml:/etc/prometheus/prometheus.yml --link fnserver prom/prometheus

prometheusとfn serverを紐づけるためのdockerIPを確認
# docker network inspect bridge -f '{{range .IPAM.Config}}{{.Gateway}}{{end}}'
※以下のように表示されます。
172.17.0.1

設定確認
tail -1 ${GOPATH}/src/github.com/fnproject/fn/examples/grafana/prometheus.yml
      - targets: ['fnserver:8080'] # Uses /metrics by default
※Fnサーバーのホスト名とポートが指定されています。
```
Prometheusのブラウザ確認
http://Fn ServerのIP:9090/graph
Status > Targetsより fnseverのメトリクスが取得されています。
<img width="519" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131627/165f6d80-ce5b-d358-9ea2-28dd6dfa59d3.png">

```
Grafanaインストール
# docker run --name=grafana -d -p 3000:3000 --link prometheus grafana/grafana
```
### Grafnaの設定
ブラウザで以下指定します。
http://Fn ServerのIP:3000/graph

ユーザ名:パスワードはどちらも admin。
Prometheusからメトリックを取得するためのデータソースを作成します。

データソースの追加でPrometheusをクリックしてください。
開かれたフォームで：
・Name: PromDS（任意）
・Type: Prometheus
・URL: http://prometheus:9090
・Access: Server(Default)
上記入力し、「Save and test」をクリックします。

次に、Fnサーバーからのメトリックを表示するサンプルダッシュボードをインポートします。

・左メニュー「+」をクリックして、Importを選択します。
・以下パスにあるjsonファイルをアップロードします。
$GOPATH/src/github.com/fnproject/fn/examples/grafana/fn_grafana_dashboard.json
・先ほど作成したPrometheusデータソース(PromDS)を指定します。
・Importをクリック

ダッシュボードが表示されます。まだファンクションを実行していないため、値が表示されていません。
<img width="385" alt="キャプチャ2.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131627/e93e508f-12f1-d830-e781-032260ccf70b.png">


$GOPATH/src/github.com/fnproject/fn/examples/grafanaにあるjsonファイルはあと2つありますので、そちらもインポートしてください。
各ダッシュボードのjsonファイルは、先ほど説明した3つのメトリクスを表示します。
・機能数メトリクス - fn_grafana_dashboard.json
・操作期間メトリクス - fn_grafana_dashboard2.json
・Docker統計メトリクス - fn_grafana_dashboard3.json

## Goで関数を作成する。
簡単にテスト関数を作成して実行してみます。
まずfn initコマンドで、関数作成のための基本的な機能を備えたファイルを配置します。

```
# fn init --runtime go --trigger http gofn
※上記は--runtimeで言語を選択。httpトリガーを設定し、関数名がgofnとなります。

gofnディレクトリが作成されるので移動
# cd gofn
```

ディレクトリには3つのファイルが作成されています。

```
# ls
func.go  func.yaml  go.mod
```
### 各ファイルの説明
func.goは関数のメイン処理を記載します。
↓はjson形式で name に値を指定して渡すと {"Hello ,nameで渡した値"} と表示され、値を渡さないと{"Hello ,World"} と表示する関数です。

```go
# cat func.go
package main

import (
        "context"
        "encoding/json"
        "fmt"
        "io"

        fdk "github.com/fnproject/fdk-go"
)

func main() {
        fdk.Handle(fdk.HandlerFunc(myHandler))
}

type Person struct {
        Name string `json:"name"`
}

func myHandler(ctx context.Context, in io.Reader, out io.Writer) {
        p := &Person{Name: "World"}
        json.NewDecoder(in).Decode(p)
        msg := struct {
                Msg string `json:"message"`
        }{
                Msg: fmt.Sprintf("Hello %s", p.Name),
        }
        json.NewEncoder(out).Encode(&msg)
}
```

func.yamlは関数の設定ファイルです。
※下記の内容。
・schema_version - この関数のスキーマのバージョン
・name - 関数の名前 ディレクトリ名と一致します。
・version - バージョン。自動的に0.0.1から始まる。
・runtime - ランタイム/言語の名前
・entrypoint - 関数を実行した時に呼び出す実行可能ファイルの名前。この場合 ./func
・triggers - トリガーのエンドポイントを決めます。上記の場合⇒http://localhost:8080/t/appname/gofn-triggerで実行されます。

```
# cat func.yaml
schema_version: 20180708
name: gofn
version: 0.0.2
runtime: go
entrypoint: ./func
triggers:
- name: gofn
  type: http
  source: /gofn
```

Gopkg.tomlは関数のすべての依存関係を指定するファイルです。

#### デプロイ
```
関数を格納するアプリケーション作成
# fn create app goapp

関数をデプロイ
# fn -v deploy --app goapp --local
※--localでローカルサーバーにデプロイされます。
```

## 関数を呼び出す
関数実行方法は複数あります。


fn invokeコマンドを使います。

```
# fn invoke goapp gofn
※{"message":"Hello World"} と表示されます。
```

json形式で値を渡して実行してみます。

```
# echo -n '{"name":"Bob"}' | fn invoke goapp gofn --content-type application/json
※{"message":"Hello Bob"}と表示されます。
```


また今回 fn initコマンド時にhttpトリガーを設定しているため、エンドポイントをcurlして実行することもできます。

```
エンドポイント確認
# fn list triggers goapp
例)
FUNCTION    NAME            TYPE    SOURCE        ENDPOINT
gofn        gofn-trigger    http    /gofn-trigger http://localhost:8080/t/goapp/gofn-trigger

Curlで実行
# curl -H "Content-Type: application/json" http://localhost:8080/t/goapp/gofn-trigger
もしくは
# curl -H "Content-Type: application/json" -d '{"name":"Bob"}' http://localhost:8080/t/goapp/gofn-trigger
※結果はfn invokeの時と同じです。
```

httpトリガーを設定すれば、他のサーバから関数を呼び出しfn serverが処理を実行してくれるということが可能です。

とりあえず実行環境が整ったということで本記事はここまでです。
ワークフローの可視化、実行管理などをするFn Flowなど、他のUIツールも導入すれば
更に使い勝手がよくなると思います。