Code EngineのFunctionsで非同期処理を実行する

# はじめに
[前回紹介した記事](https://qiita.com/katahiro/items/5ab7d16710da3ba8faa0)では、IBM CloudのCode Engineで利用可能なFunctionsのサービスについてお話しましたが、そこで記載したように、Functionsの設定画面で直接書けるNode.jsの `main()` コードは `async` 付きで**呼び出されない**ので、その内部の非同期処理を書こうとすると Call Back地獄に陥ります。

今回の記事では、Functionsで非同期処理を実行する場合、どのような手続きが必要かを紹介します。

# そもそものお話
Code EngineのFunctionsでは、実行するコードをポータルで直接記述することが可能ですが、非同期処理を行うコードを実行する場合、そのコードをUploadしなければなりません。
ソースコードのUploadはIBM CloudのCLIを通じて行いますが、Code Engineのサービスにそのコードを保管するのではなく、別のContainer Registryのサービスにそのコードを保管した上で、Code Engine上のランタイムで動作させる仕組みを取ります。なので、Code Engineのサービスで完結しない点はご注意ください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/fc10f5b8-d48f-eba7-3998-38877f15adf6.png)

このContainer Registryにコードを保管する部分は自動的に行われ、仮にContainer Registryのサービスを作成していない場合は自動で作成されます。
Container Registryは本来コンテナイメージを保管するサービスのため、コンテナ化された状態で保管されて、その容量分課金されると思われるかもしれませんが、その点は安心してください。

次のキャプチャは、Code EngineのFunctionsでソースコードをUploadした際にContainer Registryに保管された情報になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/188c0a2b-f2b4-4afc-7fc2-37218b9e6cd2.png)
突っ込みどころはいくつかあるのですが、利用している容量が800Bとなっています。一般的にコンテナイメージだと小さいもので数十MB、数百MBになることが普通であることを考えると、容量はソースコード分だけ計算されている形になります。
厳密には実行したいソースコードの `main()`のみUploadすると、それを実行するための `package.json` が自動生成されて、その分の容量が計算されている点には注意が必要です。
Container RegistryはStandardプランだと500MBまでの容量は無料なので、ちょっと使うぐらいでは課金されないかなと思います。古いものはどんどん削除していけば良いですしね。

Container Registryに保管される際に、そのイメージ名として `ce--` + `Code Engineのインスタンスに割り当てられるサブドメイン名` + `/function-` + `作成したfunction名` といったイメージ名が生成されます。
ラベルには保管された日付と時刻が入るので、同じFunctionに複数回コードをUploadし直した場合は、このラベルでどれが最新かを判断する必要があります。

イマイチなところ以下の2つですね。いつか改善されるのかな。。。
- ~~ポータル上で登録された日付が `44,870days ago` といった日付になる。つまり1900年1月1日扱いになってしまい、先ほどのラベルを見ないといつ登録したものかわからなくなる~~
- `Unsupported OS` の表示があり、アラートが上がっているようで嫌。本来Container Registryは登録されたコンテナイメージのScan機能があり、問題があればアラートを表示してくれるのですが、それが常時表示されるような形になっている
:::note info
2023/12/15追記
前者は2023年12月中頃に修正され、Buildしたタイミングの日付がContainer Registoryに登録されるようになりました。
これにより後述するRetension Policyが正常に機能するようになります。
:::

# 試しに登録してみよう
あまり長くないコードで動かせばわかる非同期処理として何をしようか考えましたが、利用しているSlackの特定のチャンネルにWebhookでポストするコードにしました。
※本来WebhookのURLは環境変数で外だしするのですが、今回は簡略化しています

```javascript:main.js
const main = async (params) => {
  const slackWebhookUrl = "https://hooks.slack.com/services/Txxxxxxxx/xxxxxxxxxx/xxxxxxxxxxxxxxxxxxx";
  const message = {
    text: params.text || "test message from code engine function"
  }
  const option = {
    method: "POST",
    headers: { 
      'Content-Type': 'application/json',
      'Accept': 'application/json',
    },
    body: JSON.stringify(message)
  }
  try{
    const fetchResult = await fetch(slackWebhookUrl, option);
    const fetchText = await fetchResult.text();
    if(fetchText === "ok"){
      return {
        statusCode: 200,
        headers: { 'Content-Type': 'application/json' },
        body: params
      };
    }
  }catch(err){
    console.log(err);
    return {
      statusCode: 500,
      headers: { 'Content-Type': 'application/json' },
      body: {message:"catch error"}
    };
  }
}

module.exports = {
  main,
}
```
これでコードが出来たので、このコードをCode EngineのFunctionsに登録します。
これを行うにはIBM CloudのCLIがインストールされていることが前提になります。

