题 在为Python项目构建Docker镜像时,如何避免重新安装包?


我的Dockerfile就像

FROM my/base

ADD . /srv
RUN pip install -r requirements.txt
RUN python setup.py install

ENTRYPOINT ["run_server"]

每次构建新映像时,都必须重新安装依赖项,这在我的区域可能会非常慢。

我想到的一种方式 cache 已安装的软件包将覆盖 my/base 图像的新图像如下:

docker build -t new_image_1 .
docker tag new_image_1 my/base

所以下次我使用这个Dockerfile构建时,我的/ base已经安装了一些软件包。

但是这个解决方案有两个问题:

  1. 并不总是可以覆盖基本图像
  2. 随着新图像在其上分层,基本图像变得越来越大

那么我可以用什么更好的解决方案来解决这个问题?

编辑##:

有关我机器上的泊坞窗的一些信息:

  test  docker version
Client version: 1.1.2
Client API version: 1.13
Go version (client): go1.2.1
Git commit (client): d84a070
Server version: 1.1.2
Server API version: 1.13
Go version (server): go1.2.1
Git commit (server): d84a070
  test  docker info
Containers: 0
Images: 56
Storage Driver: aufs
 Root Dir: /var/lib/docker/aufs
 Dirs: 56
Execution Driver: native-0.2
Kernel Version: 3.13.0-29-generic
WARNING: No swap limit support

65
2017-08-14 10:25


起源


完成图像构建后,是否删除了中间图像? - Regan
当然不是,但这是无关紧要的,因为当我重建图像时,我仍然是基于原始图像 my/base - satoru


答案:


尝试使用下面的Dockerfile构建。

FROM my/base

WORKDIR /srv
ADD ./requirements.txt /srv/requirements.txt
RUN pip install -r requirements.txt
ADD . /srv
RUN python setup.py install

ENTRYPOINT ["run_server"]

如果有一些变化 .(你的项目),docker skip pip install 通过使用缓存行。

Docker只运行 pip install 在编辑requirements.txt文件时进行构建。


我写得很简单 Hello, World! 程序。

$ tree
.
├── Dockerfile
├── requirements.txt
└── run.py   

0 directories, 3 file

# Dockerfile

FROM dockerfile/python
WORKDIR /srv
ADD ./requirements.txt /srv/requirements.txt
RUN pip install -r requirements.txt
ADD . /srv
CMD python /srv/run.py

# requirements.txt
pytest==2.3.4

# run.py
print("Hello, World")

以下是输出。

Step 1 : WORKDIR /srv
---> Running in 22d725d22e10
---> 55768a00fd94
Removing intermediate container 22d725d22e10
Step 2 : ADD ./requirements.txt /srv/requirements.txt
---> 968a7c3a4483
Removing intermediate container 5f4e01f290fd
Step 3 : RUN pip install -r requirements.txt
---> Running in 08188205e92b
Downloading/unpacking pytest==2.3.4 (from -r requirements.txt (line 1))
  Running setup.py (path:/tmp/pip_build_root/pytest/setup.py) egg_info for package pytest
....
Cleaning up...
---> bf5c154b87c9
Removing intermediate container 08188205e92b
Step 4 : ADD . /srv
---> 3002a3a67e72
Removing intermediate container 83defd1851d0
Step 5 : CMD python /srv/run.py
---> Running in 11e69b887341
---> 5c0e7e3726d6
Removing intermediate container 11e69b887341
Successfully built 5c0e7e3726d6

我只更新run.py并尝试再次构建。

# run.py
print("Hello, Python")

以下是输出。

Sending build context to Docker daemon  5.12 kB
Sending build context to Docker daemon 
Step 0 : FROM dockerfile/python
---> f86d6993fc7b
Step 1 : WORKDIR /srv
---> Using cache
---> 55768a00fd94
Step 2 : ADD ./requirements.txt /srv/requirements.txt
---> Using cache
---> 968a7c3a4483
Step 3 : RUN pip install -r requirements.txt
---> Using cache
---> bf5c154b87c9
Step 4 : ADD . /srv
---> 9cc7508034d6
Removing intermediate container 0d7cf71eb05e
Step 5 : CMD python /srv/run.py
---> Running in f25c21135010
---> 4ffab7bc66c7
Removing intermediate container f25c21135010
Successfully built 4ffab7bc66c7

如上所示,docker使用构建缓存。我这次更新了requirements.txt。

# requirements.txt

pytest==2.3.4
ipython

以下是输出。

Sending build context to Docker daemon  5.12 kB
Sending build context to Docker daemon 
Step 0 : FROM dockerfile/python
---> f86d6993fc7b
Step 1 : WORKDIR /srv
---> Using cache
---> 55768a00fd94
Step 2 : ADD ./requirements.txt /srv/requirements.txt
---> b6c19f0643b5
Removing intermediate container a4d9cb37dff0
Step 3 : RUN pip install -r requirements.txt
---> Running in 4b7a85a64c33
Downloading/unpacking pytest==2.3.4 (from -r requirements.txt (line 1))
  Running setup.py (path:/tmp/pip_build_root/pytest/setup.py) egg_info for package pytest

