构建Python镜像(连接PostgreSQL与Oracle数据库 / 体积优化)

Published: 2019-09-11

Tags: Docker Python

本文总阅读量

概要

本篇博客记录 Python 应用如何构建 Docker 镜像,主要两点内容

  1. 如何构造连接两个数据库的环境
  2. 如何尽可能减小制作出镜像的体积

目录结构说明

.
├── deps
│   ├── instantclient-basic-linux.x64-19.3.0.0.0dbru.zip
├── Dockerfile
├── Pipfile
├── Pipfile.lock
├── sources.list
├── test_oracle.py
└── test_postgresql.py

deps 是一个文件夹,里面的 oracle instanceclient 可以在这两个地方下载

Oracle 官网:https://www.oracle.com/database/technologies/instant-client/downloads.html

Github:https://github.com/epoweripione/oracle-instantclient-18

Pipfile 与 Pipfile.lock 是 pipenv 项目的依赖文件,跟 packages.json 一样的作用

[[source]]
name = "pypi"
url = "https://pypi.tuna.tsinghua.edu.cn/simple/"
verify_ssl = true

[dev-packages]

[packages]
psycopg2 = "*"
cx-oracle = "*"

[requires]
python_version = "3.7"

以上是 Pipfile 内容,添加新软件时候,以往用 pip install flask 可以使用 pipenv install flask,执行脚本的时候使用 pipenv run python3 1.py

关于 Pipenv,可以参考:https://github.com/pypa/pipenv

sources.list 是debian源文件,放在 /etc/apt/ 目录下

阿里源

deb http://mirrors.aliyun.com/debian/ buster main non-free contrib
deb http://mirrors.aliyun.com/debian-security buster/updates main
deb http://mirrors.aliyun.com/debian/ buster-updates main non-free contrib
deb http://mirrors.aliyun.com/debian/ buster-backports main non-free contrib

清华源

deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster main contrib non-free
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster-updates main contrib non-free
deb https://mirrors.tuna.tsinghua.edu.cn/debian/ buster-backports main contrib non-free
deb https://mirrors.tuna.tsinghua.edu.cn/debian-security buster/updates main contrib non-free

test_oracle.py 与 test_postgresql.py 都是最基础的连接两个数据库的代码

# 可以获取 Oracle 版本号
conn.version

# 可以获取 PostgreSQL 版本号
cur = conn.cursor("public")
cur.execute("SELECT version();")
version = cur.fetchone()[0]

Python 连接数据库

借助 psycopg2cx_oracle 模块,Python 可以连接到 PostgreSQL 与 Oracle

之前在 《Fedora 29 系统使用 Python3 连接Oracle 11.2数据库》 整理过Instance配置

构建镜像

普通的 Dockerfile

FROM python:3.7.4

WORKDIR /opt/myproj

# 一些文件复制到镜像
COPY Pipfile ./
COPY Pipfile.lock ./
COPY deps deps/
COPY sources.list ./

# 设置Oracle客户端环境变量
ENV TNS_ADMIN=/opt/oracle/instantclient_19_3
ENV LD_LIBRARY_PATH=/opt/oracle/instantclient_19_3

# * libaio libnsl: for cx_Oracle
# * libpq: required for psycopg2

ENV PACKAGES="\
  unzip \
  libaio1 \
  libpq-dev \
"

ENV BUILD_PACKAGES="\
  apt-utils \
  build-essential \
"

