Lambdaの「invalid ELF header」エラーを解消する - EC2からlambda-uploaderを使用してLambdaにデプロイする方法

# 今回のお題
[PythonでSSH経由のMySQLアクセスをして、WordPressの投稿情報を取得する](https://qiita.com/seisyu1985/items/e910d85b2ca6f30db4f2)
こちらを元に作成したLambda関数をデプロイし、
[AWSのLambdaからLambdaを呼んで、Slackにメッセージを送信する](https://qiita.com/seisyu1985/items/76a7a10543ea38dd07de)
このような形で、RequestとResponseでデータをやりとりするように改造して見ました。
しかし、その際に下記の2点が課題になりました。

1. サイズが大きいとソースが見れない・・・
<img width="860" alt="スクリーンショット 2017-11-22 0.02.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/204329/5f06b4eb-712b-2ee8-79d1-87b4887c229b.png">
このように表示され、ソースが見れなくなります(**解決策はないようです**)
2. 実行した際にELFエラーになる<br>
<img width="563" alt="スクリーンショット 2017-11-22 0.03.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/204329/c8332cf2-fa55-373e-8c12-8e00a691b61a.png"><br>
**このようなエラーが表示されます。今回はこれを解消するための手順を備忘録として記載します。**

## 原因
使用している「sshtunnel」のパッケージが原因でした。
どうやらpipでインストールする際に、バイナリファイルを生成しておりますが、私の開発環境がMacOSのため、Amazon Linux環境でのバイナリ形式と異なり正常に読み取れないようです。
パッケージをデプロイし実行した結果、エラーに**invalid ELF header**と出力される場合は、Amazon linux上でデプロイパッケージを作成する必要があるようです。

## 解消法(2017/11/27時点)
1. ユーザに必要なアクセス権限を付与する
2. IAMからアクセスキーと、シークレットアクセスキーを取得する。
3. EC2を作成し、Amazon Linuxのインスタンスを立ち上げる。
4. lambda-uploaderを入れる
5. Lambdaにアップロード

となります。方法はいくつかあると思いますが、シンプルそうなので、EC2で環境を整えて、lambda-uploderを使用してLambdaにデプロイします。
**MaxOS内にvirtualenvを導入し、python-lambda-localを入れて見ましたがダメでした・・・**
**やはりバイナリファイルを生成する場合のOSを同一にしなければいけないようです。。。**

# ユーザロールを設定する
実行するユーザのロールに必要なポリシーをアタッチします。
ちゃんと調べたわけでなくすみませんが、自分は下記でいけました。
1. AmazonEC2FullAccess
2. AWSLambdaFullAccess
3. IAMFullAccess
4. AmazonVPCFullAccess

## ポリシーをアタッチする
IAM>ユーザで、対象のユーザを選択します。
アクセス権限の追加ボタンを押下します。
<img width="1060" alt="スクリーンショット 2017-11-27 22.38.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/204329/5b8f0622-261c-1353-3ece-373420027c5c.png">

既存のポリシーを直接アタッチを選択
<img width="608" alt="スクリーンショット 2017-11-27 22.39.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/204329/45462100-d874-cd2c-1e7b-0f06bc397a40.png">

必要なポリシーを選択し、次のステップ：確認ボタンを押下します。
<img width="1107" alt="スクリーンショット 2017-11-27 22.25.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/204329/4e525f5f-329a-da6e-743e-8c344428fecb.png">
<img width="1113" alt="スクリーンショット 2017-11-27 22.26.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/204329/a7b2097a-004f-775c-53ab-b67428b0e93a.png">
<img width="1106" alt="スクリーンショット 2017-11-27 22.27.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/204329/5dc80fc8-19b8-3ba9-ffbc-fb4bddebac1c.png">
<img width="1111" alt="スクリーンショット 2017-11-27 22.28.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/204329/265c09f6-5306-db3d-6f5f-3c673be5834b.png">

内容に問題がなければ、アクセス権限の追加を行う。
<img width="1160" alt="スクリーンショット 2017-11-27 22.28.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/204329/b7807ca2-21be-8ad6-9916-43575b0e96e6.png">


# アクセスキーを取得する
デプロイする実行するユーザのアクセスキーIDとシークレットアクセスキーが必要になるので、下記の画面から確認しメモをしておきます。
IAM>ユーザ>概要の認証情報をクリックします。
<img width="508" alt="スクリーンショット 2017-11-26 19.30.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/204329/c1d73b99-a2f6-ca13-e1af-83039952d7da.png">

アクセスキーの作成を押下する
<img width="1014" alt="スクリーンショット 2017-11-26 19.31.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/204329/7a106314-8f20-3fc6-68d4-d369713286e2.png">

アクセスキーが作成されるので、内容をメモを取ります。
<img width="821" alt="スクリーンショット 2017-11-26 19.32.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/204329/a7167769-1891-87e6-0a06-7b3db337ce0c.png">

アクセスキーとシークレットキーをメモします。CSV保存だけして、あとで削除するもの有りです。

# EC2を作成する。
**無料枠でEC2を作成します。作成する際のOSはAmazon Linuxを設定してください**
デフォルトで、Python2.7系が入っているようなので今回はそちらを使用します。
Python3系で環境を作りたい場合は、下記のAWSのドキュメントを参考にコマンド入力していきます。
[AWSのドキュメント](http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/with-s3-example-deployment-pkg.html#with-s3-example-deployment-pkg-python)
作成したら、ssh経由で接続し、以下のコマンドを入れていきます。

## lambda-uploaderを導入する
lambdaへのアップロードを行ってくれるパッケージです。こちらを導入し、デプロイを行いたいと思います。

lambda-uploaderを入れるコマンド

```
$ pip install lambda-uploader
```

## アクセスキーを設定する
aws configureを入力し、AWSの設定を行います。

```
$ aws configure
$ AWS Access Key ID:"取得したアクセスキーを入力しエンター"
$ AWS Secret Access Key:"取得したシークレットキーを入力しエンター"
$ Default region name: ap-northeast-1(東京の場合)
$ Default output format [None]: (何も入力せずにエンター)
```

## lambda-uploaderを使用するためのファイルを作成する。
下記の4つが必要のようで、全て同一ディレクトリに配置します。
1. requirements.txt
2. lambda.json
3. XXXXX.py(実行する処理を記載したPythonファイル)
4. event.json(必要なら)

1は、使用するパッケージの内容を羅列したもの。
2は、lambdaにデプロイする際の情報の定義
3は、登録するpythonのソース
4は、イベント定義です。
今回は特にイベントを設定しないので、1〜3を順番に設定していきます。

下記では、必要なものだけを抜粋してますが定義ファイルなどはgitを参照願います。
https://github.com/rackerlabs/lambda-uploader

### 1. インストールするパッケージの定義を行う
適当な作業ディレクトリを作成し移動します。
下記のコマンドを叩いて、ファイルを作成し中身をpipでインストールするパッケージを定義します。

```
$ mkdir ~/python-deploy
$ cd ~/python-deploy
$ touch requirements.txt
$ echo PyMySQL >> requirements.txt
$ echo sshtunnel >> requirements.txt
$ cat requirements.txt
PyMySQL
sshtunnel
```

今回はSSHTunnelとMySQLが必要なので、この２つを定義しました。
他にもrequestsなど、必要に応じて記載します。

### 2. Lambdaアップロード用の定義ファイルを作成する。
デプロイ時の設定が必要ですが、その際にロールを設定する場面が出てきます。
適用するロールを開き、arnの内容を確認しておきます。
あとでLambdaの画面から変更できるので、デフォルトで用意されている「lambda-basic-exetcution」をとりあえず設定します。

先ほどと同階層のディレクトリに、lambda.jsonを作成し、中身を記載します。

```
$ touch lambda.json
$ vi lambda.json
``` 

記載内容はこのような形になります。

```lambda.json
{
  "name": "lambdaに設定する名前",
  "description": "lambdaの説明",
  "runtime": "使用するランタイム",
  "region": "リージョン(東京ならap-northeast-1)",
  "handler": "Pythonのファイル名.実行関数名(lambda.handlerみたいに記載)",
  "role": "arn:aws:iam::アカウントID:role/ロール名",
  "timeout": タイムアウト値(数値:300が最大値),
  "memory": 使用メモリ(数値)
}
```

仮で書くと下記のような形になると思います。

```lambda.json
{
  "name": "lambda-upload-test",
  "description": "lambda uploaderからのデプロイテスト",
  "runtime": "python2.7",
  "region": "ap-northeast-1",
  "handler": "ssh.ssh_test",
  "role": "arn:aws:iam::000000000000:role/lambda-basic-exetcution",
  "timeout": 300,
  "memory": 128
}
```

### 3. 実行ファイルを定義
lambda.jsonに記載したファイル名を記載しますので、下記のようなファイルができると思います。
SSH接続してMySQL接続は、下記を参照。
[PythonでSSH経由のMySQLアクセスをして、WordPressの投稿情報を取得する](https://qiita.com/seisyu1985/items/e910d85b2ca6f30db4f2)
LambdaのRequestとResponseの利用方法は、下記を参照。
[AWSのLambdaからLambdaを呼んで、Slackにメッセージを送信する](https://qiita.com/seisyu1985/items/76a7a10543ea38dd07de)

```ssh.py
# -*- coding: utf-8 -*-
from sshtunnel import SSHTunnelForwarder
# モジュール読み込み
import pymysql.cursors

def ssh_test(event, date):
    is_write = is_wordpress_write(event["id"], event["date"])
    return {"ret": is_write}

def is_wordpress_write(id, date):
    # SSH関連の設定
    with SSHTunnelForwarder(
    ("SSH接続先ホスト名", SSH接続ポート),
    ssh_host_key="SSHホストキー（使用しないならNone）",
    ssh_pkey="SSHファイルの鍵のパス",
    ssh_username="接続ユーザ名(使用しないならNone)",
    ssh_password="接続パスワード or 鍵ファイルのパスフレーズ(使用しないならNone)",
    remote_bind_address=("127.0.0.1(ローカルホストとしてMySQLに接続するので)", MySQLのポート)
    ) as ssh:
        # MySQLに接続する
        conn = pymysql.connect(host='127.0.0.1（localhostなので）',
                                    user='ユーザ名',
                                    password='パスワード',
                                    db='DB名',
                                    charset='utf8',
                                    cursorclass=pymysql.cursors.DictCursor)
        # select
        # SQLを実行する
        cursor = conn.cursor()
        # select
        # SQLを実行する
        with connection.cursor() as cursor:
            sql = "select count(id) as count " \
                    " from " \
	                " wp_posts " \
                    " where " \
	                " post_author = %s " \
	                " and " \
	                " post_date > %s " \
	                " and " \
	                " post_status = 'publish' " \
	                " and " \
	                " post_type = 'post' "
            cursor.execute(sql, (id, date))

            # Select結果を取り出す
            rets = cursor.fetchall()
            is_write = False
            for r in rets:
                if r["count"] >= 1:
                    is_write = True
            # cursorクローズ
            cursor.close
        # MySQLから切断する
        connection.close()
    return is_write
```

接続情報は、直書きしたくないので、KMSを利用して暗号化したテキストを定数に置いたり、
環境変数に定義したりしてください。使用する際に複合化します。
あとはRequestで検索条件をもらい、Responseで結果を返すものにしました。
[Lambdaの環境変数をKMSで暗号化して、Pythonで複合化する](https://qiita.com/seisyu1985/items/880c5e49e8c8677bc9e3)

## デプロイを実行する。
上記で作成したファイルを格納したディレクトリに移動し、コマンドを実行します。

```
$ cd ~/python-deploy
$ lambda-uploader
λ Building Package
λ Uploading Package
λ Fin
```
と表示されれば無事にアップロード完了です。
もしエラーが起きる場合は、定義ミスか、ファイルサイズの容量（最大10MB）、asw configureの設定や、実行ユーザの権限のどれかだと思います。

# Lambdaを確認する
一覧に追加されています。
名前や説明も設定どおりです。
<img width="940" alt="スクリーンショット 2017-11-27 22.50.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/204329/13e15e2f-2a13-44b7-13a4-51940896ecdf.png">

ハンドラも大丈夫そうです。
<img width="904" alt="スクリーンショット 2017-11-27 22.50.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/204329/cadfddec-5dbd-bba8-232c-0a3ddf7038db.png">

## テスト実行する
<img width="876" alt="スクリーンショット 2017-11-27 22.51.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/204329/77c7d32d-96c7-c465-5b84-cd914ef873e8.png">
結果も無事に取得できました。

以上