开工福利|免费学 2200+ 精品线上课,企业成员人人可得! 了解详情
写点什么

如何将你的 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:192642
用户头像
陈思 InfoQ编辑

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

关注

评论

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

Java泛型介绍

TaurusCode

Java泛型

中台的悖论

agnostic

中台

CMS系统是什么?

源字节1号

开源 软件开发 前端开发 后端开发 小程序开发

如何实现云数据治理中的数据安全?

京东科技开发者

数据库 云计算 京东云 京东技术

作为移动开发你不能不了解的编译流程

京东科技开发者

编译器 移动开发 京东云 京东技术

如何把握未来技术的演进方向

Ethan

Bytebase 体验官之狂飙的 ChatGPT

朱亚光

利用 ChangeStream 实现 Amazon DocumentDB 表级别容灾复制

亚马逊云科技 (Amazon Web Services)

实现常驻任务除了避免昙花线程,还需要避免重返线程池

newbe36524

C#

在京东如何做好前端系统的可观测性

京东科技开发者

前端 京东云 京东技术

开源可观测性平台SigNoz

骑牛上青山

开源 调用链 OpenTelemetry signoz

软件测试/测试开发丨持续交付-Pipeline入门

测试人

软件测试 自动化测试 测试开发

手把手带你上手ChatGPT

老周聊架构

3月月更 ChatGPT

系统设计的端到端原则

俞凡

架构

人工智能与软件工程

紫晖

人工智能 机器学习 软件工程 工程

运维训练营第19周作业

好吃不贵

架构实战营 - 模块五作业(微博评论)

🐢先生

架构实战营

基于 Kafka 和 Elasticsearch 构建实时站内搜索功能的实践

京东科技开发者

MySQL ES 京东云 京东物流 京东技术

站在工作的角度体验一下文心一言

IT蜗壳-Tango

IT蜗壳 ChatGPT 文心一言 文心一言测试

CPU基础知识详解

timerring

cpu

软件测试/测试开发丨持续交付-Jenkinsfile 语法

测试人

软件测试 自动化测试 测试开发

软件测试/测试开发丨MockServer 服务框架设计

测试人

软件测试 自动化测试 测试开发

什么是容器编排及编排的优点

黎博

容器编排 Kubernetes Serverless

「百幄」系列 | 在线会议套件,让政企协作更安全高效

融云 RongCloud

通信 办公 政企 数智化转型 在线会议

能快速构建和定制网络拓扑图的WPF开源项目-NodeNetwork

沙漠尽头的狼

开源WPF项目 网络拓扑图

希望计算机专业同学都知道这些宝藏博主

程序员大彬

自学编程 计算机 计算机专业

交易履约之产品中心实践

京东科技开发者

交易 京东云 京东技术 京东科技 产品中心

测试人社区软件测试技术沙龙,基于代码链路分析的白盒精准测试方案

测试人

软件测试 自动化测试 精准测试 测试开发

软件测试/测试开发丨持续交付-Blue Ocean 应用

测试人

软件测试 自动化测试 测试开发

AR市场为何频频“呼唤”苹果?

Alter

AR

常用对话框基本使用

芯动大师

dialog timepicker progress

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