OCI Functions+APIGateway最初の一歩

:::note
2024/3/23更新

Functionsをプライベートサブネットに置く場合はこちらの記事をどうぞ

[OCI Functions+APIGatewayやりなおし](https://qiita.com/daredeshow/items/16870ec0c4f6de14d25c)
:::

自分用メモ。 なんか最初の一歩ばっかやってるな・・・~~クッソタルい~~チュートリアルを最速で脱したい貴方へ・・・
# 1. 環境

- 手元の端末:
    - Ubuntu Desktop 23.10

ISOからインストール直後からスタート
# 2. 事前準備

## 2.1. 前提のパッケージをインストール

```shell
$ sudo apt update
$ sudo apt install curl docker.io

$ sudo usermod -aG docker [ユーザー名]
$ sudo chmod 666 /var/run/docker.sock
```

`docker ps` がエラーなしで実行できればOK

## 2.2. OCI CLIを入れる

[公式の手順(https://docs.oracle.com/ja-jp/iaas/Content/API/SDKDocs/cliinstall.htm#InstallingCLI__linux_and_unix)](https://docs.oracle.com/ja-jp/iaas/Content/API/SDKDocs/cliinstall.htm#InstallingCLI__linux_and_unix)

```shell
$ bash -c "$(curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh)"
```
とりあえずここでは入れるだけで設定はしない。あとでやる。

# 3. fnをインストール, fn serverを起動

[手順(https://fnproject.io/tutorials/install/)](https://fnproject.io/tutorials/install/)に従う

インストール

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

fn serverを起動

```shell
$ fn start
```
fn serverは起動したままにしておく

以降は新しい端末を開いてそこで操作する

# 4. ローカルでアプリケーションを作成、動作確認
## 4.1. fnアプリケーションを作成

[fn projectのチュートリアル(https://fnproject.io/tutorials/python/intro/)](https://fnproject.io/tutorials/python/intro/)に従ってサンプルアプリケーションを作る。

```shell
$ fn init --runtime python pythonfn
$ cd pythonfn
$ fn create app pythonapp
$ fn --verbose deploy --app pythonapp --local
```

## 4.2. 動作確認

ローカルで動作確認

```shell
# fnコマンドで動作確認
$ fn invoke pythonapp pythonfn
{"message": "Hello World"}
$ echo -n '{"name":"Bob"}' | fn invoke pythonapp pythonfn --content-type application/json
{"message": "Hello Bob"}

# curl用のエンドポイントを取得する
$ fn inspect function pythonapp pythonfn
{
	"annotations": {
		"fnproject.io/fn/invokeEndpoint": "http://localhost:8080/invoke/01HS3ET2PVNG8G00GZJ0000002"
	},
	"app_id": "01HS3ERMCWNG8G00GZJ0000001",
	"created_at": "2024-03-16T11:02:03.739Z",
	"id": "01HS3ET2PVNG8G00GZJ0000002",
	"idle_timeout": 30,
	"image": "pythonfn:0.0.2",
	"memory": 256,
	"name": "pythonfn",
	"timeout": 30,
	"updated_at": "2024-03-16T11:02:03.739Z"
}

# curlコマンドで動作確認
$ curl -XPOST -H "Content-Type: application/json" "http://localhost:8080/invoke/01HS3ET2PVNG8G00GZJ0000002"
{"message": "Hello World"}
$ curl -XPOST -H "Content-Type: application/json" -d '{"name":"Bob"}' "http://localhost:8080/invoke/01HS3ET2PVNG8G00GZJ0000002"
{"message": "Hello Bob"}
```

# 5. OCI側準備
## 5.1. 認証トークンの作成
[マネジメントコンソール](https://cloud.oracle.com)にログイン

[メニュー] - [Identity & Security] - [Domains] を選択

使用するドメイン(デフォルトはDefault)を選択

Usersをクリック

デプロイするユーザをクリック

左のメニューから "Auth Tokens" を選択

[Generate Token] をクリック
![Screenshot from 2024-03-16 20-32-58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/58bd4404-5a7b-1909-709f-e79d482078c3.png)

名前を指定して、 [Generate Token] をクリック
![Screenshot from 2024-03-16 20-34-41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/ed9021f1-37b2-853c-39d0-dae746b19d28.png)

![Screenshot from 2024-03-16 20-35-23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/08ac8ee1-0ddf-270e-09ed-ded6b78121f7.png)

作成されたトークンをコピーしてどこかにメモしておく。後で使用する。この画面を閉じると二度と取得できないので注意。なくしたら再作成すればいい。
![Screenshot from 2024-03-16 20-36-24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/99da5193-2fe6-c4b9-7a81-71052b43dbff.png)

## 5.2. VCN, パブリックサブネットの作成

:::note warn
~~パブリックAPIゲートウェイを経由してOCI Functionsを呼び出す場合、インターネットゲートウェイを経由してFunctionsを呼び出す必要があるため、プライベートサブネット上にFunctionsをデプロイすることはできない。([参照](https://docs.oracle.com/ja-jp/iaas/Content/APIGateway/Tasks/apigatewayusingfunctionsbackend.htm))~~
Functionsをプライベートサブネットに置くこともできる。こちらの記事をどうぞ

https://qiita.com/daredeshow/items/16870ec0c4f6de14d25c
:::

### 5.2.1. VCN, パブリックサブネットの作成

[メニュー] - [Networking] - [Virtual Cloud Networks] を選択

VCNを選択（なければ作る）
![Screenshot from 2024-03-16 20-54-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/698aa3ac-0d9b-b972-5ece-e95cd3045569.png)

すでにパブリックサブネットがあればそれを使ってもいい。作るときは [Create Subnet]
![Screenshot from 2024-03-16 20-55-24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/9af4986d-84b3-7ff2-e8c8-698a163624e8.png)

値を指定して [Create Subnet]
![Screenshot from 2024-03-16 20-57-23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/ddbeead9-5a23-b396-cb7c-e757562814e0.png)
![Screenshot from 2024-03-16 20-59-08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/6374cf74-3663-91e7-0bb0-33d59d0ab5d7.png)
![Screenshot from 2024-03-16 21-01-44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/d50e0844-25f4-0cc3-af1d-e43f64646ef5.png)

### 5.2.2. セキュリティリストの作成とパブリックサブネットへのアタッチ
左のメニューから "Security Lists" をクリック

[Create Security List]
![Screenshot from 2024-03-16 21-04-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/68adb726-12d6-d808-6641-f1eae4814a99.png)
![Screenshot from 2024-03-16 21-05-44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/004af52a-4ae6-de7f-5924-eff83755b04b.png)
![Screenshot from 2024-03-17 20-04-42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/4f484515-4c8a-83e2-9259-ba89477764b5.png)

左のメニューから "Subnets" をクリック


上で作成したサブネットを選択
![Screenshot from 2024-03-16 21-10-49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/06fb09ed-6229-d11b-366f-2f99928901f2.png)

[Add Security List] をクリック
![Screenshot from 2024-03-16 21-11-48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/7552197c-a67d-ed28-b64d-f6995c6c8b4b.png)

上で作成したセキュリティリストを選択して、[Add Security List]
![Screenshot from 2024-03-16 21-14-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/78feb382-4bd8-f353-3f10-0a6d309cbf0f.png)

要らないセキュリティリストは必要に応じて剥がしておく

## 5.4. コンテナレジストリの作成

[メニュー] - [Developer Services] - [Container Registry] を選択

[Create Repository] をクリック

値をセットして、 [Create] をクリック
![Screenshot from 2024-03-16 22-02-21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/1d6a32fa-512f-eaf2-aade-04991cc54376.png)

namespaceの値をメモしておく。後で使う
![Screenshot from 2024-03-16 22-03-53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/5106aac8-639c-7a24-2232-d5128c00def4.png)

## 5.5. OCI上のFunctionの作成
[マネジメントコンソール](https://cloud.oracle.com)を開く

[メニュー] - [Developer Services] - [Functions] を選択

[Create Application]をクリック
![Screenshot from 2024-03-17 10-09-21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/b8906f36-721a-249a-f5bc-0fc2f2fcde01.png)

値をセットして、[Create]をクリック
![Screenshot from 2024-03-17 10-10-34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/100d4f8f-7652-c4e5-34f2-bd8cab6d2e2f.png)

## 5.6. API Signing Keyを設定, OCI CLIの設定
[メニュー] - [Identity & Security] - [Domains] を選択

使用中のドメイン名を選択(今回の例では Default)

左側のメニューから "Users" を選択

Functionsをデプロイするユーザを選択

左側のメニューから "API Keys" を選択

[Add API Keys] をクリック
![Screenshot from 2024-03-17 10-48-54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/8dc85fb6-120d-20ba-b75a-d9c22c166f3c.png)

[Generate API Key] を選択し、[Download API Key] ボタンをクリックして秘密鍵をダウンロードする。(公開鍵はダウンロード不要)
![Screenshot from 2024-03-17 10-49-55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/f83d97ad-2e29-31d7-50fc-5d5abebe0adb.png)

[Add] をクリックするとOCI CLIの設定ガイドが表示される。"Copy"リンクをクリックして、内容をコピーする
![Screenshot from 2024-03-17 10-52-37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/392bd321-d335-dd7d-f3ff-531a268d9fd2.png)

コピーした内容を `/home/<ユーザ名>/.oci/config` ファイルとして保存する

ダウンロードした秘密鍵を `/home/<ユーザ名>/.oci/apikey.priv.pem` として保存する

先程作成した `/home/<ユーザ名>/.oci/config` ファイルを編集して、秘密鍵のパスを指定する
```text:/home/<ユーザ名>/.oci/config
[DEFAULT]
user=ocid1.user.oc1..aaaaaaaac4l3ej2qwae2bw74ajafmcos2ufkzx3xwpteilaxbnadbswfxpqq
fingerprint=5b:6c:b2:52:52:29:93:be:b6:dc:f8:18:fe:e0:59:33
tenancy=ocid1.tenancy.oc1..aaaaaaaakpgyb3s46vmuzkzyh5pk5yyax2hiyzl7b3itdv6gz7gdgoms5z4a
region=ap-tokyo-1
key_file=/home/oreore/.oci/apikey.priv.pem
```
各ファイルのパーミッションを修正する
```shell
chmod 600 /home/oreore/.oci/*
```

`oci iam user get --user-id <ユーザのOCID>` と打ってみて、ユーザ情報が取れればOK

```shell
oci iam user get --user-id <ユーザのOCID>
{
  "data": {
    "capabilities": {
      (省略)
    },
    "compartment-id": "compartmentのOCID",
    (そのほかいろいろ)
  },
  "etag": "ETagの内容"
}
```

# 6. アプリケーションをOCI上のコンテナレジストリ、OCI Functionsにデプロイ

:::note
【書式】
- `<context名>`: fn contextの名前。任意の値
- `<compartment-id>`: FunctionsがあるコンパートメントのOCID。ルートコンパートメントの場合はテナンシのOCIDになる
- `<region-id>`: リージョン識別子。東京リージョンの場合は `ap-tokyo-1`。[一覧はこちら(https://docs.oracle.com/ja-jp/iaas/Content/General/Concepts/regions.htm)](https://docs.oracle.com/ja-jp/iaas/Content/General/Concepts/regions.htm)
- `<region-key>`: リージョンキー。東京リージョンの場合は `nrt`。[一覧はこちら(https://docs.oracle.com/ja-jp/iaas/Content/General/Concepts/regions.htm)](https://docs.oracle.com/ja-jp/iaas/Content/General/Concepts/regions.htm)
- `<namespace>`: OCIRのnamespace名。手順5.4.でメモしたもの
- `<repository-name>`: OCIRのリポジトリ名。なんでもいいがローカルのコンテナ名と合わせたほうがわかりやすいと思う
- `<user-id>`: ユーザのログインID。メールアドレス
- `<application-name>`: Functions作成時に指定したアプリケーション名

```shell
cd ~/pythonfn
fn create context <context名> --provider oracle
fn use context <context名>
fn update context oracle.compartment-id <compartment-id>
fn update context api-url https://functions.<region-id>.oraclecloud.com
fn update context registry <region-key>.ocir.io/<namespace>/
docker login -u '<namespace>/<user-id>' <region-key>.ocir.io

# ここでOCI上のFunctionsに作成したアプリケーション名を確認する
$ fn list app

# アプリケーション名を指定してデプロイ
$ fn deploy --app <application-name>
```
:::

例えばこんな感じにやる
```shell
$ cd ~/pythonfn/
$ fn create context oci --provider oracle
$ fn use context oci
$ fn update context oracle.compartment-id ocid1.tenancy.oc1..aaaaaaaakpgyb3s46vmuzkzyh5pk5yyax2hiyzl7b3itdv6gz7gdgoms5z4a
$ fn update context api-url https://functions.ap-tokyo-1.oraclecloud.com
$ fn update context registry nrt.ocir.io/nrigsmd1mh6f/

$ docker login -u 'nrigsmd1mh6f/oreore@example.com' nrt.ocir.io
Password: (Auth tokenを入れる)
Login Succeeded

$ fn list app
NAME	ID											
fn	ocid1.fnapp.oc1.ap-tokyo-1.aaaaaaaad2vfqcqc3zpvrcnvqx4cezpunhdz2u3epn2ou4dtltx2lsi5e4rq

$ fn deploy --app fn
Deploying pythonfn to app: fn
Bumped to version 0.0.3
Using Container engine docker
Building image nrt.ocir.io/nrigsmd1mh6f/pythonfn:0.0.3 TargetedPlatform:  amd64HostPlatform:  amd64
....
Using Container engine  docker  to push
Pushing nrt.ocir.io/nrigsmd1mh6f/pythonfn:0.0.3 to docker registry...The push refers to repository [nrt.ocir.io/nrigsmd1mh6f/pythonfn]
7ed176f9b9e6: Pushed 
3d50e2e6cae1: Pushed 
3209481e9925: Pushed 
ac030d1fc3bc: Pushed 
03d5f9d77ddf: Pushed 
401c9daff908: Pushed 
7e9f3f6c7a0a: Pushed 
0.0.3: digest: sha256:2be9d60ff3daa2741d72732fc33ec53149ca200c3fbb5c34ab8da1ace703aa92 size: 1779
Updating function pythonfn using image nrt.ocir.io/nrigsmd1mh6f/pythonfn:0.0.3...
Successfully created function: pythonfn with nrt.ocir.io/nrigsmd1mh6f/pythonfn:0.0.3
```

動作確認は以下のように行う
```shell
# fnコマンドを使う場合
$ fn invoke fn pythonfn
{"message": "Hello World"}
$ echo -n '{"name":"Bob"}' | fn invoke fn pythonfn --content-type application/json
{"message": "Hello Bob"}

# curl用のエンドポイントを取得する
$ fn inspect function fn pythonfn
	"annotations": {
		"fnproject.io/fn/invokeEndpoint": "https://tx2lsi5e4rq.ap-tokyo-1.functions.oci.oraclecloud.com/20181201/functions/ocid1.fnfunc.oc1.ap-tokyo-1.aaaaaaaaop736j5edwurloncg2eortyvgvt3ov6sn6m4wmkjxj3jcevpvmda/actions/invoke",
		(略)
	},
	(そのほかいろいろ)
}

# curlコマンドで動作確認(アクセスできないのが正しい)
$ curl -XPOST -H "Content-Type: application/json" "https://tx2lsi5e4rq.ap-tokyo-1.functions.oci.oraclecloud.com/20181201/functions/ocid1.fnfunc.oc1.ap-tokyo-1.aaaaaaaaop736j5edwurloncg2eortyvgvt3ov6sn6m4wmkjxj3jcevpvmda/actions/invoke"
{"code":"NotAuthenticated","message":"Not authenticated"}
$ curl -XPOST -H "Content-Type: application/json" -d '{"name":"Bob"}' "https://tx2lsi5e4rq.ap-tokyo-1.functions.oci.oraclecloud.com/20181201/functions/ocid1.fnfunc.oc1.ap-tokyo-1.aaaaaaaaop736j5edwurloncg2eortyvgvt3ov6sn6m4wmkjxj3jcevpvmda/actions/invoke"
{"code":"NotAuthenticated","message":"Not authenticated"}
```

# 7. APIゲートウェイを作成する

## 7.1. APIゲートウェイを作成する

[マネジメントコンソール](https://cloud.oracle.com)を開く

[メニュー] - [Developer Services] - [API Management] を選択

[Create Gateway]をクリック
![Screenshot from 2024-03-17 20-25-48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/b47bced3-76dc-bef7-6d5d-21899313028b.png)

値をセットして、[Create Gateway]をクリック
![Screenshot from 2024-03-17 20-26-52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/8ef5386f-600b-0eca-477b-c7b84cbd5ca5.png)
![Screenshot from 2024-03-17 20-27-05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/8efd962c-7f3a-9b02-90cb-b103d24696df.png)

作成には2,3分ほどかかるので待つ

左のメニューから "Deployment" をクリック

[Create Deployment]をクリック
![Screenshot from 2024-03-17 20-31-15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/ac5c60df-8b7f-278f-f49c-374448f60cea.png)

値をセットして、[Next]をクリック
API request policies, API logging policies は変更しなくていい
![Screenshot from 2024-03-17 20-32-28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/ec805729-3d78-6d60-e05d-cbe66d42b667.png)
![Screenshot from 2024-03-17 20-33-57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/e1d774d7-33c9-3f90-4bb3-a333fcbb2068.png)
![Screenshot from 2024-03-17 20-34-17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/b2c45082-516c-b2f2-d344-7dc4c22ea476.png)

"No Authentication" を選択して、[Next]をクリック
![Screenshot from 2024-03-17 20-35-14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/a702dda5-e166-6455-0397-7a4c2f1c21cd.png)

値をセットして、[Next]をクリック
![Screenshot from 2024-03-17 20-38-55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/2cffce87-45c2-4dbb-e982-b2a9a376e841.png)

内容をレビューして、[Create]をクリック
完了までに2,3分ほどかかるので待つ

完了するとエンドポイントが出てくるのでメモする
![Screenshot from 2024-03-17 20-42-34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/a5e394b7-a9c7-3e69-1e3e-d8674a01042f.png)

エンドポイントのパスは
`https://<endpoint-hostname>/<path-prefix>/<deployment-path>` という形式になる
今回の場合はそれぞれ以下のようになる
- `<endpoint-hostname>`: `etwuensebazfriwi2yxygw6aka.apigateway.ap-tokyo-1.oci.customer-oci.com`
- `<path-prefix>`: `v1`
- `<deployment-path>`: `fn`

## 7.2. アクセスポリシーの作成
APIゲートウェイからFunctionsを呼び出すためのポリシーを作成する

[メニュー] - [Identity & Security] - [Policies] を選択

[Create Policy]をクリック
![Screenshot from 2024-03-17 21-05-24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/209d20e1-e786-2ae2-8c45-e86ea066972b.png)

値をセットして。[Create]をクリック
![Screenshot from 2024-03-17 21-06-47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52237/ee5b3fff-fb82-9bba-4e09-e43679209b38.png)

:::note
【書式】
- `<compartment-name>`: APIゲートウェイがあるコンパートメントの名前。Rootコンパートメントの場合は `tenancy`となる
- `<compartment-id>`: コンパートメントのOCID
```
ALLOW any-user to use functions in <compartment-name> where ALL {request.principal.type = 'ApiGateway', request.resource.compartment.id = '<compartment-id>'}
```
参照: [ファンクションへのAPIゲートウェイ・ユーザー・アクセスを提供するポリシーの作成](https://docs.oracle.com/ja-jp/iaas/Content/APIGateway/Tasks/apigatewaycreatingpolicies.htm#usersfunctionspolicy)
:::

例えばこんな感じになる
```text
ALLOW any-user to use functions-family in tenancy where ALL {request.principal.type= 'ApiGateway', request.resource.compartment.id = 'ocid1.tenancy.oc1..aaaaaaaakpgyb3s46vmuzkzyh5pk5yyax2hiyzl7b3itdv6gz7gdgoms5z4a'}
```

# 8. 動作確認

動作確認は以下のように行う
```shell
# curlコマンドで動作確認
$ curl -XPOST -H "Content-Type: application/json" "https://etwuensebazfriwi2yxygw6aka.apigateway.ap-tokyo-1.oci.customer-oci.com/v1/fn"
{"message": "Hello World"}
$ curl -XPOST -H "Content-Type: application/json" -d '{"name":"Bob"}' "https://etwuensebazfriwi2yxygw6aka.apigateway.ap-tokyo-1.oci.customer-oci.com/v1/fn"
{"message": "Hello Bob"}
```

# 9. そうじ
消すときは以下の順序で行う

APIGW-deployment -> APIGW -> Function-app(下のfunctionまで一気に消してくれる) -> Container Repository(下のイメージまで一気に消してくれる) -> Subnet -> Security List -> Auth Token, API Key

# 10. さいごに

おつかれさまでした。なんかもう、ひたすらめんどくさいッスね・・・もうちょっとなんとかならないものかしら・・・