# 修改Debian源为国内的地址
# 安装依赖并配置Oracle客户端
RUN echo \
  && mv /etc/apt/sources.list /etc/apt/sources.list.bak && mv /opt/myproj/sources.list /etc/apt/ \
  && apt-get update -y \
  && apt-get install -y --no-install-recommends $PACKAGES \
  && mkdir /opt/oracle && cd /opt/oracle \
  && mv /opt/myproj/deps/instantclient-basic-linux.x64-19.3.0.0.0dbru.zip /opt/oracle/client.zip \
  && unzip client.zip && rm client.zip \
  && cd /opt/oracle/instantclient_19_3 \
  && ln -s /lib/x86_64-linux-gnu/libnsl.so.1 /usr/lib/libnsl.so.1 \
  && rm -rf /var/lib/apt/lists/* \
  && echo

# 此处使用 pipenv 包来管理依赖
# 保证项目依赖的版本号稳定
RUN pip3 install --no-cache-dir pipenv -i https://pypi.tuna.tsinghua.edu.cn/simple
RUN pipenv install

CMD echo "hello world!"

生成镜像

$ sudo docker build -t db-conn .

启动容器

$ sudo docker run --rm -it -v $PWD:/opt/myproj db-conn
hello world!

不带任何参数启动容器,那么会执行在 dockerfile 写的默认命令

使用镜像执行本地脚本

$ sudo docker run --rm -it -v $PWD:/opt/myproj db-conn-big pipenv run python3 test_oracle.py

PS:此处把当前目录映射到容器的 /opt/myproj 目录,使用容器里的 pipenv 执行脚本,执行完成后容器自动销毁

容器的好处之一就是可以把各种运行时做到镜像里,避免污染物理机的环境和物理机差异导致应用报错

镜像很大

上边制作的镜像已经集成了Python 使用 Oracle 和 PostgreSQL 的环境,还存在一个优化问题,即镜像应该在稳定并实现功能的前提下尽可能小,目前存在的问题的,制作出来的镜像很大,本身 python:3.7.4 就有 929MB,加入了 228MB (解压缩后的)的 instantclient_19_3,此时构建出来镜像有 1.29GB 大小

现在镜像导出后,压缩成 .tar.bz2 压缩包也有 495M 大小,接下来着手减小镜像体积

减小镜像体积

要减小镜像的体积,就要考虑哪些部分可以优化

  1. 基础镜像
  2. 构建使用的工具集(GCC等)

基础镜像部分,3.7.4-alpine3.10 看起来很吸引人,只有 98.7MB,但是因为Alpine使用的是 musl c library 替代的 glibc,所以一些模块不能正常工作,我在测试的时候,在 Alpine 下使用 Oracle 客户端工具就一直报错

由于 Alpine 精简了不少内容,如果想要制作一个稳定且后续可能添加很多第三方模块的应用,那么它不是一个好的基础镜像选择

红帽系的镜像总是很大,相比之下,Debian系的小很多, 3.7.4-buster 镜像只有 179MB

一些模块在使用的时候,需要进行编译,那么系统就需要有GCC等一系列构建工具,这些工具在编译完模块后就不再需要了,那么可以把这些工具移除

可能很容易想到使用 remove 来移除这些软件,但是要移除安装这些软件时候依赖安装的软件好像不太容易

其实,构建和运行时是两个镜像就可以很好解决这个问题了,构建镜像用来安装编译工具,把模块都编译完成,然后把模块复制到新的镜像中,构建镜像就可以删除了,新的镜像只需要安装必要依赖就可以

两步构建的 dockerfile

FROM python:3.7-slim-buster as base

FROM base as builder

WORKDIR /opt/myproj

# Bundle APP files
COPY Pipfile ./
COPY Pipfile.lock ./
COPY sources.list ./
COPY pipenv-require.txt ./
COPY sources.list ./

# Change Mirrors
RUN mv /etc/apt/sources.list /etc/apt/sources.list.bak && mv /opt/myproj/sources.list /etc/apt/

# * libaio libnsl: for cx_Oracle
# * libpq: required for psycopg2

ENV PACKAGES="\
  libaio1 \
  libpq-dev \
"

ENV BUILD_PACKAGES="\
  apt-utils \
  build-essential \
"

RUN echo \
  && apt-get update -y \
  && apt-get install -y --no-install-recommends $BUILD_PACKAGES \
  && apt-get install -y --no-install-recommends $PACKAGES \
  && rm -rf /var/lib/apt/lists/* \
  && echo

RUN pip3 install --no-cache-dir pipenv -i https://pypi.tuna.tsinghua.edu.cn/simple
RUN pipenv run pip install -r pipenv-require.txt --target /packages

# === Final Image ===

FROM base

WORKDIR /opt/myproj

ENV TNS_ADMIN=/opt/oracle/instantclient_19_3
ENV LD_LIBRARY_PATH=/opt/oracle/instantclient_19_3

ENV PACKAGES="\
  unzip \
  libaio1 \
  libpq-dev \
"

# 复制之前镜像中生成的包到最终镜像的包默认安装路径
COPY --from=builder /packages /usr/local/lib/python3.7/site-packages
# 将Oracle Instance复制到镜像
COPY deps deps/
COPY sources.list ./

RUN echo \
  && mv /etc/apt/sources.list /etc/apt/sources.list.bak && mv /opt/myproj/sources.list /etc/apt/ \
  && apt-get update -y \
  && apt-get install -y --no-install-recommends $PACKAGES \
  && mkdir /opt/oracle && cd /opt/oracle \
  && mv /opt/myproj/deps/instantclient-basic-linux.x64-19.3.0.0.0dbru.zip /opt/oracle/client.zip \
  && unzip client.zip && rm client.zip \
  && cd /opt/oracle/instantclient_19_3 \
  && ln -s /lib/x86_64-linux-gnu/libnsl.so.1 /usr/lib/libnsl.so.1 \
  && rm -rf /var/lib/apt/lists/* \
  && echo

CMD echo "hello world!"

生成镜像

$ pipenv lock -r > pipenv-require.txt

$ sudo docker build -t db-conn .

这里要说明的是,构建时使用 pipenv 是因为能保证每次安装包都能保证依赖相同

再最终的镜像中,直接使用 python3 运行就可以了,因为一个镜像已经是一个经过控制的运行时了

$ sudo docker run --rm -it -v $PWD:/opt/myproj db-conn python3 test_oracle.py

经过修改基础镜像并移除构建工具,现在导出后的.tar.bz2 压缩包有 200M 大小,比对优化之前的 495M 减小了百分之六十的大小