Oracleに接続可能ななるべく小さいpython実行環境のDocker imageをビルドする
今回のお題
接続先がOracleなシステムでPythonが実行可能なコンテナイメージを作りたい。
小さいイメージは正義。
tl;dr
- Oracle公式のイメージを利用する。
- Docker multi stage buildを活用する。
- alpineは苦行。
完成形は最後のほうにあります。
要件
MUST
- 接続先は既存システムのOracle。変更不可。
- 実行環境はパブリッククラウド内。
- コードはgit管理。
- 開発言語は任意。(今回はData Classなどの機能を使ってみたかったのでpython3.7とした)
WANT
- 環境構築は手軽にやりたい。
- デプロイは高速にしたい。
構築までの道のり
まずは素朴に
ローカル確認
Dockernizeする前に、ローカルでの動作を確認します。
pythonからOracleに接続するための公式のライブラリは cx_Oracleです。 *1*2
pipenvで入れましょう。*3
$ pipenv install $ pipenv install cx_Oracle $ cat Pipfile [[source]] name = "pypi" url = "https://pypi.org/simple" verify_ssl = true [dev-packages] [packages] cx-oracle = "*" [requires] python_version = "3.7"
Oracleインスタンスの構築
接続先のOracleインスタンスもDockerで立てておきます。*4
macOSでOracle Database使いたい - Qiita
上記に従い本体のバイナリを入手します。バージョンは 19.3.0 Standard Edition 2 としました。 *5
$ git clone git@github.com:oracle/docker-images.git $ cd docker-images/OracleDatabase/SingleInstance/dockerfiles $ mv /your/download/path/to/LINUX.X64_193000_db_home.zip 19.3.0/ $ ./buildDockerImage.sh -i -s -v 19.3.0 # 完了まで10分ぐらいかかる
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE oracle/database 19.3.0-se2 91da406c90ef 23 seconds ago 6.65GB oraclelinux 7-slim 874477adb545 6 weeks ago 118MB
無事イメージが作成されました。
とくにカスタマイズはせず起動します。
$ mkdir -p $HOME/tmp/oradata $ docker run --name docker_oracle \ -p 1521:1521 -p 5500:5500 \ -v $HOME/tmp/oradata:/opt/oracle/oradata \ oracle/database:19.3.0-se2 # ..snip.. ######################### DATABASE IS READY TO USE! #########################
起動しました。コンテナが立ち上がっていることが確認できます。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 6c7d9db8a2bb oracle/database:19.3.0-se2 "/bin/sh -c 'exec $O…" 16 minutes ago Up 16 minutes (healthy) 0.0.0.0:1521->1521/tcp, 0.0.0.0:5500->5500/tcp docker_oracle
せっかくなのでコンテナ内からOracleに接続してみましょう。
$ docker exec -it docker_oracle /bin/sh $ sqlplus /nolog $ SQL> connect sys/cO11MX9C5Bk=1@ORCLCDB as sysdba Connected. SQL> select SYSDATE from DUAL; SYSDATE --------- 26-SEP-19 SQL>
実行
さて先程構築したOracleに接続してSYSDATEをSELECTする単純なpythonを書きます。
#!/usr/bin/env python3 import cx_Oracle print(cx_Oracle.clientversion()) # パスワードは勝手に払い出される conn = cx_Oracle.connect(user='sys', password='cO11MX9C5Bk=1', dsn='localhost:1521/ORCLCDB', mode=cx_Oracle.SYSDBA) curs = conn.cursor() curs.execute('select SYSDATE from DUAL') row = curs.fetchone() print(f'Hello Oracle ! {row[0]}')
このまま実行するとクライアントライブラリがないエラーがでます。
$ pipenv run ./main.py Traceback (most recent call last): File "./main.py", line 5, in <module> print(cx_Oracle.clientversion()) cx_Oracle.DatabaseError: DPI-1047: Cannot locate a 64-bit Oracle Client library: "dlopen(libclntsh.dylib, 1): image not found". See https://oracle.github.io/odpi/doc/installation.html#macos for help
Oracleの公式*7からライブラリをダウンロードして任意の場所に展開します。
今回は $HOME/opt/oracle/instantclient_19_3
としました。 LD_LIBRARY_PATH
も通しておきましょう。
$ pipenv run ./main.py (19, 3, 0, 0, 0) Hello Oracle ! 2019-09-26 02:16:43
無事動きました。ここまで前フリでした。
Dockerfileを書く
ようやくDockerに手を付けます。
上記のpython環境をDockerfileで記述します。
まずはpython3.7およびpipenvの入ったイメージを作成します。
ベースイメージは python:3.7-slim-buster
としました。*8*9
0版
## # Building runtime image. ## FROM python:3.7-slim-buster WORKDIR /usr/local/app ADD Pipfile . ADD Pipfile.lock . ADD main.py . RUN apt-get update -y \ && apt-get -y install \ && pip install --no-cache-dir pipenv \ && pipenv install --deploy ENTRYPOINT ["pipenv", "run"]
$ docker build . -t cx_oracle-slim # 出力は省略 $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE cx_oracle-slim latest e7b3dc4bafad About a minute ago 239MB
この時点でのイメージサイズは 239MB です。
課題と対応
さてこの状態ではinstantclientが入っていないため、当然実行時にエラーとなります。
docker run -it cx_oracle-slim ./main.py Traceback (most recent call last): File "./main.py", line 5, in <module> print(cx_Oracle.clientversion()) cx_Oracle.DatabaseError: DPI-1047: Cannot locate a 64-bit Oracle Client library: "libclntsh.so: cannot open shared object file: No such file or directory". See https://oracle.github.io/odpi/doc/installation.html#linux for help
ではDockerfileでinstantclientを取得できるようにしたいところですが、Oracleのリソース取得にはライセンスの同意が必要です。
つまり、wgetやcurlでの取得が面倒です。
対処としては以下のような方策が考えられます。
instantclientのzipをgitにコミットしておきイメージビルド時に展開する。一番手軽ではありますが、gitに大きなバイナリ*10をコミットするのは望ましくありません。
instantclientのzipをコードとは異なるgitリポジトリにコミットしておく。コードとは異なるリポジトリにzipのみをコミットしておき、ビルド時にcloneしておく方策です。 コードのリポジトリはキレイになりますが、どこにリポジトリを作るのか、バージョンアップはどうするのか、という将来の課題を残します。*11
Oracle公式イメージをベースにしてpython環境を構築する。Oracle公式がgithubで公開しているイメージ
oraclelinux:x-slim
*12を利用すると、ライセンスの同意などを回避できる模様。素のイメージは368MBです。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE oracle/instantclient 19 bdf41080d895 4 seconds ago 368MB
1版
運用保守を鑑みて、1,2は捨て、3の方針としました。
このときのDockerfileは以下です。
FROM oraclelinux:7-slim ARG release=19 ARG update=3 RUN yum -y install oracle-release-el7 \ && yum-config-manager --enable ol7_oracle_instantclient \ && yum -y install \ oracle-instantclient${release}.${update}-basic \ oracle-instantclient${release}.${update}-devel \ && rm -rf /var/cache/yum WORKDIR /usr/local/app ADD Pipfile . ADD Pipfile.lock . ENV LC_ALL=en_US.UTF-8 RUN yum update -y \ && yum -y install \ libaio \ python36 \ && rm -rf /var/cache/yum/* \ && yum clean all \ && pip3 install --no-cache-dir pipenv \ && pipenv install --deploy ADD main.py . ENTRYPOINT ["pipenv", "run"]
Dockerで立てたOracleサーバと通信するためにdocker run時にlinkしてやる必要があります。
main.pyの接続先名を localhost
から ora_server
に置き換えたのち実行します。
$ docker run -it --link docker_oracle:ora_server cx_oracle-slim ./main.py (19, 3, 0, 0, 0) Hello Oracle ! 2019-09-26 04:51:55
無事当初の目的である、Docker内からOracleと通信が可能になりました。
課題
しかしながら、oraclelinux自体がCentOSをベースにしているようで、yumでは2019-09-26現在python37は入りません。
python36を妥協して使うにしても出来上がったイメージのサイズは 685MB となかなかなサイズとなりました。
pythonのライブラリが増えてきた時を考えるともう少しダイエットさせたい気持ちがあります。
軽量化の取り組み
さてようやく本題です。
満たしたい事項を箇条書きにします。
- python37を使いたい
- instantclientのバイナリを自前で管理したくない
- イメージサイズは小さくしたい
上記を解決するために、Dockerのmulti-stage buildsを利用します。
疲れてきたのでいきなり最終的なDockerを示します。
最終版
## # Buidling intermediate image. # instantclientのみほしいが、wgetなどではユーザ認証が必要になるため、公式のイメージから拝借する ## FROM oraclelinux:7-slim AS oracle-client ARG release=19 ARG update=3 RUN yum -y install \ oracle-release-el7 \ && yum-config-manager --enable ol7_oracle_instantclient \ && yum -y install \ oracle-instantclient${release}.${update}-basic \ oracle-instantclient${release}.${update}-devel \ && rm -rf /var/cache/yum ## # Building runtime image. ## FROM python:3.7-slim-buster COPY --from=oracle-client /usr/lib/oracle /usr/lib/oracle ## XXX: バージョン指定がバラけてる ARG release=19 ARG update=3 ENV LD_LIBRARY_PATH=/usr/lib/oracle/${release}.${update}/client64/lib WORKDIR /usr/local/app ADD Pipfile . ADD Pipfile.lock . RUN apt-get update -y \ && apt-get -y install \ --no-install-recommends \ libaio1 \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* \ && pip install --no-cache-dir pipenv\ && pipenv install --system --deploy \ && pip uninstall -y \ pipenv \ virtualenv-clone \ virtualenv ADD main.py . ENTRYPOINT ["./main.py"]
最終版のイメージサイズは426MBと、第1版の40%減となりました。
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
cx_oracle-slim latest bd5a907578db 6 seconds ago 426MB
トピック
以下最終版に至るまでのトピックです。
まず oraclelinux:7-slim
から実行に必要な必要な資材を特定しました。
oracle-instantclient${release}.${update}-xxx
は /usr/lib/oracle
配下にyumでインストールされるようでした。
oraclelinux:7-slim
を AS oracle-client
としておき、こちらを COPY --from=/usr/lib/oracle
でpython37のベースイメージに持ってきます。
この時点でイメージサイズは459MBと、第1版よりも200MB以上小さくなりました。また、python3.7が使える環境です。
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
cx_oracle-slim latest 9377e5551843 6 seconds ago 459MB
/usr/lib/oracle
配下のファイルは全体で225MB、cx_Oracleにはほぼ必須のもので、削れるにしてもojdbc8.jarなどの4MBでしたのでDockerfileの可読性を踏まえてこれ以上は追求しないこととしました。
次にapt, pipenv周りを削ります。
apt-getでは --no-install-recommends
を付与することで余計なパッケージのインストールを抑止できますが、今回は効果がありませんでした。*13
pipenvは仮想環境を構築しますが、今回はDocker内のためシステムにインストールされるPythonは単一のバージョンです。したがって仮想環境用のバイナリをコピーする必要はありません。
--system
を付与することで、pipenvに書かれたパッケージをsystemにインストールしてくれます。 さらに、pipenvはinstall実行後に不要となりますので、pipからも削除可能です。これで更に30MB削減です。*14
以上です。
*1:https://oracle.github.io/python-cx_Oracle/
*2:プロダクションではSQLAlchemyを経由するのが一般的だと思うが今回は使わない。
*3:pipenvは各自入れておいてください。
*4:Oracle Cloudで楽に済ませようとしたらユーザ登録でカード決済がなぜか通らなくて泣いた。
*5:https://www.oracle.com/database/technologies/oracle-database-software-downloads.html
*6:小一時間かかった…。
*7:Instant Client for macOS (Intel x86)
*8:The best Docker base image for your Python application (July 2019)
*9:当初 `python:3.7-alpine` で試みましたがglibc周りで断念。
*10:zipは75MB程度ある
*11:経験として誰も管理せず腐る傾向にある。
*12:https://github.com/oracle/docker-images/blob/master/OracleInstantClient/dockerfiles/19/Dockerfile
*13:今後必要なパッケージが増える際に効いてくるはずです