项目如何发布

导入

近来看了不少python的开源项目,大多是个人项目,所以有个问题比较突出,就是可复现性很多都比较差。虽然很多稍微调试下也都能跑通,但是并不是我懒得调试,而是不能直接运行的项目不能算是一个健全的项目。如果是生产环境下,要求就更高了。我敢说就连他们自己过段时间都不能跑通自己的代码,因为他们的电脑环境也在不断变化,python版本,第三方库版本的变化都可能影响项目的跑通。

虚拟环境

对于个人python项目,除非是一个文件的简单脚本,只要是稍微有点代码量的项目,我都是建议进入代码阶段之前一定要先创建一个虚拟环境,就用python自带的标准库venv就行,一个项目一个环境,保证哪怕几年后,你还能正常跑通这个项目。还有anaconda的虚拟环境,我更倾向于是全局环境,比如一个全局的特定python版本,或者pytorch,tensorflow版本,如果针对每个项目都创建一个anaconda环境,那么你可能过段时间都不知道哪个环境对应哪个项目了,因为太多了,而anaconda环境是在anaconda的安装目录的。因此我更建议使用轻量话的 venv标准库,venv创建的环境是在项目目录中的,永远不会混淆。

关于venv的具体用法就不多说了,非常简单,关键就是有没有随时创建独立虚拟环境的这种习惯。

创建虚拟环境的另一个好处是发布时候很方便,因为虚拟环境中使用pip freeze生成依赖包,也就是requirement.txt非常的准确,不用虚拟环境的话,使用pip freeze是会包含一些没有用到的库的。发布项目,比如上传github,一定要带依赖包requirement.txt,并且要精确使用库的具体版本,这样别人才好复现,只需要把项目下载到本地,创建虚拟环境,然后在激活的环境中安装requirement.txt中的依赖即可。

anaconda的虚拟环境中可以使用conda env export > environment.yml导入yaml文件的环境配置,然后其他人只需要利用这个yaml文件创建conda环境即可,比如conda env create -f environment.yml

docker

虚拟环境更多的还是自己以后复现自己的代码简单,不至于过段时间就突然跑不通了。发布时候只需要写好requirement.txt,并且精准到版本,一般也没有太大问题。但是真正的产品级发布还是要用docker。docker容器技术是虚拟化技术的一种,所有的虚拟化技术的本质都是资源隔离,我不会在这篇博客中过多介绍docker,我只能说,docker一定是建议所有开发者都要学习的,并且docker本身并不难。用了docker就完全解决了一个问题”明明我的电脑上可以运行的“。

我简单的拿一个小python脚本举例如何使用docker发布项目:

import requests

url = "https://api.bilibili.com/pgc/web/timeline?types=1&before=6&after=6"

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
    "Chrome/89.0.4389.114 Safari/537.36",
}

response = requests.get(url=url, headers=headers)
data_json = response.json()
for item in data_json["result"]:
    tmp = []
    tmp.append(item["date"])
    tmp.append(item["day_of_week"])
    for episode in item["episodes"]:
        tmp.append(episode["title"])
        tmp.append(episode["pub_index"])
        tmp.append(episode["pub_time"])
        print(tmp)

这是一个简单的爬取动漫时间表的小脚本,这个脚本用来测试docker其实不太好,跟准确来说用来发布不太好,因为用到了api,而这个api不知道什么时候就会失效,这时候即使用docker也运行不了,不过这里只是随便找个例子。

我们只需要在这个目录下新建一个Dockerfile,然后把这个脚本的运行环境配置好就行,具体环境是:

  • 装有python的系统

  • 有requests第三方库

对着个脚本来说就这么简单,因此:

FROM python:3.5-slim

WORKDIR /app

# 安装依赖
RUN pip install requests

# 复制代码文件
COPY bangumi.py ./

# 设置默认执行命令
CMD ["python", "bangumi.py"]

选择官方的python镜像的轻量化slim版本作为基础镜像,背后的运行环境是Debian,然后设置工作目录,安装依赖,复制文件到工作目录下,执行脚本即可。