Downloading/unpacking ipython (from -r requirements.txt (line 2))
Downloading/unpacking py>=1.4.12 (from pytest==2.3.4->-r requirements.txt (line 1))
  Running setup.py (path:/tmp/pip_build_root/py/setup.py) egg_info for package py

Installing collected packages: pytest, ipython, py
  Running setup.py install for pytest

Installing py.test script to /usr/local/bin
Installing py.test-2.7 script to /usr/local/bin
  Running setup.py install for py

Successfully installed pytest ipython py
Cleaning up...
---> 23a1af3df8ed
Removing intermediate container 4b7a85a64c33
Step 4 : ADD . /srv
---> d8ae270eca35
Removing intermediate container 7f003ebc3179
Step 5 : CMD python /srv/run.py
---> Running in 510359cf9e12
---> e42fc9121a77
Removing intermediate container 510359cf9e12
Successfully built e42fc9121a77

而docker不使用构建缓存。如果它不起作用,请检查您的docker版本。

Client version: 1.1.2
Client API version: 1.13
Go version (client): go1.2.1
Git commit (client): d84a070
Server version: 1.1.2
Server API version: 1.13
Go version (server): go1.2.1
Git commit (server): d84a070

87
2017-08-14 12:01



这似乎不起作用,因为每当docker看到一个 ADD 指令,缓存无效。 - satoru
我添加简单的例子并解释它如何适用于我的答案。 - nacyot
我不确定为什么它不起作用。但是requirements.txt没有任何变化(<src> on ADD ./requirements.txt /srv/requirements.txt),然后docker必须使用缓存。看到 添加seciton 在Dockerfile文档上。 - nacyot
是的,如果requirements.txt没有改变,它将使用缓存。但是,如果requirements.txt发生更改,则会下载所有要求。有没有什么办法可以将pip缓存卷挂载到docker容器中以从缓存中加载? - Jitu
这个答案的关键是你添加requirements.txt(ADD requirements.txt /srv 在你运行pip之前(RUN pip install -r requirements.txt),并添加所有其他文件 后 跑点。因此,它们应按以下顺序排列:(1) ADD requirements.txt /srv; (2) RUN pip install -r requirements.txt; (3) ADD . /srv - engelen


为了最大限度地减少网络活动,您可以指出 pip 到主机上的缓存目录。

运行您的docker容器,将主机的pip缓存目录bind挂载到容器的pip缓存目录中。 docker run 命令应如下所示:

docker run -v $HOME/.cache/pip/:/root/.cache/pip image_1

然后在您的Dockerfile中安装您的需求作为其中的一部分 ENTRYPOINT 声明(或 CMD 声明)而不是作为 RUN 命令。这很重要,因为(正如评论中所指出的)在图像构建期间(当时 RUN 语句被执行)。 Docker文件应该如下所示:

FROM my/base

ADD . /srv

ENTRYPOINT ["sh", "-c", "pip install -r requirements.txt && python setup.py install && run_server"]

可能最好将主机系统的默认pip目录用作缓存(例如 $HOME/.cache/pip/ 在Linux或Linux上 $HOME/Library/Caches/pip/ 在OSX上),就像我在示例中建议的那样 docker run 命令。


7
2017-10-29 15:19



不是OP在他的用例中寻找的东西,但如果你制作一个构建服务器这是一个好主意 - oden


我发现更好的方法是将Python site-packages目录添加为卷。

services:
    web:
        build: .
        command: python manage.py runserver 0.0.0.0:8000
        volumes:
            - .:/code
            -  /usr/local/lib/python2.7/site-packages/

这样我就可以只需要安装新的库而无需进行完全重建。

编辑:忽略这个答案, jkukul的 上面的回答对我有用。我的意图是缓存 站点包 夹。那看起来更像是:

volumes:
   - .:/code
   - ./cached-packages:/usr/local/lib/python2.7/site-packages/

缓存下载文件夹虽然很简洁。这也缓解了车轮,因此它正确地完成了任务。


-4
2017-09-07 04:30



当您尝试在另一台计算机上构建此dockerfile时会发生什么。这不是一个可持续的解决方案。 - Aaron McMillin
真的很困惑,这竟然是马车,我不确定为什么。你能提供更多细节吗? - jaywhy13
您的docker镜像取决于主机系统的状态。这使得docker的大部分功能都无效。应该在其中安装图像所需的一切。使用Dockerfile安装所有依赖项。如果你想避免每次从jkukul构建答案时重新下载软件包,那么就可以使用pip缓存了。 - Aaron McMillin
灯泡刚刚离开谢谢。我实际上是尝试从VM而不是主机安装site-packages目录。相当疏忽。我认为在精神上我试图像jkulkul建议的那样做。谢谢你的清晰度! - jaywhy13