Docker学习记录

学习使用Docker做代码执行器

背景

比特山项目卡在实现一个代码运行器上了。这是比特山的核心。如果有了代码运行器,比特山就有了一切。任何CodingGame都需要使用代码运行器。

以前尝试了fastAPI,用ulimit指令来运行python代码和编译C++代码。但是发现总是过一会,服务器上的进程就被kill掉了。并且没有实现沙箱环境。一个qq好友和我说,你用关键词检测禁用C++的一些东西,是禁不完的。

接下来我打算尝试Docker来做沙箱环境了。顺便一提的是:早该学Docker了!不管做不做比特山。Docker肯定是有用的。

开始

首先直接搜Docker,在官网下载了桌面版,然后

1
docker -v

看到了版本号,安装成功了。

实现一个helloword

首先创建一个项目文件夹,在项目文件夹中创建一个 app.py,里面写一个helloword。尝试在docker中运行这个helloword。

就需要在项目根目录下创建一个 ‘Dockerfile’ 文件,没有后缀名。然后在里面写

1
2
3
FROM python:3
COPY app.py /app.py
CMD ["python", "/app.py"]

然后在项目文件夹的控制台下,构建docker镜像。 ‘my-python-app’是可以自定义一个项目名字,docker镜像名字

1
docker build -t my-python-app .

构建的过程花了90多秒

然后执行这个镜像

1
docker run my-python-app

就可以看到helloword被打印出来了。

如果忘了镜像名字了,可以用指令来查看所有的镜像

1
docker images

发现刚刚构建的python小镜像有一个G多。

查看所有容器:

1
docker ps -a

docker run指令会创建一个容器,结果我好像发现我创建了三个。。。

原来是我弄混了镜像和容器的概念。

镜像与容器

镜像创建容器。

一个镜像可以创建多个容器的主要原因是,镜像本身是只读的,而容器是可写的。

一个镜像可能有一个多G,但是一个容器可能会很小。

据已有的镜像,创建一个容器

1
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
  • [OPTIONS] 是可选的参数,用于指定容器的配置,如端口映射、挂载卷、环境变量等。
  • IMAGE 是要使用的镜像的名称或ID。
  • [COMMAND] [ARG...] 是容器内部要执行的命令及其参数。

于是,我执行了这个命令

1
docker run --name py-test1 docker-test-py

根据镜像【docker-test-py】创建了一个【py-test1】的容器

启动已经创建的容器

my-container 替换为刚刚创建的容器的名字或者id

1
docker start my-container

这个指令输入完了之后,会打印容器名称或者id。并不会打印程序print的内容。

要查看程序print的内容,需要

1
docker logs my-container

结果我发现我运行了三次,就会打印三行

修改容器中的代码

直接覆盖文件,写入

1
docker cp app.py py-test1:/app.py

可以把容器当成一个硬盘,盘符就是容器名

所以右边两个部分换一下,就是从容器中往本地复制文件。

然后还需要重新启动一下容器

1
docker restart my-container

实际上发现不执行上面的命令也是可以的。因为我的代码是运行一下瞬间就结束了的程序。

重新启动已经停止的容器时,Docker 引擎会再次执行容器中的默认命令,从而重新运行你的代码

给容器加限制

创建一个具有内存限制的容器

1
docker run -d --name my-container --memory=512m my-image

不能直接修改已经创建的容器的参数。可能要删了重新创建。

删除容器

1
docker rm my-container

在给容器里放入了一个可能会超过内存限制的代码:

1
2
3
4
5
6
7
8
9
# python_script.py
def main():
# 申请一个很大的列表,以占用大量内存
big_list = [0] * (1024 * 1024 * 128) # 占用128MB内存
print("List size:", len(big_list), "elements")


if __name__ == "__main__":
main()

但是这时候又遇到了坑,运行了一下容器,然后发现logs里并没有输出内容。可能需要手动进入容器,手动运行代码。

因为可能是报错信息导致程序退出,并没有进入docker的logs

这时候可能需要用到创建并进入临时容器了。

临时容器

根据镜像创建临时容器,代价非常低也非常迅速

创建一个临时容器,并在其中启动一个 Bash 终端:

1
docker run -it --rm your_image_name /bin/bash

-it 参数来分配一个交互式的终端

--rm 表示容器结束退出的时候自动删除

使用挂载卷或临时文件

创建临时容器又遇到了问题:直接进入了临时容器就不能使用docker指令了,也就不能把用户代码放到临时容器了。这个时候用一点小操作把用户代码放进来。

在刚才创建临时容器并携带交互式控制台的命令上,再加点参数

-v 就是用来挂载临时文件用的

1
docker run -it --rm -v /path/to/host/file:/path/to/container/file your_image_name /bin/bash

将宿主机上的 /path/to/host/file 文件挂载到容器内部的 /path/to/container/file 路径

于是我执行了

1
docker run -it --rm -v ./tmp/test.py:/test.py docker-test-py /bin/bash

不出意外,出意外了

1
2
docker: Error response from daemon: create tmp/test.py: "tmp/test.py" includes invalid characters for a local volume name, only "[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed. If you intended to pass a host directory, use absolute path.
See 'docker run --help'.

原来发现挂载的不是文件,而是文件夹

1
docker run -it --rm -v "/tmp:/tmp" docker-test-py /bin/bash

改成挂载文件

结果发现成功了,但是进入tmp文件发现里面是空的。

发现原来不能用相对路径,原来就得老老实实用绝对路径来挂载

1
2
docker run -it --rm -v "D:/Projects/Project-Test/docker-test/tmp:/tmp_my" docker-te
st-py /bin/bash

发现在windows的资源管理器的导航栏复制的路径全都是反斜杠,不是正斜杠。用上面的指令创建。

1
2
3
root@d9ae1d00f583:/# ls
'/tmp_my' bin dev home lib64 mnt proc run srv tmp var
app.py boot etc lib media opt root sbin sys usr

创建出来一个这么抽象的文件夹名字。

1
2
3
root@d9ae1d00f583:/# cd '/tmp_my'
root@d9ae1d00f583://tmp_my# ls
test.py

再进入这个文件夹,发现文件终于存在了!

很好,再继续增加内存的限制使用情况。(吃一堑长一智,冒号后面的部分一定要用正斜杠)

1
docker run -it --rm -m 128m --memory-swap 128m -v "D:/Projects/Project-Test/docker-test/tmp:/tmp_my" docker-test-py /bin/bash
  • -m 128m:表示限制容器的内存使用量为 128MB。
  • --memory-swap 128m:表示限制容器的虚拟内存交换空间为 128MB。这个参数通常设置为与 --memory 相同的值,以避免容器使用虚拟内存。

可以看到控制台直接出现了

1
Killed

表示超内存了。

再加一个10秒的限制时间

1
--ulimit cpu=10:10

如果执行了死循环代码,在屏幕上不停的输出东西,可以用ctrl + C来

先记录到这里,——2024年3月10日

临时容器中的异常情况

在临时容器的交互式控制台中不小心输入了python,进入了python的控制台对话,结果就卡住了

可以狂按ctrlC,退出。这样就直接退出到底,销毁临时容器了。目前只知道这个方法解决卡住不动的问题。

退出临时容器

用exit指令