写点什么

如何将你的 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


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

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

关注

评论

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

社交电商如何规避传销风险

源字节1号

软件开发

Alibaba永远滴神!阿里顶级技术官500页网络协议手记,限时开源

Java架构追梦

Java 华为 网络协议 后端开发

Alluxio 2.8版本重磅发布!3大提升抢先打开数据新世界

Alluxio

分布式缓存 数据管理 Alluxio 大数据 开源

WordPress 如何重置密码

海拥(haiyong.site)

5月月更

阿里大牛两万字总结+40张图文详解,不信你还参透不了并发编程

Java架构追梦

高并发 java面试 后端开发

使用小程序容器技术快速构建智能电视应用平台

Speedoooo

小程序 物联网 移动开发 小程序容器 智能电视

web前端培训interface和type的区别分析

@零度

typescript 前端开发

北明软件加入昇腾万里伙伴计划,与华为共建昇腾AI生态,共同推动人工智能产业繁荣发展

科技热闻

大数据培训MySQL 索引B+Tree

@零度

大数据

2022年医疗+AI,将会如何蓄力发展?

易观分析

医疗AI

云天励飞与华为签署合作协议,共同推进昇腾AI产业持续发展

科技热闻

看 Amazon 如何通过 Nitro System 构建技术优势

亚马逊云科技 (Amazon Web Services)

Builder 专栏

前端 “一键换色“ 的几种方案

CRMEB

比心云平台基于阿里云容器服务 ACK 的弹性架构实践

阿里巴巴云原生

阿里云 容器 云原生 客户案例 阿里云容器服务ACK

Kubernetes 1.24 - 走向成熟的 Kubernetes

Daocloud 道客

Kubernetes 云原生 容器编排

边缘工业协议网关软件Neuron正式开源,连接海量异构工业设备

EMQ映云科技

开源 物联网 IoT mqtt emq

如何基于盘古开发框架开发Dubbo微服务网关

码农大熊

微服务架构 网关

“操作系统的「冷板凳」要坐多久?”万字长文解读16年开源人的坚持

OpenAnolis小助手

Linux 开源 操作系统 坚持 龙蜥社区

【Python】此集合非彼集合

謓泽

5月月更

【多云管理】国内多云管理平台厂家名单汇总

行云管家

云计算 多云管理 多云 云管平台

【案例】锐明技术:灵活部署,实现会话质量和安全的双重保障

行云管家

运维 等保 IT运维 等保2.0

未来的神AIoT!全网第一份AIoT系统学习指南,限时开源

Java架构追梦

Java 后端开发 ALOT

双管齐下, 清华教授亲码JDK和HotSpot源码笔记,一次性学个明白

Java架构追梦

Java 后端开发

SpringBoot集成开源IM框架MobileIMSDK,实现即时通讯IM聊天功能

JackJiang

网络编程 即时通讯 IM TCP协议

python进阶-装饰器

AIWeker

Python 人工智能 5月月更

浅析分布式系统之体系结构 - 事务与隔离级别(多对象、多操作)上篇

snlfsnef

数据库 架构 设计原则 一致性 事务隔离

百亿级数据同步,如何基于 SeaTunnel 的 ClickHouse 实现?

Apache SeaTunnel

Apache 大数据 开源 DolphinScheduler workflow

到底什么是企业应用现代化?

Daocloud 道客

云原生 应用现代化

PingCode Flow技术架构揭秘

PingCode研发中心

Apache ShardingSphere 企业行|走进携程

SphereEx

Apache 数据库 ShardingSphere SphereEx 企业行

恭喜 Kvrocks 加入 Apache 软件基金会孵化器

Kvrocks

redis 开源 apache 社区

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