AWS Lambda Pythonからpsycopg2でRDS PostgreSQLに接続する

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

AWS Lambda 関数では、実行に必要なプログラムをZIPでパッケージ化します。 AWS Lambda PythonからRDS PostgreSQL に接続する場合はどうでしょうか?

PostgreSQLアダプターのpsycopg2をビルドしてLambda関数のルートディレクトリに同梱するだけで済みそうですが、話はそう簡単ではありません。

libpqをpsycopg2に動的リンクさせた場合の弊害

psycopg2 はPostgreSQLクライアント用Cインターフェース libpq のラッパーであり、標準では libpq を動的リンクします。

残念なことに、AWS Lambda の実行環境では libpq は共有ライブラリとしてインストールされていないため、libpq を動的リンクしていると libpq が見つからず、以下のようなエラーが発生します

Unable to import module 'lambda_function': libpq.so.5: cannot open shared object file: No such file or directory

libpqをpsycopg2に静的リンクさせて解決

2016年05月時点ではAWS 側が Lambda 実行環境に libpq を共有ライブラリとして提供してくれていない以上、libpq を静的リンクさせて解決します。

以下の手順で作業します。

  1. AWS Lambda実行環境と同じAMIでEC2を起動
  2. PostgreSQLのインストール
  3. psycopg2をビルド(libpqを静的リンク)
  4. psycopg2を使ったLambda関数の実行

1. AWS Lambda実行環境と同じAMIでEC2を起動

AWS Lamba の実行環境は AMI(OSはAmazon Linux)で提供されています。 現時点では、「AMI name: amzn-ami-hvm-2015.09.1.x86_64-gp2」という名前で、東京リージョンであれば「AMI ID:ami-383c1956」で提供されています。

このAMIでEC2を起動します。

lambda-python-ami

$ uname -a
Linux ip-172-31-22-214 4.1.10-17.31.amzn1.x86_64 #1 SMP Sat Oct 24 01:31:37 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux

AWS Lambda の実行環境の詳細は次の URL をご確認ください。

http://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html

2. PostgreSQLのインストール

次に psycopg2 のビルドに必要な PostgreSQL をインストールします。

まずは PostgreSQL のコンパイルに必要な開発ツールをインストールします。

$ sudo yum -y groupinstall "Development Tools"

次に PostgreSQL をインストールします。 インストール先を環境変数 PG_DIR で定義しています。

$ wget https://ftp.postgresql.org/pub/source/v9.4.7/postgresql-9.4.7.tar.gz
$ tar zxfv postgresql-9.4.7.tar.gz
$ cd postgresql-9.4.7
$ PG_DIR=/tmp/pg
$ ./configure --prefix $PG_DIR --without-readline --without-zlib
$ make
$ make install

インストールされるライブラリ libpq のバージョンと接続先PostgreSQLサーバーのバージョンが異なっていても、問題 なく接続できます。 最新の PostgreSQL をインストールしましょう。

psycopg2 のドキュメントから引用します。

Note psycopg2 usually depends at runtime on the libpq dynamic library. However it can connect to PostgreSQL servers of any supported version, independently of the version of the libpq used: just install the most recent libpq version or the most practical, without trying to match it to the version of the PostgreSQL server you will have to connect to.

http://initd.org/psycopg/docs/install.html

3. psycopg2をビルド(libpqを静的リンク)

次に Python 向け PostgreSQL アダプターの psycopg2 をインストールします。

$ cd # back to home directory
$ wget http://initd.org/psycopg/tarballs/PSYCOPG-2-6/psycopg2-2.6.1.tar.gz
$ tar zxfv psycopg2-2.6.1.tar.gz
$ cd psycopg2-2.6.1

ここでビルド設定を記載する setup.cfg を編集します。

次の2点を修正しています。

  • pg_config のパスを先ほどインストールしたパスに書き換え
  • static_libpq=1 にして静的リンク

修正後の setup.cfg が以下です。

[build_ext]
define=