まだ導入していないよ、という方は下記のマニュアルを参考に事前に導入をお願いします。
- [IBM Cloud CLI の概説](https://cloud.ibm.com/docs/cli?topic=cli-getting-started)：CLIのインストール手順
- [IBM Cloud CLI リポジトリーからのプラグインのインストール](https://cloud.ibm.com/docs/cli?topic=cli-plug-ins#install-from-repo)：Code Engine(ce)のプラグインを導入してください

それではCode EngineにFunctionsとしてこのコードを登録してみましょう。前提として以下を満たしてください。
- ターミナル(プロンプト)で事前にIBM Cloudにログインしている
- `ibmcloud target -r [jp-tok/jp-osa] -g Default`といったように、Code Engineが存在しているリージョン、またCode Engineに紐づくリソースグループを選択している状態になっている
- `ibmcloud ce project select --name [Code Engineのインスタンス名]`といったように、操作対象となるCode Engineのプロジェクトを選択した状態になっている
- 先ほど作成した `main.js` が存在するディレクトリ(フォルダ)にカレントディレクトリを移動している

```
ibmcloud ce fn create --name qiita-functions --runtime nodejs-18 --cpu 0.25 --memory 1G --build-source main.js
```
:::note warn
Code EngineのFunctionで指定できるリソースサイズは[ドキュメントに従う](https://cloud.ibm.com/docs/codeengine?topic=codeengine-fun-runtime#fun-supported-combo)と以下の3種類のみになります。CLIで作成する際に何も指定しない場合はデフォルトのサイズになります。
- 0.25 vCPU および 1 GB メモリー
- 0.5 vCPU および 2 GB メモリー (デフォルト)
- 1 vCPU および 4 GB のメモリー

ちなみに、2023年10月の初旬だともっと小さいサイズで作れたけど、仕様が変わった模様。つらたん。
:::

```console
C:\node-apps\function-test>ibmcloud ce fn create --name qiita-functions --runtime nodejs-18 --cpu 0.25 --memory 1G --build-source main.js
ビルド・プッシュ用に関数「qiita-functions」を準備しています ...
機能「qiita-functions」を作成しています...
パス「main.js」からアップロードするファイルのパッケージ化...
ビルド実行 'qiita-functions-run-231107-132823481'を送信中...
イメージ 'private.jp2.icr.io/ce--xxxxx-xxxxxxxx/function-qiita-functions:231107-0428-g8tzg' を作成しています ...
ビルド実行が完了するのを待機しています...
ビルド実行状況: '実行中'
ビルド実行が正常に完了しました。
ビルド実行の状況を確認するには、'ibmcloud ce buildrun get -n qiita-functions-run-231107-132823481' を実行してください。
関数「qiita-functions」が作動可能になるのを待っています ...
関数「qiita-functions」は作動可能です。
OK
詳細を表示するには、'ibmcloud ce function get -n qiita-functions' を実行してください。

https://qiita-functions.xxxxxxxx.jp-osa.codeengine.appdomain.cloud

C:\node-apps\function-test>
```
公開できない部分は隠しましたが、コマンドの出力内容を解釈すると以下の通りです。
- `qiita-functions`の名称でFunctionsを作成するよ
- 指定された `main.js` からビルドするためのパッケージを作成するよ
- イメージとして `private.jp2.icr.io/ce--xxxxx-xxxxxxxx/function-qiita-functions:231107-0428-g8tzg` に保存したよ
- `qiita-functions`は無事に作成できました
- アクセス先のURLは `https://qiita-functions.xxxxxxxx.jp-osa.codeengine.appdomain.cloud` です

Container Registryを確認すると、確かに今回作成したFunctionsのコードが登録されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/5858e4a3-0cc7-6469-5d68-695c57bfae99.png)

注意として、Container Registryに登録されるイメージに付与されるタグにある日付情報は`UTC`である、ということです。
コンソールに出力されたビルド情報にあるように、コマンドを実行したのは、2023年11月7日13時28分ごろです。
```console
ビルド実行 'qiita-functions-run-231107-132823481'を送信中...
```
一方、Container Registryのタグは `231107-0428-g8tzg` と2023年11月7日4時28分となっています。タグで検索するときはこの時差を認識しておかないと、日本時間より古い時間だから古いイメージだと思って消すと、誤って最新のものを消してしまう可能性があります。
```console
イメージ 'private.jp2.icr.io/ce--xxxxx-xxxxxxxx/function-qiita-functions:231107-0428-g8tzg' を作成しています ...
```

:::note info
Container Registryには[Retention Policy](https://cloud.ibm.com/docs/Registry?topic=Registry-registry_retention)という条件に合致したイメージだけ保管する、という機能があります。こちらを利用して、Code EngineでFunctionsを作成する際に自動で作成されたNamespaceにイメージの保管を1世代にするよう設定すれば、わざわざ古いコードを削除する、という手間は発生せずに済みます
:::

# 試しに動かしてみよう
普段vscodeの[Rest Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client)を普段使っているので、そちらのキャプチャになります。
作成したFunctionにGET要求を投げましたが、無事に応答が返ってきていますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/64e8065e-c8f9-cfed-7f4b-298805baaaab.png)
また、Slackにも意図した通りWebhookでメッセージが投稿されていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/3bdcb515-2b56-2bd7-c8f7-bd144766dc49.png)

動きとしては問題無さそうですね。
では、URLに `text` のパラメータを指定した場合の動作も見てみましょう。
レスポンスを見る限り正しくパラメータを受け取れてますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/313fb172-19ed-3129-429f-4e87f0d1911b.png)
Slackにも意図した通り書き込まれました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651598/d20360c9-6cb4-1941-fadf-5a70b509ce81.png)
# さいごに
いかがでしたでしょうか？Code EngineのFunctionsで非同期処理を動かすためにはひと手間必要になりますが、CLIに慣れれば、vscodeといったコードエディタでコードを記述した後に、そのままターミナルでIBM Cloudにログインし、コマンド実行してコードを適用する、という流れは、慣れてしまえば手早いものです。

次回はあまり内容が大きく変わるわけではないですが、素のNode.js 18だけではなく、モジュールをインストールして動作させたい場合にどうするのか、またこのFunctionsのコードを作成する際にどういったコードを用意しておくと便利そうか、といった情報を合わせて紹介したいと思います。

次回記事(2023/11/8)

https://qiita.com/katahiro/items/7dd1eda7da0751242284