docker-lambda + minioで環境構築

AWS S3互換であるminioと、lamda function開発用のdocker imageであるdocker-lambdaを使って、ローカルで開発環境を作ろうというお話です。


## Minio
MinioはAWS S3互換のObject Strageで、ローカルにS3環境を作ることができます。
[公式ドキュメント](https://docs.min.io/docs/minio-quickstart-guide.html)も用意されており、Dockerがあれば手軽にはじめられます。

起動するとこんな感じです。
![Screenshot from 2019-12-26 18-45-04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263384/7fa426b0-5e99-040e-3846-3a96a1d74ae7.png)

![Screenshot from 2019-12-26 18-45-41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263384/db1188db-623d-4e44-c259-f2502b6b08ab.png)

### minio server
サーバーの立ち上げは以下のコマンド。

```
$ docker run \
  --name minio
  -p 9000:9000 \
  -p 9001:9001 \
  -e "MINIO_ROOT_USER=minioadmin" \
  -e "MINIO_ROOT_PASSWORD=minioadmin" \
  quay.io/minio/minio server /data --console-address ":9001"
```


### minio client
このminioにはコマンドも用意されている。これによってローカルで立てたminioサーバーに対してコマンドから操作を行うことができる。
mcを起動後、さきほどminio serverを立ち上げたときに表示されていた```mc```ではじまるコマンドをコピペする。
これでminio serverとの接続が完了。この先はコマンドでminio serverを操作することが可能となる。

```
$ docker pull minio/mc
$ docker run --name mc -it --entrypoint=/bin/sh minio/mc
# mc alias set minio http://minio:9000 minioadmin minioadmin
```

## docker-lambda
そしてlambda fuction開発用のdocker imageである[docker-lambda](https://github.com/lambci/docker-lambda)。
Amazon Lambda Functionの中身はAmazon linuxらしく（知らなかった）、それをDocker内で環境構築し、必要なパッケージをインストールしてzipで固めて〜...を簡単に行えるようにしたもの。

コマンドの基本形は以下

```
$ docker run --rm -v <code_dir>:/var/task lambci/lambda:<runtime> <filename>.<funcname>
```

以下のファイルを適当なディレクトリで作成。

```function.py
def my_handler(event, context):
    message = 'Hello {} {}!'.format(event['first_name'],
                                    event['last_name'])
    return {
        'message' : message
    }
```
上記ファイルがあるディレクトリで以下のコマンドを叩くと、それでテストができる

```
$ docker run --rm -v "$PWD":/var/task lambci/lambda:python3.7 function.my_handler \
'{"first_name": "Kenji", "last_name": "Yamada"}'
```

上記コマンドを叩いた場合はイベントが起こった前提でのテストができ、即終了となるが、```DOCKER_LAMBDA_STAY_OPEN=1```というオプションをつけることでAPIサーバーとしてdocker-lambdaを起動することができる。

```
$ docker run --rm \
  -e DOCKER_LAMBDA_STAY_OPEN=1 \
  -p 9001:9001 \
  -v "$PWD":/var/task \
  lambci/lambda:python3.7 \
  function.my_handler
```

以下のコマンドでイベントを投げることができる。

```
$ curl -d '{"first_name": "Kenji", "last_name": "Yamada"}' http://localhost:9001/2015-03-31/functions/myfunction/invocations
```

## 本題
ここまでが今回使うツールの説明である。
そして上記で説明したものはだいたい検索すれば出てくる。
今回やりたいことは以下の２つ

- Minioにはwebhookの設定があるらしい
- Minioにファイルをおいたタイミングでdocker-lambdaを動かしたい

### ネットワークの設定
複数のコンテナを立ち上げて通信するため、まずはネットワークの設定を行う

```
$ docker network create minio-lambda-net
```
### minio サーバーを立ち上げ
使い回せるようにdocker-composeファイルを用意した

[ここ](https://github.com/nakano16180/s3_practice)からcloneして立ち上げる
```
$ docker-compose up
```

### lambdaをAPIサーバーとして立ち上げ

まず、以下のようなlambda functionを作成する。
内容はシンプルに、S3にファイルをおいたタイミングでS3の情報を表示するだけ。
今回はS3の代わりにMinioを使っているので、endpointにminioのurlをしていする。

```function.py
from __future__ import print_function

import os
import json
import urllib
import boto3

print('Loading function')

print('AWS_ACCESS_KEY_ID: ', os.environ.get('AWS_ACCESS_KEY_ID'))
print('AWS_SECRET_ACCESS_KEY: ', os.environ.get('AWS_SECRET_ACCESS_KEY'))

s3_endpoint_url = 'http://minio:9000'
#http://boto3.readthedocs.io/en/latest/reference/services/s3.html#client
s3 = boto3.client('s3', endpoint_url=s3_endpoint_url)


def lambda_handler(event, context):

    print(event)
    bucket_name = event['Records'][0]['s3']['bucket']['name']
    print('==== bucket information =====')
    print(bucket_name)
    print('=============================')


    #https://github.com/bloomberg/chef-bcs/blob/master/cookbooks/chef-bcs/files/default/s3-example-boto3.py
    print('==== your bucket list ====')
    buckets = s3.list_buckets()
    for bucket in buckets['Buckets']:
        print(bucket.get('Name'))

    print('==== file list in bucket ====')
    # https://github.com/boto/boto3/issues/134
    AWS_S3_BUCKET_NAME = 'test'
    s3_resource = boto3.resource('s3', endpoint_url=s3_endpoint_url)
    bucket = s3_resource.Bucket(AWS_S3_BUCKET_NAME)
    result = bucket.meta.client.list_objects(Bucket=bucket.name, Delimiter='/')
    for o in result.get('Contents'):
        print(o.get('Key'))


    print('==== uploaded file ====')
    for rec in event['Records']:
        print(rec['s3']['object']['key'])
    print('=============================')

    print("Received event: " + json.dumps(event, indent=2))

```

サーバーの起動は以下のコマンド。

```
$ docker run --rm \
  --name d-lambda \
  --net=minio-lambda-net \
  -p 3000:3000 \
  -e AWS_ACCESS_KEY_ID=minioadmin \
  -e AWS_SECRET_ACCESS_KEY=minioadmin \
  -e DOCKER_LAMBDA_STAY_OPEN=1 \
  -v "$PWD":/var/task \
  lambci/lambda:python3.7 \
  function.lambda_handler
```

### Minio serverのwebhookの設定
Minio serverには[Bucket Notification](https://docs.min.io/docs/minio-bucket-notification-guide.html)という機能があり、方法は色々あるがwebhookを使ってMinioでのイベントを通知してみようというお話。

注意すべき点はendpointの設定。さっきd-lambdaという名前でサーバーを起動しているので、ホスト名をd-lambdaにする。

```
# mc alias set minio http://minio:9000 minioadmin minioadmin
# mc admin config set minio notify_webhook:1 queue_limit="0"  endpoint="http://d-lambda:3000/2015-03-31/functions/myfunction/invocations" queue_dir=""

ここで一度サーバーを再起動するように指示されるので、それに従う。
# mc admin service restart myminio

# mc mb minio/test
# mc event add minio/test arn:minio:sqs::1:webhook --event put --suffix .mp4
```

上記の設定を行うことで、testという名前のbucketに*.mp4というファイルをアップロードしたとき、webhookの通知を行うことができる。

以上で設定は完了となる。
http://localhost:9000 にアクセスするとMinioのWebUIが表示されるので、そこからファイルをアップロードしてみましょう。
lambdaを立てたコンソールで結果が見えるはずです。

## 参考
- https://docs.min.io/docs/minio-docker-quickstart-guide.html
- https://docs.min.io/docs/minio-client-complete-guide.html
- https://github.com/minio/minio/tree/master/docs/orchestration/docker-compose
- https://nodejs.org/ja/docs/guides/nodejs-docker-webapp/
- https://stackoverflow.com/questions/36637912/how-to-stop-running-node-in-docker