# PSYCOPG_DISPLAY_SIZE enable display size calculation (a little slower)
# HAVE_PQFREEMEM should be defined on PostgreSQL >= 7.4
# PSYCOPG_DEBUG can be added to enable verbose debug information

# "pg_config" is required to locate PostgreSQL headers and libraries needed to
# build psycopg2. If pg_config is not in the path or is installed under a
# different name uncomment the following option and set it to the pg_config
# full path.
pg_config=/tmp/pg/bin/pg_config # XXX CHANGED

# Set to 1 to use Python datetime objects for default date/time representation.
use_pydatetime=1

# If the build system does not find the mx.DateTime headers, try
# uncommenting the following line and setting its value to the right path.
#mx_include_dir=

# For Windows only:
# Set to 1 if the PostgreSQL library was built with OpenSSL.
# Required to link in OpenSSL libraries and dependencies.
have_ssl=0

# Statically link against the postgresql client library.
static_libpq=1  # XXX CHANGED

# Add here eventual extra libraries required to link the module.
#libraries=

次にライブラリ libpq のパスを LD_LIBRARY_PATH で渡して、ビルドを実行します。

$ LD_LIBRARY_PATH=$PG_DIR/lib:$LD_LIBRARY_PATH python setup.py build

ビルドされた psycopg2 モジュールを確認します。

$ ls -1 build/lib.linux-x86_64-2.7/psycopg2/
errorcodes.py
extensions.py
extras.py
__init__.py
_json.py
pool.py
psycopg1.py
_psycopg.so
_range.py
tests
tz.py
$ file build/lib.linux-x86_64-2.7/psycopg2/_psycopg.so
build/lib.linux-x86_64-2.7/psycopg2/_psycopg.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=bccde9363fccf85d899e3eecc4bc97bd07dbd2da, not stripped
$ ldd build/lib.linux-x86_64-2.7/psycopg2/_psycopg.so
	linux-vdso.so.1 =>  (0x00007ffc61b27000)
	libpython2.7.so.1.0 => /usr/lib64/libpython2.7.so.1.0 (0x00007f526936e000)
	libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f5269152000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f5268d8f000)
	libdl.so.2 => /lib64/libdl.so.2 (0x00007f5268b8b000)
	libutil.so.1 => /lib64/libutil.so.1 (0x00007f5268988000)
	libm.so.6 => /lib64/libm.so.6 (0x00007f5268685000)
	/lib64/ld-linux-x86-64.so.2 (0x000055b1aaec3000)

4. psycopg2を使ったLambda関数の実行

psycopg2 モジュールをインポートできることを確認するだけのシンプルな Lambda 関数を用意します。

$ mkdir ~/lambda
$ cp -r build/lib.linux-x86_64-2.7/psycopg2/ ~/lambda/
$ cd ~/lambda/
$ cat <<EOF >> lambda_function.py
> import psycopg2
> def lambda_handler(event, context):
>     return 'ok'
> EOF
$ zip -r lambda.zip .

この lambda.zip を管理画面からアップロードし、「Test」ボタンから実行します。

lambda-pg-execution

ログに「ok」と出れば成功です。 おめでとうございます。

Unable to import module 'lambda_function': libpq.so.5: cannot open shared object file: No such file or directory

というようなエラーメッセージが表示された場合は、ビルド手順を遡って確認してください。

まとめ

今回は PostgreSQL/psycopg2 を題材に、Python のC拡張モジュールが共有ライブラリに依存する場合の AWS Lambda での利用方法を解説しました。

毎回この作業をするのは手間なため、 psycopg2 以外にも

  • MySQL-Python
  • numpy
  • OpenCV
  • Pillow (PIL)
  • LXML

といった他のC共有ライブラリに依存するモジュールをプリコンパイルして AWS Lambda 向けにパッケージ化したライブラリも存在します。

https://github.com/Miserlou/lambda-packages/

今回紹介したアイデアを psycopg2 以外のモジュールにも活かしていただければ幸いです。

参考リンク