OracleFunctionsを利用するための「開発・デプロイ環境構築」及び「デプロイ方法」

始めまして(Qiita初投稿)、ジーアールソリューションズの趙と申します。
この記事は[グロースエクスパートナーズ Advent Calendar 2020](https://qiita.com/advent-calendar/2020/gxp)の19日目です。

OracleCloudの資格を取得するため、OracleCloudのサービスを勉強しました。この間Oracle Functionsにも触っていました。インターネットで調べると、Oracle Cloud Shell を利用してFunctionsのデプロイ環境設定してデプロイする記事は沢山ありますが、OracleCloudのインスタンスを利用してFunctionsの開発・デプロイ環境構築の記事が少ないことに気づきました。
今回はOracleCloudのサーバーレスサービス(Functions)を利用したファンクションのデプロイ、およびOracleCloudのインスタンスを使った開発・デプロイ環境の構築を紹介します。

## OracleCloud Functionsとは
Oracle Functionsは、オープンソースFnProjectをベースとした Oracle Cloud Infrastructureに組み込まれている、フルマネージド、マルチテナント、高拡張性なオンデマンドのFunctions-as-a-Service(FaaS)プラットフォームです。
他のパブリック・クラウドにもファンクションが提供されており、AWS Lambdaは有名なファンクションサービスの一つです。比較するとAWS Lambdaで開発したファンクショはAWSの環境しか実行できないため、大きなロックイン性があります。でOracle FunctionsはFnProjectをベースに開発したファンクショサービスなので、簡単に移植することができます。

## Oracle Functionsデプロイ
###デプロイ流れ概要
①開発・デプロイ環境を構築します。
②ファンクションからDockerイメージを作成します。
③func.yamlファイルでファンクション定義します。
④指定したDockerレジストリにイメージをプッシュします。
⑤ファンクションのメタデータをFnサーバーにアップロードします。
⑥OracleCloudのコンソールにファンクションを追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/941566/d0bef8bf-a471-c566-39c8-8f681c16dfb7.png)
※図のソース：「docs.cloud.oracle.com」
###環境準備
Oracle Functionsを使用してファンクションを作成及びデプロイする前に、ファンクション開発のためのテナンシ設定する必要があります。

設定のタスクおよび手順のリンクは以下となります。
※本記事はOracleFunctionsで使用するグループとユーザーを存在する前提で記載します。

####Oracle FunctionsはVCN及びリポジトリを使用できるようにポリシーの作成
1.OracleCloudのコンソールにログインします。
2.ナビゲーション・メニューを開いて「アイデンティティ」⇒「ポリシー」をクリックします。
3.ルート・コンパートメントを選択して、「ポリシー作成」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/941566/9434acb3-a739-61e2-9101-a1c657a9c49c.png)
4.ポリシー作成画面で、ポリシーを入力して、「作成」ボタンをクリックします。
Oracle Functionsサービスは、Oracle Cloud Infrastructure Registryのリポジトリに格納されたファンクションのイメージに対する読取りアクセス権及びVNCの使用権限が必要なので、以下のポリシー・ルールの設定が必要です。

`Allow service FaaS to read repos in tenancy`
`Allow service FaaS to use virtual-network-family in tenancy`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/941566/fb74dc85-b35b-c9e5-5e87-36ae5492b792.png)

