这里主要使用编写Dockerfile以build docker的方式。
需求
编写Dockerfile,定制一个有nvidia驱动、anaconda、python相关包的镜像
参考资料
基础docker命令以及几个Dockerfile案例docker hub 用于查询已有的docker镜像以及Dockerfile书写方式
Dockerfile简易教程
步骤
1.寻找基础镜像
Dockerfile的第一行 FROM xxx
需要一个基础镜像,即在docker hub上寻找基础镜像: 这里以anaconda为例子
点击Tag按钮可以看到不同的版本
点击其中一个版本,我们可以发现实际上这里的anaconda镜像也是基于Dockerfile文件建立的。
实际上这就是这一基础镜像的Dockerfile。
2. 在基础镜像上安装所需要的软件、包:
我的情况比较特殊,因为我想要的是同时有nvidia驱动和anaconda的基础镜像,但是docker hub只有纯nvidia驱动的基础镜像和只有anaconda的基础镜像,不能满足我的需求。
联系第1点:anaconda镜像也是基于Dockerfile文件建立的。
因此可以在有nvidia驱动的基础镜像上编写Dockerfile,以安装anaconda(反过来也可以)
Dockerfile:
FROM nvidia/cuda:9.0-base
#下面三行基本上抄anaconda镜像的
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
RUN apt-get update && apt-get update --fix-missing && apt-get install -y wget bzip2 ca-certificates libglib2.0-0 libxext6 libsm6 libxrender1 git mercurial subversion && apt-get clean && wget --quiet https://repo.anaconda.com/archive/Anaconda3-2020.11-Linux-x86_64.sh -O ~/anaconda.sh && /bin/bash ~/anaconda.sh -b -p /opt/conda && rm ~/anaconda.sh && ln -s /opt/conda/etc/profile.d/conda.sh /etc/profile.d/conda.sh && echo ". /opt/conda/etc/profile.d/conda.sh" >> ~/.bashrc && echo "conda activate base" >> ~/.bashrc && find /opt/conda/ -follow -type f -name '*.a' -delete && find /opt/conda/ -follow -type f -name '*.js.map' -delete && /opt/conda/bin/conda clean -afy
ENV PATH=/opt/conda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
2-4行是安装配置anaconda的命令。这里我稍微(魔改)了一下ananconda的Dockerfile命令(因为看不懂dockerhub上的命令)。
3.生成requirements.txt
我使用了生成requirements.txt方式来安装python代码中需要的所有包。
在网上查阅资料发现有两种生成requirements.txt的方法
- pipreqs
# 安装
pip install pipreqs
# 在当前目录生成
pipreqs . --encoding=utf8 --force
但是使用这种方式存在问题:
无法加载依赖包的依赖
例如我打包的镜像中需要用到bert4keras,而在这里bert4keras是基于tensorflow-gpu的,那么用这种方式扫描当前目录,其实没有办法找到tensorflow-gpu这个包,因此requirements.txt里面自然也没有tensorflow-gpu了。
2. pip freeze
pip list --format=freeze > requirements.txt
ps:之前使用的命令是pip freeze > requirements.txt
,会出现输出文件中出现文件路径而非版本号,参考文章:
改成了上面的命令
这种情况会把当前环境下的所有包都加入requirements.txt中,相当于是项目所需要包的超集。虽然会有用不上的包,但是能保证项目所需要的包一定都在里面。
4. 准备python环境
这一步我们将在定制镜像中准备好python以及python包。
Dockerfile:
RUN conda create --name py36_env python=3.6
RUN conda init bash
#激活,以后pip的包安装到3.6环境里
SHELL ["conda", "run", "-n", "py36_env", "/bin/bash", "-c"]
COPY ./requirements.txt /home/release/requirements.txt
# 换成阿里云pip源(不然可能比较慢),以及安装python相关包
RUN pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/ && pip install -r /home/release/requirements.txt
5.copy所需要的代码以及各类文件
pass
6.编写sh脚本文件
上面的涉及到的Dockerfile所有步骤实际上都只会在docker build的时候进行,假如我们希望docker run镜像的时候执行某些命令,就需要编写exec.sh脚本文件了,这里文件名可以任意,但是需要有.sh后缀名。
Dockerfile:
# nohup不能直接写,会被kill掉,run的时候运行nohup才有用
COPY ./exec.sh /your/path/exec.sh
#设置工作目录
WORKDIR /your/path/
RUN chmod 777 exec.sh
#告诉容器,run之后要运行什么命令
ENTRYPOINT ["sh","/your/path/exec.sh"]
exec.sh:
#!/bin/sh
echo hello
nohup /opt/conda/envs/py36_env/bin/gunicorn -w 1 -b 0.0.0.0:5050 -t 60000 xxcontroller:app >> /home/server.log 2>&1 &
#保证容器不会停止
tail -f /dev/null
这一步有几个需要注意的问题:
- 换行符的坑
tail: cannot open '/dev/null'$'\r' for reading: No such file or directory
tail: no files remaining
原因在于:
使用linux自带的vim编辑器exec.sh后会自动为exec.sh的最后一行加上CR LF换行符,导致linux没法正确识别tail -f /dev/null这个命令。在本地使用Notepad之后删掉了最后一行的换行符之后就可以正常运行了。
- 使用虚拟环境的坑
按照平时的做法,假如想使用名为py36_env的虚拟环境,则应该
conda activate py36_env
python xxx
但是在exec.sh里面这样写会报错(错误忘记记录下来了)。查阅了其他博客发现要这样写才行
{py36_env虚拟环境的路径} xxx
也就是要直接使用虚拟环境python的路径,而不是用conda activate的方式
7.创建镜像以及容器
# 使用当前目录下的Dockerfile构建一个镜像
# 注意上面使用的requirements.txt、exec.sh路径要正确
docker build -t [镜像名] .
#使用刚刚build的镜像创建一个容器
docker run --gpus '"device=0,1"' -p [宿主机端口]:[容器中的端口] --name [容器名] -d [镜像名] /bin/bash
其他需要注意的问题
- 关于anaconda
如果不指定版本,默认会安装最新版的anaconda,目前最新版的是python 3.8,然而3.7以上的python版本无法安装tensorflow 1.x,因此需要创建虚拟环境
# Dockerfile中的写法
RUN conda create --name py36_env python=3.6
RUN conda init bash
#激活,以后pip的包安装到3.6环境里
SHELL ["conda", "run", "-n", "py36_env", "/bin/bash", "-c"]
- 关于gunicorn
在使用gunicorn命令启动flask controller的时候会莫名其妙出现下面的错误
[CRITICAL] WORKER TIMEOUT (pid: xxx)
查了下原因,这是因为gunicorn默认超过30s就会把线程kill掉,但是我这里第一个模型加载比较久,还没加载完就被kill了。
解决方法:在启动gunicorn时指定等待时间
/opt/conda/envs/py36_env/bin/gunicorn -w 4 -b 0.0.0.0:5051 -t 60000 xxxController:app
这里 -t就是指定等待时间
-w是指启动的线程