【ArchSummit架构师峰会】探讨数据与人工智能相互驱动的关系>>> 了解详情
写点什么

如何将你的 Python 项目全面自动化?

  • 2020-10-13
  • 本文字数:7004 字

    阅读完需:约 23 分钟

如何将你的Python项目全面自动化?

每个项目——无论你是在从事 Web 应用程序、数据科学还是 AI 开发——都可以从配置良好的 CI/CD、Docker 镜像或一些额外的代码质量工具(如 CodeClimate 或 SonarCloud)中获益。所有这些都是本文要讨论的内容,我们将看看如何将它们添加到 Python 项目中!


本文最初发布于 Martin Heinz 的个人博客,由 InfoQ 中文站翻译并分享。


开发环境中可调试的 Docker 容器

有些人不喜欢 Docker,因为容器很难调试,或者构建镜像需要花很长的时间。那么,就让我们从这里开始,构建适合开发的镜像——构建速度快且易于调试。


为了使镜像易于调试,我们需要一个基础镜像,包括所有调试时可能用到的工具,像bashvimnetcatwgetcatfindgrep等。它默认包含很多工具,没有的也很容易安装。这个镜像很笨重,但这不要紧,因为它只用于开发。你可能也注意到了,我选择了非常具体的映像——锁定了 Python 和 Debian 的版本——我是故意这么做的,因为我们希望最小化 Python 或 Debian 版本更新(可能不兼容)导致“破坏”的可能性。


作为替代方案,你也可以使用基于 Alpine 的镜像。然而,这可能会导致一些问题,因为它使用musl libc而不是 Python 所依赖的glibc。所以,如果决定选择这条路线,请记住这一点。


至于构建速度,我们将利用多阶段构建以便可以缓存尽可能多的层。通过这种方式,我们可以避免下载诸如gcc之类的依赖项和工具,以及应用程序所需的所有库(来自requirements.txt)。


为了进一步提高速度,我们将从前面提到的python:3.8.1-buster创建自定义基础镜像,这将包括我们需要的所有工具,因为我们无法将下载和安装这些工具所需的步骤缓存到最终的runner镜像中。


说的够多了,让我们看看Dockerfile


# dev.DockerfileFROM python:3.8.1-buster AS builderRUN apt-get update && apt-get install -y --no-install-recommends --yes python3-venv gcc libpython3-dev && \    python3 -m venv /venv && \    /venv/bin/pip install --upgrade pipFROM builder AS builder-venvCOPY requirements.txt /requirements.txtRUN /venv/bin/pip install -r /requirements.txtFROM builder-venv AS testerCOPY . /appWORKDIR /appRUN /venv/bin/pytestFROM martinheinz/python-3.8.1-buster-tools:latest AS runnerCOPY --from=tester /venv /venvCOPY --from=tester /app /appWORKDIR /appENTRYPOINT ["/venv/bin/python3", "-m", "blueprint"]USER 1001LABEL name={NAME}LABEL version={VERSION}
复制代码


从上面可以看到,在创建最后的runner镜像之前,我们要经历 3 个中间镜像。首先是名为builder的镜像,它下载构建最终应用所需的所有必要的库,其中包括gcc和 Python 虚拟环境。安装完成后,它还创建了实际的虚拟环境,供接下来的镜像使用。


接下来是build -venv镜像,它将依赖项列表(requirements.txt)复制到镜像中,然后安装它。缓存会用到这个中间镜像,因为我们只希望在requirement .txt更改时安装库,否则我们就使用缓存。


在创建最终镜像之前,我们首先要针对应用程序运行测试。这发生在tester镜像中。我们将源代码复制到镜像中并运行测试。如果测试通过,我们就继续构建runner


对于runner镜像,我们使用自定义镜像,其中包括一些额外的工具,如vimnetcat,这些功能在正常的 Debian 镜像中是不存在的。


你可以在 Docker Hub:https://hub.docker.com/repository/docker/martinheinz/python-3.8.1-buster-tools 中找到这个镜像;


你也可以在base.Dockerfilehttps://github.com/MartinHeinz/python-project-blueprint/blob/master/base.Dockerfile 中查看其非常简单的Dockerfile


