背景
最近写了个系统,后端部分拆成了两个端,
Golang写的Center,负责调度任务、登录鉴权等基本操作。
Python写的Node,负责计算Center分配的任务。
显然,Python写的Node需要分布式部署计算才快(要不然Center还调度个啥)
Python我已经使用了uv 来管理环境,git 管理代码,但是一些东西不能上传git(模型之类的),而且图方便,像GPT-Sovits直接从GitHub下载了整合包,这些文件只在我的开发机上存在。
显然,部署起来很麻烦,而且分布式需要我在各个服务器上做重复工作。
有没有一种方法,可以方便地在每台电脑上部署呢?
当然有!(废话)
我可以直接在开发机上把项目复制过去!
显然,复制东西太不优雅了。
这种情况下,还是主流解决方案,Docker!
(下面是在Windows的Docker上测试的,但是并没有用图形界面,所以Linux也完全一样)
Docker中文文档:https://yeasy.gitbook.io/docker_practice
Windows Docker Desktop注意事项
Docker Desktop总是会安装在C:\Program Files\Docker ,存储数据总是在C:\Users\H3C\AppData\Local\Docker 。
据测试,Docker Desktop似乎是不能用任何方法移动到其他地方的,但是可以通过软链接的方式使其数据放在其他地方。
数据也是,而且动不动就几十上百G,这个可以在Docker装好后修改,也可以软链接。
什么?你不会用mklink命令建软链接?试试我在Github瞟见的这个工具?
如果构建镜像时硬盘用完了,实测会报一个错误,需要重启机器后删除存储文件才能再次启动Docker(放心删,会再创建,但是一切容器和镜像都会丢失。你该不会在开发用的机器上部署了生产的东西吧?)(我不知道多等等能不能解决)
为了避免硬盘用完,构建错的镜像及时删除,即使清除构建缓存:
docker builder prune构建镜像
首先明确一点,宿主机可以是任何Linux发行版,Ubuntu啦、CentOS啦、Debian啦,它们本质上是在Linux内核上加了自己的各种工具。但Docker不需要这些工具,Docker创建出来的容器也不允许你使用宿主机上的工具。
构建镜像可以从零开始,也可以从基于某个发行版镜像、别人的构建好的镜像。
如果基于了某个发行版镜像,那么Docker容器内就可以使用这个发行版的各种工具,发行版镜像类型和宿主机发行版类型没有任何关系。
选一个基础镜像
DockerHub是个好地方。
测试一遍构建镜像的流程
构建镜像一般通过Dockerfile,但是不经过测试直接写Dockerfile,万一失败了就得从头重新跑,先预演一下是很好的选择。
参考文档。
使用下面的命令,创建一个容器,然后进入它的Shell:
docker run -it --gpus all --entrypoint bash 镜像名使用下面的命令可以看到所有容器:
docker ps -a如果你不小心停止了它(没停止也能再次进入终端),那么可以再次启动它:
docker start -ai 容器ID或名称如果它正在跑(所有容器都适用),可以让它再开一个终端:
docker exec -it 容器ID或名称 bash于是可以像下面这样,创建容器并导入当前项目的文件进行测试:
D:\project\MetaSpeaker_Node>docker run -it --gpus all --entrypoint bash ubuntu:20.04
root@81c2a5779eb9:/# exit
D:\project\MetaSpeaker_Node>docker cp ./checkpoints 81c2a5779eb9:/app/checkpoints
Successfully copied 993MB to 81c2a5779eb9:/app/checkpoints
D:\project\MetaSpeaker_Node>docker start -ai 81c2a5779eb9
root@81c2a5779eb9:/# cd /app/checkpoints
root@81c2a5779eb9:/app/checkpoints# ls -l
total 969636
-rwxr-xr-x 1 root root 348632874 Apr 4 04:22 GFPGANv1.3.pth
-rwxr-xr-x 1 root root 85331193 Jul 6 05:48 parsing_parsenet.pth
-rwxr-xr-x 1 root root 15217721 Apr 4 05:38 rvm_mobilenetv3.pth
-rwxr-xr-x 1 root root 107905875 Apr 4 05:38 rvm_resnet50.pth
-rwxr-xr-x 1 root root 435807851 Apr 3 14:38 wav2lip.pth
root@81c2a5779eb9:/app/checkpoints# 在完成所有操作后,可以使用docker commit 容器,把修改做成一个镜像。
但是一般来说不会这样做镜像,因为总不能每次版本更新都手动去做镜像吧,所以,这个容器测试过后直接删除即可,我们需要自动化的方式。
写Dockerfile
参考文档。
虽然我是第一次使用Docker,但是不用多阶段构造我会很不舒服,把构造使用的工具打包进镜像我觉得很不舒服。
下面是导入当前目录,安装python环境,然后将产物拷贝到一个CUDA环境,供参考:
FROM ubuntu:20.04 AS builder
COPY . /app
WORKDIR /app
ENV PATH="/root/.local/bin:$PATH" DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y curl git cmake build-essential&&\
curl -LsSf https://astral.sh/uv/install.sh | sh
RUN uv sync
FROM nvidia/cuda:12.1.0-cudnn8-runtime-ubuntu20.04 AS runtime
RUN apt-get update && apt-get install -y ffmpeg
ENV GPU_ID=0 NODE_NAME="" CENTER_ADDRESS="" RUNNER_KEY=""
COPY --from=0 /app /app
COPY --from=0 /root/.local /root/.local
WORKDIR /app
ENTRYPOINT [".venv/bin/python","main.py"]小贴士:不要像我这样做写Copy整个项目复制过去,非要这样也要把Dockerfile排除调,不然Copy的缓存总是不命中,反复修改Dockerfile测试时是很烦的···
构建
工作目录在Dockerfile所在目录,然后执行一条命令等着就行啦:
docker build . -t 镜像名:版本号完成后就能用docker images 看到啦。
如果你不小心忘记写-t了,导致它的名字和版本都是<none>,可以使用下面的方法再打标签:
首先找到这个镜像的ID:docker images
然后执行:
docker tag 镜像ID 镜像名:版本号部署
基础创建容器
最基本的创建命令(需要加其他选型配置其他东西):
docker run -it --gpus all 镜像ID或名字和版本号其中对于用显卡的应用来说--gpus all 是必要的。
我觉得这样很麻烦,还要配置环境变量、卷、端口甚至网络之类的。
所以当然有更方便的方法啦~( ¯•ω•¯ )~
写docker-compose.yml
参考文档。
示例文件:
version: "3"
services:
MetaSpeaker_Node:
image: ffeng123/metaspeaker_node:1.0
ports: []
volumes: []
environment:
- GPU_ID=0
- NODE_NAME="本地计算单片"
- CENTER_ADDRESS=""
- RUNNER_KEY="XXX"
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=all
deploy: {}
runtime: nvidia一键部署
工作目录在docker-compose.yml所在目录,执行一条命令:
docker-compose up分发镜像
上面的所有操作都是在本机上进行的。
分布式部署,肯定要多机啦,分发镜像就是必要的啦。
往DockerHub上面提交就算啦,看看只在本地处理的方法:
# 导出
docker save -o XXX.tar 镜像名:版本号
# 导入
docker load -i XXX.tar然后其他机器就可以愉快地一键部署啦~
临时环境
某些时候我很喜欢使用Google Colab,在我的笔记本上有一个8GB大小的内存盘,我觉得装环境太麻烦,而且如果只是临时使用,清理环境更麻烦,而且很多时候因为各种原因还装不上环境,于是,Docker现在成了我的测试环境、临时使用的首选。
对于需要用显卡的场景:
docker run -it --gpus all -p 6222:22 --entrypoint bash nvidia/cuda:12.6.0-cudnn-runtime-ubuntu20.04不用显卡的话:
docker run -it -p 6222:22 --entrypoint bash ubuntu:20.04进去之后装个ssh方便传文件。
apt update && apt install -y openssh-server net-tools
sed -i 's/#\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
sed -i 's/#\?PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
echo 'root:123456' | chpasswd
service ssh start
netstat -tlnp | grep ssh看容器IP:docker inspect -f "{{ .NetworkSettings.IPAddress }}" 容器名 (并不需要)
IP创建的时候已经映射到 6222
注意Windows上Windows和WSL之间有一个网桥,这导致Windows无法使用容器的IP+端口访问。
总结
以前觉得永远用不到的东西也用到了呢。
咱也是为未来分布式打下基础了呢~