面向数据科学家的Docker最佳实践

  作为数据科学家,我每天都在与Docker交战。 创建图像,旋转包含内容已经和为我编写Python脚本一样普遍。 这次旅程有其成就和时刻,"我希望我以前知道这一点"。

  本文讨论了将Docker用于数据科学项目时的一些最佳实践。 绝不是详尽的清单。 但这涵盖了我作为数据科学家遇到的大多数事情。

  本文假定您具有Docker的基础知识。 例如,您应该知道Docker的用途,并且应该能够轻松编写Dockerfile并了解Docker命令(如RUN,CMD等)。否则,请从Docker官方网站上通读本文。 您也可以浏览那里找到的文章。

  为什么选择Docker?

  自从Docker发布以来,它已席卷全球。 在Docker时代之前,虚拟机曾经填补了这一空白。 但是Docker提供的功能远不止虚拟机。

  Docker容器的优点

  · 隔离-隔离的环境,无论基础操作系统/基础架构,已安装的软件,更新如何变化

  · 轻量级-共享OS内核,避免每个容器都有OS内核

  · 性能—轻巧,可以在同一操作系统上同时运行多个容器

  Docker入门

  Docker具有三个重要概念。

  图像—这是一组可运行的库和二进制文件,代表开发/生产/测试环境。 您可以通过以下方式下载/创建图像。

  · 从映像注册表中提取:例如 码头工人拉高山。 此处发生的是,Docker将在您的计算机中本地查找名为alpine的映像,如果找不到,它将在Dockerhub中查找

  · 使用Dockerfile在本地构建映像:例如 码头工人建设。 -t <图像名称>:<图像版本>。 在这里,您并不是要下载/拉取图像,而是要构建自己的图像。 但这并非完全正确,因为Dockerfile包含以FROM 开头的行,该行查找以其开头的基本映像,该映像可能是从Dockerhub中提取的。

  容器-这是图像的运行实例。 您可以使用语法`docker container run 来站起来一个容器,例如,使用阿尔卑斯镜像使用docker container run -it alpine / bin / bash命令创建一个容器。

  卷-卷用于永久/临时存储数据(例如日志,下载的数据)以供容器使用。 此外,卷可以在多个容器之间共享。 您可以通过两种方式使用卷。

  · 绑定安装卷:您还可以使用-v :语法将主机上的现有卷绑定到容器。 例如,如果您需要将/ my_data卷作为/ data卷装载到容器,则可以执行以下操作:docker container运行-it -v / my_data:/ data alpine / bin / bash命令。 您在安装点所做的更改将反映在主机上。

  1.创建镜像

  1.缩小镜像,避免缓存

  构建图像时必须做的两件事是,

  · 安装Linux软件包

  · 安装Python库

  在安装这些软件包和库时,软件包管理器将缓存数据,因此,如果要再次安装它们,将使用本地数据。 但这不必要地增加了图像尺寸。 并且docker镜像应该尽可能轻巧。

  在安装Linux软件包时,请记住通过在apt-get install命令中添加最后一行来删除所有缓存的数据。

  RUN apt-get update && apt-get install tini && \ rm -rf /var/lib/apt/lists/*

  在安装Python软件包时,为避免缓存,请执行以下操作。

  RUN pip3 install --no-cache-dir`

  2.将Python库分离到requirements.txt

  您看到的最后一条命令将我们带到了下一点。 最好将Python库分离到requirements.txt文件,并使用以下语法使用该文件安装库。

  RUN pip3 install -r requirements.txt --no-cache-dir

  这样可以很好地区分Dockerfile做" Docker东西",而不必(明确地)担心" Python东西"。 此外,如果您有多个Dockerfile(例如用于生产/开发/测试),并且它们都希望安装相同的库,则可以轻松地重用此命令。 requirements.txt文件只是一堆库名。

  numpy==1.18.0scikit-learn==0.20.2pandas==0.25.0

  3.修复库版本

  请注意,如何在requirements.txt中冻结要安装的版本。 这个非常重要。 因为否则,每次构建Docker映像时,您可能会安装不同版本的不同事物。 "依赖地狱"是真实的。

  运行容器

  1.拥抱非root用户

  运行容器时,如果您未指定运行用户身份,则它将假定为root用户。 我不会撒谎。 我的天真自我曾经爱过使用sudo或成为root可以按我的方式做事的能力(尤其是获得许可)。 但是,如果我学到了一件事,那就是拥有不必要的特权会加剧这种情况,从而引发更多的问题。

  要以非root用户身份运行容器,只需执行

  或者,如果您想跳到现有容器中,

  docker exec -it -u <用户ID>:<组ID> <容器ID> <命令>

  例如,可以通过将分配为$(id -u)和分配为$(id -g)来匹配主机的用户ID和组ID。

  注意不同的操作系统如何分配用户ID和组ID。 例如,您在MacOS上的用户ID /组ID可能是Ubuntu容器中的预先分配/保留的用户ID /组ID。

  2.创建一个非特权用户

  能够以非root用户身份从主机登录到我们的主机真是太好了。 但是,如果您以这种方式登录,则您是没有用户名的用户。 因为,很明显,容器不知道该用户ID的来源。 而且,您每次想将一个容器或执行程序合并为一个容器时,都需要记住并键入这些用户ID和组ID。 因此,您可以将此用户/组创建内容作为Dockerfile的一部分包含在内。

  ARG UID=1000

  ARG GID=1000

  · 首先将ARG UID=1000和ARG GID=1000添加到Dockerfile中。 UID和GID是容器中的环境变量,您将在docker buildstage将该值传递给该容器(默认为1000)。

  · 然后使用RUN groupadd -g $ GID john-group在映像中添加具有组ID GID的Linux组。

  · 接下来,使用useradd -N -l -u $ UID -g john-group -G sudo john在映像中添加具有某些用户ID UID的Linux用户。 您可以看到这里我们将john添加到sudo组中。 但这是可选的。 如果您100%确定不需要sudo权限,则可以将其忽略。

  然后,在映像构建期间,您可以为这些参数传递值,例如,

  例如,

  docker build -t docker-tut:latest --build-arg UID=$(id -u)--build-arg GID=$(id -g)

  拥有非特权用户可以帮助您运行不具有root权限的进程。 例如,为什么要以root身份运行Python脚本,而它所要做的只是从目录中读取数据(例如数据)并写入其中(例如模型)。 另外一个好处是,如果在容器内匹配主机的用户ID和组ID,则您创建的所有文件都将拥有主机用户的所有权。 因此,如果您绑定安装这些文件(或创建新文件),它们看起来仍然像是您在主机上创建的。

  创建卷

  1.使用卷分离工件

  作为数据科学家,显然您将处理各种工件(例如数据,模型和代码)。 您可以将代码放在一个卷中(例如/ app),将数据放在另一个卷中(例如/ data)。 这将为您的Docker映像提供一个不错的结构,并摆脱任何主机级工件依赖关系。

  我所说的工件依赖是什么意思? 假设您在/ home / <用户> / code / src中有代码,在/ home / <用户> / code / data中有数据。 如果将/ home / <用户> / code / src 复制/装入到卷 / app,将 / home / <用户> / code / data 复制/装入到卷 / data。 主机上的代码和数据的位置是否更改都没有关系。 只要您安装这些工件,它们将始终在Docker容器内的同一位置可用。 因此,您可以按照以下步骤在Python脚本中很好地修复这些路径。

  data_dir="/data"

  model_dir="/models"

  src_dir="/app"

  您可以使用以下方式将必要的代码和数据复制到图像中

  COPY test-data /data

  COPY test-code /app

  请注意,测试数据和测试代码是主机上的目录。

  2.在开发过程中绑定目录

  绑定安装的好处在于,您在容器中所做的任何事情都会反映在主机本身上。 当您进行开发并想调试项目时,这非常好。 我们来看一个例子。

  假设您通过运行以下命令创建了docker映像:

  docker build <构建目录> <镜像名称>:<镜像版本>

  现在,您可以使用以下命令从该图像中站起一个容器:

  docker run -it <镜像名称>:<镜像版本> -v / home / <用户> / my_code:/ code

  现在,您可以在容器中运行代码并同时进行调试,对代码所做的更改将反映在主机上。 这样就可以回到在容器中使用相同的主机用户ID和组ID的好处。 您所做的所有更改似乎都来自主机上的用户。

  3.永远不要绑定主机的关键目录

  好笑的故事! 我曾经将机器的主目录挂载到Docker容器并设法更改了主目录的权限。 不用说我以后无法登录系统,并且花了好几个小时来修复它。 因此,仅安装所需的内容。

  例如,假设您有三个要在开发期间挂载的目录:

  / home / <用户> / my_data/ home / <用户> / my_code/ home / <用户> / my_model

  您可能很想用单行代码挂载/ home / 。 但是绝对值得编写三行代码来分别挂载这些单独的子目录,因为这将为您节省数小时(甚至不是几天)的辛苦工作。

  其他提示

  1.了解ADD和COPY之间的区别

  您可能知道有两个名为ADD和COPY的Docker命令。 有什么不同?

  当使用ADD 时,可以使用ADD从URL下载文件给定压缩文件(例如tar.gz)时,ADD会将文件提取到提供的位置。COPY将给定的文件/文件夹复制到容器中的指定位置。

  2. ENTRYPOINT和CMD之间的区别

  我想到的一个很好的类比是,将ENTRYPOINT视为车辆,将CMD视为该车辆中的控件(例如加速器,制动器,方向盘)。 ENTRYPOINT它本身不会执行任何操作,它只是您要在该容器中执行操作的容器。 对于您推送到容器的任何传入命令,它只是处于备用状态。

  命令CMD是在容器内实际执行的命令。 例如,bash会在您的容器中创建一个外壳,以便您可以像在Ubuntu的普通终端上一样使用该容器。

  3.将文件复制到现有容器

  再没有! 我已经创建了这个容器,却忘记了将此文件添加到图像中。 建立图像需要花费很长时间。 有什么办法可以作弊并将其添加到现有容器中?

  是的,您可以使用docker cp命令执行此操作。 简单地做,

  docker cp <容器>:<目标>

  下次进入容器时,将在看到复制的文件。但是请记住实际更改Dockerfile以在构建时复制必要的文件。

  3.结论

  很棒! 那就是所有人。 我们讨论过了,

  · 什么是Docker映像/容器/卷?

  · 如何编写一个好的Dockerfile

  · 如何以非root用户身份启动容器

  · 如何在Docker中进行适当的卷挂载

  · 还有一些奖励技巧,例如使用docker cp 来节省一天的时间

  现在,您应该增强信心,可以直视Docker,并说"您不能吓me我"。 除了开玩笑,知道您对Docker所做的一切总是值得的。 因为如果您不小心,可能会导致整个服务器瘫痪,并破坏在同一台计算机上工作的其他所有人的工作。

posted @ 2022-02-04 15:58  ebuybay  阅读(59)  评论(0编辑  收藏  举报