OCI Functionで郵便番号から住所を検索する

本ブログは、[オラクル・クラウドの個人ブログ](https://qiita.com/letian/items/10152a4fec813c1b7d98)の1つです。

# 初めに
OCI外部から、APIゲートウェイ経由でOCI Functionを呼び出し、オブジェクト・ストレージに格納されるファイルをアクセスできます。例えば、HTTPSリクエストで郵便番号から住所を検索するのは可能です。簡単なデモを紹介させていただきます。

このデモの目的は、OCI Functionが「API Gateway」及び「Object Storage」との連携を示すことで、サーバレスの特徴を少し感じていただければと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1121622/86830ddc-1fff-78b1-b250-789441e969c4.png)

__検証環境__

|分類|項目|内容|コメント|
|--|--|--|--|
|OCI Function|言語|Python|
||メモリ|<strong>512 GB</strong>|デフォルトの256 GBより引き上げた|
||タイムアウト|30 秒|デフォルトのまま|
|Object Storage|ファイルフォーマット|JSON|全日本の<strong>12万件</strong>の郵便番号は登録済|
||エンコード|UTF-8|改行: LFのみ (Unix)|
|呼出元|Cloud Shell|Linux|On-PのLinuxからでもOK|
||On-Pクライアント|Windows|コマンド・プロンプト、PowerShell|

