Fn Projectを触ってみる

こんにちは id:dhigashi です。

7/27 に開催された Innovation Summit Tokyo 2018Fn Project: 今最も注目すべき「サーバレス」クラウドアプリケーション開発基盤 というタイトルで Fn Project についてのセッションがありました。

面白そうでしたので、今回は Oracle Cloud ではなく Fn Project について簡単に触ってみたいと思います。

セッション資料はこちらから見る事ができます。
http://www.oracle.co.jp/campaign/innovation/2018/pdfs/ist18_b-3.pdf

尚、Fn Project は Oracle Cloud 上で提供される FaaS の Oracle Functions として利用できるようになるようです。

Fn Project

インストールの前に

事前条件として以下を満たす必要があります。

  • Docker 17.10.0-ce 以上がインストールされていること
  • Docker Hub のアカウントを持ち、ログイン済みであること

尚、検証に使用した環境は以下の通りです。

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.5 LTS"
$ docker version
Client:
 Version:           18.06.0-ce
 API version:       1.38
 Go version:        go1.10.3
 Git commit:        0ffa825
 Built:             Wed Jul 18 19:11:02 2018
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          18.06.0-ce
  API version:      1.38 (minimum version 1.12)
  Go version:       go1.10.3
  Git commit:       0ffa825
  Built:            Wed Jul 18 19:09:05 2018
  OS/Arch:          linux/amd64
  Experimental:     false

インストール

fnproject/fn - quickstart に従いインストールします。

$ curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh
fn version 0.4.140

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

インストールが完了したら Fn サーバを起動します。

$ fn start
Unable to find image 'fnproject/fnserver:latest' locally
latest: Pulling from fnproject/fnserver
略

        ______
       / ____/___
      / /_  / __ \
     / __/ / / / /
    /_/   /_/ /_/
        v0.3.520

起動できたらバージョンを確認してみます。

$ fn version
Client version is latest version: 0.4.140
Server version:  0.3.520

尚、Fn サーバは Dockerコンテナとして起動しています。

$ docker ps
CONTAINER ID        IMAGE                COMMAND             CREATED             STATUS              PORTS                              NAMES
7156fd345aa1        fnproject/fnserver   "./fnserver"        2 hours ago         Up 2 hours          2375/tcp, 0.0.0.0:8080->8080/tcp   fnserver

最後に、環境変数 FN_REGISTRY に自身の Docker Hub のユーザ名を設定し準備は完了です。

export FN_REGISTRY=my_dockerhub_username

関数の作成

fn init コマンドで新しい関数を作成します。

$ fn init --help

DEVELOPMENT COMMANDS
  fn init -   Create a local func.yaml file

USAGE
  fn [global options] init [command options] [function-subdirectory]

DESCRIPTION
  This command creates a func.yaml file in the current directory.

COMMAND OPTIONS
  --name value                   Name of the function. Defaults to directory name in lowercase.
  --force                        Overwrite existing func.yaml
  --runtime value                Choose an existing runtime - dotnet, go, java8, java9, java, lambda-nodejs4.3, lambda-node-4, node, php, python, python3.6, ruby, rust, kotlin
  --entrypoint value             Entrypoint is the command to run to start this function - equivalent to Dockerfile ENTRYPOINT.
  --cmd value                    Command to run to start this function - equivalent to Dockerfile CMD.
  --version value                Set initial function version (default: "0.0.1")
  --working-dir value, -w value  Specify the working directory to initialise a function, must be the full path.
  --trigger value                Specify the trigger type.
  --memory value, -m value       Memory in MiB (default: 0)
  --type value, -t value         Route type - sync or async
  --config value, -c value       Route configuration
  --headers value                Route response headers
  --format value, -f value       Hot container IO format - default or http
  --timeout value                Route timeout (eg. 30) (default: 0)
  --idle-timeout value           Route idle timeout (eg. 30) (default: 0)
  --annotation value             Route annotation (can be specified multiple times)
$ fn init --runtime go hello
Creating function at: /hello
Runtime: go
Function boilerplate generated.
func.yaml created.

今回はランタイムに go 言語を選択しましたが、ヘルプを見ると現時点では dotnet、java、node、python、ruby...などの言語が選択できるようです。
特に rust、kotlin は珍しい気がします。

function-subdirectory として指定した hello ディレクトリ以下にファイルが作成されました。

$ cd hello
$ ls -1
Gopkg.toml
func.go
func.yaml
test.json

