tag:crieit.net,2005:https://crieit.net/users/tylor02/feed 平の投稿 - Crieit Crieitでユーザー平による最近の投稿 2020-04-21T14:04:20+09:00 https://crieit.net/users/tylor02/feed tag:crieit.net,2005:PublicArticle/15858 2020-04-21T13:51:26+09:00 2020-04-21T14:04:20+09:00 https://crieit.net/posts/AWS-Lambda-cx-Oracle-OracleDB AWS Lambdaでcx_Oracleを使ってOracleDBへ接続する <h1 id="前書き"><a href="#%E5%89%8D%E6%9B%B8%E3%81%8D">前書き</a></h1> <p>Pythonで書かれた Lambdaファンクションから、OracleDBへ接続する方法を記載します。</p> <p>LambdaファンクションからMySQLへの接続はたくさん記事があるんだけど、OracleDBへの接続はあまり記事がなく、実現まで結構時間がかかったので、自分用の備忘録に。</p> <h1 id="結論"><a href="#%E7%B5%90%E8%AB%96">結論</a></h1> <ul> <li>LambdaファンクションからOracleDBに接続する時には、cx_Oracleのモジュールだけではなく、Oracle Instant Clientのモジュールも、デプロイパッケージに入れてあげる必要がある。</li> <li>すべてのモジュールを一つのデプロイパッケージに入れると、大きすぎてAWS Lambdaのインラインエディタで編集できなくなる。なので、cx_OracleとOracle Instant Clientのモジュールはレイヤに分けると良い。</li> </ul> <h1 id="手順"><a href="#%E6%89%8B%E9%A0%86">手順</a></h1> <h2 id="前提"><a href="#%E5%89%8D%E6%8F%90">前提</a></h2> <p>2020年4月時点で、使用した環境は下記の通りです。<br /> python3のインストールまで完了している状態で、後続の作業を開始します。</p> <ul> <li>Amazon Linux 2(AWS EC2上で作業)</li> <li>python3.7</li> <li>cx_Oracle7.3</li> <li>Oracle Instant Client 19.6</li> </ul> <h2 id="手順の流れ"><a href="#%E6%89%8B%E9%A0%86%E3%81%AE%E6%B5%81%E3%82%8C">手順の流れ</a></h2> <ol> <li>必要なモジュールをインストールする</li> <li>デプロイパッケージを作る</li> <li>Lambdaファンクションを作成する</li> <li>レイヤ用のデプロイパッケージを作成する</li> <li>Lambdaファンクションにレイヤを適用する</li> </ol> <h2 id="1. 必要なモジュールをインストールする"><a href="#1.+%E5%BF%85%E8%A6%81%E3%81%AA%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%99%E3%82%8B">1. 必要なモジュールをインストールする</a></h2> <h3 id="Oracle Instant Client"><a href="#Oracle+Instant+Client">Oracle Instant Client</a></h3> <p>まずは、Oracle Instant Client から。<br /> 最新版のOracle Instant Clientを公式サイトからダウンロードします。作業環境が Amazon Linux 2 なので、Linux用の64ビット版を使います。<br /> * <a target="_blank" rel="nofollow noopener" href="https://www.oracle.com/technetwork/jp/database/database-technologies/instant-client/overview/index.html">Oracle Instant Client</a><br /> * <a target="_blank" rel="nofollow noopener" href="https://www.oracle.com/database/technologies/instant-client/linux-x86-64-downloads.html">Instant Client Downloads for Linux x86-64 (64-bit)</a></p> <p>インストールしたのは、とりあえずbasicとsqlplusのふたつだけ。LambdaファンクションからのDB接続だけを考えるなら、sqlplusは不要かも。</p> <pre><code>$ 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 </code></pre> <p>インストール完了後、SQL*PLUSでDBに接続できることを確認しておきます。<br /> なお、Oracle Instant Clientを使用する分には、ORACLE_HOMEなどの設定は不要らしいです。</p> <pre><code>$ 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 </code></pre> <p>サンプルで作っておいたテーブルの中身が見えており、Oracle Instant Clientのインストールは完了です。</p> <h3 id="cx_Oracle"><a href="#cx_Oracle">cx_Oracle</a></h3> <p>PythonでOracleDBに接続するために、cx_Oracleもインストールしておきます。</p> <pre><code>$ sudo pip3 install cx_oracle </code></pre> <h3 id="EC2からcx_Oracleを使ってOracleDBへ接続する"><a href="#EC2%E3%81%8B%E3%82%89cx_Oracle%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6OracleDB%E3%81%B8%E6%8E%A5%E7%B6%9A%E3%81%99%E3%82%8B">EC2からcx_Oracleを使ってOracleDBへ接続する</a></h3> <p>ここまで完了したら、pythonでOracleDBへ接続できるようになっているはずなので、簡単なサンプルでOracleDBに接続できるか確認します。例えば下記のような感じ。ファイル名は<code>dbconnect_oracle.py</code>とします。</p> <pre><code>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) ) } </code></pre> <p>本来はハンドラの中でDB接続処理を行わない方が良いけど、まあとりあえずということでひとつ。早速実行してみます。</p> <pre><code>$ 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)] </code></pre> <p>Oracle Instant Client 19.6 を使用してOracleDBでselectを実行できていることが確認できました。</p> <h2 id="2. デプロイパッケージを作る"><a href="#2.+%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%82%92%E4%BD%9C%E3%82%8B">2. デプロイパッケージを作る</a></h2> <p>EC2上での動作は確認できたので、AWS Lambdaの関数として定義するためにデプロイパッケージを作成します。</p> <h3 id="デプロイパッケージ構成"><a href="#%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E6%A7%8B%E6%88%90">デプロイパッケージ構成</a></h3> <p>Lambdaファンクションで外部ライブラリのimportを行う場合、外部ライブラリを含んだデプロイパッケージを作る必要があります。今回はさらに、cx_OracleがOracle Instant Clientを利用するので、Oracle Instant Clinentもデプロイパッケージに含める必要があります。</p> <p>というわけで、目指すべき構成は下記の形。作業ディレクトリは<code>/tmp/dbconnect_oracle_pack</code>とします。</p> <pre><code>/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 </code></pre> <h3 id="デプロイパッケージ作成"><a href="#%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E4%BD%9C%E6%88%90">デプロイパッケージ作成</a></h3> <p>デプロイパッケージにcx_Oracleを含めるため、作業ディレクトリへcx_Oracleを持ってきます。作業ディレクトリへのインストールなので、今回は<code>sudo</code>は不要です。</p> <pre><code>$ pip3 install cx_Oracle -t /tmp/dbconnect_oracle_pack </code></pre> <p>Oracle Instant Clientのモジュールは、インストール先からコピーします。<code>libaio.so.1</code>だけは、<code>/usr/lib64/</code>から持ってきます。</p> <p>なお、記事を書く際に再度確認したら、<code>libaio.so.1</code>はシンボリックリンクで、実体は<code>libaio.so.1.0.1</code>でした。<br /> 下記の通りでも動作はしますが、気になる方は、<code>libaio.so.1.0.1</code>をコピーしてそこからシンボリックリンクを作成する方が良いかもしれません。</p> <pre><code>$ 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 </code></pre> <p>最初に挙げたデプロイパッケージ構成に不足している部分を、シンボリックリンクで作成します。<br /> デプロイパッケージのサイズ上限は250MBのため、絞れるところは絞りましょう。</p> <pre><code>$ 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 </code></pre> <p>ここまで完了すると、デプロイパッケージ構成通りの状態となっているはずです。zip圧縮して、デプロイパッケージを作成します。圧縮後のファイルは70MB弱くらいになると思います。<br /> シンボリックリンクを含んでいるので、<code>--symlinks</code>オプションを忘れないように。また、圧縮対象は<code>/tmp/dbconnect_oracle_pack</code>ディレクトリではなく、その配下であることに注意してください。</p> <pre><code>$ 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 </code></pre> <h2 id="3. Lambdaファンクションを作成する"><a href="#3.+Lambda%E3%83%95%E3%82%A1%E3%83%B3%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B">3. Lambdaファンクションを作成する</a></h2> <p>完成したデプロイパッケージ元にLambdaファンクションを作成します。</p> <p>ですが、作成したデプロイパッケージが50MBを超えているため、Lambdaファンクションとして直接アップロードを行う事ができません。一旦S3上にアップロードした後、LambdaファンクションにS3からアップロードする形になります。なお、Lambdaファンクションのサイズ上限は諸々含めて250MB。cx_OracleとOracle Instant Client だけで200MBを超えていて、結構カツカツです。</p> <p>Lambdaファンクションとしてのアップロードが完了すると、インラインエディタで確認ができる…のが通常なのですが、サイズが大きすぎてインラインエディタでは編集できない旨の警告が表示されます。実行はできるようなので、良しとしておきます。</p> <p><a href="https://crieit.now.sh/upload_images/97382e5555931319dd5fd600612effab5e9e5b7d04327.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/97382e5555931319dd5fd600612effab5e9e5b7d04327.png?mw=700" alt="デプロイパッケージが大きすぎてインラインコード編集を有効にできません" /></a></p> <p>OracleDBに限らず、DBに接続するLambdaファンクションにはIPアドレスの付与が必要です。VPC設定を行い、VPC設定、サブネット、セキュリティグループを指定します。</p> <p>また、初回実行に限り、そこそこの時間がかかります。デフォルトのタイムアウト値は3秒となっていますが、10秒程度に増やしておいたほうがよいでしょう。</p> <p>上記の設定を完了したら、テスト実行を行います。サンプルは引数不要のイベントハンドラなので、空のテストイベントを作成してテストを行いましょう。</p> <p><a href="https://crieit.now.sh/upload_images/1874ff95cb0fe728e14c95e0dc913dae5e9e5d40e3989.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/1874ff95cb0fe728e14c95e0dc913dae5e9e5d40e3989.png?mw=700" alt="実行結果:成功" /></a></p> <p>ヨシ! ちゃんと、Oracle Instant Client 19.6が使われているようです。<br /> 初回は時間がかかりましたが、2回目からは1秒かからずに実行されます。時間がかかるのは、IP付与によるものかしら。</p> <h2 id="4. レイヤ用のデプロイパッケージを作成する"><a href="#4.+%E3%83%AC%E3%82%A4%E3%83%A4%E7%94%A8%E3%81%AE%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B">4. レイヤ用のデプロイパッケージを作成する</a></h2> <p>当初の目的である、cx_Oracleを使ってOracleDBへ接続するLambdaファンクションの作成は、ここまでで実現できました。</p> <p>ただ、インラインエディタでの編集ができないのはちょっといただけません。サイズが大きい原因はcx_OracleとOracle Instant Clientなので、この部分をcx_Oracleレイヤとして切り出せば良いのではないか?</p> <h3 id="cx_Oracleレイヤ用のデプロイパッケージ構成"><a href="#cx_Oracle%E3%83%AC%E3%82%A4%E3%83%A4%E7%94%A8%E3%81%AE%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E6%A7%8B%E6%88%90">cx_Oracleレイヤ用のデプロイパッケージ構成</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-layers.html">AWS Lambda レイヤー</a></p> <blockquote> <p>関数コードからランタイム依存関係を移動するには、それらをレイヤーに配置します。Lambda ランタイムには、関数コードを使用して、レイヤーに含まれるライブラリにアクセスできることを確認する、<code>/opt</code>ディレクトリのパスが含まれます。</p> <p>ライブラリをレイヤーに含めるには、ランタイムでサポートされているいずれかのフォルダにそれらを配置します。<br /> * <strong>Python</strong> – <code>python</code>、<code>python/lib/python3.8/site-packages</code>(サイトディレクトリ)</p> </blockquote> <p>レイヤ用のデプロイパッケージを作る場合、pyhon関連のライブラリは<code>python</code>配下に、配置します。なので、cx_Oracle関連のモジュールはこちらに置きます。</p> <p><a target="_blank" rel="nofollow noopener" href="https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-envvars.html">AWS Lambda 環境変数の使用</a></p> <blockquote> <p>予約されていない環境変数<br /> (略)<br /> <code>LD_LIBRARY_PATH</code> – システムライブラリのパス<br /> (<code>/lib64:/usr/lib64:$LAMBDA_RUNTIME_DIR:$LAMBDA_RUNTIME_DIR/lib:$LAMBDA_TASK_ROOT:$LAMBDA_TASK_ROOT/lib:/opt/lib</code>)。</p> </blockquote> <p>Oracle Instant Clientのモジュールについては、<code>LD_LIBRARY_PATH</code>で指定されたパスに配置する必要があります。Lambdaファンクションの設定で編集しても良いのですが、デフォルトで指定されたパス<code>/opt/lib</code>があるので、そちらを使いましょう。レイヤに含まれるライブラリは<code>/opt</code>配下に配置されるとのことなので、<code>lib</code>配下です。</p> <p>というわけで、レイヤ用デプロイパッケージの構成は下記の通りになります。作業ディレクトリは<code>/tmp/dbconnect_oracle_pack/layer</code>とします。</p> <pre><code>/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 </code></pre> <h3 id="レイヤ用デプロイパッケージ作成"><a href="#%E3%83%AC%E3%82%A4%E3%83%A4%E7%94%A8%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E4%BD%9C%E6%88%90">レイヤ用デプロイパッケージ作成</a></h3> <p>配置ができたらzip圧縮し、レイヤ用デプロイパッケージを作成します。ファイル名は何でも良いですが、<code>oraclelib.zip</code>としてみました。</p> <pre><code>$ cd /tmp/dbconnect_oracle_pack/layer $ zip -r --symlinks oraclelib.zip ./python ./lib </code></pre> <h2 id="5. Lambdaファンクションにレイヤを適用する"><a href="#5.+Lambda%E3%83%95%E3%82%A1%E3%83%B3%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AB%E3%83%AC%E3%82%A4%E3%83%A4%E3%82%92%E9%81%A9%E7%94%A8%E3%81%99%E3%82%8B">5. Lambdaファンクションにレイヤを適用する</a></h2> <p>レイヤを作るため、AWS Lambdaの左側メニューから「レイヤー」を選択し、レイヤの作成を行います。</p> <p>先程と同様に、S3経由でアップロードする形になります。また、「互換性のあるランタイム」には、作業環境であるAmazon Linux 2にインストールしたのと同じ Python3.7 を指定します。</p> <p><a href="https://crieit.now.sh/upload_images/731c2c355d8a949923189d7ba3ba8e7b5e9e7566a330b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/731c2c355d8a949923189d7ba3ba8e7b5e9e7566a330b.png?mw=700" alt="レイヤー作成設定" /></a></p> <p>レイヤの作成が完了したら、Lambdaファンクションにレイヤを適用します。</p> <p>作成したLambdaファンクションの画面から「レイヤーの追加」を行う事ができます。<br /> Lambdaファンクションのランタイムを「Python3.7」にした上で、「レイヤーの追加」を行ってください。</p> <p>選択できるレイヤーは、Lambdaファンクションで設定しているランタイムを「互換性のあるランタイム」で指定したレイヤーだけです。<br /> <a href="https://crieit.now.sh/upload_images/505ac0668a3516271d2a73ce9c2b569a5e9e765c9c085.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/505ac0668a3516271d2a73ce9c2b569a5e9e765c9c085.png?mw=700" alt="関数にレイヤーを追加" /></a></p> <p>こうしておくと、Lambdaファンクションそのものは、今回のサンプルの<code>dbconnect_oracle.py</code>だけにしても大丈夫です。インラインコード編集も可能となります。</p> <p><a href="https://crieit.now.sh/upload_images/7d20015f8e9283b81e552707518b66645e9e77bc2ef5e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7d20015f8e9283b81e552707518b66645e9e77bc2ef5e.png?mw=700" alt="OK" /></a></p> <p>動作も問題ありません。</p> <h1 id="後書き"><a href="#%E5%BE%8C%E6%9B%B8%E3%81%8D">後書き</a></h1> <p>一番わからなかったのは、Oracle Instant Client 19 だとどのlibをデプロイパッケージに含めればよいのか、ということです。今回記事に残そうとしたのも、それが要因です。</p> <p>それにしても、Oracle Instant Client だけで200MBオーバーなんだけど、そのうちOracle Instant ClientだけでLambdaのサイズ制限を超えてしまうのではないだろうか。</p> <h1 id="参考サイト"><a href="#%E5%8F%82%E8%80%83%E3%82%B5%E3%82%A4%E3%83%88">参考サイト</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/aidy91614/items/92987d547c318e0483f5">Lambda から RDS にアクセスする方法 (python)</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/ikeisuke/items/45ba4d88b887d793c444">AWS Lambda (Node.js-v4.3.2)からOracleに接続する(ORA-21561への対応)</a></li> <li><p><a target="_blank" rel="nofollow noopener" href="http://wiki.nikhil.io/Lambda,_Python,_and_Oracle/raw">Lambda, Python, and Oracle</a></p> <p>その他、検索に引っかかったフォーラムの記事とか色々参考にしました。</p></li> </ul>