然后使用docker build -t test .即可构建docker镜像,然后利用docker run --rm test即可利用这个镜像运行一个容器,镜像和容器的关系就是模具和糕点的关系,模具可以生产糕点,并且可以生成多个糕点,而容器本身就是一个进程,通过docker ps即可查看,docker images可以查看镜像列表,包括大小,这个镜像大小近200MB。而镜像就是我们发布的产品,发布镜像到docker hub中,别人就可以docker pull进行拉取,拉取完创建容器就能运行。因此对于这个小脚本发布却近200MB实在有点大了,不过我们可以进一步优化。

使用多阶段构建

  • 构建阶段

    • 选择一个基础镜像

    • 安装requests第三方库

    • 安装pyinstaller,把脚本打包成一个可执行文件

  • 运行阶段

    • 选择一个更加轻量级的发布镜像,不需要包含构建的工具,比如pyinstaller

    • 将构建阶段的可执行文件复制到运行阶段的工作目录中

    • 执行可执行文件

到最后我们发布的就只是运行阶段的镜像,不包含构建的工具,因此镜像大小会显著降低。修改使用多阶段构建的Dockerfile是这样:

# ===== 构建阶段 =====
FROM python:3.5-alpine AS builder

# 设置工作目录
WORKDIR /app

# 安装依赖:pyinstaller 用于打包,requests 是脚本的依赖
RUN pip install --no-cache-dir pyinstaller requests

# 复制脚本到工作目录
COPY bangumi.py .
RUN apk update && apk add  binutils

# 使用 PyInstaller 将脚本打包成单个可执行文件
RUN pyinstaller --name bangumi --onefile --clean bangumi.py


# ===== 运行阶段 =====
# 使用一个非常轻量的 Alpine 镜像作为最终的运行环境
FROM alpine:latest

# 设置工作目录
WORKDIR /app

# Alpine 默认缺少一些 Python 网络请求可能需要的证书和库,这里安装一下
RUN apk add --no-cache ca-certificates libcrypto3 libssl3

# 从构建阶段(builder)复制已编译好的可执行文件到当前阶段
COPY --from=builder /app/dist/bangumi .

# 设置容器启动时要执行的命令
CMD ["/app/bangumi"]

注意点:

  • 运行阶段使用Alpine Linux,只有几MB的大小,非常轻量

  • 构建阶段的基础镜像也使用了Alpine,这是因为Alpine是没有glibc的,而是musl libc作为标准c库,如果还是使用Debian的镜像,会出现很多问题。而多阶段构建,这种问题还是比较多的,因此为了不那么麻烦,构建阶段也使用带python的Alpine。

  • pyinstaller 打包需要objdump工具,包含在GNU二进制工具包binutils中,需要安装,而Alpine的包管理工具是apk,安装使用apk add

  • Alpine 默认缺少一些 Python 网络请求可能需要的证书和库,需要运行阶段安装

好了,接下来运行docker build -t test2 .构建即可,同样使用docker run --rm test2运行镜像对应的容器,不出意外,就能看到脚本运行结果了。如果使用docker images查看镜像列表,你就会发现,此时镜像大小只有20多MB,大幅减少,因此多阶段构建也是实际发布时候建议使用的。

总结

docker一定会成为生产发布的标准,因此一定要学习使用;docker构建多采用多阶段构建,减少发布的镜像体积;尽量不要在运行阶段运行脚本,而是都在构建阶段打包成可执行文件。

docker还有很多重要的部分,比如挂在卷,网络,端口等

此外docker还有一个compose技术很重要,不是什么项目都能打包成一个exe的,有的项目包含多个组件,比如服务器,客户端,数据库,如果都装在一起,不仅复杂度提升,而且也不好维护,牵一发动全身。而compose技术就是将多个容器组合在一起,虽然容器本身是隔离的,包括网络,但是通过network技术可以让容器之间处于同一网络环境下,进而可以通信。