####Oracle Functionsで使用するVCN及びサブネットの作成
1.OracleCloudのコンソールにログインします。
2.ナビゲーション・メニューを開いて「ネットワーキング」⇒「仮想クラウド・ネットワーク」をクリックします。
3.Oracle Functionsを所有するコンパートメントを選択して、「VCNウィザードの起動」をクリックします。
4.「VCNウィザードの起動」の画面で、「インターネット接続性を持つVCN」を選択して、「VCNウィザードの起動」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/941566/0c7cb8e2-6890-7651-1656-0ffecf06243d.png)
5.「構成」画面でVCN名およびVCN CIDRブロックを入力して「次」ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/941566/68806e76-0f70-8313-20b9-4053b06d2052.png)
6.「確認及び作成」画面で設定した項目を確認して、「作成」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/941566/235fca3e-76b9-8c34-81c5-d040ae1c98da.png)
####Oracle Functionsの開発及びデプロイ用インスタンス作成
1.OracleCloudのコンソールにログインします。
2.ナビゲーション・メニューを開いて「コンピュート」⇒「インスタンス」をクリックします。
3.Oracle Functionsを所有するコンパートメントを選択して、「インスタンスの作成」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/941566/934e80be-df2d-5869-5dca-1b4eafa27a26.png)
4.インスタンスの作成画面で、「Oracle Cloud Developer Image」イメージを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/941566/16e7468d-ec9b-fbd3-f7a8-a7a7125d701f.png)
5.リモートからアクセスできるように、Oracle Functionsと同じVCNを選択し、「パブリック・サブネット」及び「パブリックIPv4アドレスの割当て」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/941566/bc68aebd-2973-84cc-7fdc-ed9af97503c1.png)
6.SSHキーとデータボリュームを設定して、「作成」ボタンをクリックしてインスタンスを作成します。
####インスタンスはFunctions及びリポジトリにアクセスできるように権限付与します。
1.新規作成したインスタンスの詳細画面を開いて、インスタンスのOCIDをコピーします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/941566/5b5a1f62-c365-f694-0b55-beadcf1e4878.png)
2.ナビゲーション・メニューを開いて「アイデンティティ」⇒「動的グループ」をクリックします。
3.「動的グループの作成」をクリックして、動的グループの作成画面を開きます。
4.動的グループの作成画面で、インスタンスのID及びコンパートメントのIDを入力します。
例：
`Any {instance.id = 'ocid1.instance.oc1.XXXXX', instance.compartment.id = 'ocid1.compartment.oc1..XXXX'}`
※コンパートメントのOCIDはコンパートメントの詳細画面で、コピーできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/941566/6da97759-6874-10ea-b898-dd28a6590728.png)

5.ナビゲーション・メニューを開いて「アイデンティティ」⇒「ポリシー」をクリックします。
6.ルート・コンパートメントを選択して、「ポリシー作成」をクリックします。
7.ポリシー作成画面で、ポリシーを入力して、「作成」ボタンをクリックします。
例：
`Allow dynamic-group Training-FaaS-DyGRP to manage functions-family in compartment <コンパートメント名>`
`Allow dynamic-group Training-FaaS-DyGRP to use virtual-network-family in compartment <コンパートメント名>`
`Allow dynamic-group Training-FaaS-DyGRP to read repos in tenancy`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/941566/a75f10f2-5081-9d75-4743-fc6296b4894e.png)

####Oracle Functionsを実施するOracleクラウドユーザーのAuth Token を取得
1.OracleCloudのコンソールにログインします。
2.ナビゲーション・メニューを開いて「アイデンティティ」⇒「ユーザー」をクリックします。
3.ユーザー一覧画面で、対象ユーザーをクリックして、ユーザーの詳細画面に遷移します。
4.ユーザー詳細画面で、「認証トークン」をクリックして、「認証トークンの作成」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/941566/b9ada034-9be6-35fe-b895-3c3623c28ba3.png)
5.説明を記載して、「トークンの生成」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/941566/c5c6e57d-7048-3e98-1946-69b982b0ac6e.png)
6.トークンの作成画面を表示して、トークンをコピーして、保存します。
この画面を閉じたら、トークンの再表示ができないため、トークンの再作成が必要！！

###２．Oracle Functionsの実行環境作成
####Oracle Functionsの開発環境作成
Oracle Functionsの開発環境を構成するには、以下３つの方法があります。
  ・クラウド・シェルの設定
  ・ローカル・マシンの設定
  ・Oracle Cloud Infrastructureコンピュート・インスタンスの設定

最も簡単な方法ではクラウド・シェルを利用して、Functionsの開発環境を構築します。本記事は「Oracle Cloud Infrastructureコンピュート・インスタンス」を使用してFunctionsの開発環境を構成します。

1.環境準備で作成したインスタンスにログインします。
2.Dockerのバージョンを確認します。

```
$ sudo docker version
Client: Docker Engine - Community
 Version:           18.09.8-ol
 API version:       1.39
 Go version:        go1.10.8
 Git commit:        76804b7
 Built:             Fri Sep 27 21:00:18 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          18.09.8-ol
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.8
  Git commit:       76804b7
  Built:            Fri Sep 27 20:54:00 2019
  OS/Arch:          linux/amd64
  Experimental:     false
  Default Registry: docker.io
```

