AWS LambdaでPythonのparamikoを使う方法

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

モバイルアプリサービス部の五十嵐です。

MacでPythonのparamiko(sshのライブラリ)を使ったLambda Functionを作成しデプロイしたところ動かなくて思いのほか大変だったので記事にまとめておきます。

デプロイパッケージの構成

まずはじめにAWS Lambdaにデプロイするパッケージの構成をおさらいしておきます。

Pythonを使う場合の構成は、 デプロイパッケージの作成 (Python) - AWS Lambda に記載されているように、 Lambda Functionとライブラリを全てルートレベルに保存 します。

pipを使ってライブラリをインストールする場合は、以下のように -t オプションでルートディレクトリを指定します。

$ pip install paramiko -t . # カレントディレクトリがルートディレクトリである想定

最後にLambda Functionを含むルートディレクトリ以下を全てzipファイルにまとめ、AWS Lambdaへデプロイします。

$ zip -r deploy.zip . # カレントディレクトリがルートディレクトリである想定
$ aws lambda update-function-code --function-name hoge-function --zip-file fileb://deploy.zip

invalid ELF header

しかし、この状態でLambda Functionを実行すると以下のエラーが発生しました。

{
  "stackTrace": [
    [
      "/var/task/lambda.py",
      26,
      "lambda_handler",
      "client.connect(SOURCE_HOST, username=SOURCE_USER, key_filename=SOURCE_KEY)"
    ],
    [
      "/var/task/paramiko/client.py",
      338,
      "connect",
      "t.start_client()"
    ],
    [
      "/var/task/paramiko/transport.py",
      493,
      "start_client",
      "raise e"
    ]
  ],
  "errorType": "ImportError",
  "errorMessage": "/var/task/cryptography/hazmat/bindings/_constant_time.so: invalid ELF header"
}

あまり詳しいことは分かりませんが、EFLファイルというのはバイナリ形式の実行ファイルのことで、作成した環境によってheader情報が異なるようです。なので、Amazon Linux を開発環境として paramiko をインストールしてデプロイパッケージを作ることにします。

後で気付きましたが、 Lambdaの基盤実行環境 にも以下の注意書きがありました。

コードにネイティブバイナリを使用している場合は、必ずこの環境でコンパイルしてください。

開発環境

開発環境は以下のとおりですが、pipのバージョンは新しい物があるとメッセージが出るので上げておきます。

$ cat /etc/system-release
Amazon Linux AMI release 2016.03

$ python --version
Python 2.7.10

$ sudo pip install --upgrade pip
You are using pip version 6.1.1, however version 8.1.2 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
Collecting pip
  Downloading pip-8.1.2-py2.py3-none-any.whl (1.2MB)
    100% |████████████████████████████████| 1.2MB 394kB/s
Installing collected packages: pip
  Found existing installation: pip 6.1.1
    Uninstalling pip-6.1.1:
      Successfully uninstalled pip-6.1.1
Successfully installed pip-8.1.2

$ source ~/.bash_profile

$ pip --version
pip 8.1.2 from /usr/local/lib/python2.7/site-packages (python 2.7)

paramikoのインストール

必要なライブラリがいくつかあるようなのでyumでインストールしておきます。

$ sudo yum -y install gcc gcc-c++ kernel-devel python-devel libxslt-devel libffi-devel openssl-devel

paramikoをカレントディレクトリにインストールします。

$ pip install paramiko -t .
Collecting paramiko
  Using cached paramiko-2.0.1-py2.py3-none-any.whl
Collecting pyasn1>=0.1.7 (from paramiko)
  Using cached pyasn1-0.1.9-py2.py3-none-any.whl
Collecting cryptography>=1.1 (from paramiko)
  Using cached cryptography-1.4.tar.gz
Collecting idna>=2.0 (from cryptography>=1.1->paramiko)
  Using cached idna-2.1-py2.py3-none-any.whl
Collecting six>=1.4.1 (from cryptography>=1.1->paramiko)
  Using cached six-1.10.0-py2.py3-none-any.whl
Collecting setuptools>=11.3 (from cryptography>=1.1->paramiko)
  Using cached setuptools-24.0.2-py2.py3-none-any.whl
Collecting enum34 (from cryptography>=1.1->paramiko)
  Using cached enum34-1.1.6-py2-none-any.whl
Collecting ipaddress (from cryptography>=1.1->paramiko)
  Using cached ipaddress-1.0.16-py27-none-any.whl
Collecting cffi>=1.4.1 (from cryptography>=1.1->paramiko)
  Using cached cffi-1.7.0-cp27-cp27mu-manylinux1_x86_64.whl
Collecting pycparser (from cffi>=1.4.1->cryptography>=1.1->paramiko)
  Using cached pycparser-2.14.tar.gz
Installing collected packages: pyasn1, idna, six, setuptools, enum34, ipaddress, pycparser, cffi, cryptography, paramiko
  Running setup.py install for pycparser ... done
  Running setup.py install for cryptography ... done
Successfully installed cffi cryptography enum34 idna ipaddress paramiko-1.15.1 pyasn1-0.1.7 pycparser setuptools-12.2 six-1.8.0

あとは最初に記載したようにデプロイパッケージを作成すれば完成です。

まとめ

このエントリーではさらっと手順を書いていますが、Pythonを触るのはこれが初めてだったこともあり、他にもMacのEl Capitanでpipがうまく動かなかったり、pipの--targetオプションのバグっぽいのを踏んだり、paramikoの依存ライブラリがバージョンによって違ったりとハマりどころが多かったです。参考になったページのリンクを以下にまとめておきました。何かのお役に立てれば幸いです。

参照