Oracle Functions (Fn) で、Custom DockerImageを使ってFunction実行

# はじめに

Oracle Function で、独自に作成したDockerImage を使用して Function を実行する方法を書きます。
シンプルな関数を実行する場合であればカスタムする必要はありませんが、複雑なDockerImageを使用しなければならない場合には、とても有効な手段です。


Oracle Functions 自体ののチュートリアルはこちらを参照してください
<https://qiita.com/sugimount/items/018e08f575ecefb1546c> 



# 独自に作成するDockerImageの概要

今回は、以下の内容のDockerImageを作成します。Dockerコンテナ内に事前に必要なファイルを格納しておきたいニーズに出会ったことがあり、これを想定しています。

- DockerImageに、テスト用のファイルを格納して build する
- DockerImage内で Go言語を動かして、テスト用のファイルの中身を出力し、JSONとしてResponseする



この内容を確認することで、DockerImageを独自に作成して Oracle Functions で実行する流れが掴めると思います。



# Function の初期テンプレートを生成

適当な作業ディレクトリへ移動します。自分の場合はGo言語で作成するため、GOPATH が通っているディレクトリを選んでいます。

```
mkdir /home/ubuntu/go-third-party/src/fn_work
cd /home/ubuntu/go-third-party/src/fn_work
```



fn init コマンドで、go言語の初期テンプレートを生成します。

```
fn init --runtime go custom_runtime
```



自動生成されたディレクトリへ移動します

```
cd custom_runtime
```



# 独自のDockerImage作成

まずは、テスト用のファイルを生成します。

```
echo 'Hello! I am file!' >> iamfile.txt
```



Go言語のソースコードを作ります。今回は以下のソースコードを作ります。
環境変数 `FUNC_OPEN_FILEPATH` で与えたファイルを読み込み、JSONとしてResponseする内容です。

```go
package main

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

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

// BUFSIZE 読み込みバッファーサイズ
const BUFSIZE = 1024

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

func printFile(ctx context.Context, in io.Reader, out io.Writer) {
        // file read
        file, err := os.Open(os.Getenv("FUNC_OPEN_FILEPATH"))
        if err != nil {
                fmt.Println("error!")
                return
        }

        defer file.Close()

        buf := make([]byte, BUFSIZE)
        context := ""
        for {
                n, err := file.Read(buf)
                if n == 0 {
                        break
                }
                if err != nil {
                        // Readエラー処理
                        break
                }

                context = string(buf[:n])
        }

        // make struct
        msg := struct {
                Message string `json:"message"`
        }{}

        msg.Message = context

        json.NewEncoder(out).Encode(&msg)
}
```



fn で使用する、定義ファイルを編集します。runtimeを `go` から、独自のDockerImageを使用することを意味する `docker`へ変更します。

```
> vim func.yaml
```

```
schema_version: 20180708
name: custom_runtime
version: 0.0.1
runtime: docker <======== go から dockerに書き換え
entrypoint: ./func
format: http-stream
```



Dockerfileを作ります。マルチステージビルドを使っています。マルチステージビルド自体は、[こちら](https://qiita.com/carimatics/items/01663d32bf9983cfbcfe)を読むとわかりやすいと思います。
今回確認したいポイントは以下の通りです

- 作成したテストファイル `iamfile.txt` を、`/function/iamfile.txt` に格納している

```
vim Dockerfile
```

```
FROM golang:1.11.5 as builder
WORKDIR /go/src/funcdir
COPY Gopkg.toml .
COPY func.go .
RUN go get -u github.com/golang/dep/cmd/dep && \
    dep ensure
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o func .

FROM alpine:3.9
WORKDIR /function/
COPY --from=builder /go/src/funcdir/func .
COPY iamfile.txt /function/iamfile.txt
ENTRYPOINT ["/function/func"]
```



以下のファイル構成になっているとよいです

```
> tree /home/ubuntu/go-third-party/src/fn_work/custom_runtime
/home/ubuntu/go-third-party/src/fn_work/custom_runtime
├── Dockerfile
├── Gopkg.toml
├── func.go
├── func.yaml
└── iamfile.txt
```



正常にDockerImageをBuild出来るか確認を行います (docker build で直接やってもよいです)

```
fn -v build
```



実行例

```
> fn -v build
Building image phx.ocir.io/sugimount2/oracle-function/custom_runtime:0.0.1
FN_REGISTRY:  phx.ocir.io/sugimount2/oracle-function
Current Context:  sugi-context
Sending build context to Docker daemon  41.47kB
Step 1/10 : FROM golang:1.11.5 as builder
 ---> 901414995ecd
Step 2/10 : WORKDIR /go/src/funcdir
 ---> Using cache
 ---> 279adebb201e
Step 3/10 : COPY Gopkg.toml .
 ---> Using cache
 ---> a0ed62dd646f
Step 4/10 : COPY func.go .
 ---> Using cache
 ---> 70919180fbb6
Step 5/10 : RUN go get -u github.com/golang/dep/cmd/dep &&     dep ensure
 ---> Using cache
 ---> 6d10b5c6b642
Step 6/10 : RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o func .
 ---> Using cache
 ---> a05adf4cd5e2
Step 7/10 : FROM alpine:3.9
 ---> caf27325b298
Step 8/10 : WORKDIR /function/
 ---> Using cache
 ---> b75514413267
Step 9/10 : COPY --from=builder /go/src/funcdir/func .
 ---> Using cache
 ---> d13fa27ed5ba
Step 10/10 : ENTRYPOINT ["/function/func"]
 ---> Using cache
 ---> 10382affeec1
Successfully built 10382affeec1
Successfully tagged phx.ocir.io/sugimount2/oracle-function/custom_runtime:0.0.1

Function phx.ocir.io/sugimount2/oracle-function/custom_runtime:0.0.1 built successfully.
```



正常に build できたら、fn deploy を実行して、Oracle Functions に Deploy します。

```
fn --verbose deploy --app env-app
```



環境変数を設定します。`FUNC_OPEN_FILEPATH` に、DockerFIleで格納したテスト用ファイルの格納先を示しています

```
fn config func env-app custom_runtime FUNC_OPEN_FILEPATH /function/iamfile.txt
```



確認

```
> fn ls config func env-app custom_runtime
KEY                     VALUE
FUNC_OPEN_FILEPATH      /function/iamfile.txt
```



Functionを実行します

```
fn --verbose invoke env-app custom_runtime
```



以下のように、テスト用のファイルの中身をReadして、JSONとしてResponseしているのが確認できました。

```
> fn --verbose invoke env-app custom_runtime
{"message":"Hello! I am file!\n"}
```





# 参考URL

Creating a Function from a Docker Image
http://fnproject.io/tutorials/ContainerAsFunction/

Packaging your Function
https://github.com/fnproject/docs/blob/master/fn/develop/packaging.md

Fn Tutorial
http://fnproject.io/tutorials/