Fn projectのアーキテクチャを理解する

# はじめに
OCIのサーバレスプラットフォームとしてFunctionsがあります。これはAWSのLambdaに相当するサービスです。

https://docs.oracle.com/ja-jp/iaas/Content/Functions/home.htm

Functionsを使い始めたのですが、アーキテクチャがよくわからなかったので、以下のFn projectのQuickstartをやりながら、アーキテクチャを整理したいと思います。

https://github.com/fnproject/fn#top

Functionsはfn projectのマネージドサービスです。

# 事前準備

以下のようなサーバとクライアントを用意します。
なお、図ではサーバとクライアントが別れていますが、今回サーバとクライアントは同一のマシンを使用します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492530/87b83d81-2d13-2657-ff55-aff507091627.png)

OSはOracle Linux、Dockerはインストール済みです。

```
$ cat /etc/oracle-release 
Oracle Linux Server release 8.8
$ docker -v
Docker version 24.0.2, build cb74dfc
```

# CLI Toolのインストール

CLI Toolをインストールします。

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

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

```

```
$ fn --version
fn version 0.6.25
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492530/103783e8-ef9f-1074-6d64-c38793d3e4b6.png)

接続先のサーバは自分自身になっています。

```
$ fn list context
CURRENT NAME    PROVIDER        API URL                 REGISTRY
*       default default         http://localhost:8080
```

# fn serverの起動

fn serverを起動します。
fn serverはコンテナで実行されるFunctionのオーケストレーターです。

```
$ fn start
2023/07/01 07:00:31 ¡¡¡ 'fn start' should NOT be used for PRODUCTION !!! see https://github.com/fnproject/fn-helm/
Unable to find image 'fnproject/fnserver:latest' locally
latest: Pulling from fnproject/fnserver
ff3a5c916c92: Pull complete 
1a649ea86bca: Pull complete 
ce35f4d5f86a: Pull complete 
b6206661264b: Pull complete 
b8b71dba24d3: Pull complete 
3873004a68ee: Pull complete 
f4205b132661: Pull complete 
91a85eeeb257: Pull complete 
93c96d032b32: Pull complete 
bb761748d6e1: Pull complete 
81f6c51c4ac2: Pull complete 
2ba715696dba: Pull complete 
f46c2b56aaf3: Pull complete 
66d07b1eeeb9: Pull complete 
bf871b66822c: Pull complete 
Digest: sha256:2a2071f8af6ed6ad6476ad2e794f757874dad347b5ef14869dd31ee252810655
Status: Downloaded newer image for fnproject/fnserver:latest
time="2023-07-01T07:00:39Z" level=info msg="Setting log level to" fields.level=info
time="2023-07-01T07:00:39Z" level=info msg="Registering data store provider 'sql'"
time="2023-07-01T07:00:39Z" level=info msg="Connecting to DB" url="sqlite3:///app/data/fn.db"
time="2023-07-01T07:00:39Z" level=info msg="datastore dialed" datastore=sqlite3 max_idle_connections=256 url="sqlite3:///app/data/fn.db"
time="2023-07-01T07:00:39Z" level=info msg="agent starting cfg={MinDockerVersion:17.10.0-ce ContainerLabelTag: DockerNetworks: DockerLoadFile: DisableUnprivilegedContainers:false FreezeIdle:50ms HotPoll:200ms HotLauncherTimeout:1h0m0s HotPullTimeout:10m0s HotStartTimeout:5s DetachedHeadRoom:6m0s MaxResponseSize:0 MaxHdrResponseSize:0 MaxLogSize:1048576 MaxTotalCPU:0 MaxTotalMemory:0 MaxFsSize:0 MaxPIDs:50 MaxOpenFiles:0xc42037b2d0 MaxLockedMemory:0xc42037b2d8 MaxPendingSignals:0xc42037b2e0 MaxMessageQueue:0xc42037b2e8 PreForkPoolSize:0 PreForkImage:busybox PreForkCmd:tail -f /dev/null PreForkUseOnce:0 PreForkNetworks: EnableNBResourceTracker:false MaxTmpFsInodes:0 DisableReadOnlyRootFs:false DisableDebugUserLogs:false IOFSEnableTmpfs:false EnableFDKDebugInfo:false IOFSAgentPath:/iofs IOFSMountRoot:/home/opc/.fn/iofs IOFSOpts: ImageCleanMaxSize:0 ImageCleanExemptTags: ImageEnableVolume:false}"
time="2023-07-01T07:00:39Z" level=info msg="no docker auths from config files found (this is fine)" error="open /root/.dockercfg: no such file or directory"
time="2023-07-01T07:00:40Z" level=info msg="available memory" cgroup_limit=9223372036854771712 head_room=1461135769 total_memory=14611357696
time="2023-07-01T07:00:40Z" level=info msg="ram reservations" avail_memory=13150221927
time="2023-07-01T07:00:40Z" level=info msg="available cpu" avail_cpu=2000 total_cpu=2000
time="2023-07-01T07:00:40Z" level=info msg="cpu reservations" cpu=2000
time="2023-07-01T07:00:40Z" level=info msg="\n        ______\n       / ____/___\n      / /_  / __ \\\n     / __/ / / / /\n    /_/   /_/ /_/\n"
time="2023-07-01T07:00:40Z" level=info msg="Fn serving on `:8080`" type=full version=0.3.750
```