デフォルトでは一般ユーザーがDockerを使用する際は、sudoで実施します。一般ユーザーでファンクションをデプロイする際に、Dockerの権限がないと、デプロイ失敗します。
一般ユーザーでDockerを使えるように、対象ユーザーはDockerグループに追加が必要！！
本記事はopcユーザーを使用するため、以下のように設定して、opcユーザーは再度ログインします。

```
$sudo usermod -g docker opc
$sudo /bin/systemctl restart docker.service

####opcユーザーでインスタンスに再度ログイン
$ docker info
Containers: 3
 Running: 0
 Paused: 0
 Stopped: 3
Images: 1
Server Version: 18.09.8-ol
(略)
```

3.FnProject CLIをインストールします。
OracleDevelopイメージはFnProject CLIを含まれていないため、インストールが必要です。

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

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

$ fn version
Client version is latest version: 0.6.1
Server version:  ?
```
4.Oracle Cloudのリポジトリにログインします。
リポジトリにログインする前に、Oracle Cloud ナビゲーション・メニューを開いて「管理」⇒「テナンシ詳細」の「オブジェクト・ストレージ・ネームスペース」の値を取得してコピーします。

```
$ docker login <region-key>.ocir.io
Username: <object-storage-namespace>/<username> 
Password:<user's AuthToken>
WARNING! Your password will be stored unencrypted in /home/opc/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded
```
「Login Succeeded」が表示されたら、Oracle Cloudのリポジトリにログイン成功！！

※<region-key>は3文字の英文であり、[各リージョンのリージョンキーはここで確認](https://docs.cloud.oracle.com/ja-jp/iaas/Content/Registry/Concepts/registryprerequisites.htm#regional-availability)できます。東京リージョンでは「nrt」となります。

5.FnProject CLIをセットアップ

```
####FN CLIのコンテキストをリストします。
$ fn list context
CURRENT NAME    PROVIDER        API URL                 REGISTRY
*       default default         http://localhost:8080

####新しいFN CLIのコンテキストを作成します。
######Oracle Cloudのインスタンで構築する場合、パラメータ「--provider oracle-ip」が必要
$ fn create context <context-name(任意)> --provider oracle-ip
Successfully created context: training-faas

####新規したコンテキストを使用することを指定します。
$ fn use context training-faas
Now using context: training-faas

####FN CLIのコンテキストを再度リストします。
$ fn list context
CURRENT NAME            PROVIDER        API URL                 REGISTRY
        default         default         http://localhost:8080
*       training-faas   oracle-ip

####コンパートメントIDとOracle Functions API URLでコンテキストを更新します
######コンパートメントの詳細画面でコンテキストのIDを取得できます。
$ fn update context oracle.compartment-id <対象コンパートメントのID>
Current context updated oracle.compartment-id with ocid1.compartment.oc1.XXXXX

######以下は東京リージョンのAPI-URLの例です。
$ fn update context api-url https://functions.ap-tokyo-1.oraclecloud.com
Current context updated api-url with https://functions.ap-tokyo-1.oraclecloud.com

####Oracle Functionsを使用するレジストリの場所でコンテキストを更新します
$ fn update context registry nrt.ocir.io/nretiuoewxik/<リポジトリ名(任意)>
Current context updated registry with nrt.ocir.io/<object-storage-namespace>/training-repo
```
ここまでで、Functionsの開発環境の構築が完了しました。この環境でFunctionsの作成、デプロイ、実行をできます。

####Oracle Functionsのアプリケーション作成
1.OracleCloudのコンソールにログインします。
2.ナビゲーション・メニューを開いて「開発者サービス」⇒「ファンクション」をクリックします。
3.Oracle Functionsを所要するコンパートメントを選択して、「アプリケーションの作成」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/941566/89f34f67-4849-3f80-3a71-fbaa8b02481b.png)
4.「アプリケーションの作成」画面でアプリケーションの名前、所属VCN及びサブネットを選択して「作成」ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/941566/fe059017-8e9b-ee0f-d84e-06872fe1e101.png)

####Oracle Functions作成
現時点でOracle Functionsは「go、java、node、python」をサポートします。
本記事はPythonを使ってファンクションの作成、デプロイ及び実行を行います。

1.ファンクションを初期化

```
$ fn init --runtime python <functions-name>
Creating function at: ./training-func1
Function boilerplate generated.
func.yaml created.