※、この例で使用しているソースは、[ここ](https://github.com/liu-wei1/oci_function_zipcode)より取得できます。

__ステップ__
- [1. 事前準備](#1-事前準備)
  - [1-1. APIキーの追加](#1-1-apiキーの追加)
  - [1-2. OCI構成ファイルの作成](#1-2-oci構成ファイルの作成)
  - [1-3. 検索対象ファイルをオブジェクト・ストレージに保存](#1-3-検索対象ファイルをオブジェクトストレージに保存)
- [2. API GWとFunctionの作成](#2-api-gwとfunctionの作成)
- [3. Functionの呼び出し](#3-functionの呼び出し)
  - [3-1. Linuxクライアントからの呼び出し](#3-1-linuxクライアントからの呼び出し)
  - [3-2. Windowsクライアントからの呼び出し](#3-2-windowsクライアントからの呼び出し)
- [トラブル・シューティング](#トラブルシューティング)
- [サマリ](#サマリ)

# 1. 事前準備
## 1-1. APIキーの追加
オブジェクト・ストレージへアクセスするため、APIキーは必要です。追加方法は、以下の記事の[STEP 1-1](https://qiita.com/liu-wei/items/3f5593eab06c5885a83a#1-1-apiキーの追加)をご参照ください。既にお持ちの場合、このステップを飛ばしてもよいです。
[OCIカスタム・メトリックでディスク使用率を監視](https://qiita.com/liu-wei/items/3f5593eab06c5885a83a)

## 1-2. OCI構成ファイルの作成
APIキー作成後、ほかの認証情報と一緒に構成ファイルに格納します。作成方法は、上記の同記事の[STEP 1-2](https://qiita.com/liu-wei/items/3f5593eab06c5885a83a#1-2-oci構成ファイルの作成)をご参照ください。

ファイルの保存場所は、Functionを作成する端末です。
Linuxの場合(Cloud Shellも)：`/home/<your_user_name>/.oci/config`
Windowsの場合：`C:\Users\<your_user_name>\.oci\config`

## 1-3. 検索対象ファイルをオブジェクト・ストレージに保存
OCIのバケットに、対象ファイルをアップロードします。バケットの可視性(Visibility)は、<strong>プライベート</strong>でOKです。バケット名をメモし、Function作成時、固定値として使います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1121622/26de8587-b2c1-4088-1157-8558516f1710.png)

この例で使用しているファイルはJSONフォーマットで、イメージは以下のようです。
```json
{
  "japan": [
...<omitted>...
    {
      "zipcode": "1070061",
      "address1": "東京都",
      "address2": "港区",
      "address3": "北青山"
    },
...<omitted>...
  ]
}
```

# 2. API GWとFunctionの作成
OCI Functionを作成するのに、「Cloud Shell」を利用することは、OCIの推奨方法です。もちろん、ローカルの端末か、クラウド上のVMでも作成できます。その場合、OCI CLI、DockerとFn Projectのインストールと設定作業は必要です。

今回は「Cloud Shell」を例にして紹介します。作成方法の全体流れは、この記事（[API GWでOCI Functionsの呼び出し](https://qiita.com/liu-wei/items/37be0b66db040537448b)）をご参照ください。

↑この例は、"Hello World"の出力だけで、Object Storageの利用がないから、関連ポリシーを含めません。FunctionからObject Storageへアクセスするため、以下の動的グループとポリシーを追加してください。

|リソース|名前|内容|
|--|--|--|
|動的グループ|Func_DG|コンパートメント内のすべてのファンクションがリソースにアクセスできるようにする場合は、次のようなルールを追加：<br>`ALL {resource.type = 'fnfunc', resource.compartment.id = 'ocid1.compartment.oc1..aaaaaaaa23______smwa'}`|
|||特定のファンクションがリソースにアクセスできるようにする場合は、次のようなルールを追加：<br>`resource.id = 'ocid1.fnfunc.oc1.iad.aaaaaaaaacq______dnya'`|
|ポリシー|Func_DG_Policy|特定のオブジェクト・ストレージ・バケットに対する読取りおよび書込みができるようにするには、次のようなルールを追加:<br>`allow dynamic-group Func-DG to manage objects in compartment PoC where {target.bucket.name='<BucketName>'}`|

※「Cloud Shell」で作成する時、ほかの関連ポリシーについて、以下のドキュメントをご参照ください。
[Cloud ShellでのファンクションQuickStart - 4.グループおよびサービス用のポリシーの作成](https://docs.oracle.com/ja-jp/iaas/Content/Functions/Tasks/functionsquickstartcloudshell.htm#ariaid-title6)

[ここ](https://github.com/liu-wei1/oci_function_zipcode_python)からFunctionソース(３個)を取得して、初期化で作成された下記ソースを置き換えたら、Deployしてください。
`func.py`, `func.yaml`, `requirements.txt`

公式なOCIドキュメントを参照したい場合、[関数QuickStartガイド](https://docs.oracle.com/ja-jp/iaas/Content/Functions/Tasks/functionsquickstartguidestop.htm)をご覧ください。「Cloud Shell」、ローカル・ホスト及び「OCI Computeインスタンス」の選択肢がありますので、作成場所を選んでください。


# 3. Functionの呼び出し
## 3-1. Linuxクライアントからの呼び出し
以下のシェル・スクリプトを用意しております。
シェル・スクリプト名：`call_oci_func_zipcode.sh`
引数: 郵便番号（７桁数値）

シェルに、引数の有無チェックと数値チェックが入っています。チェックを通した後、入力された郵便番号をJSONフォーマットの文字列に変換し、CURLコマンドを実行します。
CURLコマンドの形式：`curl -k -X GET https://<Endpoint_URL> -d '{"zipcode": "1070061"}'`
出力結果のフォーマット：JSON形式

__初回目__
初回目は、コンテナの準備時間は必要であり、時間がかかりました。平均リスポンス時間は<strong>35秒以内</strong>でした。
```bash
liu_wei@cloudshell:shell (ap-tokyo-1)$ ./call_oci_func_zipcode.sh 1070061
---------------------------------------
CURL command:
curl -k -X GET https://<OCID>.apigateway.ap-tokyo-1.oci.customer-oci.com/v1/zipcode -d '{"zipcode": "1070061"}'

---------------------------------------
12:15:24.467  - Begin invoke function.
{"zipcode": "1070061","address1": "東京都","address2": "港区","address3": "北青山"}
12:15:55.224  - End.
---------------------------------------
liu_wei@cloudshell:shell (ap-tokyo-1)$ 
```

__二回目__
二回目から大分速くなり、およそ<strong>２～３秒</strong>ほどでした。
```bash
liu_wei@cloudshell:shell (ap-tokyo-1)$ ./call_oci_func_zipcode.sh 1070061
...<omitted>...
---------------------------------------
12:17:05.002  - Begin invoke function.
{"zipcode": "1070061","address1": "東京都","address2": "港区","address3": "北青山"}
12:17:07.383  - End.
---------------------------------------
liu_wei@cloudshell:shell (ap-tokyo-1)$ 
```

Functionがしばらくアイドルになったら、コンテナが自動的に削除されます。再度呼出の時、またコンテナの準備時間は必要です。これはFunctionの特徴です。

## 3-2. Windowsクライアントからの呼び出し
引数を渡す時、Linuxの書き方と少し差異がありますので、ご注意ください (`'\'`は必要)。

コマンド・プロンプトからの場合： (<font color=blue>ダブル</font>・クォーテーションで引数を囲む)
`curl -k -X GET https://<Endpoint_URL> -d "{\"zipcode\": \"1070061\"}"`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1121622/0b38aa9e-f1a4-713c-4cf5-43295e9e619e.png)

PowerShellからの場合： (<font color=blue>シングル</font>・クォーテーションで引数を囲む)
`curl.exe -k -X GET https://<Endpoint_URL> -d '{\"zipcode\": \"1070061\"}'`   (<font color=blue>curl.exe</font>はフルーネーム)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1121622/7334f47f-38bc-64ad-bf3a-4c972b7a8de5.png)

# トラブル・シューティング
API GWのエンドポイントを呼び出して、エラーが発生した場合、以下の対処方法をご参考ください。

## 問題-1「API GWに接続できない」
__エラーメッセージ__： `curl: Failed to connect to <API_Gateway_Hostname> 443 after xxxxxx ms: Couldn't connect to server`
<strong>API_Gateway_Hostname</strong>の例：`<OCID>.apigateway.<Region_ID>.oci.customer-oci.com`
__可能の原因__：API GWがアタッチされているサブネットは、外部からのアクセスが許可されていない。
__対策__：サブネットに紐づいているセキュリティ・リストを編集し、アクセス元のIPアドレスと宛先ポート（TCP 443）を追加する。

## 問題-2「オブジェクト・ストレージにアクセスできない」
__エラーメッセージ__：`{"Message": "Failed: Either the bucket named '<BucketName>' does not exist in the namespace '<tenancy_namespace>' or you are not authorized to access it"}`
__可能の原因__：関連ポリシーが漏れた。
__対策__：次のようにポリシーを追加する。
`allow dynamic-group Func-DG to manage objects in compartment PoC where {target.bucket.name='<BucketName>'}`

## 問題-3「内部サーバーエラー」
__エラーメッセージ__：`{"code":500,"message":"Internal Server Error"}`
__可能の原因__：関連ポリシーが漏れた。
__対策__：API GWからファンクションを呼び出せるように、次のポリシーを追加する。
`Allow dynamic-group <API-GW-DG> to use functions-family in compartment PoC`

# サマリ
今回のデモは、基本的に全部「[Always Free Tier](https://www.oracle.com/jp/cloud/free/)」でカバーできます。
- OCI Functionsの価格について、呼出回数と実行時間(GB-秒)により課金しますが、<strong>毎月無料枠</strong>（詳細は[こちら](https://www.oracle.com/jp/cloud/price-list.html#functions)へ）がありますので、このようなデモには全然余裕があります。
- Functionの作成は、<font color=blue>無料</font>の「<font color=blue>Cloud Shell</font>」を利用するのは<strong>推奨</strong>です。その理由は、Fn ProjectとDockerは既にインストール済で、環境設定の手間を省きます。
- VMを利用したい方は、「Always Free」のComputeシェープを指定していただければOKです。OCPUとメモリは比較的に低いけど、Functionの作成と呼出には問題ないです。
- オブジェクト・ストレージの無料利用枠は<font color=blue>10GB</font>です。今回のJSONファイルはMB単位で、心配がありません。

ご興味のある方は、ゼロコストで体験できますので、ぜひ試していただければと思います。


以上です。

__関連記事__
[オラクル・クラウドの個人ブログ一覧](https://qiita.com/liu-wei/items/10152a4fec813c1b7d98)
[API GWでOCI Functionsの呼び出し](https://qiita.com/liu-wei/items/37be0b66db040537448b)
[「OCI Functions」と「 AWS Lambda」の比較](https://qiita.com/liu-wei/items/519abc36c32eb40c461c)

----
__ドキュメント__
[ファンクションの実行による他のOracle Cloud Infrastructureリソースへのアクセス](https://docs.oracle.com/ja-jp/iaas/Content/Functions/Tasks/functionsaccessingociresources.htm)