【ArchSummit】如何通过AIOps推动可量化的业务价值增长和效率提升?>>> 了解详情
写点什么

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

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

关注

评论

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

toB企业的客户续约提醒

明道云

【CSS 学习总结】第二篇 - HTML 扩展简介

Brave

CSS 12月日更

区块链数字藏品平台开发助力潮玩行业新发展

电微13828808271

20个提升效率的JS简写技巧

CRMEB

今年读了多少书?(22/28)

赵新龙

28天写作

作业三

施正威

Spring Boot开发,有这个包就够了!

sleeper

Java DevOps springboot 框架

优秀程序员的30种思维--理解认知篇

hackstoic

程序员 架构思维

CRM系统如何帮助企业改进销售流程

低代码小观

低代码 销售管理 CRM 无代码 CRM系统

1300亿次!百度地图发布生态全景3.0 日均位置服务请求次数再创新高

百度开发者中心

人工智能

公安重点人员动态预警管控系统建设,警务情指一体可视化决策系统

电微13828808271

【等保小知识】等保一级二级三级哪个要求更高?

行云管家

网络安全 等保 等级保护 等保20

手把手教你配置php的redis扩展

恒生LIGHT云社区

php redis php扩展

公安合成作战指挥中心平台建设方案,情指勤一体化系统开发建设

a13823115807

公安合成作战系统开发 情指勤一体化合成作战系统

盘点 2021|学习、分享、努力中成长

小隐乐乐

盘点2021

百度王海峰:深耕自然语言处理近30年,推进AI融合创新

百度大脑

人工智能

DongTai 社区双周报 | v1.1.4 版本新功能解析来啦!

火线安全

恒源云(GPUSHARE)_云GPU服务器如何使用OpenCV?

恒源云

OpenCV 计算机视觉 图像识别

【年度评选】让我们为2021留下浓墨重彩的一笔

InfoQ写作社区官方

2021年度评选 热门活动

三步开启你的网络服务全球动态加速之旅

声网

网络 人工智能「

《谈谈人生选择的思考逻辑》读后感

Changing Lin

12月日更

公安大数据合成作战平台建设,合成作战指挥中心平台开发

电微13828808271

重启iptables为啥内核参数不对了?

BUG侦探

Linux iptables

云脑启智 开源赋能 | 2021新一代人工智能院士高峰论坛暨启智开发者大会顺利举办

OpenI启智社区

人工智能 院士峰会 启智社区

盘点 2021|代码之外:人生最大的幸运就是努力没有白费 -- 我的2021年度总结

宇宙之一粟

职场成长 生活记录 12月日更 盘点2021 代码之外

CSS之变量(三)心形加载条

Augus

CSS 12月日更

OPPO大数据离线计算平台架构演进

安第斯智能云

大数据 spark YARN

如何突破浏览器12px限制

云小梦

CSS JavaScript html Vue transform

详解 Rainbond Ingress 泛解析域名机制

北京好雨科技有限公司

52 K8S之Harbor项目

穿过生命散发芬芳

k8s 28天写作 12月日更

【AI最前线】精准优质-资讯|分享|热议第45期

百度大脑

人工智能「

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