那么,我们在这个最终镜像中要做的是——首先我们从tester镜像中复制虚拟环境,其中包含所有已安装的依赖项,接下来我们复制经过测试的应用程序。现在,我们的镜像中已经有了所有的资源,我们进入应用程序所在的目录,然后设置ENTRYPOINT,以便它在启动镜像时运行我们的应用程序。出于安全原因,我们还将USER设置为1001,因为最佳实践告诉我们,永远不要在root用户下运行容器。最后两行设置镜像标签。它们将在使用make目标运行构建时被替换/填充,稍后我们将看到。

针对生产环境优化过的 Docker 容器

当涉及到生产级镜像时,我们会希望确保它们小而安全且速度快。对于这个任务,我个人最喜欢的是来自 Distroless 项目的 Python 镜像。可是,Distroless 是什么呢?


这么说吧——在一个理想的世界里,每个人都可以使用FROM scratch构建他们的镜像,然后作为基础镜像(也就是空镜像)。然而,大多数人不愿意这样做,因为那需要静态链接二进制文件,等等。这就是 Distroless 的用途——它让每个人都可以FROM scratch


好了,现在让我们具体描述一下 Distroless 是什么。它是由谷歌生成的一组镜像,其中包含应用程序所需的最低条件,这意味着没有 shell、包管理器或任何其他工具,这些工具会使镜像膨胀,干扰安全扫描器(如CVE),增加建立遵从性的难度。


现在,我们知道我们在干什么了,让我们看看生产环境的Dockerfile……实际上,这里我们不会做太大改变,它只有两行:


# prod.Dockerfile#  1. Line - Change builder imageFROM debian:buster-slim AS builder#  ...#  17. Line - Switch to Distroless imageFROM gcr.io/distroless/python3-debian10 AS runner#  ... Rest of the Dockefile
复制代码


我们需要更改的只是用于构建和运行应用程序的基础镜像!但区别相当大——我们的开发镜像是 1.03GB,而这个只有 103MB,这就是区别!我知道,我已经能听到你说:“但是 Alpine 可以更小!”是的,没错,但是大小没那么重要。你只会在下载/上传时注意到镜像的大小,这并不经常发生。当镜像运行时,大小根本不重要。比大小更重要的是安全性,从这个意义上说,Distroless 肯定更有优势,因为 Alpine(一个很好的替代选项)有很多额外的包,增加了攻击面。


关于 Distroless,最后值得一提的是镜像调试。考虑到 Distroless 不包含任何 shell(甚至不包含sh),当你需要调试和查找时,就变得非常棘手。为此,所有 Distroless 镜像都有调试版本。因此,当遇到问题时,你可以使用debug标记构建生产镜像,并将其与正常镜像一起部署,通过 exec 命令进入镜像并执行(比如说)线程转储。你可以像下面这样使用调试版本的python3镜像:


docker run --entrypoint=sh -ti gcr.io/distroless/python3-debian10:debug
复制代码

所有操作都只需一条命令

所有的Dockerfiles都准备好了,让我们用Makefile实现自动化!我们首先要做的是用 Docker 构建应用程序。为了构建 dev 映像,我们可以执行make build-dev,它运行以下目标:


# The binary to build (just the basename).MODULE := blueprint# Where to push the docker image.REGISTRY ?= docker.pkg.github.com/martinheinz/python-project-blueprintIMAGE := $(REGISTRY)/$(MODULE)# This version-strategy uses git tags to set the version stringTAG := $(shell git describe --tags --always --dirty)build-dev: @echo "\n${BLUE}Building Development image with labels:\n" @echo "name: $(MODULE)" @echo "version: $(TAG)${NC}\n" @sed                                 \     -e 's|{NAME}|$(MODULE)|g'        \     -e 's|{VERSION}|$(TAG)|g'        \     dev.Dockerfile | docker build -t $(IMAGE):$(TAG) -f- .
复制代码


这个目标会构建镜像。它首先会用镜像名和 Tag(运行git describe创建)替换dev.Dockerfile底部的标签,然后运行docker build


接下来,使用make build-prod VERSION=1.0.0构建生产镜像:


build-prod: @echo "\n${BLUE}Building Production image with labels:\n" @echo "name: $(MODULE)" @echo "version: $(VERSION)${NC}\n" @sed                                     \     -e 's|{NAME}|$(MODULE)|g'            \     -e 's|{VERSION}|$(VERSION)|g'        \     prod.Dockerfile | docker build -t $(IMAGE):$(VERSION) -f- .
复制代码


这个目标与之前的目标非常相似,但是在上面的示例1.0.0中,我们使用作为参数传递的版本而不是git标签作为版本 。


