Ubuntu16.04LTSでIronFunctionsを使ってみる

# IronFunctions
![logo-black-400w.png](https://qiita-image-store.s3.amazonaws.com/0/170124/5e113290-edab-407f-d9e6-bccd2596009c.png)

Iron.ioによって提供されているFaaSのオープンソース版であるIronFunctionsの環境を構築してみます (2017/03/22時点)

IronFunctionsは、CLIやREST APIからリクエストを受け付け、コンテナ内でファンクションを実行し、実行し終えたらコンテナを破壊するといったライフサイクルになります。実行するファンクションはコンテナイメージとして事前に作成/保存され、通常はDocker Hubなどにプッシュするようです。
基本的な機能は揃っている印象ですが、現状で `あるイベントが発生した(閾値を超えたなど)際に自動的にファンクションを実行する`という機能が無いのが惜しいところです。（Ceilometerならいける?)
また、Amazon Lambda以外のFaaSプロバイダと連携できないので今後に期待です。

IronFunctionsの他にも色々なFaaS(EPから有志による実装まで)があったのですが、構築の容易さと仕組みが理解しやすそうという理由からIronFunctionsを選択しました。その他のモノは以下になります。
色々と調べてみたのですが、オープンソースのFaaSってあまり無いのですね。

1. Enterprise
    - Amazon lambda: https://aws.amazon.com/jp/lambda/
    - IBM Bluemix OpenWhisk: https://www.ibm.com/cloud-computing/bluemix/ja/openwhisk
    - Microsoft Azure Function: https://azure.microsoft.com/ja-jp/services/functions/
    - Google Cloud Function: https://cloud.google.com/functions/
    - Webtask.io: https://webtask.io
    - Iron.io: https://www.iron.io
    - Hook.io: https://hook.io
2. OpenSource
    - Apache OpenWhisk: http://openwhisk.org
    - IronFunctions: http://open.iron.io
    - Fission: http://fission.io
    - Funktion: https://funktion.fabric8.io

## 事前準備
- Ubuntu16.04LTSを使用
- DockerHubに自身のログインしておく必要があるのですが、今回は必要無いので割愛

※ 実行されるコンテナイメージはDockerHubからダウンロードされるため、事前にログインしておく必要がある。しかし、今回はローカルだけで実行するので必要ない。
（これ、HarborみたいなPrivate Repository利用できないのかな。今度試してみよう)

### 環境のアップデート
```bash:環境のアップデート
ubuntu@ubuntu-xenial:~$ sudo apt-get update
```

### Dockerのインストール
```bash:Dockerのインストール
ubuntu@ubuntu-xenial:~$ sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
ubuntu@ubuntu-xenial:~$ sudo apt-add-repository 'deb https://apt.dockerproject.org/repo ubuntu-xenial main'
ubuntu@ubuntu-xenial:~$ sudo apt-get update
ubuntu@ubuntu-xenial:~$ sudo apt-get install -y docker-engine

ubuntu@ubuntu-xenial:~/workdir$ docker version
Client:
 Version:      17.03.0-ce
 API version:  1.26
 Go version:   go1.7.5
 Git commit:   60ccb22
 Built:        Thu Feb 23 11:02:43 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.03.0-ce
 API version:  1.26 (minimum version 1.12)
 Go version:   go1.7.5
 Git commit:   60ccb22
 Built:        Thu Feb 23 11:02:43 2017
 OS/Arch:      linux/amd64
 Experimental: false

```

