面向数据科学家的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所做的一切总是值得的。 因为如果您不小心,可能会导致整个服务器瘫痪,并破坏在同一台计算机上工作的其他所有人的工作。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了