当你运行 Docker 中的东西时,有时候你还需要在 Docker 中调试它,为此,有以下目标:


# Example: make shell CMD="-c 'date > datefile'"shell: build-dev @echo "\n${BLUE}Launching a shell in the containerized build environment...${NC}\n"  @docker run                                                     \   -ti                                                     \   --rm                                                    \   --entrypoint /bin/bash                                  \   -u $$(id -u):$$(id -g)                                  \   $(IMAGE):$(TAG)             \   $(CMD)
复制代码


从上面我们可以看到,入口点被bash覆盖,而容器命令被参数覆盖。通过这种方式,我们可以直接进入容器浏览,或运行一次性命令,就像上面的例子一样。


当我们完成了编码并希望将镜像推送到 Docker 注册中心时,我们可以使用make push VERSION=0.0.2。让我们看看目标做了什么:


REGISTRY ?= docker.pkg.github.com/martinheinz/python-project-blueprintpush: build-prod @echo "\n${BLUE}Pushing image to GitHub Docker Registry...${NC}\n" @docker push $(IMAGE):$(VERSION)
复制代码


它首先运行我们前面看到的目标build-prod,然后运行docker push。这里假设你已经登录到 Docker 注册中心,因此在运行这个命令之前,你需要先运行docker login


最后一个目标是清理 Docker 工件。它使用被替换到Dockerfiles中的name标签来过滤和查找需要删除的工件:


docker-clean: @docker system prune -f --filter "label=name=$(MODULE)"
复制代码


你可以在我的存储库中找到Makefile的完整代码清单:https://github.com/MartinHeinz/python-project-blueprint/blob/master/Makefile。

借助 GitHub Actions 实现 CI/CD

现在,让我们使用所有这些方便的make目标来设置 CI/CD。我们将使用 GitHub Actions 和 GitHubPackage Registry 来构建管道(作业)及存储镜像。那么,它们又是什么呢?


  • GitHub Actions 是帮助你自动化开发工作流的作业/管道。你可以使用它们创建单个的任务,然后将它们合并到自定义工作流中,然后在每次推送到存储库或创建发布时执行这些任务。

  • GitHub Package Registry 是一个包托管服务,与 GitHub 完全集成。它允许你存储各种类型的包,例如 Ruby gems 或 npm 包。我们将使用它来存储 Docker 镜像。如果你不熟悉 GitHub Package Registry,那么你可以查看我的博文,了解更多相关信息:https://martinheinz.dev/blog/6


现在,为了使用 GitHubActions,我们需要创建将基于我们选择的触发器(例如 push to repository)执行的工作流。这些工作流是存储库中.github/workflows目录下的 YAML 文件:


.github└── workflows    ├── build-test.yml    └── push.yml
复制代码


在那里,我们将创建两个文件build-test.ymlpush.yml。前者包含 2 个作业,将在每次推送到存储库时被触发,让我们看下这两个作业:


jobs:  build:    runs-on: ubuntu-latest    steps:    - uses: actions/checkout@v1    - name: Run Makefile build for Development      run: make build-dev
复制代码


第一个作业名为build,它验证我们的应用程序可以通过运行make build-dev目标来构建。在运行之前,它首先通过执行发布在 GitHub 上名为checkout的操作签出我们的存储库。


jobs:  test:    runs-on: ubuntu-latest    steps:    - uses: actions/checkout@v1    - uses: actions/setup-python@v1      with:        python-version: '3.8'    - name: Install Dependencies      run: |        python -m pip install --upgrade pip        pip install -r requirements.txt    - name: Run Makefile test      run: make test    - name: Install Linters      run: |        pip install pylint        pip install flake8        pip install bandit    - name: Run Linters      run: make lint
复制代码