func.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)
}

myHandler 関数をみると in から入力し out へ出力するだけのシンプルなものです。
入力は json を期待しているようです。

次に func.yaml を見てみます。

$ cat func.yaml
name: hello
version: 0.0.1
runtime: go
entrypoint: ./func
format: json

関数の名前やバージョン、ランタイム、実行ファイル名などが記されています。
ここに format として json が指定されており、関数の入出力に json を用いる事を示しています。

ローカルで関数の実行

fn init コマンドによって作成された関数をローカルで実行してみます。
ローカルで実行するには fn run コマンドを使用します。

$ fn run
Building image my_dockerhub_username/hello:0.0.1 ............................
{"message":"Hello World"}

入力に何も指定していないので、出力された message の値が Hello World となっています。
次に標準入力で値を渡して実行してみます。

$ echo -n '{"name":"Jason"}' | fn run
Building image my_dockerhub_username/hello:0.0.1 ...
{"message":"Hello Jason"}

標準入力で与えられた値によって出力を変える事ができました。

関数のデプロイ

ローカルで動作を確認した関数を Fn サーバへデプロイします。
デプロイを行うためには fn deploy コマンドを使用します。

$ fn deploy --help

DEVELOPMENT COMMANDS
  fn deploy -   Deploys a function to the functions server (bumps, build, pushes and updates route).

USAGE
  fn [global options] deploy [command options] [function-subdirectory]

DESCRIPTION
  This command deploys one or all (--all) functions to the function server.

COMMAND OPTIONS
  --app value                     App name to deploy to
  --verbose, -v                   Verbose mode
  --no-cache                      Don't use Docker cache for the build
  --local, --skip-push            Do not push Docker built images onto Docker Hub - useful for local development.
  --registry --registry username  Set the Docker owner for images and optionally the registry. This will be prefixed to your function name for pushing to Docker registries  eg: --registry username will set your Docker Hub owner. `--registry registry.hub.docker.com/username` will set the registry and owner.
  --all app.yaml                  If in root directory containing app.yaml, this will deploy all functions
  --no-bump                       Do not bump the version, assuming external version management
  --build-arg value               Set build time variables
  --working-dir value, -w value   Specify the working directory to deploy a function, must be the full path.
$ fn deploy --app myapp --local
Deploying hello to app: myapp at path: /hello
Bumped to version 0.0.2
Building image my_dockerhub_username/hello:0.0.2 ...
Updating route /hello using image my_dockerhub_username/hello:0.0.2...

アプリケーション名に myapp と指定しデプロイを行います。
※ 今回 Docker Hub へのプッシュを行わないため、--local オプションを指定しています。

アプリケーションの一覧を取得すると myapp が存在する事が確認できます。

$ fn list apps
NAME
myapp

また myapp のルーティングを確認すると、先程デプロイした /hello が存在する事が確認できます。

$ fn list routes myapp
PATH    IMAGE                   ENDPOINT
/hello  my_dockerhub_username/hello:0.0.2     localhost:8080/r/myapp/hello

デプロイした関数の実行

Fn サーバへデプロイした関数を実行してみます。
関数の呼び出しには fn call コマンドを使用します。

ローカルで実行した時と同じように、デプロイした関数を実行できます。

$ fn call myapp /hello
{"message":"Hello World"}
$ echo -n '{"name":"Jason"}' | fn call myapp /hello
{"message":"Hello Jason"}

また、デプロイした関数は HTTP 経由でも実行する事ができます。

$ curl -H "Content-Type: application/json" http://localhost:8080/r/myapp/hello
{"message":"Hello World"}
$ curl -H "Content-Type: application/json" -d '{"name": "Jason"}' http://localhost:8080/r/myapp/hello
{"message":"Hello Jason"}

ダッシュボード(おまけ)

Fn Project には UI が用意されており、ダッシュボードで関数のメトリクスを参照する事ができます。

github.com

f:id:dhigashi:20180731185914p:plain

先程デプロイした関数とそのルーティングや、呼び出された数などを確認する事ができました。

まとめ

今回は チュートリアル に沿ってFn Project を簡単に触ってみました。

Fn Project には Fn Flow という機能があり、セッション資料では以下のように説明されています。

Fork-join/連鎖/遅延/エラーハンドリングなどの要素を含む高信頼、スケーラブルな長期に渡る実行が可能なfunction

次はこちらを使用してより実用的なアプリケーションを作成してみたいと思います。