Lambda関数でEmbulkコマンドを実行してみる

# はじめに
この記事は [BeeX Advent Calendar 2020](https://qiita.com/advent-calendar/2020/beex) の12/16の記事です。

==

前の記事でLambdaのコンテナイメージでの実行を試してみました。

* [Lambda関数をコンテナイメージで動かしてみた](https://qiita.com/handy-dd18/items/d0fb849a4b893aa35a46)

今回はそれに加えて、最近よく使っているEmbulkコマンドを使えないか試してみました。

# 環境
PC：Windows 10
Docker：Docker version 19.03.13, build 4484c46d9d

# 前提
* Dockerの初期設定は完了済み
* Python3はインストール済み(Lambdaランタイムと同じかそれ以上)
* AWSアカウントは作成済み
* Amazon ECRリポジトリは作成済み

# 実際の操作
## lambda functionの作成
@shiro01さんの記事を参考にLambda Functionを作成します。
今回はEmbulkの実行の確認なのでシンプルにヘルプコマンドを実行してみます。

```lambda_function.py
import subprocess

def lambda_handler(event, context):
    cmd = ['/usr/bin/embulk','help']
    out = subprocess.run(cmd,shell=True , stdout=subprocess.PIPE)
    print(out.stdout.decode())
```

1つ注意点として、subprocess.runの引数に"shell=True"を追加しています。
これを追加しないとOSErrorが発生します。

### 参考
* [AWS LambdaのPythonからLinuxコマンド実行](https://qiita.com/shiro01/items/a6f2516fa89a3f2c4bfe)
* [subprocess サブプロセス管理](https://docs.python.org/ja/3/library/subprocess.html)

## Dockerfileの作成
続いてDockerfileを作成します。
Embulkは事前にインストールして実行ファイルを作成しておきます。
DockerfileのCOPYでEmbulkとlambda_functionをコンテナ上にコピーします。

```Dockerfile
FROM amazon/aws-lambda-python:3.7

COPY lambda_function.py ./
COPY embulk /usr/bin

RUN chmod +x /usr/bin/embulk

CMD [ "lambda_function.lambda_handler" ]
```

## イメージをBuildする
以下のコマンドでイメージをビルドします。

```
$ cd [DockerFile格納先DIR]
$ docker build -t lambda_embulk .
```

イメージが作成されます。

```
$ docker images
REPOSITORY 　　　TAG       IMAGE ID       CREATED              SIZE
lambda_embulk　latest    472005da7cf7   About a minute ago   980MB
```

## ローカルテスト
以下のコマンドでローカルでLambdaを実行することが可能です。

```
$ docker run -p 9000:8080 lambda_embulk:latest
time="2020-12-15T06:11:57.95" level=info msg="exec '/var/runtime/bootstrap' (cwd=/var/task, handler=)"
```

別のウィンドウを開いて、以下のコマンドを実行します。
curlコマンドのレスポンスとしてはnullが返ってきます。

```
$ curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'
null
```

コンテナを実行しているウィンドウを確認すると実行された結果がコンソールに表示されています。
"embulk help"コマンドが実行されているので、ローカルテストは問題なさそうです。

```
$ docker run -p 9000:8080 lambda_embulk:latest
time="2020-12-15T06:13:53.604" level=info msg="exec '/var/runtime/bootstrap' (cwd=/var/task, handler=)"
START RequestId: 0ed577b3-6a07-447a-bccb-c03d255c0201 Version: $LATEST
time="2020-12-15T06:13:57.601" level=info msg="extensionsDisabledByLayer(/opt/disable-extensions-jwigqn8j) -> stat /opt/disable-extensions-jwigqn8j: no such file or directory"
time="2020-12-15T06:13:57.601" level=warning msg="Cannot list external agents" error="open /opt/extensions: no such file or directory"
Embulk v0.9.23
Usage: embulk [-vm-options] <command> [--options]
Commands:
   mkbundle   <directory>                             # create a new plugin bundle environment.
   bundle     [directory]                             # update a plugin bundle environment.
   run        <config.yml>                            # run a bulk load transaction.
   cleanup    <config.yml>                            # cleanup resume state.
   preview    <config.yml>                            # dry-run the bulk load without output and show preview.
   guess      <partial-config.yml> -o <output.yml>    # guess missing parameters to create a complete configuration file.
   gem        <install | list | help>                 # install a plugin or show installed plugins.
   new        <category> <name>                       # generates new plugin template
   migrate    <path>                                  # modify plugin code to use the latest Embulk plugin API
   example    [path]                                  # creates an example config file and csv file to try embulk.
   selfupdate [version]                               # upgrades embulk to the latest released version or to the specified version.

VM options:
   -E...                            Run an external script to configure environment variables in JVM
                                    (Operations not just setting envs are not recommended nor guaranteed.
                                     Expect side effects by running your external script at your own risk.)
   -J-O                             Disable JVM optimizations to speed up startup time (enabled by default if command is 'run')
   -J+O                             Enable JVM optimizations to speed up throughput
   -J...                            Set JVM options (use -J-help to see available options)
   -R--dev                          Set JRuby to be in development mode

Use `<command> --help` to see description of the commands.

END RequestId: 0ed577b3-6a07-447a-bccb-c03d255c0201
REPORT RequestId: 0ed577b3-6a07-447a-bccb-c03d255c0201  Init Duration: 1.03 ms  Duration: 591.61 ms     Billed Duration: 600 mMemory Size: 3008 MB     Max Memory Used: 3008 MB
```

## ECRリポジトリへのPush
AWSコンソールからリポジトリを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/696fe50e-dbef-ebbd-0eff-072468a596ef.png)

以下のコマンドで作成したECRリポジトリにイメージをPushします。
ここら辺は前回の記事をほぼ同じです。
Embulkが少々サイズがあるので、前回よりPushに時間がかかります。

```
$ docker tag lambda_embulk:latest XXXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-embulk
$ docker images
REPOSITORY                                                                     TAG       IMAGE ID       CREATED          SIZE
941996685139.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-embulk                latest    472005da7cf7   11 minutes ago   980MB
```

```
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin XXXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com
Login Succeeded
```

```
$ docker push XXXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-embulk:latest
The push refers to repository [XXXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/lambda-embulk]
5f70bf18a086: Pushed
65f9fe7cdd01: Pushed
1965e83122e7: Pushed
701bdcbf3b47: Pushed
6e660533f001: Pushed
069cd8bd11dd: Pushed
6e191121f7ea: Pushed
d6fa53d6caa6: Pushed
1fb474cee41c: Pushed
b1754cf6954d: Pushed
464c816a7003: Pushed
latest: digest: sha256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX size: 2624
```

無事にアップロードできました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/f3dd63fb-72ec-e88e-9bc9-7a75f101c4d8.png)

## Lambda作成・実行
AWSのコンソールからLambda関数を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/78a897fc-2f0e-162e-935a-d025c3459eb8.png)

Lambda関数が作成されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/02c9d02f-fb18-9832-74cc-c177fa87862b.png)

## 実行結果確認
timeoutでエラーが出ました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/0c924fd7-c2cf-5dbb-3cd1-0ece24d43329.png)

Lambdaの基本設定でタイムアウト値が3秒になっていたので、とりあえず3分に変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/6d84b60a-364d-093f-5aa3-34446fc7aba1.png)

少し時間はかかりましたが、正常終了しました。
出力内容を見る限りEmbulkのhelpコマンドも実行できているみたいです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440403/b2127170-4536-11d9-70d9-b78a732e6ee3.png)

# さいごに
これが良いかどうかは別として、無事にLambdaでEmbulkを実行することができました。
多分実運用するならLambdaのスペックとかdiffファイルどこで保持するとか色々検討すべきことはありますが、とりあえずs3に置いてそこから小規模の処理を動かすとかならいけるんじゃないですかね？知らんけど。

ただEmbulkの処理ってLambdaの実行時間に収まることあまりないから、その辺りは難しいところかもしれません。