IBM Cloud FunctionsをElixirで

# はじめに

※使用ライブラリを、`AWS Lambda`, `Azure Functions`, `IBM Cloud Functions`で使える共通ライブラリにしたので、記事を若干修正しました。（2021/07/07）

AWS Lambda、Azure Functionsに続き、今回、IBM Cloud FunctionsのアプリケーションをElixirで簡単に実装できるライブラリを作ってみたので、それを使ってIBM Cloud Functionsへのデプロイまでをやってみたいと思います。

ライブラリはこちらで公開
→　https://github.com/imahiro-t/faas_base
→　https://hex.pm/packages/faas_base

以前の投稿
→ [Azure FunctionsをElixirで](https://qiita.com/erin/items/e76fa2aa5fd9f4644579)
→ [ElixirでAWS LambdaでGithubActionsで継続デプロイ](https://qiita.com/erin/items/6d968c94fa7619b726f2)

# ElixirでIBM Cloud Functions

IBM Cloud Functionsは[Apache OpenWhisk](https://openwhisk.apache.org/)というサーバレス基盤のオープンソース（と言ってもIBMが主体となって進めていたもの）に準拠していて、現時点でElixir用のライブラリは用意されてはいませんが、それ以外の言語であっても、CLIアプリケーションのようなバイナリを用意してあげることでDockerイメージとして動作させることができるようです。

この辺りについては作成したライブラリ側で吸収しているので、今回はこの辺りをあまり意識せずにデプロイまでやっていくことになります。

# やってみる

## IBM Cloud CLIのセットアップ

IBM Cloudをターミナルからデプロイしたりできるツールです。[こちら](https://cloud.ibm.com/docs/cli?topic=cli-install-ibmcloud-cli)の手順に従ってセットアップします。

続いて、下記コマンドでCloud Functions CLIプラグインのセットアップを行います。

```bash
$ ibmcloud plugin install cloud-functions
```

## IBM Cloud Functions環境の作成

下記コマンドでIBM Cloud Functionsをデプロイする名前空間を作成・設定します。

```bash
$ ibmcloud fn namespace create my_namespace
$ ibmcloud fn property set --namespace my_namespace
```

## アプリケーション作成

まずは`mix new upcase`とプロジェクトを作成し、`mix.exs`にIBM Cloud Functions用のライブラリを追加します。

```elixir
  defp deps do
    [
      {:faas_base, "~> 1.0.2"}
    ]
  end
```

```bash
$ mix deps.get
```

次に`upcase.ex`に下記のようにハンドラを実装します。（サンプルとして小文字を大文字にするだけの簡単な関数アプリです）

```elixir
defmodule Upcase do
  use FaasBase, service: :ibm
  alias FaasBase.Logger
  alias FaasBase.Ibm.Request
  alias FaasBase.Ibm.Response
  @impl FaasBase
  def init(context) do
    {:ok, context}
  end
  @impl FaasBase
  def handle(%Request{body: body} = request, event, context) do
    Logger.info(request)
    Logger.info(event)
    Logger.info(context)
    {:ok, Response.to_response(body |> String.upcase, %{}, 200)}
  end
end
```

IBM Cloud Functionsのランタイムからはリクエスト情報が格納された`event`と環境変数等の情報が格納された`context`がMap型で渡ってきます。

第一引数の`request`には`event`情報の`body`やら`headers`やら`method`やらを扱いやすいように展開して格納しています。

戻り値は{:ok, String.t}か{:error, String.t}のように文字列のみを返すか、{:ok, FaasBase.Ibm.Response.t}か{:error, FaasBase.Ibm.Response.t}のようにヘッダー情報やステータスコードを含めた情報を返す必要があります。

試しにコンソールでハンドラの動作確認をしてみます。

```bash
$ iex -S mix
```

```bash
iex> alias FaasBase.Ibm.Request
iex> Upcase.handle(%Request{body: %{"data" => "hello"}}, %{}, %{})
{:ok,
 %FaasBase.Ibm.Response{
   body: "{\"DATA\":\"HELLO\"}",
   headers: %{},
   status_code: 200
 }}
```

問題なさげです。

## デプロイ

IBM Cloud Functionsの実行環境はLinuxになるので、ビルドは、Linux上で行う必要があります。ライブラリには、ビルド用のmixタスクが用意されていて、こちらを実行すると、ビルドとともに、にデプロイに必要なzipファイルが作られます。

```bash
$ handle_module=Upcase
$ mkdir -p _build
$ docker run -d -it --rm --name elx erintheblack/elixir-ibm-functions-builder:1.10.4
$ docker cp lib elx:/tmp
$ docker cp mix.exs elx:/tmp
$ docker exec elx /bin/sh -c "mix deps.get; MIX_ENV=prod mix ibm.release ${handle_module}"
$ docker cp elx:/tmp/_ibm ./_build
$ docker stop elx
```

下記コマンドでWEBアクションとしてデプロイします。

```bash
$ cd ./_build/_azure
$ ibmcloud fn action create upcase upcase-0.1.0.zip --native --web true --param LOG_LEVEL info
```

エンドポイントは下記コマンドで取得できます。

```bash
$ ibmcloud fn action get upcase --url

ok: got action upcase
https://us-south.functions.appdomain.cloud/api/v1/web/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/default/upcase
```

## 動作確認

curlで叩いて正常に動作することを確認します。

```bash
$ curl -H "Content-Type: application/json" https://us-south.functions.appdomain.cloud/api/v1/web/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/default/upcase -d '{"data": "hello"}'

{"DATA":"HELLO"}I
```

# さいごに

FaaSのElixirのライブラリ作成は今回で3つ目ですが、毎回異なるアプローチでカスタム言語に対応した基盤が作られている感じがします。

今回特徴的だったのが、毎回出力して終了するCLIのようなアプリを作成するというもので、Azure Functionsと同様に簡単にWEBからのリクエストが受けられるとは言え、Elixirで書けるからという理由だけで選択するには、かなりシチュエーションを選ぶ気がします。

また、現状では環境変数がこちらから設定できないみたいで、デプロイ時にパラメータとしての設定は可能ですが、リクエスト時にPOSTデータと混在して渡されるなど、使い勝手も。。。な感じでした。

デプロイ時のフラグに`--web-secure true`を付けるだけで、リクエストヘッダーにセキュアな値設定を必須にできるなど、痒いところに手が届く機能もあるようなので、オープンソースを活かし今後どんどん使い勝手が良くなっていくかもです。

残るはIBMじゃない方のCloud Functionsですが、こちらはまだカスタム言語に対応していないみたいです。対応予定はあるのかな。。