第二个作业稍微复杂一点。它测试我们的应用程序并运行 3 个 linter(代码质量检查工具)。与上一个作业一样,我们使用checkout@v1操作来获取源代码。在此之后,我们运行另一个已发布的操作setup-python@v1,设置 python 环境(要了解详细信息,请点击这里:https://github.com/actions/setup-python )。


我们已经有了 Python 环境,我们还需要requirements.txt中的应用程序依赖关系,这是我们用pip安装的。这时,我们可以着手运行make test目标,它将触发我们的 Pytest 套件。如果我们的测试套件测试通过,我们继续安装前面提到的 linter——pylint、flake8 和 bandit。最后,我们运行make lint目标,它将触发每一个 linter。


关于构建/测试作业的内容就这些,但 push 作业呢?让我们也一起看下:


on:  push:    tags:    - '*'jobs:  push:    runs-on: ubuntu-latest    steps:    - uses: actions/checkout@v1    - name: Set env      run: echo ::set-env name=RELEASE_VERSION::$(echo ${GITHUB_REF:10})    - name: Log into Registry      run: echo "${​{ secrets.REGISTRY_TOKEN }}" | docker login docker.pkg.github.com -u ${​{ github.actor }} --password-stdin    - name: Push to GitHub Package Registry      run: make push VERSION=${​{ env.RELEASE_VERSION }}
复制代码


前四行定义了何时触发该作业。我们指定,只有当标签被推送到存储库时,该作业才启动(*指定标签名称的模式——在本例中是任何名称)。这样,我们就不会在每次推送到存储库的时候都把我们的 Docker 镜像推送到 GitHub Package Registry,而只是在我们推送指定应用程序新版本的标签时才这样做。


现在我们看下这个作业的主体——它首先签出源代码,并将环境变量RELEASE_VERSION设置为我们推送的git标签。


这是通过 GitHub Actions 内置的::setenv特性完成的(更多信息请点击这里:https://help.github.com/en/actions/automating-your-workflow-with-github-actions/development-tools-for-github-actions#set-an-environment-variable-set-env )。


接下来,它使用存储在存储库中的 secretREGISTRY_TOKEN登录到 Docker 注册中心,并由发起工作流的用户登录(github.actor)。最后,在最后一行,它运行目标push,构建生产镜像并将其推送到注册中心,以之前推送的git标签作为镜像标签。


感兴趣的读者可以从这里签出完整的代码清单:https://github.com/MartinHeinz/python-project-blueprint/tree/master/.github/workflows。

使用 CodeClimate 进行代码质量检查

最后但同样重要的是,我们还将使用 CodeClimate 和 SonarCloud 添加代码质量检查。它们将与上文的测试作业一起触发。所以,让我们添加以下几行:


# test, lint...- name: Send report to CodeClimate  run: |    export GIT_BRANCH="${GITHUB_REF/refs\/heads\//}"    curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter    chmod +x ./cc-test-reporter    ./cc-test-reporter format-coverage -t coverage.py coverage.xml    ./cc-test-reporter upload-coverage -r "${​{ secrets.CC_TEST_REPORTER_ID }}"- name: SonarCloud scanner  uses: sonarsource/sonarcloud-github-action@master  env:    GITHUB_TOKEN: ${​{ secrets.GITHUB_TOKEN }}    SONAR_TOKEN: ${​{ secrets.SONAR_TOKEN }}
复制代码


我们从 CodeClimate 开始,首先输出变量GIT_BRANCH,我们会用环境变量GITHUB_REF来检索这个变量。接下来,我们下载 CodeClimate test reporter 并使其可执行。接下来,我们使用它来格式化由测试套件生成的覆盖率报告,而且,在最后一行,我们将它与存储在存储库秘密中的 test reporter ID 一起发送给 CodeClimate。


至于 SonarCloud,我们需要在存储库中创建sonar-project.properties文件,类似下面这样(这个文件的值可以在 SonarCloud 仪表板的右下角找到):


sonar.organization=martinheinz-githubsonar.projectKey=MartinHeinz_python-project-blueprintsonar.sources=blueprint
复制代码


除此之外,我们可以使用现有的sonarcloud-github-action,它会为我们做所有的工作。我们所要做的就是提供 2 个令牌——GitHub 令牌默认已在存储库中,SonarCloud 令牌可以从 SonarCloud 网站获得。


注意:关于如何获取和设置前面提到的所有令牌和秘密的步骤都在存储库的自述文件中:https://github.com/MartinHeinz/python-project-blueprint/blob/master/README.md

小 结

就是这样!有了上面的工具、配置和代码,你就可以构建和全方位自动化下一个 Python 项目了!如果关于本文讨论的主题,你想了解更多信息,请查看存储库中的文档和代码:https://github.com/MartinHeinz/python-project-blueprint,如果你有什么建议/问题,请在存储库中提交问题库,或者如果你喜欢我的这个小项目,请为我点赞。


查看英文原文:


https://martinheinz.dev/blog/17


公众号推荐:

跳进 AI 的奇妙世界,一起探索未来工作的新风貌!想要深入了解 AI 如何成为产业创新的新引擎?好奇哪些城市正成为 AI 人才的新磁场?《中国生成式 AI 开发者洞察 2024》由 InfoQ 研究中心精心打造,为你深度解锁生成式 AI 领域的最新开发者动态。无论你是资深研发者,还是对生成式 AI 充满好奇的新手,这份报告都是你不可错过的知识宝典。欢迎大家扫码关注「AI前线」公众号,回复「开发者洞察」领取。

2020-10-13 14:192335
用户头像
陈思 InfoQ编辑

发布了 576 篇内容, 共 262.5 次阅读, 收获喜欢 1293 次。

关注

评论

发布
暂无评论
发现更多内容

ChatGPT 背后的英雄——AI芯片

天翼云开发者社区

人工智能 AI芯片

IOS上架流程详解,包含审核避坑指南!

雪奈椰子

Nautilus Chain:我们将支持EIP6969

EOSdreamer111

Django笔记四十一之Django中使用es

Hunter熊

Python django elasticsearch

C语言编程-共用体

二哈侠

6 月 优质更文活动

Nautilus Chain:我们将支持EIP6969

大瞿科技

Nautilus Chain:我们将支持EIP6969

BlockChain先知

QR防伪溯源系统追溯原理是什么?

天翼云开发者社区

防伪溯源 防伪技术 QR技术

为什么说 Go 语言字符串是不可变的?

AlwaysBeta

Go 源码 面试 字符串

技术分享 | 如何编写同时兼容 Vue2 和 Vue3 的代码?

LigaAI

JavaScript vue.js Vue 前端 企业号 6 月 PK 榜

北美 2023 被裁员的感悟

HoneyMoose

【Python金融-001】如何快速计算股票的收益?1行代码,高效做T

程序员晚枫

Python 金融 股票

人脸面部表情识别的原理及其应用

来自四九城儿

Web3 游戏团队如何在项目发布前奠定成功基础 ——以真实用户支持为核心的运营策略

Footprint Analytics

区块链游戏 NFT Web3 游戏

总结vue3 的一些知识点:​Vue.js 条件语句​

雪奈椰子

文心一言 VS 讯飞星火 VS chatgpt (29)-- 算法导论5.2 1题

福大大架构师每日一题

ChatGPT 文心一言

Nautilus Chain:我们将支持EIP6969

股市老人

以技术实践赋能开源安全|2023开放原子全球开源峰会开源安全技术与实践分论坛即将启幕

开放原子开源基金会

开源 开源软件供应链 开源安全技术

推进开源法律知识普及|2023开放原子全球开源峰会开源法律与合规分论坛即将启幕

开放原子开源基金会

开源 法律与合规

重磅升级|ONES Resource 资源管理解决方案

万事ONES

ChatGpt账号注册

楚少AI

ChatGPT chatgpt注册

JavaScript深度剖析之变量、函数提升:从表面到本质

控心つcrazy

2023-06-02:给定一个二进制数组 nums 和一个整数 k, k位翻转 就是从 nums 中选择一个长度为 k 的 子数组, 同时把子数组中的每一个 0 都改成 1 ,把子数组中的每一个 1

福大大架构师每日一题

golang 算法 rust 福大大

九大亮点+六大好处,瑞云科技虚拟仿真实训平台引领教育信息化新潮流

3DCAT实时渲染

虚拟仿真 云仿真 3D实时云渲染

存储接口测试简介与测试方法

天翼云开发者社区

测试 存储

Linea奥德赛没有测试币ETH怎么办?3种渠道领取

加密先生

测试币 Linea

ChatGPT App苹果版下载

楚少AI

ChatGPT ChatGPT4 chatgpt app

Generative AI 新世界 | 大型语言模型(LLMs)概述

亚马逊云科技 (Amazon Web Services)

机器学习 tensorflow 开源 PyTorch Amazon SageMaker

火山引擎边缘云,助力泛娱乐产业数字化转型升级

火山引擎边缘云

Qcon 云产品 火山引擎边缘云

总结vue3 的一些知识点:Vue.js 安装

雪奈椰子

ChatGPT介绍与使用场景

楚少AI

openai ChatGPT ChatGPT4

如何将你的Python项目全面自动化?_AI&大模型_Martin Heinz_InfoQ精选文章