### FunctionsServerとFunctionsUIの起動
```bash:FunctionsServerとFunctionUIの起動
ubuntu@ubuntu-xenial:~$ sudo docker run --rm -it -d --name functions -v ${PWD}/data:/app/data -v /var/run/docker.sock:/var/run/docker.sock -p 8080:8080 iron/functions
ubuntu@ubuntu-xenial:~$ sudo docker run --rm -it -d --link functions:api -p 4000:4000 -e "API_URL=http://api:8080" iron/functions-ui

ubuntu@ubuntu-xenial:~/workdir$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                              NAMES
9a9775f5251f        iron/functions-ui   "npm start"              4 hours ago         Up 4 hours          0.0.0.0:4000->4000/tcp             kind_mahavira
30a6e1bb2de7        iron/functions      "/usr/local/bin/en..."   4 hours ago         Up 4 hours          2375/tcp, 0.0.0.0:8080->8080/tcp   functions

```
### CLIツールのインストール
```bash:CLIツールのインストール
ubuntu@ubuntu-xenial:~$ curl -LSs https://goo.gl/VZrL8t | sh
ubuntu@ubuntu-xenial:~$ fn
fn 0.2.43

IronFunctions command line tools

USAGE:
   Check the manual at https://github.com/iron-io/functions/blob/master/fn/README.md

ENVIRONMENT VARIABLES:
   API_URL - IronFunctions remote API address

COMMANDS:
     init     create a local func.yaml file
     apps     manage applications
     routes   manage routes
     images   manage function images
     lambda   create and publish lambda functions
     version  displays fn and functions daemon versions
     help, h  Shows a list of commands or help for one command

ALIASES:
     build    (images build)
     bump     (images bump)
     deploy   (images deploy)
     run      (images run)
     call     (routes call)
     push     (images push)

GLOBAL OPTIONS:
   --help, -h     show help
   --version, -v  print the version
```

## ファンクションのデプロイ

### 作業用フォルダの作成
```bash:作業用フォルダの作成
ubuntu@ubuntu-xenial:~$ mkdir -p ~/workdir
ubuntu@ubuntu-xenial:~$ cd workdir
```

### サンプルコードの作成
以下のコードを`func.go`として作成します。今回は公式サイトから引っ張ってきました。

```go:func.go
package main

import (
	"encoding/json"
	"fmt"
	"os"
)

type Person struct {
	Name string
}

func main() {
	p := &Person{Name: "World"}
	json.NewDecoder(os.Stdin).Decode(p)
	fmt.Printf("Hello %v!", p.Name)
}
```

```bash:確認
ubuntu@ubuntu-xenial:~/workdir$ ls
func.go
```

### ファンクションの初期化からデプロイまで

* ファンクションを初期化する

```bash:ファンクションを初期化する
# 本来であれば、"myapp"の箇所にDockerHubのアカウント名を指定するのですが、ローカルだけでやるのであれば適当な名前でOKです
ubuntu@ubuntu-xenial:~/workdir$ fn init myapp/hello
assuming go runtime
func.yaml created.

ubuntu@ubuntu-xenial:~/workdir$ cat func.yaml
name: myapp/hello
version: 0.0.1
runtime: go
entrypoint: ./func
```

* ファンクションをビルドする

```bash:ファンクションをビルドする
ubuntu@ubuntu-xenial:~/workdir$ fn build
Running prebuild command: docker run --rm -v /home/ubuntu/workdir:/go/src/github.com/x/y -w /go/src/github.com/x/y iron/go:dev go build -o func
Building image myapp/hello:0.0.1
Sending build context to Docker daemon 2.062 MB
Step 1/4 : FROM iron/go
 ---> c05f82fa066a
Step 2/4 : WORKDIR /function
 ---> Using cache
 ---> c7dbf8446c0a
Step 3/4 : ADD . /function/
 ---> 39fb743114b4
Removing intermediate container 691b263da564
Step 4/4 : ENTRYPOINT ./func
 ---> Running in b0e4fdaf5517
 ---> a36cab9bc0fe
Removing intermediate container b0e4fdaf5517
Successfully built a36cab9bc0fe
Function myapp/hello:0.0.1 built successfully.
```

* ファンクションを直接実行する

```bash:ファンクションを直接実行する
ubuntu@ubuntu-xenial:~/workdir$ fn run
Hello World!
```

* アプリケーションを作成する

```bash:アプリケーションを作成する
ubuntu@ubuntu-xenial:~/workdir$ fn apps create myapp
myapp created
```

* ルートを作成する

```bash:ルートを作成する
ubuntu@ubuntu-xenial:~/workdir$ fn routes create myapp /hello
/hello created with myapp/hello:0.0.1
```