Fn serverがコンテナで実行されていることがわかります。

```
$ docker ps 
CONTAINER ID   IMAGE                       COMMAND        CREATED          STATUS          PORTS                                                 NAMES
1c5c069f2dfc   fnproject/fnserver:latest   "./fnserver"   10 minutes ago   Up 10 minutes   2375/tcp, 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp   fnserver
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492530/303e4cb3-a53b-a897-0263-8a519af5559b.png)

# Functionの雛形作成
Goの雛形を作成します。引数の`go`を`java`や`python`などに変えれば、各言語の雛形が作成されます。

```
$ fn init --runtime go hello
Creating function at: ./hello
Function boilerplate generated.
func.yaml created.
```

作成された雛形を確認します。

```
$ tree hello/
hello/
|-- func.go
|-- func.yaml
`-- go.mod
$ cd hello/
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492530/aa97031a-6ce3-e812-f9e9-945182093e38.png)

各ファイルの中身を確認してみます。
詳しくはわからないのですが、Hello Worldを返すコードです。

```func.go
package main

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

        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),
        }
        log.Print("Inside Go Hello World function")
        json.NewEncoder(out).Encode(&msg)
}
```

Functionはコンテナで実行されますが、実装言語やBuildする時のベースイメージ、ENTRYPOINTなどを指定します。

```func.yaml
schema_version: 20180708
name: hello
version: 0.0.2
runtime: go
build_image: fnproject/go:1.15-dev
run_image: fnproject/go:1.15
entrypoint: ./func
```

Goモジュールのパスを書いておくファイルです。

```go.mod
module func

require github.com/fnproject/fdk-go v0.0.33
```

# アプリケーションの作成
複数のFunctionやその他の要素を管理するための単位としてアプリケーションを作成します。

```
$ fn create app myapp
Successfully created app:  myapp
$ fn list a
NAME    ID
myapp   01H484S9DMNG8G00GZJ0000001
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492530/06b79a4f-cc3b-e5aa-b3f4-4bfa18237517.png)

# Functionのデプロイ
作成したアプリケーションにFunctionをデプロイします。`--local`オプションはBuildしたコンテナイメージをリモートリポジトリにPushしないで、Localに保存するのみとするオプションです。

```
$ fn deploy --app myapp --local
Deploying hello to app: myapp
Bumped to version 0.0.2
Using Container engine docker
Building image hello:0.0.2 .......................
Updating function hello using image hello:0.0.2...
Successfully created function: hello with hello:0.0.2
```

Functionのイメージ（hello）が作成されています。

```
$ docker images
REPOSITORY           TAG       IMAGE ID       CREATED          SIZE
hello                0.0.2     736144c8de40   14 seconds ago   116MB
fnproject/fnserver   latest    49e4484aa70f   6 weeks ago      162MB
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492530/a83197a3-2b04-7a72-8d61-2386d3c087c2.png)

# Functionの実行
アプリケーション名、Function名を指定して、Functionを実行します。

```
$ fn invoke myapp hello
{"message":"Hello World"}
```

`Hello World`が表示されています。

Function実行直後に確認するとFunctionのコンテナが起動しています。

```
$ docker ps
CONTAINER ID   IMAGE                       COMMAND        CREATED          STATUS                  PORTS                                                 NAMES
f33c04e23747   hello:0.0.2                 "./func"       6 seconds ago    Up 6 seconds (Paused)                                                         01H4850XQ4NG8G00GZJ0000004
1c5c069f2dfc   fnproject/fnserver:latest   "./fnserver"   17 minutes ago   Up 17 minutes           2375/tcp, 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp   fnserver
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492530/ffa27f1e-89af-b34b-0c02-8461a80a24a3.png)


数秒後に再度確認すると、コンテナが削除されています。

```
$ docker ps
CONTAINER ID   IMAGE                       COMMAND        CREATED          STATUS          PORTS                                                 NAMES
1c5c069f2dfc   fnproject/fnserver:latest   "./fnserver"   17 minutes ago   Up 17 minutes   2375/tcp, 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp   fnserver
```

```
$ docker ps -a
CONTAINER ID   IMAGE                       COMMAND        CREATED          STATUS          PORTS                                                 NAMES
1c5c069f2dfc   fnproject/fnserver:latest   "./fnserver"   22 minutes ago   Up 22 minutes   2375/tcp, 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp   fnserver
```

Function（Fn project）のライフサイクルを辿ることで、アーキテクチャが理解できたような気がします。Kubernetesと似てる印象です。