Docker 交互模式和执行脚本

50
我有一个Python脚本在我的Docker容器中需要执行,但同时我也需要在容器创建后进行交互式访问(使用/bin/bash)。
我希望能够创建我的容器,执行我的脚本并且在容器内部查看发生的更改/结果(无需手动执行我的Python脚本)。
目前我面临的问题是,如果我在Docker文件中使用CMD或ENTRYPOINT命令,我将无法重新进入已创建的容器。我尝试使用docker start和docker attach,但是我遇到了错误:
sudo docker start containerID
sudo docker attach containerID
"You cannot attach to a stepped container, start it first"

理想情况下,应该接近这样:
sudo docker run -i -t image /bin/bash python myscript.py

假设我的Python脚本包含以下内容(与其功能无关,在这种情况下,它只是创建一个带有文本的新文件):
open('newfile.txt','w').write('Created new file with text\n')

当我创建容器时,我希望我的脚本能够执行,并且我想能够查看文件的内容。因此,类似以下的方式:
```bash docker run myimage sh -c "my_script.sh && cat my_file.txt" ```
请注意保留HTML标签。
root@66bddaa892ed# sudo docker run -i -t image /bin/bash
bash4.1# ls
newfile.txt
bash4.1# cat newfile.txt
Created new file with text
bash4.1# exit
root@66bddaa892ed#

在上面的示例中,我的Python脚本将在创建容器时执行,以生成新文件newfile.txt。这正是我所需要的。

1
我不太确定你在这里问什么... 你的“myscript.py”在做什么? 你能粘贴源代码吗? - James Mills
1
请查看nsenter。详见:http://jpetazzo.github.io/2014/06/23/docker-ssh-considered-evil/ - Mark O'Connor
@jamesMills 我编辑了我的帖子,并且给出了我正在寻找的示例。 - will.fiset
6个回答

58

我的做法与一些优势略有不同。实际上,这是一个多会话服务器而不是脚本,但在某些情况下可能更加易用:

# Just create interactive container. No start but named for future reference.
# Use your own image.
docker create -it --name new-container <image>

# Now start it.
docker start new-container

# Now attach bash session.
docker exec -it new-container bash

主要优点是您可以将多个bash会话附加到单个容器上。例如,我可以使用一个带有bash的会话来告诉日志,在另一个会话中执行实际命令。

顺便说一下,当您分离最后一个“exec”会话时,您的容器仍在运行,因此它可以在后台执行操作。


4
这正是我在寻找的。 - jimh
你可以直接执行 docker run -dit ... - jellycsc

10
你可以通过单个命令运行Docker镜像、执行脚本并进行交互式会话:
sudo docker run -it bash -c "; bash"
上述命令中的第二个 "bash" 命令将保持交互式终端会话开启,无论该Dockerfile映像使用哪种CMD命令创建,因为CMD命令被上面的"bash-c"命令覆盖了。
在Python脚本(或Shell脚本)中也不需要添加类似"local("/bin/bash")"的命令。
假设脚本尚未通过"ADD" Dockerfile命令从Docker主机传输到Docker镜像中,我们可以映射卷并从其中运行脚本:
sudo docker run -it -v :/scripts bash -c "/scripts/; bash"
例如,假设原始问题中的Python脚本已经在Docker镜像上,我们可以省略 "-v" 选项,命令如下所示:
sudo docker run -it image bash -c "python myscript.py; bash"

抱歉回复晚了。我尝试了Roman的答案,但是在docker start之后除非镜像已经创建了一个永不停止的CMD服务(例如交互式bash),否则镜像将立即停止。James的好答案之所以有效,是因为使用了local("/bin/bash")。在我的情况下,我正在寻找一个bash脚本而不是Python脚本,如果我在shell脚本的末尾附加一个bash命令,那么同样可以工作。好主意。 - Olli
有没有任何想法可以从Dockerfile中实现这个? - Alexandros Ioannou
1
@AlexandrosIoannou:在 Dockerfile 中有几种方法可以实现这个功能。1)CMD["bash", "-c", "<your-script-full-path>; bash"] 将在 Dockerfile 中定义一个默认命令。这样,您可以运行 'sudo docker run -it <image-name>' 而不需要指定命令。2)另一种方法是以类似的方式定义 ENTRYPOINT。ENTRYPOINT 不会被附加到 docker run 命令的命令所覆盖(但可以使用 --entrypoint 选项进行覆盖)。3)第三个选项是在 RUN 命令中运行脚本,这将把脚本执行嵌入到 Docker 镜像中。 - Olli
这对我不起作用。 docker run -it image2 bash -c“python script.py; bash” 退出并未进入bash shell。 - Max888

6

为什么不选择这个?

docker run --name="scriptPy" -i -t image /bin/bash python myscript.py
docker cp scriptPy:/path/to/newfile.txt /path/to/host
vim /path/to/host

或者,如果您希望它保留在容器中。
docker run --name="scriptPy" -i -t image /bin/bash python myscript.py
docker start scriptPy
docker attach scriptPy

希望这个对您有所帮助。

3
我认为这就是你的意思。
注意:这里使用了Fabric(因为我太懒和/或没有时间正确地连接标准输入/输出/错误到终端,但你可以花时间使用直接的subprocess.Popen):
输出:
$ docker run -i -t test
Entering bash...
[localhost] local: /bin/bash
root@66bddaa892ed:/usr/src/python# cat hello.txt
Hello World!root@66bddaa892ed:/usr/src/python# exit
Goodbye!

Dockerfile:

# Test Docker Image

FROM python:2

ADD myscript.py /usr/bin/myscript

RUN pip install fabric

CMD ["/usr/bin/myscript"]

myscript.py:

#!/usr/bin/env python


from __future__ import print_function


from fabric.api import local


with open("hello.txt", "w") as f:
    f.write("Hello World!")


print("Entering bash...")
local("/bin/bash")
print("Goodbye!")

1
这很有趣。不使用Python作为基础是否可能实现?我编辑了我的帖子,以便提供我正在寻找的示例,谢谢! - will.fiset
@Caker,我不太确定你的问题是什么。您的问题说明要执行“myscript.py”,我假设一旦运行此脚本,您希望通过生成“bash”子进程来检查容器? - James Mills

1
有时候你不能简单地执行 $ docker run -it <image>,因为它可能会运行 entrypoint。在这种情况下,你可以执行以下命令(以镜像python:3.9-slim为例):
$ docker run -itd python:3.9-slim
    b6b54c042af2085b0e619c5159fd776875af44351d22096b0c574ac1a7798318

$ docker ps
    CONTAINER ID        IMAGE                      COMMAND                  NAMES
    b6b54c042af2        python:3.9-slim            "python3"                sleepy_davinci

$ docker exec -it b6b54c042af2 bash

我只是运行了docker ps命令,以显示它正在运行和相应的容器ID。但你也可以使用从docker run -itd ...返回的完整容器ID(b6b54c042af2085b0e619c5159fd776875af44351d22096b0c574ac1a7798318)。


0
有时候,你的Python脚本可能会调用文件夹中的不同文件,比如另一个Python脚本、CSV文件、JSON文件等等。
我认为最好的方法是与容器共享目录,这样可以更轻松地创建一个环境,使其可以访问所有所需的文件。
创建一个文本脚本。
sudo nano /usr/local/bin/dock-folder

将此脚本添加为其内容。
#!/bin/bash

echo "IMAGE = $1"

## image name is the first param
IMAGE="$1"

## container name is created combining the image and the folder address hash
CONTAINER="${IMAGE}-$(pwd | md5sum | cut -d ' ' -f 1)"
echo "${IMAGE} ${CONTAINER}"

# remove the image from this dir, if exists
## rm                                      remove container command
## pwd | md5                               get the unique code for the current folder
## "${IMAGE}-$(pwd | md5sum)"                   create a unique name for the container based in the folder and image
## --force                                 force the container be stopped and removed
if [[ "$2" == "--reset" || "$3" == "--reset" ]]; then
        echo "## removing previous container ${CONTAINER}"
        docker rm "${CONTAINER}" --force
fi

# create one special container for this folder based in the python image and let this folder mapped
## -it                                     interactive mode
## pwd | md5                               get the unique code for the current folder
## --name="${CONTAINER}"                   create one container with unique name based in the current folder and image
## -v "$(pwd)":/data                       create ad shared volume mapping the current folder to the /data inside your container
## -w /data                                define the /data as the working dir of your container
## -p 80:80                                some port mapping between the container and host ( not required )
## pyt#hon                                  name of the image used as the starting point
echo "## creating container ${CONTAINER} as ${IMAGE} image"
docker create -it --name="${CONTAINER}" -v "$(pwd)":/data -w /data -p 80:80 "${IMAGE}"

# start the container
docker start "${CONTAINER}"

# enter in the container, interactive mode, with the shared folder and running python
docker exec -it "${CONTAINER}" bash

# remove the container after exit
if [[ "$2" == "--remove" || "$3" == "--remove" ]]; then
        echo "## removing container ${CONTAINER}"
        docker rm "${CONTAINER}" --force
fi

添加执行权限

sudo chmod +x /usr/local/bin/dock-folder 

然后您可以调用脚本到您的项目文件夹中:

# creates if not exists a unique container for this folder and image. Access it using ssh.
dock-folder python

# destroy if the container already exists and replace it
dock-folder python --replace

# destroy the container after closing the interactive mode
dock-folder python --remove

这个调用将创建一个新的Python容器,共享您的文件夹。这使得文件夹中的所有文件都可以作为CSV或二进制文件访问。

使用这种策略,您可以快速在容器中测试您的项目,并与容器交互以进行调试。

这种方法的一个问题是可重复性。也就是说,您可能会使用shell脚本安装一些应用程序运行所需的内容。但是,这个更改只发生在您的容器内部。因此,任何尝试运行您的代码的人都必须弄清楚您已经做了什么来运行它并执行相同的操作。

因此,如果您可以在不安装任何特殊内容的情况下运行您的项目,则这种方法可能非常适合您。但是,如果您必须在容器中安装或更改某些内容才能运行您的项目,则可能需要创建一个Dockerfile来保存这些命令。这将使从加载容器、进行所需更改和加载文件的所有步骤易于复制。


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