在运行时向Docker镜像添加pip要求

14

我希望能够为自己创建的Docker镜像添加一些额外的要求。我的策略是使用一个CMD命令从Dockerfile构建镜像,该命令将使用运行时挂载的卷执行“pip install -r”命令。

这是我的Dockerfile:

FROM ubuntu:14.04

RUN apt-get update
RUN apt-get install -y python-pip python-dev build-essential 
RUN pip install --upgrade pip

WORKDIR /root

CMD ["pip install -r /root/sourceCode/requirements.txt"]

有了Dockerfile,我构建了镜像:

sudo docker build -t test .

最后,我尝试使用以下命令附加我的新需求:

sudo docker run -v $(pwd)/sourceCode:/root/sourceCode -it test /bin/bash

我的本地文件夹“sourceCode”中有一个有效的requirements.txt文件(它仅包含一个值为“gunicorn”的行)。 当我得到提示时,我可以看到要求文件在那里,但如果我执行pip freeze命令,则不会列出gunicorn软件包。

为什么requirements.txt文件被正确附加,但pip命令无法正常工作?

3个回答

8

简述

pip命令无法运行,因为您告诉Docker运行的是/bin/bash

docker run -v $(pwd)/sourceCode:/root/sourceCode -it test /bin/bash
                                                              ^
                                                             here

更详细的解释

容器的默认 ENTRYPOINT/bin/sh -c。你不需要在 Dockerfile 中覆盖它,因此它保持不变。默认的 CMD 指令可能是空的。但你在 Dockerfile 中覆盖了它。当你运行时 (为了简洁,请忽略卷)

docker run -it test

容器内实际执行的是

/bin/sh -c pip install -r /root/sourceCode/requirements.txt

非常直接,看起来启动容器时会运行pip

现在让我们看一下您用于启动容器的命令(再次忽略卷)

docker run -v -it test /bin/bash

容器内实际执行的是什么

/bin/sh -c /bin/bash

您在Dockerfile中指定的CMD参数将被您在命令行中指定的COMMAND覆盖。请注意,docker run命令采取此形式
docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]

进一步阅读

  1. 这个答案非常简明地解释了CMDENTRYPOINT指令的作用。

    ENTRYPOINT指定了容器启动时总是要执行的命令。

    CMD指定了要传递给ENTRYPOINT的参数。

  2. 这篇博客文章介绍了ENTRYPOINTCMD指令之间的区别,值得一读。


感谢@ROMANARMY的详细解释...现在我更好地理解了docker在运行时如何接收参数。 - ralvarez

7
您可以将最后一个语句即CMD更改为以下内容。
--在下面的语句中指定pip位置的绝对路径
CMD ["/usr/bin/pip", "install", "-r", "/root/sourceCode/requirements.txt"]

更新:根据评论添加额外的答案。

必须注意一件事情,如果需要带有附加要求的自定义图像,则应将其作为图像的一部分而不是在运行时执行。

使用以下基础图像进行测试:

docker pull colstrom/python:legacy

因此,应使用Dockerfile的RUN命令来运行安装软件包。而CMD应该用于在容器内部实际运行的应用程序进程。
通过运行以下命令检查基本映像是否具有任何pip软件包,并且结果为空
docker run --rm --name=testpy colstrom/python:legacy /usr/bin/pip freeze

这里有一个简单的示例以演示相同的内容:
Dockerfile。
FROM colstrom/python:legacy
COPY requirements.txt /requirements.txt
RUN ["/usr/bin/pip", "install", "-r", "/requirements.txt"]
CMD ["/usr/bin/pip", "freeze"]

requirements.txt

selenium

使用pip包构建镜像 希望您知道将Dockerfile和requirements.txt文件放置在新目录中。

D:\dockers\py1>docker build -t pypiptest .
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM colstrom/python:legacy
 ---> 640409fadf3d
Step 2 : COPY requirements.txt /requirements.txt
 ---> abbe03846376
Removing intermediate container c883642f06fb
Step 3 : RUN /usr/bin/pip install -r /requirements.txt
 ---> Running in 1987b5d47171
Collecting selenium (from -r /requirements.txt (line 1))
  Downloading selenium-3.0.1-py2.py3-none-any.whl (913kB)
Installing collected packages: selenium
Successfully installed selenium-3.0.1
 ---> f0bc90e6ac94
Removing intermediate container 1987b5d47171
Step 4 : CMD /usr/bin/pip freeze
 ---> Running in 6c3435177a37
 ---> dc1925a4f36d
Removing intermediate container 6c3435177a37
Successfully built dc1925a4f36d
SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.

现在运行这个镜像 如果你没有传递任何外部命令,那么容器将从 CMD 中获取命令,该命令只显示 pip 包的列表。在这种情况下,是 selenium

D:\dockers\py1>docker run -itd --name testreq pypiptest
039972151eedbe388b50b2b4cd16af37b94e6d70febbcb5897ee58ef545b1435

D:\dockers\py1>docker logs testreq
selenium==3.0.1

所以,以上显示包已经成功安装。
希望这对你有帮助。

谢谢@Rao,但是这个更改没有产生任何结果。在运行新镜像之后,gunicorn仍然不在pip freeze显示的软件包中。 - ralvarez
我还注意到,如果我不使用-it /bin/bash选项运行图像sudo docker run -v $(pwd)/sourceCode:/root/sourceCode test,控制台会出现以下消息`Downloading/unpacking gunicorn==19.6.0 (from -r /root/sourceCode/requirements.txt (line 1)) Installing collected packages: gunicorn Compiling /tmp/pip_build_root/gunicorn/gunicorn/workers/_gaiohttp.py ... File "/tmp/pip_build_root/gunicorn/gunicorn/workers/_gaiohttp.py", line 84 yield from self.wsgi.close() ^ SyntaxError: invalid syntaxSuccessfully installed gunicorn Cleaning up...`。 - ralvarez
尽管CMD语法可能不正确,但这可能并不是问题,因为它会被命令行覆盖掉。 - Roman
@ralvarez,也许您想检查更新的答案并查看它是否更易理解? - Rao
谢谢你的详细解释,@Rao,但是在运行时添加要求的能力对我的客户来说是“必须的”。无论如何,你的答案对我非常有用,因为现在我更好地理解了CMD命令和docker logs命令。 - ralvarez
1
我相信容器中一定有某些应用程序或进程在运行,容器不仅仅是为了满足需求而存在。因此,需求应该是镜像的一部分,并且在运行容器时应传递不同的应用程序。如果您认为这个答案有用,可以考虑点赞或接受它作为答案,这将不胜感激。 - Rao

1

使用@Rao和@ROMANARMY在他们的答案中解释的概念,我最终找到了一种方法来实现我想要的功能:将额外的Python要求添加到自己创建的Docker镜像中。

我的新Dockerfile如下:

FROM ubuntu:14.04

RUN apt-get update
RUN apt-get install -y python-pip python-dev build-essential 
RUN pip install --upgrade pip

WORKDIR /root

COPY install_req.sh .

CMD ["/bin/bash" , "install_req.sh"]

我已经添加了一个作为第一条命令执行的shell脚本,其内容如下:

#!/bin/bash
pip install -r /root/sourceCode/requirements.txt
pip freeze > /root/sourceCode/freeze.txt

最后,我使用以下命令构建并运行镜像:

docker build --tag test .
docker run -itd --name container_test -v $(pwd)/sourceCode:/root/sourceCode test <- without any parameter at the end

正如我在帖子开头解释的那样,我在本地文件夹中有一个名为sourceCode的文件夹,其中包含一个有效的requirements.txt文件,只有一行“gunicorn”

因此,最终我可以将一些额外的要求(以此示例中的gunicorn软件包)添加到给定的docker镜像中。

构建和运行实验后,如果我检查日志(docker logs container_test),我会看到类似于以下内容:

Downloading gunicorn-19.6.0-py2.py3-none-any.whl (114kB)
    100% |################################| 122kB 1.1MB/s 
Installing collected packages: gunicorn

此外,容器已在挂载的卷内创建了一个名为freeze.txt的文件,其中包含所有安装的pip软件包,包括所需的gunicorn。
chardet==2.0.1
colorama==0.2.5
gunicorn==19.6.0
html5lib==0.999
requests==2.2.1
six==1.5.2
urllib3==1.7.1

现在我遇到了新创建的文件权限的其他问题,但这可能会在一个新的帖子中讨论。 谢谢!

就此而言,Rao的建议将要求构建到镜像中是一个好主意。构建镜像非常快。Docker还具有层缓存功能,因此如果要求文件不更改,则无需重新运行该步骤。这使得构建新镜像变得更快。这就是在此处描述的方法。虽然文章是关于Rails的,但它解决了相同的问题,即管理依赖项。 - Roman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接