これでファンクションのデプロイが完了です。意外とあっさり。
あとは、`ファンクションを実行する`箇所のコマンドを打てば良いだけ。

## IronFunctionsの使い方まとめ

### ファンクションを作成/削除する

```bash:ファンクションを作成/削除する
# CLI
ubuntu@ubuntu-xenial:~/workdir$ fn apps create myapp
ubuntu@ubuntu-xenial:~/workdir$ fn apps delete myapp

# cURL
ubuntu@ubuntu-xenial:~/workdir$ curl -H "Content-Type: application/json" -X POST -d '{"app": { "name":"myapp" }}' http://localhost:8080/v1/apps
```

### ルートを作成/削除する
```bash:ルートを作成/削除する
# CLI
ubuntu@ubuntu-xenial:~/workdir$ fn routes create myapp /hello -i myapp/hello
ubuntu@ubuntu-xenial:~/workdir$ fn routes delete myapp /hello -i myapp/hello

# cURL
ubuntu@ubuntu-xenial:~/workdir$ curl -H "Content-Type: application/json" -X POST -d '{"route": {"path":"/hello", "image":"iron/hello"}}' http://localhost:8080/v1/apps/myapp/routes
```

### ファンクションを実行する

```bash:ファンクションを実行する
# CLIからファンクションを直接実行する
ubuntu@ubuntu-xenial:~/workdir$ fn run
Hello World!

# CLIからRoutes経由でファンクションを実行する
ubuntu@ubuntu-xenial:~/workdir$ fn call myapp /hello
Hello World!

# cURL経由で実行する
ubuntu@ubuntu-xenial:~/workdir$ curl http://localhost:8080/r/myapp/hello
Hello World!

# CLIから引数を与えてファンクションを実行する
ubuntu@ubuntu-xenial:~/workdir$ echo '{"name":"Johnny"}' | fn call myapp /hello
Hello Johnny! # World が Johnny に変わる

# cURLから引数を与えてファンクションを実行する
ubuntu@ubuntu-xenial:~/workdir$ curl -H "Content-Type: application/json" -X POST -d '{"name":"Johnny"}' http://localhost:8080/r/myapp/hello
```

### ファンクションを更新する

```bash:ファンクションを更新する
# ファンクションのバージョンアップとイメージのビルド
ubuntu@ubuntu-xenial:~/workdir$ fn bump && fn build
Bumped to version 0.0.2
Running prebuild command: docker run --rm -v /home/ubuntu/workdir:/go/src/github.com/x/y -w /go/src/github.com/x/y iron/go:dev go build -o func
Building image myapp/hello:0.0.2
Sending build context to Docker daemon 2.062 MB
Step 1/4 : FROM iron/go
 ---> c05f82fa066a
Step 2/4 : WORKDIR /function
 ---> Using cache
 ---> c7dbf8446c0a
Step 3/4 : ADD . /function/
 ---> 8ddafac107b4
Removing intermediate container b241757fc9dd
Step 4/4 : ENTRYPOINT ./func
 ---> Running in be42371c6d38
 ---> 5234e6fedd28
Removing intermediate container be42371c6d38
Successfully built 5234e6fedd28
Function myapp/hello:0.0.2 built successfully.

# ルートのアップデート
ubuntu@ubuntu-xenial:~/workdir$ fn routes update myapp /hello
```

### 非同期ファンクションの作成と実行

