如何将你的Python项目全面自动化?
每个项目, 不管你是搞Web应用程序开发还是做数据科学工作, 亦或是进行AI开发, 都能从配置完善的CI/CD、镜像, 或者某些额外的代码质量工具比如 或那里获得益处。所有这些, 都是本文要探讨的内容, 我们会瞧瞧怎样把它们添加到 项目里面开发环境中可调试的 容器有的人不乐意, 鉴于容器极难调试, 又或搭建镜像得耗费漫长时间。如此, 那我们从此处着手, 搭建适配开发的镜像——搭建速度快且便于调试。为了能让镜像在调试时变得相对容易调试一些, 我们的确是需要有一个基础镜像来达成这一目的, 这个基础镜像当中涵盖了所有在调试期间有可能会使用到的工具, 诸如bash、vim、wget、cat、find、grep等等也在其中。它在默认状态下就已然包含了许多工具, 就算有一些工具是没有的, 不过安装起来也并非难事。此镜像虽说很是笨重, 然而这并没有什么要紧之处, 毕竟它仅仅是被用于开发用途而已。你或许也已经留意到了, 我挑选了极为具体的映像, 也就是锁定了某些特定工具的和特定工具的版本, 我这般做是有着明确意图的, 这是因为我们期望能够将因某些特定工具或某些特定工具版本进行更新而这种更新很有可能出现不兼容的情况从而导致“破坏”的可能性降到最低限度。作为替代的办法, 你同样能够选用基于 的镜像, 不过, 这极有可能引发一些状况, 缘由在于它运用的是musl libc而非依据 所依赖的glibc, 所以呢, 要是确定选取这条路径, 务必要牢记这一点。至于构建时的速度, 我们会借助多阶段构建, 进而能够缓存尽可能多些的层, 借助这类方式, 我们可以规避下载如gcc这类的依赖项以及工具, 以及应用程序所需的全部库源自.txt对于进一步提升速度而言, 我们会从前面所提及的, 3.8.1 - 创建自定义基础镜像着手, 这当中会涵盖我们所需的全部工具, 鉴于我们没办法把下载以及安装这些工具所需的步骤缓存至最终的镜像里。讲得已经不少了, 让我们瞧瞧:。# dev.DockerfileFROM python:3.8.1-buster AS builder RUN apt-getupdate apt-getinstall-y--no-install-recommends --yes python3-venv gcc libpython3-dev \python3 -m venv /venv \ /venv/bin/pipinstall--upgrade pipFROMbuilderASbuilder-venv COPY requirements.txt /requirements.txt RUN /venv/bin/pipinstall-r /requirements.txtFROMbuilder-venvAStester COPY . /app WORKDIR /app RUN /venv/bin/pytestFROMmartinheinz/python-3.8.1-buster-tools:latestASrunner COPY--fromtester /venv /venvCOPY--fromtester /app /appWORKDIR /app ENTRYPOINT [/venv/bin/python3,-m,blueprint]USER1001LABELname{NAME} LABELversion{VERSION}可以从上面看到, 在创建最后的那个镜像以前, 我们要历经3个中间镜像。首先是有个特定名称的镜像, 它去下载构建最终应用所必需的全部必要的库, 这里面包括gcc以及虚拟环境。安装完毕之后, 它还搭建出实际的虚拟环境, 以供接下来的镜像去使用。接下来是build -venv镜像, 它会将那个依赖项列表.txt复制到镜像里边, 然后把它安装。缓存会用到此中间镜像, 原因是我们仅期望在.txt产生更改的时候安装库, 不然的话我们就使用缓存。于创建最终镜像之前, 我们首要针对应用程序开展运行测试, 这种测试发生于镜像之中, 我们会把源代码复制至镜像里头并运行测试, 要是测试得以通过, 我们便会继续进行构建。在镜像方面, 我们所采用的是自定义镜像, 此自定义镜像之中涵盖着一些额外的工具, 像vim之类的, 而这些功能于正常镜像里是不存在的。那么, 我们于这个最终镜像里所要做的是, 首先, 我们自镜像中复制虚拟环境, 其中涵盖着所有已安装的依赖项呀, 紧接着, 我们复制那经过测试的应用程序。此刻, 我们的镜像内已存有了所有的资源, 我们进入到应用程序所处的目录, 随后进行设置, 以使它在启动镜像的时候运行我们的应用程序。鉴于安全缘故, 我们还将USER设置成1001, 因为最佳实践告知我们, 切莫在root用户之下运行容器。最后两行设置镜像标签。它们会在运用make目标去运行构建之际, 被予以替换, 进而被填充, 稍后我们就会见到其事。针对生产环境优化过的 容器当关联到生产级镜像之际, 我们会期望保证它们体积小又具备安全性且运行速度快。针对这个任务, 我个人最为偏爱的是源自项目的镜像。然而, 究竟是什么呢?那就是这样来讲——于一个理想化的世界当中了, 每一个人都能够运用FROM去构建属于他们自己的镜像, 接着将其作为基础镜像也便是空镜像的那种。可是, 绝大多数的人都是不情愿去这么做的, 这是由于那是需要进行静态链接二进制文件之类的操作等等情况。这便是它所具备的用途了——它能够使得每一个人都达成以FROM的方式去做某事。好了, 此刻咱们来详细说一说究竟是什么。它是谷歌出品的一组镜像, 这里面涵盖了该系统所需的最低条件, 此即意味着无 shell、无包管理器以及无随便哪种其他工具, 而这些工具会致使镜像规模增大, 对安全扫描器像是那个 CVE形成不良扰乱影响, 从而让建立遵从性变难, 有更大阻碍。此时此刻, 我们已然明晰自身所从事之事, 那就让我们来瞧瞧生产环境的情况……事实上, 在此处我们不会实施太大的变动, 它仅仅存在两行:# prod.Dockerfile# 1. Line - Change builder imageFROM debian:buster-slimASbuilder# ...# 17. Line - Switch to Distroless imageFROM gcr.io/distroless/python3-debian10ASrunner# ... Rest of the Dockefile只是我们所需要去更改的, 乃是用于构建以及运行应用程序的基础镜像然而区别十分不小——我们的开发镜像是1.03GB, 可这只存在103MB, 这便是突出区分我心里明白, 我已然能够听到你在讲: “可是能够更小哇”确实是这样, 没错, 可大小并非那么关键重要。仅仅是你只会于下载 / 上传之际留意到镜像的大小, 而这并非时常会发生。当镜像处于运行状态的时候, 大小根本就不重要。更重要的并非是比大小, 而是安全性, 从这样的意义来讲, 无疑是更具备优势的, 原因在于, 存在着一个很好的替代选项, 其拥有许多额外的包, 这就扩大了攻击面。对于某个事物而言, 最后值得提及的是镜像调试。鉴于该事物不涵盖任何的 shell, 甚至连 sh 都不包含时, 当你有需要进行调试以及查找之际, 就会变得极为棘手。基于此, 所有该事物的镜像都设有调试版本。所以, 在碰到问题之际, 你能够运用debug标记去构筑生产镜像, 并且把它跟正常镜像一块儿进行部署, 借由exec命令进入到镜像当中且开展诸如线程转储的操作。你可以按下述这般来运用调试版本的镜像:docker run --entrypointsh -ti gcr.io/distroless/python3-debian10:debug所有操作都只需一条命令无一遗漏全都准备好了, 让咱们藉助实现自动化去达成我们头需要做的事情系采用来构建应用程序。为了得以构建出一种有关开发情况的映像, 我们能够实施一项名为使构建过程适合开发要求这一操作, 它施展运行往下这般目标:# The binary to build (just the basename).MODULE : blueprint# Where to push the docker image.REGISTRY ? IMAGE :$(REGISTRY)/$(MODULE)# This version-strategy uses git tags to set the version stringTAG :$(shellgit describe --tags --always --dirty)build-dev:echo\n${BLUE}Building Development image with labels:\nechoname:$(MODULE)echoversion:$(TAG)${NC}\nsed \ -e s|{NAME}|$(MODULE)|g \ -e s|{VERSION}|$(TAG)|g \ dev.Dockerfile | docker build -t$(IMAGE):$(TAG)-f- .是这个目标, 进行构建镜像的操作。它会先采用镜像名以及 Tag此 Tag 通过运行 git 创建, 更换 dev.底部的标签, 随后运行 build。稍后, 运用make build - prod等于1.0.0去构造生产镜像:build-prod: echo\n${BLUE}Building Production image with labels:\nechoname:$(MODULE)echoversion:$(VERSION)${NC}\nsed \ -es|{NAME}|$(MODULE)|g\ -es|{VERSION}|$(VERSION)|g\ prod.Dockerfile | docker build -t $(IMAGE):$(VERSION) -f- .这一目标和先前的目标极为相像, 然而在上面所提及的示例 1.0.0 里, 我们运用作为参数予以传递的版本而非 git 标签当作版本, 当你运行其中的内容时, 有时你还得在其中调试它, 为达成此目的, 存在以下目标:# Example: make shell CMD-c date datefileshell: build-devecho\n${BLUE}Launching a shell in the containerized build environment...${NC}\ndockerrun \ -ti \ --rm \ --entrypoint /bin/bash \ -u $$(id -u):$$(id -g) \ $(IMAGE):$(TAG) \ $(CMD)从上边能够看到, 入口点被bash给覆盖了, 容器命令被参数给覆盖了。经由此种方式, 能够直接进入容器去浏览, 或者运行一次性命令, 就如同上边的示例那般。在我们做完编码, 且期望把镜像推送至注册中心之际, 我们能够运用make push 0.0.2。来瞧瞧目标做了些什么:REGISTRY ?push: build-prod echo\n${BLUE}Pushing image to GitHub Docker Registry...${NC}\ndockerpush$(IMAGE):$(VERSION)一开始它运行我们之前所见到的目标build - prod, 随后运行push。这里是假定你已经登录至注册中心, 所以在运行此命令之前, 你得先运行login。其中一个目标是清理, 这个目标是最后出现的, 它是工件清理。它借助被以被替换的方式放置到其内的name标签, 去查找那些要被删除的工件, 进而对工件展开过滤。docker-clean:docker system prune -f --filterlabelname$(MODULE)你可以在我的存储库中找到的完整代码清单借助 实现 CI/CD此刻, 致使我们得以运用全部这类便利的make目标以便去设定CI/CD。我们会借助 以及 来架构管道作业并贮存镜像。如此一来, 它们究竟又属于是什么呢。当今此刻, 鉴于要加以运用, 故而我们必须去构建那会依据我们所挑选的触发器予以施行的工作流。此等触发器诸如是推送至之类的。这些工作流属于存储库里./目录下的YAML文件。.github└──workflows├──build-test.yml└──push.yml于彼处, 我们会去创建两个文件, 分别是build - test.yml以及push.yml。其中前者包蕴着2个作业, 这会在每一回推送至存储库之际被触发, 就让我们瞧瞧这两个作业:。jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkoutv1 - name: Run Makefile build for Development run: make build-dev第一个作业, 其名称是build, 它去验证, 我们的应用程序, 能够借助运行make build-dev目标, 来实现构建。它在运行之前, 先签出我们的存储库, 这是通过执行发布在其上一款项名为的操作来达成的。jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv1 - uses: actions/setup-pythonv1with: python-version:3.8-name:InstallDependencies run: | python -m pipinstall--upgrade pippipinstall-r requirements.txt -name: Run Makefiletestrun: maketest-name:InstallLinters run: | pipinstallpylint pipinstallflake8 pipinstallbandit -name: Run Linters run: make lint稍微复杂一点的是第二个作业, 它对我们的应用程序进行测试, 并且运行3个代码质量检查工具, 和上一个作业相同的是, 我们运用v1操作去获取源代码, 在这之后, 我们运行另一个已发布的操作setup - v1来设置环境, 若想要了解详细信息, 请看这里, 我们已经具备了环境, 我们还需要.txt中的、用pip安装的应用程序依赖关系。在这个时候, 我们能够开始去运行make test目标, 此目标会引发我们的套件。要是我们的测试套件顺利通过测试, 我们接着去安装之前所提及的—— 、 和。最终, 我们运行make lint目标, 这一目标会触发每一个。关于构建 / 测试作业的情况就是这些, 然而push作业又如何呢? 让我们一同来看一下:on: push: tags: -*jobs: push: runs-on: ubuntu-latest steps: - uses: actions/checkoutv1 - name: Set env run:echo::set-env nameRELEASE_VERSION::$(echo${GITHUB_REF:10}) - name: Log into Registry run:echo${{ secrets.REGISTRY_TOKEN }}| docker login -u${{ github.actor }} --password-stdin - name: Push to GitHub Package Registry run: make push VERSION${{ env.RELEASE_VERSION }}前四行, 定义了, 何时触发, 该作业。我们指定, 只有当, 标签被推送到, 存储库时, 该作业才启动, 指定标签名称的模式, 在本例中是, 任何名称。这样, 我们就不会在每次, 推送到, 存储库的时候, 都把我们的, 镜像推送到, 而只是在我们, 推送指定, 应用程序新版本的, 标签时, 才这样做。当下, 我们来瞧一瞧这个作业的主体部分, 它先是签出源代码, 接着把环境变量设定为我们推送的git标签, 这借助内置的::特性得以达成, 更多信息可查看此处: 。随后, 其运用存储于存储库里的, 登录至注册中心, 且让发起工作流的用户进行登录.actor。最终, 在末尾一行, 它执行目标push, 打造生产镜像并且将此镜像推送至注册中心, 把先前推送的git标签用作镜像标签了。感兴趣的读者可以从这里签出完整的代码清单使用 进行代码质量检查终归而言却并同样具重要性可言的是, 我们也还会运用以及添加代码质量检查。它们会会同上文所提及的测试工作一同引发。因而, 让我们增添如下几行:# test, lint...- name: Send report to CodeClimate run: |exportGIT_BRANCH${GITHUB_REF/refs\/heads\//}curl -Ltest-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-actionmaster env: GITHUB_TOKEN:${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN:${{ secrets.SONAR_TOKEN }}我们起始于, 率先输出变量, 我们会借由环境变量去检索此变量。紧接着, test 并使之具备可执行性。随后, 我们运用它来对测试套件所生成的覆盖率报表予以格式化。再者, 在最末一行处, 我们把它与存储于存储库秘密里的 test ID 一同发送给。对于, 我们得于存储库中创就 sonar - 文件, 仿若如下这般该文件的值能够在 仪表板右下角寻觅到:sonar.organizationmartinheinz-github sonar.projectKeyMartinHeinz_python-project-blueprint sonar.sourcesblueprint另外, 我们能够利用现有的那个东西, 那个东西会替我们完成全部的工作。我们需要做的仅仅, 仅仅是提供两个令牌, 两个令牌已经预先在存储库里了, 两个令牌也能够从那个网站获取得到。注意, 关于怎样去获取那些前面所提到的全部令牌, 以及如何去设置那些先前提及的所有秘密的步骤, 都位于存储库的自述文件当中:小 结就是这样的情况具备了上面所提及的工具, 还有相应的布局设置以及代码, 你进而能够去构建, 且达成全方位的自动化, 针对下一个项目要是针对本文着重探讨的主题, 你期望知晓更多的讯息, 那就去查看存储在库中的文档以及代码: