Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

今回のお題

PythonでSSH経由のMySQLアクセスをして、WordPressの投稿情報を取得する
こちらを元に作成したLambda関数をデプロイし、
AWSのLambdaからLambdaを呼んで、Slackにメッセージを送信する
このような形で、RequestとResponseでデータをやりとりするように改造して見ました。
しかし、その際に下記の2点が課題になりました。

  1. サイズが大きいとソースが見れない・・・ スクリーンショット 2017-11-22 0.02.33.png このように表示され、ソースが見れなくなります(解決策はないようです)
  2. 実行した際にELFエラーになる
    スクリーンショット 2017-11-22 0.03.40.png
    このようなエラーが表示されます。今回はこれを解消するための手順を備忘録として記載します。

原因

使用している「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>ユーザで、対象のユーザを選択します。
アクセス権限の追加ボタンを押下します。
スクリーンショット 2017-11-27 22.38.30.png

既存のポリシーを直接アタッチを選択
スクリーンショット 2017-11-27 22.39.01.png

必要なポリシーを選択し、次のステップ:確認ボタンを押下します。
スクリーンショット 2017-11-27 22.25.24.png
スクリーンショット 2017-11-27 22.26.34.png
スクリーンショット 2017-11-27 22.27.14.png
スクリーンショット 2017-11-27 22.28.49.png

内容に問題がなければ、アクセス権限の追加を行う。
スクリーンショット 2017-11-27 22.28.58.png

アクセスキーを取得する

デプロイする実行するユーザのアクセスキーIDとシークレットアクセスキーが必要になるので、下記の画面から確認しメモをしておきます。
IAM>ユーザ>概要の認証情報をクリックします。
スクリーンショット 2017-11-26 19.30.41.png

アクセスキーの作成を押下する
スクリーンショット 2017-11-26 19.31.43.png

アクセスキーが作成されるので、内容をメモを取ります。
スクリーンショット 2017-11-26 19.32.18.png

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

EC2を作成する。

無料枠でEC2を作成します。作成する際のOSはAmazon Linuxを設定してください
デフォルトで、Python2.7系が入っているようなので今回はそちらを使用します。
Python3系で環境を作りたい場合は、下記のAWSのドキュメントを参考にコマンド入力していきます。
AWSのドキュメント
作成したら、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が必要なので、この2つを定義しました。
他にも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の投稿情報を取得する
LambdaのRequestとResponseの利用方法は、下記を参照。
AWSのLambdaからLambdaを呼んで、Slackにメッセージを送信する

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で複合化する

デプロイを実行する。

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

$ cd ~/python-deploy
$ lambda-uploader
λ Building Package
λ Uploading Package
λ Fin

と表示されれば無事にアップロード完了です。
もしエラーが起きる場合は、定義ミスか、ファイルサイズの容量(最大10MB)、asw configureの設定や、実行ユーザの権限のどれかだと思います。

Lambdaを確認する

一覧に追加されています。
名前や説明も設定どおりです。
スクリーンショット 2017-11-27 22.50.04.png

ハンドラも大丈夫そうです。
スクリーンショット 2017-11-27 22.50.46.png

テスト実行する

スクリーンショット 2017-11-27 22.51.49.png
結果も無事に取得できました。

以上

seisyu1985
現在、個人事業主で働きつつプログラムスキルを1から勉強し直し中。 簡単なものでも備忘録兼アウトプットしたいなと考えてQiita始めました。 あと就活のこやしになればいいな笑
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away