Fn Projectを使ってPythonで書いたfunctionを動かしてみる

# 概要

このエントリでは、OSSのFaaSサーバである「Fn Project」を使い、Pythonの関数を動かすパターンを扱います。

下図のようなPythonによる掛け算の関数をFnのサーバにデプロイして動かします。

![calcfn_multiply.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65002/0820cfb3-ea98-b683-2204-8fcc26c8246f.png)


## 想定読者

- Fn ProjectでのPythonプログラムまだ自分で動かしていない方
- [「Fn Projectを使ってJavaScriptで書いたfunctionをNodeで動かしてみる」](https://qiita.com/hrkt/items/8dd1cd0e0e68e8575b6e)のPython版に興味がある方

# 準備

Fn Projectを動かすまでのところは、別エントリ[「OCIのMicro InstanceでCentOSにFn Projectのサーバをインストールしてみる」](https://qiita.com/hrkt/items/e37bab448c8aa1b00117)を参照ください。

このエントリでは、筆者はOCIのMicro Instanceの上で作業しています。

# Pythonでfunctionを動かす

作業の基本的な流れは、[Introduction to Fn with Python](https://fnproject.io/tutorials/python/intro/)に書いてあるものに従っています。

## Fn上のアプリ

別のエントリ[「Fn Projectを使ってJavaScriptで書いたfunctionをNodeで動かしてみる」](https://qiita.com/hrkt/items/8dd1cd0e0e68e8575b6e)で、「fn create app」で、fn上に「calc-fn-app」アプリを作ってあるものにFunctionを追加する形をとります。

## 登録

/multiplyという位置にPythonで関数を一つ作るため、「fn init」を実行します。

```console
$ fn init --runtime python --trigger http multiply
Creating function at: ./multiply
Function boilerplate generated.
func.yaml created.
```

下記のような3つのファイルができています。

```console
$ ls
func.py  func.yaml  requirements.txt
```

## コードを変更

下記のような、入力値のleftとrightを掛けて返すような関数に書き換えます。

```python
import io
import json
from decimal import *

from fdk import response


def handler(ctx, data: io.BytesIO=None):
    value = Decimal('NaN')
    try:
        body = json.loads(data.getvalue())
        left = Decimal(body.get('left'))
        right = Decimal(body.get('right'))
        value = left * right
    except (Exception, ValueError) as ex:
        print(str(ex))

    return response.Response(
        ctx, response_data=json.dumps(
            {"result": str(value)}),
        headers={"Content-Type": "application/json"}
    )
```

アプリデプロイします。「-w」で作業ディレクトリを指定して、登録しています。

```console
fn --verbose deploy --app calc-fn-app --local -w /home/opc/calc-fn-app/multiply
```

初回実行時には、以下のような流れとなるようです。

```console
$ fn --verbose deploy --app calc-fn-app --local -w /home/opc/calc-fn-app/multiply
Deploying multiply to app: calc-fn-app
Bumped to version 0.0.2
Building image fndemouser/multiply:0.0.2
FN_REGISTRY:  fndemouser
Current Context:  default
Sending build context to Docker daemon  6.144kB
Step 1/12 : FROM fnproject/python:3.6-dev as build-stage
3.6-dev: Pulling from fnproject/python
80369df48736: Pull complete
aaba0609d543: Pull complete
a97b990f94a5: Pull complete
af4a941e5376: Pull complete
709c35256bb6: Pull complete
3c3deb8445b4: Pull complete
Digest: sha256:8ac8c28a68fd0442b9ddcdf6a41f30230482d72d1024cafca06c9f1ac0bd821c
Status: Downloaded newer image for fnproject/python:3.6-dev
 ---> c5dbe9a0175b
Step 2/12 : WORKDIR /function
 ---> Running in f7aaa0e58f7a
Removing intermediate container f7aaa0e58f7a
 ---> 697f68e69e7c
Step 3/12 : ADD requirements.txt /function/
 ---> c4472f73a275
Step 4/12 : RUN pip3 install --target /python/  --no-cache --no-cache-dir -r requirements.txt &&                         rm -fr ~/.cache/pip /tmp* requirements.txt func.yaml Dockerfile .venv
 ---> Running in 4f758b74080b
Collecting fdk
  Downloading https://files.pythonhosted.org/packages/1d/b8/41b81bf76766f7e810627728647a8076626070a6e1d01a18a8ed16bd3d3f/fdk-0.1.12-py3-none-any.whl (46kB)
Collecting iso8601==0.1.12
  Downloading https://files.pythonhosted.org/packages/ef/57/7162609dab394d38bbc7077b7ba0a6f10fb09d8b7701ea56fa1edc0c4345/iso8601-0.1.12-py2.py3-none-any.whl
Collecting httptools>=0.0.10
  Downloading https://files.pythonhosted.org/packages/1b/03/215969db11abe8741e9c266a4cbe803a372bd86dd35fa0084c4df6d4bd00/httptools-0.0.13.tar.gz (104kB)
Collecting pbr!=2.1.0,>=2.0.0
  Downloading https://files.pythonhosted.org/packages/7a/db/a968fd7beb9fe06901c1841cb25c9ccb666ca1b9a19b114d1bbedf1126fc/pbr-5.4.4-py2.py3-none-any.whl (110kB)
Collecting pytest==4.0.1
  Downloading https://files.pythonhosted.org/packages/81/27/d4302e4e00497448081120f65029696070806bc8e649b83f644de006d710/pytest-4.0.1-py2.py3-none-any.whl (217kB)
Collecting pytest-asyncio==0.9.0
  Downloading https://files.pythonhosted.org/packages/33/7f/2ed9f460872ebcc62d30afad167673ca10df36ff56a6f6df2f1d3671adc8/pytest_asyncio-0.9.0-py3-none-any.whl
Collecting py>=1.5.0
  Downloading https://files.pythonhosted.org/packages/99/8d/21e1767c009211a62a8e3067280bfce76e89c9f876180308515942304d2d/py-1.8.1-py2.py3-none-any.whl (83kB)
Collecting pluggy>=0.7
  Downloading https://files.pythonhosted.org/packages/a0/28/85c7aa31b80d150b772fbe4a229487bc6644da9ccb7e427dd8cc60cb8a62/pluggy-0.13.1-py2.py3-none-any.whl
Collecting attrs>=17.4.0
  Downloading https://files.pythonhosted.org/packages/a2/db/4313ab3be961f7a763066401fb77f7748373b6094076ae2bda2806988af6/attrs-19.3.0-py2.py3-none-any.whl
Collecting setuptools
  Downloading https://files.pythonhosted.org/packages/a7/c5/6c1acea1b4ea88b86b03280f3fde1efa04fefecd4e7d2af13e602661cde4/setuptools-45.1.0-py3-none-any.whl (583kB)
Collecting more-itertools>=4.0.0
  Downloading https://files.pythonhosted.org/packages/72/96/4297306cc270eef1e3461da034a3bebe7c84eff052326b130824e98fc3fb/more_itertools-8.2.0-py3-none-any.whl (43kB)
Collecting six>=1.10.0
  Downloading https://files.pythonhosted.org/packages/65/eb/1f97cb97bfc2390a276969c6fae16075da282f5058082d4cb10c6c5c1dba/six-1.14.0-py2.py3-none-any.whl
Collecting atomicwrites>=1.0
  Downloading https://files.pythonhosted.org/packages/52/90/6155aa926f43f2b2a22b01be7241be3bfd1ceaf7d0b3267213e8127d41f4/atomicwrites-1.3.0-py2.py3-none-any.whl
Collecting importlib-metadata>=0.12; python_version < "3.8"
  Downloading https://files.pythonhosted.org/packages/8b/03/a00d504808808912751e64ccf414be53c29cad620e3de2421135fcae3025/importlib_metadata-1.5.0-py2.py3-none-any.whl
Collecting zipp>=0.5
  Downloading https://files.pythonhosted.org/packages/be/69/4ac28bf238f287f1677f41392e24d2c4ffafcf11648c23824f5f62ef6ccb/zipp-2.1.0-py3-none-any.whl
Building wheels for collected packages: httptools
  Building wheel for httptools (setup.py): started
  Building wheel for httptools (setup.py): finished with status 'done'
  Created wheel for httptools: filename=httptools-0.0.13-cp36-cp36m-linux_x86_64.whl size=217310 sha256=51eb19168c0639662416a8ad83b9df7d7ceb8eb14a58ce3bec5ffe3ab3b07a7f
  Stored in directory: /tmp/pip-ephem-wheel-cache-pwxrmfa5/wheels/e8/3e/2e/013f99b42efc25cf3589730cf380738e46b1e5edaf2f78d525
Successfully built httptools
Installing collected packages: iso8601, httptools, pbr, py, zipp, importlib-metadata, pluggy, attrs, setuptools, more-itertools, six, atomicwrites, pytest, pytest-asyncio, fdk
Successfully installed atomicwrites-1.3.0 attrs-19.3.0 fdk-0.1.12 httptools-0.0.13 importlib-metadata-1.5.0 iso8601-0.1.12 more-itertools-8.2.0 pbr-5.4.4 pluggy-0.13.1 py-1.8.1 pytest-4.0.1 pytest-asyncio-0.9.0 setuptools-45.1.0 six-1.14.0 zipp-2.1.0
WARNING: You are using pip version 19.3.1; however, version 20.0.2 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
Removing intermediate container 4f758b74080b
 ---> 5246c18d4e6f
Step 5/12 : ADD . /function/
 ---> c6181bb33bd1
Step 6/12 : RUN rm -fr /function/.pip_cache
 ---> Running in 695fe059e692
Removing intermediate container 695fe059e692
 ---> 5ea69eaafc3a
Step 7/12 : FROM fnproject/python:3.6
3.6: Pulling from fnproject/python
80369df48736: Already exists
aaba0609d543: Already exists
a97b990f94a5: Already exists
af4a941e5376: Already exists
709c35256bb6: Already exists
671870542c6c: Pull complete
936a6f40830a: Pull complete
Digest: sha256:3b438ba11405bba0f6e1e8d8819c9b8be38249da7253491f3cf9f7e5ed6c0ec6
Status: Downloaded newer image for fnproject/python:3.6
 ---> e8e10863d7cd
Step 8/12 : WORKDIR /function
 ---> Running in ddbc794f4bd9
Removing intermediate container ddbc794f4bd9
 ---> 4e3bed62a7f5
Step 9/12 : COPY --from=build-stage /python /python
 ---> b1c7d4fd7598
Step 10/12 : COPY --from=build-stage /function /function
 ---> 87446e069bd2
Step 11/12 : ENV PYTHONPATH=/function:/python
 ---> Running in af075fbcae2c
Removing intermediate container af075fbcae2c
 ---> 98a9f515e7e0
Step 12/12 : ENTRYPOINT ["/python/bin/fdk", "/function/func.py", "handler"]
 ---> Running in 555033ab1e55
Removing intermediate container 555033ab1e55
 ---> 9942a3d98858
Successfully built 9942a3d98858
Successfully tagged fndemouser/multiply:0.0.2

Updating function multiply using image fndemouser/multiply:0.0.2...
Successfully created function: multiply with fndemouser/multiply:0.0.2
Successfully created trigger: multiply
Trigger Endpoint: http://127.0.0.1:18080/t/calc-fn-app/multiply

```

fnのコマンドで確認します。(別エントリでplus,minusを作ってあります)

```console
$ fn list fn calc-fn-app
NAME            IMAGE                           ID
multiply        fndemouser/multiply:0.0.2       01E086F2D6NG8G00GZJ000001E
plus            fndemouser/plus:0.0.2           01E0393FDCNG8G00GZJ000000E
subtract        fndemouser/subtract:0.0.3       01E05KAKGENG8G00GZJ0000013
```

Dockerのコマンドで確認すると、対応するコンテナのイメージができていることがわかります。

```console
$ docker images | grep multiply
fndemouser/multiply   0.0.2               9942a3d98858        About a minute ago   175MB
```




## 実行

登録時の末尾にあったURLにcurlでアクセスしてみます。

```console
$ curl -d '{"left":"2", "right":"4"}' http://127.0.0.1:18080/t/calc-fn-app/multiply
{"result": "8"}
```

2*4の結果である8が返ってきています。

# おわりに

このエントリでは、Fn projectを使ってPythonで書いたfunctionを動かしてみることを扱いました。

このエントリで使用したコードは、[https://github.com/hrkt/calc-fn-app/releases/tag/0.0.3](https://github.com/hrkt/calc-fn-app/releases/tag/0.0.3)のタグに格納してあります。

## 補足：外部ライブラリ

このエントリでは、外部ライブラリを使いませんでしたが、requirements.txt中に記載することで、pipを利用できます。

## 補足：このエントリを書くにあたり

このエントリを書くにあたり、OCIのマイクロインスタンス上に下記の状況を作り、ノートPCからクラウド側にsshでリモートでつないで作業しました。

- sshの先にscreenで複数枚のウインドウ上げておいて作業して
- VS CodeをWEBで動かして「[cdr/code-serverとOCIのAlways FreeのMicroインスタンスでVS Codeを動かしてみる](https://qiita.com/hrkt/items/44c78f71eb72f9d29f38)」
- Fn projectのサーバもそこで動かして
- コードはGitHubに