本記事はamazonlinuxでpython3.7を使用したい人、lambda上でpythonのC拡張ライブラリを使用したい人向け。
動機
Python3.7ランタイムのlambda上でscrapyを動かそうとしたところ以下のエラーでうまく実行できなかった。
{
"errorMessage": "Unable to import module 'spider': cannot import name 'etree' from 'lxml' (/var/task/lxml/__init__.py)",
"errorType": "Runtime.ImportModuleError"
}
調べたところ、どうやらlxmlライブラリはC言語の拡張を使用しており、そのままではlambdaで使えないとのこと。
諦めればいいものの、Python3にこれから移行していくというタイミングで困ることが多そうなので頑張って解決してみる。
PCはMacを使用。
とりあえずEC2上で動かす
lambdaはサーバレスといっても裏ではamazonlinuxのサーバ上で動くため、とりあえずamazonlinux上で動けばいいのでは?
ということで、早速amazonlinuxのEC2を作成していく。
EC2の作成・ssh接続
ちなみにPython3.7まではamazonlinux、3.8はamazonlinux2上で動くらしい。
AWS Lambda ランタイム
今回はPython3.7を使用するため、amazonlinuxを使用してEC2を作成する。
VPCは適当に。
直接SSH接続したいので、パブリックIPを割り当てておく。
また、セキュリティーグループはtype:ssh プロトコル:TCP ポート範囲:22 ソース:自宅のグローバルIP/32あたりで設定しておく。
keypairを作成し、Downloads
フォルダに保存。
その後、~/.ssh
フォルダにpemファイルを保存。
Downloads $ mv libtestkey.pem ~/.ssh
.ssh $ ls
libtestkey.pem
#秘密鍵として使えるように権限を変更
.ssh $ chmod 600 libtestkey.pem
AwsマネジメントコンソールでEC2のパブリックIPを確認。
作成したEC2にsshで接続
$ ssh -i ~/.ssh/libtestkey.pem ec2-user@3.112.178.228
Warning: Permanently added '3.112.178.228' (ECDSA) to the list of known hosts.
__| __|_ )
_| ( / Amazon Linux AMI
___|\___|___|
https://aws.amazon.com/amazon-linux-ami/2018.03-release-notes/
13 package(s) needed for security, out of 26 available
Run "sudo yum update" to apply all updates.
無事に接続完了。
EC2にpython3.7を導入
[ec2-user@ip-10-4-0-246 ~]$ yum list | grep python37
$
あれ、、、ない、、、。
ということで、下記サイトを参考にソースファイルからビルドしていく。
CentOS7にPython3.7をインストール(ソースファイルからビルド)
# yumのアップデート
[ec2-user@ip-10-4-0-246 ~]$ sudo yum clean all
..
[ec2-user@ip-10-4-0-246 ~]$ sudo yum -y update
..
完了しました!
# 必要なパッケージをまとめてインストール
[ec2-user@ip-10-4-0-246 ~]$ sudo yum install zlib-devel libffi-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel libuuid-devel xz-devel
..
Is this ok [y/d/N]: y
..
完了しました!
# python3.7.4をダウンロード
[ec2-user@ip-10-4-0-246 ~]$ curl -O https://www.python.org/ftp/python/3.7.4/Python-3.7.4.tgz
..
# 解凍してディレクトリへ移動
[ec2-user@ip-10-4-0-246 ~]$ tar xf Python-3.7.4.tgz
[ec2-user@ip-10-4-0-246 ~]$ cd Python-3.7.4/
# コンパイルしてインストール
[ec2-user@ip-10-4-0-246 Python-3.7.4]$ ./configure --enable-optimizations
..
configure: error: in `/home/ec2-user/Python-3.7.4':
configure: error: no acceptable C compiler found in $PATH
See `config.log' for more details
なんか怒られた。
C コンパイラが無い?
とりあえずgccを入れてみる。
[ec2-user@ip-10-4-0-246 ~]$sudo yum install gcc
..
y
..
完了しました!
もう一度実行してみる。
[ec2-user@ip-10-4-0-246 Python-3.7.4]$ ./configure --enable-optimizations
..
creating Modules/Setup
creating Modules/Setup.local
creating Makefile
お、できたっぽい。
ビルドしてみる。
# ビルド
[ec2-user@ip-10-4-0-246 Python-3.7.4]$ make
.. # 約20分(めっちゃテストしてた)
make[1]: ディレクトリ '/home/ec2-user/Python-3.7.4' から出ます
# インストール
[ec2-user@ip-10-4-0-246 Python-3.7.4]$ sudo make altinstall
..
Successfully installed pip-19.0.3 setuptools-40.8.0
pathを通してバージョンを確認。
[ec2-user@ip-10-4-0-246 bin]$ export PATH=$PATH:/usr/local/bin
[ec2-user@ip-10-4-0-246 bin]$ python3.7 -V
Python 3.7.4
[ec2-user@ip-10-4-0-246 bin]$ pip3.7 -V
pip 19.0.3 from /usr/local/lib/python3.7/site-packages/pip (python 3.7)
ようやく準備完了。
実際にamazon linux上で動かしてみる
今回はscrapyが依存しているlxmlライブラリを使用してテストしてみる。
lambdaで動かすことを想定し、buildフォルダを作成しその中にtestファイルを作成。
[ec2-user@ip-10-4-0-246 ~]$ mkdir build
[ec2-user@ip-10-4-0-246 ~]$ cd build/
[ec2-user@ip-10-4-0-246 build]$ ls
[ec2-user@ip-10-4-0-246 build]$ vim test.py
from lxml import etree
etree.LXML_VERSION
buildフォルダ内にpip install
# -t . を指定することで現在のフォルダにインストール
[ec2-user@ip-10-4-0-246 build]$ pip3.7 install lxml -t .
..
Successfully installed lxml-4.4.2
# 実行!
[ec2-user@ip-10-4-0-246 build]$ python3.7 test.py
(4, 4, 2, 0)
動いた!
lambdaに乗っけてみる
lambdaデプロイ用に色々作る
ローカルの作業環境にlibtestというフォルダを作成、その後sam initしてテンプレートを作成。
$mkdir libtest
$cd libtest
#ランタイムはpython3.7を指定
libtest $sam init --runtime python3.7
..
[*] Project initialization is now complete
app.pyを編集
from lxml import etree
# lambda handler
def lambda_handler(event,context):
print(etree.LXML_VERSION)
ここで一旦ローカルでpip installするとどうなるか試す
hello_world $pip3 install lxml -t .
テンプレートの修正
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
sam-app
Sample SAM Template for sam-app
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 3
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.7
デプロイ用shellの作成
# バケット名は世界中で一意な任意の名前にする
aws s3 mb s3://rkhcx-libtest-bucket
# デプロイ用パッケージの作成
echo "デプロイ用のパッケージを作成します。"
aws cloudformation package --template-file template.yaml \
--output-template-file output-template.yaml \
--s3-bucket rkhcx-libtest-bucket
# デプロイ
aws cloudformation deploy --template-file output-template.yaml \
--stack-name libtest-stack \
--capabilities CAPABILITY_IAM
デプロイ
sam-app $sh deploy.sh
..
Successfully created/updated stack - libtest-stack
lambdaが作成されたので、テスト実行してみる
やはりlxmlが読み込めない模様。
先ほどEC2上でインポートしたlxmlをローカルにコピー
まずはローカルでインポートしたlxmlライブラリを削除。
その後、EC2からlxmlフォルダをscpでコピーしてくる。
hello_world $rm -rf lxml*
hello_world $scp -i ~/.ssh/libtestkey.pem -r ec2-user@3.112.178.228:/home/ec2-user/build/lxml .
再デプロイして実行!
sam-app $sh deploy.sh
..
Successfully created/updated stack - libtest-stack
実行できた!