Pythonで書かれた Lambdaファンクションから、OracleDBへ接続する方法を記載します。
LambdaファンクションからMySQLへの接続はたくさん記事があるんだけど、OracleDBへの接続はあまり記事がなく、実現まで結構時間がかかったので、自分用の備忘録に。
2020年4月時点で、使用した環境は下記の通りです。
python3のインストールまで完了している状態で、後続の作業を開始します。
まずは、Oracle Instant Client から。
最新版のOracle Instant Clientを公式サイトからダウンロードします。作業環境が Amazon Linux 2 なので、Linux用の64ビット版を使います。
* Oracle Instant Client
* Instant Client Downloads for Linux x86-64 (64-bit)
インストールしたのは、とりあえずbasicとsqlplusのふたつだけ。LambdaファンクションからのDB接続だけを考えるなら、sqlplusは不要かも。
$ sudo yum -y localinstall ./oracle-instantclient19.6-basic-19.6.0.0.0-1.x86_64.rpm
$ sudo yum -y localinstall ./oracle-instantclient19.6-sqlplus-19.6.0.0.0-1.x86_64.rpm
インストール完了後、SQL*PLUSでDBに接続できることを確認しておきます。
なお、Oracle Instant Clientを使用する分には、ORACLE_HOMEなどの設定は不要らしいです。
$ sqlplus <user>/<pass>@<hostname>:<port>/<sid>
SQL*Plus: Release 19.0.0.0.0 - Production on xxx xxx xx xx:xx:xx xxxx
Version 19.6.0.0.0
Copyright (c) 1982, 2019, Oracle. All rights reserved.
Last Successful login time: xxx xxx xx xxxx xx:xx:xx +09:00
Connected to:
Oracle Database 12c Standard Edition Release 12.1.0.2.0 - 64bit Production
SQL> select TNAME from tab;
TNAME
--------------------------------------------------------------------------------
SAMPLE_EMP
SQL> select * from SAMPLE_EMP;
EMPNO EMPNAME GENDER_F
---------- -------------------------------------------------- ----------
0000000001 EMP_A 1
0000000002 EMP_B 2
0000000003 EMP_C 1
SQL> quit
Disconnected from Oracle Database 12c Standard Edition Release 12.1.0.2.0 - 64bit Production
サンプルで作っておいたテーブルの中身が見えており、Oracle Instant Clientのインストールは完了です。
PythonでOracleDBに接続するために、cx_Oracleもインストールしておきます。
$ sudo pip3 install cx_oracle
ここまで完了したら、pythonでOracleDBへ接続できるようになっているはずなので、簡単なサンプルでOracleDBに接続できるか確認します。例えば下記のような感じ。ファイル名はdbconnect_oracle.py
とします。
import json
import cx_Oracle
def lambda_handler(event, context):
# TODO implement
# cx_oracleバージョン確認
verOraCli = cx_Oracle.clientversion()
print( verOraCli )
# oracle接続
print( "DB connect" )
connection = cx_Oracle.connect("<user>", "<pass>", "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=<hostname>) (PORT=<port>))(CONNECT_DATA=(SID=<sid>)))")
# select実行
print( "select execute" )
try:
cursor = connection.cursor()
sql = "select * from sample_emp"
cursor.execute( sql )
result = cursor.fetchall()
print( result )
finally:
cursor.close()
connection.close()
return {
'statusCode': 200,
'body': json.dumps( 'cx_oracle clientversion = ' + str(verOraCli) )
}
本来はハンドラの中でDB接続処理を行わない方が良いけど、まあとりあえずということでひとつ。早速実行してみます。
$ python3 -c "import dbconnect_oracle; dbconnect_oracle.lambda_handler(0,0)"
(19, 6, 0, 0, 0)
DB connect
select execute
[('0000000001', 'EMP_A', 1), ('0000000002', 'EMP_B', 2), ('0000000003', 'EMP_C', 1)]
Oracle Instant Client 19.6 を使用してOracleDBでselectを実行できていることが確認できました。
EC2上での動作は確認できたので、AWS Lambdaの関数として定義するためにデプロイパッケージを作成します。
Lambdaファンクションで外部ライブラリのimportを行う場合、外部ライブラリを含んだデプロイパッケージを作る必要があります。今回はさらに、cx_OracleがOracle Instant Clientを利用するので、Oracle Instant Clinentもデプロイパッケージに含める必要があります。
というわけで、目指すべき構成は下記の形。作業ディレクトリは/tmp/dbconnect_oracle_pack
とします。
/tmp/dbconnect_oracle_pack
├ dbconnect_oracle.py
├ cx_Oracle.cpython-37m-x86_64-linux-gnu.so
├ cx_Oracle-7.3.0.dist-info
│ ├ INSTALLER
│ ├ METADATA
│ ├ RECORD
│ ├ WHEEL
│ └ top_level.txt
└ lib
├ libaio.so -> ./libaio.so.1
├ libaio.so.1
├ libclntsh.so -> ./libclntsh.so.19.1
├ libclntsh.so.19.1
├ libclntshcore.so -> ./libclntshcore.so.19.1
├ libclntshcore.so.19.1
├ libipc1.so
├ libmql1.so
├ libnnz19.so
├ libocci.so -> ./libocci.so.19.1
├ libocci.so.19.1
└ libociei.so
デプロイパッケージにcx_Oracleを含めるため、作業ディレクトリへcx_Oracleを持ってきます。作業ディレクトリへのインストールなので、今回はsudo
は不要です。
$ pip3 install cx_Oracle -t /tmp/dbconnect_oracle_pack
Oracle Instant Clientのモジュールは、インストール先からコピーします。libaio.so.1
だけは、/usr/lib64/
から持ってきます。
なお、記事を書く際に再度確認したら、libaio.so.1
はシンボリックリンクで、実体はlibaio.so.1.0.1
でした。
下記の通りでも動作はしますが、気になる方は、libaio.so.1.0.1
をコピーしてそこからシンボリックリンクを作成する方が良いかもしれません。
$ mkdir /tmp/dbconnect_oracle_pack
$ cp /usr/lib/oracle/19.6/client64/lib/libclntsh.so.19.1 /tmp/dbconnect_oracle_pack/lib
$ cp /usr/lib/oracle/19.6/client64/lib/libclntshcore.so.19.1 /tmp/dbconnect_oracle_pack/lib
$ cp /usr/lib/oracle/19.6/client64/lib/libipc1.so /tmp/dbconnect_oracle_pack/lib
$ cp /usr/lib/oracle/19.6/client64/lib/libmql1.so /tmp/dbconnect_oracle_pack/lib
$ cp /usr/lib/oracle/19.6/client64/lib/libnnz19.so /tmp/dbconnect_oracle_pack/lib
$ cp /usr/lib/oracle/19.6/client64/lib/libocci.so.19.1 /tmp/dbconnect_oracle_pack/lib
$ cp /usr/lib/oracle/19.6/client64/lib/libociei.so /tmp/dbconnect_oracle_pack/lib
$ cp /usr/lib64/libaio.so.1 /tmp/dbconnect_oracle_pack/lib
最初に挙げたデプロイパッケージ構成に不足している部分を、シンボリックリンクで作成します。
デプロイパッケージのサイズ上限は250MBのため、絞れるところは絞りましょう。
$ cd /tmp/dbconnect_oracle_pack
$ ln -s ./libaio.so.1 libaio.so
$ ln -s ./libclntsh.so.19.1 libclntsh.so
$ ln -s ./libclntshcore.so.19.1 libclntshcore.so
$ ln -s ./libocci.so.19.1 libocci.so
ここまで完了すると、デプロイパッケージ構成通りの状態となっているはずです。zip圧縮して、デプロイパッケージを作成します。圧縮後のファイルは70MB弱くらいになると思います。
シンボリックリンクを含んでいるので、--symlinks
オプションを忘れないように。また、圧縮対象は/tmp/dbconnect_oracle_pack
ディレクトリではなく、その配下であることに注意してください。
$ cd /tmp/dbconnect_oracle_pack
$ zip -r --symlinks dbconnect_oracle_sample.zip ./dbconnect_oracle.py ./cx_Oracle.cpython-37m-x86_64-linux-gnu.so ./cx_Oracle-7.3.0.dist-info ./lib
完成したデプロイパッケージ元にLambdaファンクションを作成します。
ですが、作成したデプロイパッケージが50MBを超えているため、Lambdaファンクションとして直接アップロードを行う事ができません。一旦S3上にアップロードした後、LambdaファンクションにS3からアップロードする形になります。なお、Lambdaファンクションのサイズ上限は諸々含めて250MB。cx_OracleとOracle Instant Client だけで200MBを超えていて、結構カツカツです。
Lambdaファンクションとしてのアップロードが完了すると、インラインエディタで確認ができる…のが通常なのですが、サイズが大きすぎてインラインエディタでは編集できない旨の警告が表示されます。実行はできるようなので、良しとしておきます。
OracleDBに限らず、DBに接続するLambdaファンクションにはIPアドレスの付与が必要です。VPC設定を行い、VPC設定、サブネット、セキュリティグループを指定します。
また、初回実行に限り、そこそこの時間がかかります。デフォルトのタイムアウト値は3秒となっていますが、10秒程度に増やしておいたほうがよいでしょう。
上記の設定を完了したら、テスト実行を行います。サンプルは引数不要のイベントハンドラなので、空のテストイベントを作成してテストを行いましょう。
ヨシ! ちゃんと、Oracle Instant Client 19.6が使われているようです。
初回は時間がかかりましたが、2回目からは1秒かからずに実行されます。時間がかかるのは、IP付与によるものかしら。
当初の目的である、cx_Oracleを使ってOracleDBへ接続するLambdaファンクションの作成は、ここまでで実現できました。
ただ、インラインエディタでの編集ができないのはちょっといただけません。サイズが大きい原因はcx_OracleとOracle Instant Clientなので、この部分をcx_Oracleレイヤとして切り出せば良いのではないか?
関数コードからランタイム依存関係を移動するには、それらをレイヤーに配置します。Lambda ランタイムには、関数コードを使用して、レイヤーに含まれるライブラリにアクセスできることを確認する、
/opt
ディレクトリのパスが含まれます。ライブラリをレイヤーに含めるには、ランタイムでサポートされているいずれかのフォルダにそれらを配置します。
* Python –python
、python/lib/python3.8/site-packages
(サイトディレクトリ)
レイヤ用のデプロイパッケージを作る場合、pyhon関連のライブラリはpython
配下に、配置します。なので、cx_Oracle関連のモジュールはこちらに置きます。
予約されていない環境変数
(略)
LD_LIBRARY_PATH
– システムライブラリのパス
(/lib64:/usr/lib64:$LAMBDA_RUNTIME_DIR:$LAMBDA_RUNTIME_DIR/lib:$LAMBDA_TASK_ROOT:$LAMBDA_TASK_ROOT/lib:/opt/lib
)。
Oracle Instant Clientのモジュールについては、LD_LIBRARY_PATH
で指定されたパスに配置する必要があります。Lambdaファンクションの設定で編集しても良いのですが、デフォルトで指定されたパス/opt/lib
があるので、そちらを使いましょう。レイヤに含まれるライブラリは/opt
配下に配置されるとのことなので、lib
配下です。
というわけで、レイヤ用デプロイパッケージの構成は下記の通りになります。作業ディレクトリは/tmp/dbconnect_oracle_pack/layer
とします。
/tmp/dbconnect_oracle_pack/layer
├ python
│ ├ cx_Oracle.cpython-37m-x86_64-linux-gnu.so
│ └ cx_Oracle-7.3.0.dist-info
│ ├ INSTALLER
│ ├ METADATA
│ ├ RECORD
│ ├ WHEEL
│ └ top_level.txt
└ lib
├ libaio.so -> ./libaio.so.1
├ libaio.so.1
├ libclntsh.so -> ./libclntsh.so.19.1
├ libclntsh.so.19.1
├ libclntshcore.so -> ./libclntshcore.so.19.1
├ libclntshcore.so.19.1
├ libipc1.so
├ libmql1.so
├ libnnz19.so
├ libocci.so -> ./libocci.so.19.1
├ libocci.so.19.1
└ libociei.so
配置ができたらzip圧縮し、レイヤ用デプロイパッケージを作成します。ファイル名は何でも良いですが、oraclelib.zip
としてみました。
$ cd /tmp/dbconnect_oracle_pack/layer
$ zip -r --symlinks oraclelib.zip ./python ./lib
レイヤを作るため、AWS Lambdaの左側メニューから「レイヤー」を選択し、レイヤの作成を行います。
先程と同様に、S3経由でアップロードする形になります。また、「互換性のあるランタイム」には、作業環境であるAmazon Linux 2にインストールしたのと同じ Python3.7 を指定します。
レイヤの作成が完了したら、Lambdaファンクションにレイヤを適用します。
作成したLambdaファンクションの画面から「レイヤーの追加」を行う事ができます。
Lambdaファンクションのランタイムを「Python3.7」にした上で、「レイヤーの追加」を行ってください。
選択できるレイヤーは、Lambdaファンクションで設定しているランタイムを「互換性のあるランタイム」で指定したレイヤーだけです。
こうしておくと、Lambdaファンクションそのものは、今回のサンプルのdbconnect_oracle.py
だけにしても大丈夫です。インラインコード編集も可能となります。
動作も問題ありません。
一番わからなかったのは、Oracle Instant Client 19 だとどのlibをデプロイパッケージに含めればよいのか、ということです。今回記事に残そうとしたのも、それが要因です。
それにしても、Oracle Instant Client だけで200MBオーバーなんだけど、そのうちOracle Instant ClientだけでLambdaのサイズ制限を超えてしまうのではないだろうか。
その他、検索に引っかかったフォーラムの記事とか色々参考にしました。
Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント