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 | FROM python:3 |
然后在项目文件夹的控制台下,构建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 | # python_script.py |
但是这时候又遇到了坑,运行了一下容器,然后发现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 | 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. |
原来发现挂载的不是文件,而是文件夹
1 | docker run -it --rm -v "/tmp:/tmp" docker-test-py /bin/bash |
改成挂载文件
结果发现成功了,但是进入tmp文件发现里面是空的。
发现原来不能用相对路径,原来就得老老实实用绝对路径来挂载
1 | docker run -it --rm -v "D:/Projects/Project-Test/docker-test/tmp:/tmp_my" docker-te |
发现在windows的资源管理器的导航栏复制的路径全都是反斜杠,不是正斜杠。用上面的指令创建。
1 | root@d9ae1d00f583:/# ls |
创建出来一个这么抽象的文件夹名字。
1 | root@d9ae1d00f583:/# cd '/tmp_my' |
再进入这个文件夹,发现文件终于存在了!
很好,再继续增加内存的限制使用情况。(吃一堑长一智,冒号后面的部分一定要用正斜杠)
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指令