IBM Cloud Functionsで外部ライブラリを使いたい


IBM Cloud Functionsで標準対応している言語には、デフォルトで様々なライブラリが提供されています。ただ、デフォルトにはないライブラリを使って開発をするにはコマンドラインでの開発が必須になります。また、JavaはGUIの開発環境がないのでコマンドラインでの開発が必須になります。というわけで今回はPythonを例にコマンドラインでCloud Functionsの開発をしたときに詰まったことをまとめます。なお、今回は予めdockerをインストールしている前提で話を進めます。（他の言語で開発する場合も適宣読み替えていただくと開発できるかと思います。）

# まずはドキュメント通りに作ってみる
今回はpythonのLINE bot SDKを入れていきたいと思います。[こちら](https://cloud.ibm.com/docs/openwhisk?topic=cloud-functions-actions)のドキュメントを参考にして,```requirements.txt```の中身を以下の通り作成します。

```text
# Requirements.txt contains a list of dependencies for the Python Application #

# Setup modules
gevent == 1.4.0
flask == 1.0.2

# default available packages for python3action
beautifulsoup4 == 4.8.0
httplib2 == 0.13.0
kafka_python == 1.4.6
lxml == 4.3.4
python-dateutil == 2.8.0
requests == 2.22.0
scrapy == 1.6.0
simplejson == 3.16.0
virtualenv == 16.7.1
twisted == 19.7.0
PyJWT == 1.7.1

# packages for numerics
numpy == 1.16.4
scikit-learn == 0.20.3
scipy == 1.2.1
pandas == 0.24.2

# packages for image processing
Pillow == 6.2.1

# IBM specific python modules
ibm_db == 3.0.1
cloudant == 2.12.0
watson-developer-cloud == 2.8.1
ibm-cos-sdk == 2.5.1
ibmcloudsql == 0.2.23

# Compose Libs
psycopg2 == 2.8.2
pymongo == 3.8.0
redis == 3.2.1
pika == 1.0.1
elasticsearch == 6.3.1
cassandra-driver == 3.18.0
etcd3 == 0.10.0

#Additional modules
line-bot-sdk
```

テキストファイルを作成したところでdockerコマンドでFunctionsの動作環境を取得し、Pythonの仮想環境のパッケージを作成します。

```console
$ docker pull ibmfunctions/action-python-v3.7
$ docker run --rm -v "$PWD:/tmp" ibmfunctions/action-python-v3.7 bash -c "cd /tmp && virtualenv virtualenv && source virtualenv/bin/activate && pip install -r requirements.txt"
```

デプロイしたときに動かすコード(```__main__.py```)を以下に掲載します。

```python:__main__.py
def main(args):
    return {"result":"OK!"}
```
先ほど作成したパッケージとコードをzipに圧縮して、コマンドラインからアクションを作成します。なお、IBM Cloudコマンドでログインをしている状態で続けます。

```console
$ zip -r hellobot.zip virtualenv __main__.py
$ ibmcloud fn action create hellobot hellobot.zip --kind python:3.7
```

すると、実行結果が以下に出力されました。

```console
error: Unable to create action 'my-action-name': The connection failed, or timed out. (HTTP status code 413)
```

おや？タイポはしてないのにどうしてこんなエラーが出るのでしょう？

## 原因を調べてみた
このエラーの原因を調べてみたらStackoverflowでこんな記事を見つけました。
[IBM Cloud functions - Unable to create an action](https://stackoverflow.com/questions/51023636/ibm-cloud-functions-unable-to-create-an-action)
IBM Cloud Functionsの責任者らしき人の回答によると、これはコマンドライン側のバグのようで、このやり方ではアクションを作成できないようです。（それ、ドキュメントに書いてよー）

# Docker Hubを使ってアクションを作成する（忙しい方はここから読んでください）
先程のStackoverflowの中にはバグだから使えないよってことを言っているだけではなく、代替の解決策も提示してくれたので、今度はそっちで試して行きたいと思います。[こちら](http://jamesthom.as/blog/2017/08/04/large-applications-on-openwhisk/)を参考にして自前で仮想環境を構築してDocker Hubにアップしてからアクションを作成します。この方法はDockerの操作に慣れている方なら簡単な操作ですが、触ったことない方には分かりにくいかもしれません。
## 実行環境を作成する
まずはアクションの実行環境のDockerfileを用意します。

```Dockerfile
FROM openwhisk/actionloop-python-v3.7:36721d6

COPY requirements.txt requirements.txt

RUN pip install --upgrade pip setuptools six && pip install --no-cache-dir -r requirements.txt

```
Dockerfileからお分かりいただけるかと思いますがこの中に出てくる```requirements.txt```は先程作成したものをそのまま使うので中身は省略します。ここまで必要なファイルが揃ったので、以下のコマンドを実行し、イメージを作成します。完成に結構時間がかかります。

```console
$ docker build -t linebot_function .
```
作成したイメージをDocker Hubにアップロードをします。Docker Hubに登録してない方は、[こちら](https://hub.docker.com/signup?next=%2F%3Fref%3Dlogin)から登録します。登録したら、DockerのGUI画面からDocker Hubにサインインをします。サインインできたら以下のコマンドを使用して、作成したイメージにタグを付けてアップロードをします。

```console
$ docker tag linebot_function YOUR_USER_NAME/linebot_function
$ docker push YOUR_USER_NAME/linebot_function
```

##アクションを作成する
それでは実行環境を用意できたので、今度は動かすためのコードを用意します。今回は外部ライブラリとして、LINE botのPythonSDKを入れたのでそれがちゃんと入っているかを確認します。そこで以下のコードを用意します。

```python:first-linebot.py
import linebot

def main(args):
    return {"LINEbot":linebot.__version__}

```

コードを用意したら、アクションを作成していきます。まずは、IBM Cloud Functionsのプラグインをインストールしていきます。

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

以下のコマンドでリソースグループをターゲットします。グループ名に関しては[こちら](https://cloud.ibm.com/functions/learn/cli)からログインして確認します。

```console
$ ibmcloud target -o <org> -s <space> 
```


コードを用意できたので、以下のコマンドでアクションを作成します。

```console
$ ibmcloud fn action create first-linebot --docker YOUR_USER_NAME/linebot_function first-linebot.py
```
エラーなく結果が返ってきたら成功です。

# 動作確認
これで作成したアクションの動作確認を行います。ですが、Dockerの実行環境で作成したアクションは、GUIから動作確認をできないのでこれもコマンドラインで操作をして行います。以下のコマンドを実行します。

```console
$ ibmcloud fn action invoke first-linebot --result
```

実行後にLINE bot SDKのバージョンが結果として出力されたら、自前の環境でアクションが問題なく動作しています。

```json
{
    "LINEbot": "1.15.0"
}
```

これで作成できるアクションの幅が広がると思います。今回構築した環境を使ってサーバーレスのLINE botを作成してみようと思います。

LINE botを作った記事は[こちら](https://qiita.com/K_M95/items/b389a89941fe9329927c)からご覧頂けます。