当前位置: 首页 > news >正文

python如何将列表去掉引号_如何将你的 Python 项目全面自动化?

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

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

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

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

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

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

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

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

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

 复制代码

#dev.Dockerfile
FROM python:3.8.1-buster AS builder
RUN 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 pip
FROM builder AS builder-venv
COPY requirements.txt /requirements.txt
RUN /venv/bin/pip install -r /requirements.txt
FROM builder-venv AS tester
COPY . /app
WORKDIR /app
RUN /venv/bin/pytest
FROM martinheinz/python-3.8.1-buster-tools:latest AS runner
COPY --from=tester /venv /venv
COPY --from=tester /app /app
WORKDIR /app
ENTRYPOINT ["/venv/bin/python3", "-m", "blueprint"]
USER 1001
LABEL name={NAME}
LABEL version={VERSION}

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

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

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

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

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

你也可以在 base.Dockerfile :https://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 image
FROM debian:buster-slim AS builder
#...
#17. Line - Switch to Distroless image
FROM 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-blueprint
IMAGE := $(REGISTRY)/$(MODULE)
# This version-strategy uses git tags to set the version string
TAG := $(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-blueprint
push: 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.yml 和 push.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 )。

接下来,它使用存储在存储库中的 secret REGISTRY_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-github
sonar.projectKey=MartinHeinz_python-project-blueprint
sonar.sources=blueprint

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

完整项目代码获取加群:739021630

bbe63f6e94983d61ad3418a13b3c8dc0.png

相关文章:

  • 用python进行自然语言处理_用python进行图片整理
  • dbf文件怎么创建_spring boot 配置文件properties和YAML详解
  • python中类与对象之间的关系_面向对象进阶之类和类之间的关系
  • 网关是什么意思_网关的理解
  • matlab toolbox下载_Mac上Matlab常见问题
  • python redis 操作_Python redis set集合操作
  • fileinputstream读取文件_20M 文件用 Java 压缩从30秒到1秒的优化过程
  • python打印皮卡丘_用python打印你的宠物小精灵吧
  • 平方根python_python的平方根
  • python怎么测试c代码_可以使用基于Python的单元测试框架和跑步者来测试C代码
  • nginx 跨域访问配置_nginx配置用户访问认证
  • python爬虫xpath教程_Spider-Python爬虫之XPath 教程
  • python常见报错类型_python打印错误类型
  • 华为官方解锁工具_开启“应用锁”和“健康使用手机密码”的华为,哪个功能对于用户隐私更安全?...
  • vant coupon 时间戳如何计算_flink入门(八)中的时间戳如何使用?Watermark使用及原理...
  • 「前端」从UglifyJSPlugin强制开启css压缩探究webpack插件运行机制
  • 2017前端实习生面试总结
  • C++入门教程(10):for 语句
  • Java Agent 学习笔记
  • JAVA 学习IO流
  • JavaScript/HTML5图表开发工具JavaScript Charts v3.19.6发布【附下载】
  • js操作时间(持续更新)
  • js中forEach回调同异步问题
  • node和express搭建代理服务器(源码)
  • spring学习第二天
  • ViewService——一种保证客户端与服务端同步的方法
  • 搭建gitbook 和 访问权限认证
  • 更好理解的面向对象的Javascript 1 —— 动态类型和多态
  • 湖南卫视:中国白领因网络偷菜成当代最寂寞的人?
  • 如何进阶一名有竞争力的程序员?
  • 项目管理碎碎念系列之一:干系人管理
  • 用Visual Studio开发以太坊智能合约
  • ​第20课 在Android Native开发中加入新的C++类
  • ​香农与信息论三大定律
  • #define MODIFY_REG(REG, CLEARMASK, SETMASK)
  • #pragma once与条件编译
  • #我与Java虚拟机的故事#连载11: JVM学习之路
  • #在 README.md 中生成项目目录结构
  • %3cscript放入php,跟bWAPP学WEB安全(PHP代码)--XSS跨站脚本攻击
  • (04)odoo视图操作
  • (arch)linux 转换文件编码格式
  • (iPhone/iPad开发)在UIWebView中自定义菜单栏
  • (Java实习生)每日10道面试题打卡——JavaWeb篇
  • (附源码)springboot高校宿舍交电费系统 毕业设计031552
  • (附源码)ssm失物招领系统 毕业设计 182317
  • .aanva
  • .chm格式文件如何阅读
  • .equal()和==的区别 怎样判断字符串为空问题: Illegal invoke-super to void nio.file.AccessDeniedException
  • .NET DataGridView数据绑定说明
  • .Net Winform开发笔记(一)
  • .net 获取url的方法
  • .net 无限分类
  • .NET/C# 使用 SpanT 为字符串处理提升性能
  • .NET精简框架的“无法找到资源程序集”异常释疑
  • .Net语言中的StringBuilder:入门到精通