```bash:非同期ファンクションの作成と実行
# 非同期イベントを作成する
ubuntu@ubuntu-xenial:~/workdir$ curl -H "Content-Type: application/json" -X POST -d '{"route": {"type": "async", "path":"/hello-async", "image":"iron/hello"}}' http://localhost:8080/v1/apps/myapp/routes

# 非同期ファンクションを実行する
ubuntu@ubuntu-xenial:~/workdir$ curl -H "Content-Type: application/json" -X POST -d '{"name":"Johnny"}' http://localhost:8080/r/myapp/hello-async
{"call_id":"b296bca4-8574-5211-b762-7f7cb914292b"}

# 非同期ファンクションの実行結果を確認する
# 実行時に出力された`call_id`と同様のIDを持つメッセージを確認する
ubuntu@ubuntu-xenial:~/workdir$ docker logs [FUNCTIONSを起動しているコンテナID]
~~ 一部抜粋 ~~
INFO[18260] Pushed to MQ                                  call_id=b296bca4-8574-5211-b762-7f7cb914292b
INFO[18260] Added new task to queue                       action="server.handleRunnerRequest)-fm" app=myapp call_id=b296bca4-8574-5211-b762-7f7cb914292b image="iron/hello" route="/hello-async"
INFO[18260] Reserved                                      call_id=b296bca4-8574-5211-b762-7f7cb914292b
INFO[18260]                                               call_id=b296bca4-8574-5211-b762-7f7cb914292b name=run.myapp.requests runner=async type=count value=1
INFO[18260]                                               call_id=b296bca4-8574-5211-b762-7f7cb914292b name=run.myapp.waittime runner=async type=time value=0s
INFO[18260] Deleted                                       call_id=b296bca4-8574-5211-b762-7f7cb914292b
INFO[18260] Task complete                                 call_id=b296bca4-8574-5211-b762-7f7cb914292b runner=async
INFO[18261] Hello World!                                  app_name=myapp call_id=b296bca4-8574-5211-b762-7f7cb914292b image="iron/hello" path= runner=async user_log=true
INFO[18261] container status                              call_id=b296bca4-8574-5211-b762-7f7cb914292b container_error= container_finished=2017-03-21 11:34:21.807374695 +0000 UTC container_running=false container_status=exited exit_code=0 runner=async
INFO[18261]                                               call_id=b296bca4-8574-5211-b762-7f7cb914292b name=run.myapp.succeeded runner=async type=count value=1
INFO[18261]                                               call_id=b296bca4-8574-5211-b762-7f7cb914292b name=run.myapp.time runner=async type=time value=400.322041ms
INFO[18261]                                               call_id=b296bca4-8574-5211-b762-7f7cb914292b name="run.exec_time" runner=async type=time value=400.322041ms
```

## WebUIにアクセスしてみる

- `http://<FunctionServerIP>:4000`にアクセスする
    - 右上の`Create App`からアプリケーションの作成を行う事が出来る

![スクリーンショット 2017-03-22 1.39.49.png](https://qiita-image-store.s3.amazonaws.com/0/170124/a6e4544e-7205-abf5-cc5f-f17af2c920dd.png)

- アプリケーション名をクリックすると以下のような画面に遷移する
    - 右上の`Add Route`からルートを作成する事が出来る

![スクリーンショット 2017-03-22 1.39.56.png](https://qiita-image-store.s3.amazonaws.com/0/170124/7f5ecfd0-93c6-8b76-6ed6-e8fef0370c43.png)

- WebUI上からファンクションを実行する事も出来る
    - `Run Function`をクリックし、出現したウィンドウ上の`Run`をクリックする

![スクリーンショット 2017-03-22 1.44.46.png](https://qiita-image-store.s3.amazonaws.com/0/170124/0d3cb9fd-2c03-1f36-4993-e08268bb4246.png)

※ ファンクションのコンテナイメージのビルドはWebUIからは行う事が出来ないので、大抵の作業はCLIからになりそう。

## 感想
- シンプルで使い易い
- なんか名前がかっこいい、あと公式ページのアイ○ンマンみたいなキャラかっこいい
- 不足している機能もあるけど基本的な事は出来そう
- 各クラウドプロバイダとの連携に期待
- イベントドリブンな箇所を充実させて欲しい
- ユーザマネジメントの機能があったら便利そう
- beta版のリリースが遅れているのが気になる

## 参考
https://github.com/iron-io/functions
https://medium.com/iron-io-blog/ironfunctions-the-open-source-serverless-platform-21dd66d839e8#.mih24j3wg
https://medium.com/@treeder/announcing-project-picasso-openstack-functions-as-a-service-db3fff70ffea#.cwjtgq22a
https://medium.com/@SergeyNuzhdin/how-to-run-functions-in-your-kubernetes-cluster-c7aa38927188#.soubagpg9