由来

Multi-stage builds are a new feature in Docker 17.05

官方文档讲述多步构建的示例是一个Go程序,Go程序本身就可以编译成可执行文件,所以使用多步构建很容易。这么好玩的功能必须要用Python实践一下咯。

Demo

Demo目录

tornado-multi-stage/
    docker-compose.yml
    Dockerfile
    requirements.txt
    run.py

requirments.txt:依赖Tornado

tornado==4.5.1

run.py:程序入口

程序实现的功能:接收到GET请求后返回Hello World

import tornado.ioloop
import tornado.web


class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello World")


def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])


if __name__ == "__main__":
    app = make_app()
    app.listen(8888, '0.0.0.0')
    tornado.ioloop.IOLoop.current().start()

docker-compose.yml

没做什么特别的事情,就做了一下端口映射

version: "3"

services:
  web:
    build: .
    ports:
      - 8888:8888

Dockerfile:重点

FROM python:3.5 as builder
WORKDIR /app/
ADD . /app/
ADD https://github.com/pyinstaller/pyinstaller/archive/develop.zip /app/pyinstaller-develop.zip
RUN pip install -r requirements.txt && \
    pip install pyinstaller-develop.zip
RUN pyinstaller --hidden-import=tornado --onefile -d -y run.py

FROM frolvlad/alpine-glibc
WORKDIR /app/
COPY --from=builder /app/dist/run .
ENTRYPOINT ["./run"]

解释一下:

  • 为了方便浏览,第八行空白行将Dockerfile分隔成了两部分,上面为第一次构建,下面为第二次构建
  • 第一次构建
    • python:3.5作为基础镜像,安装好依赖后,利用pyinstaller
    • 打包Tornado程序打包后的可执行文件路径为/app/dist/run
  • 第二次构建
    • frolvlad/alpine-glibc作为基础镜像,从第一次构建的镜像中把/app/dist/run文件复制过来

效果

运行效果

可以通过浏览器正常访问到应用

镜像体积

可以将一个体积700M的镜像,缩小为20M docker images

打包工具

pyinstaller的官方说明 > Works out-of-the-box with any Python version 2.7 / 3.3-3.5

  • 踩坑第一步
    我电脑系统是macOS 10.12.5,安装的是Python 3.6.2,安装了PyPI上的pyinstaller,发现本地打包会报错。于是后面我直接用GitHub上最新版本,结果是能成功打包,并且可执行文件可以运行,应用也可以正常工作。
  • 踩坑第二步
    我用python:3.6.2的镜像+最新版pyinstaller,在容器里能打包成功,但是可执行文件运行不了。
  • 踩坑第三步
    只好把基础镜像换成了python:3.5,这样打包出来的可执行文件正常了。

运行环境

第二步构建的基础镜像,决定着可执行文件能不能跑起来

  • 踩坑第一步
    一开始为了镜像最小化,就用了alpine:latest镜像,发现可执行文件根本跑不起来,会报错
web_1  | standard_init_linux.go:187: exec user process caused "no such file or directory"
torandomultistage_web_1 exited with code 1

通过公司大佬指导后发现,是因为alpine:latest镜像里缺少glibc

BTW

折腾需谨慎。Python应用打包成可执行文件放在一个小镜像里面跑,很不稳(手动doge)。出个错啥的都不好排查。没必要为了弄一个超小镜像给自己挖个大坑。