####ファンクションを初期化したら<functions-name>でフォルダを作成し、以下のファイルを作成されます。
$ ls -l <functions-name>
total 12
-rw-r--r--. 1 opc opc 576 Dec 15 10:03 func.py
-rw-r--r--. 1 opc opc 143 Dec 15 10:03 func.yaml
-rw-r--r--. 1 opc opc   3 Dec 15 10:03 requirements.txt
```

「func.yaml」はファンクションの定義ファイル
例：

```
schema_version: 20180708
name: training-func1
version: 0.0.1
runtime: python
entrypoint: /python/bin/fdk /function/func.py handler
memory: 256
```

「func.py」はファンクションのソースコードが記載されたファイルです。初期化した際に、「hello-world」のソースコードが作成されます。
※Fn project で Python を書く際に、必ず handler 関数を使用する必要があります。Oracle Functions は Cloud 上のイベントサービスによって作動し、handler 関数はイベント情報が書かれた JSON ファイルを読み取るために使用されます。
例：

```
import io
import json
import logging

from fdk import response


def handler(ctx, data: io.BytesIO = None):
    name = "World"
    try:
        body = json.loads(data.getvalue())
        name = body.get("name")
    except (Exception, ValueError) as ex:
        logging.getLogger().info('error parsing json payload: ' + str(ex))

    logging.getLogger().info("Inside Python Hello World function")
    return response.Response(
        ctx, response_data=json.dumps(
            {"message": "Hello {0}".format(name)}),
        headers={"Content-Type": "application/json"}
    )
```

「requirements.txt」はファンクションのソースコード内でインポートするパッケージを記述するファイルです。
アプリケーションをデプロイする際に記述されたパッケージが自動で Docker コンテナにインストールします。
例：

```
fdk
```

2.ファンクションをデプロイします。

```
$ cd <functions-name>
$ fn deploy --app <作成したアプリケーション名>
Deploying training-func1 to app: Training-FaaS-APP
Bumped to version 0.0.3
Building image nrt.ocir.io/<object-storage-namespace>/training-repo/training-func1:0.0.3 ..............................................................
Parts:  [nrt.ocir.io <object-storage-namespace> training-repo training-func1:0.0.3]
Pushing nrt.ocir.io/<object-storage-namespace>/training-repo/training-func1:0.0.3 to docker registry...The push refers to repository [nrt.ocir.io/<object-storage-namespace>/training-repo/training-func1]
f4688b2e9584: Pushed
8298236c4bc1: Pushed
2d864fd289b8: Pushed
77237016429d: Pushed
99c8d794f17e: Pushed
83adc987852c: Pushed
bb38c3f3fcc5: Pushed
9f72a1f4c21f: Pushed
6280b41d048d: Pushed
bd8e6688d36c: Pushed
07cab4339852: Pushed
0.0.3: digest: sha256:74bcc743d163202f5dcc0154bada714e908f94d6aca95e94bfe6cdbc1e58cbdc size: 2621
Updating function training-func1 using image nrt.ocir.io/<object-storage-namespace>/training-repo/training-func1:0.0.3...
Successfully created function: training-func1 with nrt.ocir.io/<object-storage-namespace>/training-repo/training-func1:0.0.3
```
Oracle Cloudをログインして、ナビゲーション・メニューを開いて「開発者サービス」⇒「コンテナ・レジストリ」をクリックします。
ファンクションがデプロイされたことを確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/941566/43929767-848d-dd8e-d398-bb6bce0416ac.png)

ナビゲーション・メニューを開いて「開発者サービス」⇒「コンテナ・レジストリ」をクリックし、作成したアプリケーションの詳細画面でデプロイしたファンクションが確認できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/941566/377b06ae-53c4-bf1b-7057-b8afbcf9fa62.png)

最後、ファンクションを実行してみよう！

```
$ fn invoke Training-FaaS-APP training-func1
{"message": "Hello World"}
```

##まとめ
Oracle Cloud のインスタンスでファンクションのデプロイ環境の構築及びデプロイサンプルを紹介しました。
Oracle FunctionsはAPIゲートウェイもしくはイベントと連動してバッチジョブの構成もできます。
ご興味がある方、試してみてください。

参考：
https://docs.cloud.oracle.com/ja-jp/iaas/Content/Functions/Concepts/functionsoverview.htm

以上