这里主要使用编写Dockerfile以build docker的方式。

需求

编写Dockerfile,定制一个有nvidia驱动、anaconda、python相关包的镜像

参考资料

基础docker命令以及几个Dockerfile案例docker hub 用于查询已有的docker镜像以及Dockerfile书写方式
Dockerfile简易教程

步骤

1.寻找基础镜像

Dockerfile的第一行 FROM xxx 需要一个基础镜像,即在docker hub上寻找基础镜像: 这里以anaconda为例子

dockerfile mkdir 编写 编写dockerfile实例_dockerfile mkdir 编写


点击Tag按钮可以看到不同的版本

dockerfile mkdir 编写 编写dockerfile实例_运维_02


点击其中一个版本,我们可以发现实际上这里的anaconda镜像也是基于Dockerfile文件建立的

dockerfile mkdir 编写 编写dockerfile实例_容器_03


实际上这就是这一基础镜像的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的方法

  1. 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

这一步有几个需要注意的问题:

  1. 换行符的坑
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之后删掉了最后一行的换行符之后就可以正常运行了。

  1. 使用虚拟环境的坑

按照平时的做法,假如想使用名为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

其他需要注意的问题

  1. 关于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"]
  1. 关于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是指启动的线程