为生产就绪的Docker化Django应用程序进行持续集成

153 阅读7分钟

本教程包括。

  1. 克隆和设置演示项目
  2. 添加CircleCI配置文件,包括作业结构和工作流程
  3. 使用并行性和拆分测试

持续集成已经成为软件项目的一个广泛接受的做法。随着更多的技术被引入到持续集成和软件开发中,开发人员正在寻找实用的方法来从中受益。对于现实生活中的从业者来说,涵盖玩具实例的基本教程并不总是足够的。作为Django、Docker和CircleCI的实际使用者,这无疑是我的一个痛点。这就是我写这个教程的原因。

在本指南中,你将从一位同行那里学到如何为一个生产就绪的Docker化Django 3.2应用程序建立持续集成管道。

为了让你能轻松上手,我创建了一个演示项目,让你从GitHub上克隆。

当你完成本教程后,每次推送到你的项目的GitHub repo都会自动触发构建,并将覆盖文档上传到像Codecov.io这样的代码覆盖云提供商。你甚至会学到如何使用CircleCI的并行模式来更快地运行你的测试,实现更短的迭代周期。

前提条件

要从本教程中获得最大的收获,你将需要。

我们的教程是与平台无关的,但以CircleCI为例。如果你没有CircleCI账户,可以**在这里注册一个免费账户。**

因为每个Django项目都是不同的,我将带领你完成使用演示项目建立持续集成管道的步骤。

我建议你从头到尾学习本教程两次。第一遍,使用演示项目。第二遍,使用你自己的Django项目,在概念上重复同样的步骤,但在必要时对过程进行调整。

我将在本教程的后面部分提供一些提示,以便为你自己的项目调整步骤。现在,请专注于跟随演示项目,这样你就能对整个过程从头到尾的运作形成直觉。

克隆演示项目

在这一步,你将git克隆代码库,并确保你对该代码库能在本地运行测试感到满意。基本上,这个代码库遵循Django文档中相同的经典七部曲教程。在演示项目代码库中,有一些不同之处。

主要的区别在于。

  1. 演示项目维护了基于函数的视图,而不是教程中使用的基于类的视图。
  2. 演示项目有一个config 文件夹,其中的settings 子文件夹包含base.pylocal.pyproduction.py ,而不是只有settings.py
  3. 演示项目有一个dockerized_django_demo_circleci 文件夹和一个users 子文件夹。

用Git克隆该代码库。

$ git clone https://github.com/CIRCLECI-GWP/dockerized-django-demo.git

下面是结果的输出。

[secondary_label Output]
Cloning into 'dockerized-django-demo-circleci'...
remote: Enumerating objects: 325, done.
remote: Counting objects: 100% (325/325), done.
remote: Compressing objects: 100% (246/246), done.
remote: Total 325 (delta 83), reused 293 (delta 63), pack-reused 0
Receiving objects: 100% (325/325), 663.35 KiB | 679.00 KiB/s, done.
Resolving deltas: 100% (83/83), done.

现在你已经克隆了演示项目,你已经准备好测试本地版本的Docker化演示项目了。

在本地运行测试

在这一步,你将继续上一步的工作,确保测试可以在本地机器上成功运行。

启动你的Docker桌面应用程序。如果你需要回顾一下如何做,请参考本教程

![Docker Desktop已准备好]((/blog/media/2022-06-06-dockerized-django-project-docker-ready.png){: .zoomable }

进入你的Docker容器的bash,为Django运行测试。

$ cd dockerized-django-demo
$ docker-compose -f local.yml run web_django bash

按照这个输出,进入Django Docker容器的bash shell。

[secondary_label Output]
Creating network "dockerized-django-demo_default" with the default driver
Creating dockerized-django-demo_db_postgres_1 ... done
Creating dockerized-django-demo_web_django_run ... done
Going to use psycopg2 to connect to postgres
psycopg2 successfully connected to postgres
PostgreSQL is available
root@abc77c0122b3:/code#

现在,运行测试。

root@abc77c0122b3:/code# python3 manage.py test --keepdb

输出结果将是。

[secondary_label Output]
Using existing test database for alias 'default'...
System check identified no issues (0 silenced).
..........
----------------------------------------------------------------------
Ran 10 tests in 0.454s

OK
Preserving test database for alias 'default'...

这个输出意味着你的测试是好的。当你把这个推送到CircleCI时,这应该是同样的结果。

要退出你的bash shell,输入exit

root@abc77c0122b3:/code# exit

回到你的主机操作系统。

[secondary_label Output]
exit
$

要正确关闭Docker。

$ docker-compose -f local.yml down --remove-orphans

下面是输出结果。

[secondary_label Output]
Stopping dockerized-django-demo_db_postgres_1 ... done
Removing dockerized-django-demo_web_django_run_8eeefb5e1d1a ... done
Removing dockerized-django-demo_db_postgres_1               ... done
Removing network dockerized-django-demo_default

你现在可以安全地停止Docker Desktop了。

现在,你的Django项目和测试案例的工作应该令你满意。现在是时候让你把代码推送到GitHub了。

将您自己的GitHub仓库连接到CircleCI上

如果你一直在使用演示项目,并且你已经创建了自己的GitHub repo,那么你需要重新命名你克隆代码的远程。添加你的GitHub repo作为远程origin

将所有提到的greendeploy-io/test-ddp 替换为你的 repo 的实际名称。org-name/repo-name

$ git remote rename origin upstream
$ git remote add origin git@github.com:greendeploy-io/test-ddp.git
$ git push -u origin main
[secondary_label Output]
Enumerating objects: 305, done.
Counting objects: 100% (305/305), done.
Delta compression using up to 10 threads
Compressing objects: 100% (222/222), done.
Writing objects: 100% (305/305), 658.94 KiB | 2.80 MiB/s, done.
Total 305 (delta 70), reused 297 (delta 67), pack-reused 0
remote: Resolving deltas: 100% (70/70), done.
To github.com:greendeploy-io/test-ddp.git
 * [new branch]      main -> main
Branch 'main' set up to track remote branch 'main' from 'origin'.

添加CircleCI配置文件

在这一步,你将创建一个CircleCI配置文件,并编写脚本,为你的项目配置一个持续集成管道。首先,创建一个名为.circleci 的文件夹,并在其中创建一个config.yml 文件。现在,打开新创建的文件,输入这些内容。

# Python CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-python/ for more details
#
version: 2.1

# adding dockerhub auth because dockerhub change their policy
# See https://discuss.circleci.com/t/authenticate-with-docker-to-avoid-impact-of-nov-1st-rate-limits/37567/23
# and https://support.circleci.com/hc/en-us/articles/360050623311-Docker-Hub-rate-limiting-FAQ
docker-auth: &docker-auth
  auth:
    username: $DOCKERHUB_USERNAME
    password: $DOCKERHUB_PAT

orbs:
  codecov: codecov/codecov@3.2.2

jobs:
  build:
    docker:
      # specify the version you desire here
      # use `-browsers` prefix for selenium tests, e.g. `3.7.7-browsers`
      - image: cimg/python:3.8.12
        # use YAML merge
        # https://discuss.circleci.com/t/updated-authenticate-with-docker-to-avoid-impact-of-nov-1st-rate-limits/37567/35?u=kimsia
        <<: *docker-auth
        environment:
          DATABASE_URL: postgresql://root@localhost/circle_test?sslmode=disable
          USE_DOCKER: no

      # Specify service dependencies here if necessary
      # CircleCI maintains a library of pre-built images
      # documented at https://circleci.com/docs/2.0/circleci-images/
      - image: cimg/postgres:14.1 # database image for service container available at `localhost:<port>`
        # use YAML merge
        # https://discuss.circleci.com/t/updated-authenticate-with-docker-to-avoid-impact-of-nov-1st-rate-limits/37567/35?u=kimsia
        <<: *docker-auth
        environment: # environment variables for database
          POSTGRES_USER: root
          POSTGRES_DB: circle_test

    working_directory: ~/repo

    # can check if resource is suitable at resources tab in builds
    resource_class: large

    # turn on parallelism to speed up
    parallelism: 4

    steps: # a collection of executable commands
      # add deploy key when needed esp when requirements point to github url
      # - add_ssh_keys:
      #     fingerprints:
      #       - "ab:cd:ef..."
      - checkout # special step to check out source code to the working directory
      # using dockerize to wait for dependencies
      - run:
          name: install dockerize
          command: wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && sudo tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz
          environment:
            DOCKERIZE_VERSION: v0.4.0
      # the actual wait for database
      - run:
          name: Wait for db
          command: dockerize -wait tcp://localhost:5432 -timeout 1m
      - restore_cache: # restores saved dependency cache if the Branch key template or requirements.txt files have not changed since the previous run
          key: deps1-{{ .Branch }}-{{ checksum "requirements/base.txt" }}-{{ checksum "requirements/local.txt" }}
      - run: # install and activate virtual environment with pip
          command: |
            python3 -m venv venv
            . venv/bin/activate
            pip install --upgrade setuptools && pip install wheel
            pip install --upgrade pip==22.0.4
            pip install --upgrade pip-tools
            pip-sync requirements/base.txt requirements/local.txt
      - save_cache: # special step to save dependency cache
          key: deps1-{{ .Branch }}-{{ checksum "requirements/base.txt" }}-{{ checksum "requirements/local.txt" }}
          paths:
            - "venv"
      - run:
          name: run collectstatic
          command: |
            . venv/bin/activate
            python3 manage.py collectstatic --noinput
      - run: # run tests
          name: run tests using manage.py
          command: |
            # get test files while ignoring __init__ files
            TESTFILES=$(circleci tests glob "*/tests/*.py" | sed 's/\S\+__init__.py//g' | sed 's/\S\+factories.py//g')
            echo $TESTFILES | tr ' ' '\n' | sort | uniq > circleci_test_files.txt
            TESTFILES=$(circleci tests split --split-by=timings circleci_test_files.txt | tr "/" "." | sed 's/\.py//g')
            . venv/bin/activate
            # coverage's --parallel-mode will generate a .coverage-{random} file
            # usage: https://docs.djangoproject.com/en/3.2/topics/testing/advanced/#integration-with-coverage-py
            # add `--verbosity=3` between $TESTFILES --keepdb if need to debug
            coverage run --parallel-mode manage.py test --failfast $TESTFILES --keepdb
          # name: run tests using pytest
          # command: |
          #   . venv/bin/activate
          #   pytest -c pytest.ini -x --cov-report xml --cov-config=.coveragerc --cov
      - store_test_results:
          path: test-results
      - store_artifacts:
          path: test-results
          destination: tr1
      # save coverage file to workspace
      - persist_to_workspace:
          root: ~/repo
          paths:
            - .coverage*
  fan-in_coverage:
    docker:
      # specify the version you desire here
      # use `-browsers` prefix for selenium tests, e.g. `3.7.7-browsers`
      - image: cimg/python:3.8.12
        # use YAML merge
        # https://discuss.circleci.com/t/updated-authenticate-with-docker-to-avoid-impact-of-nov-1st-rate-limits/37567/35?u=kimsia
        <<: *docker-auth
    working_directory: ~/repo
    resource_class: small
    parallelism: 1
    steps:
      - checkout
      - attach_workspace:
          at: ~/repo
      - restore_cache: # restores saved dependency cache if the Branch key template or requirements.txt files have not changed since the previous run
          key: deps1-{{ .Branch }}-{{ checksum "requirements/base.txt" }}-{{ checksum "requirements/local.txt" }}
      - run: # install and activate virtual environment with pip
          command: |
            python3 -m venv venv
            . venv/bin/activate
            pip install --upgrade setuptools && pip install wheel
            # because of https://github.com/jazzband/pip-tools/issues/1617#issuecomment-1124289479
            pip install --upgrade pip==22.0.4
            pip install --upgrade pip-tools
            pip-sync requirements/base.txt requirements/local.txt
      - save_cache: # special step to save dependency cache
          key: deps1-{{ .Branch }}-{{ checksum "requirements/base.txt" }}-{{ checksum "requirements/local.txt" }}
          paths:
            - "venv"
      - run:
          name: combine coverage and generate XML report
          command: |
            . venv/bin/activate
            coverage combine
            # at this point, if combine succeeded, we should see a combined .coverage file
            ls -lah .coverage
            # this will generate a .coverage.xml file
            coverage xml
      - codecov/upload:
          # xtra_args: '-F'
          upload_name: "${CIRCLE_BUILD_NUM}"
workflows:
  main:
    jobs:
      - build
      - fan-in_coverage:
          requires:
            - build

为了从这个脚本中轻松访问Docker注册表,你在一个上下文中设置了一个docker-auth 字段,并提供了你的Docker Hub凭证。这些凭证将在后面的教程中作为环境变量列入。

接下来,Codecov球体CircleCI球体注册表中被拉入。这个球体有助于将你的覆盖率报告上传到Codecov,而无需任何复杂的配置。

两个独立的工作构建项目,在CircleCI上运行测试,并将覆盖率报告部署到Codecov。

  • build
  • fan-in_coverage

配置的工作流程将确保build 工作在fan-in_coverage 开始之前完全运行,因为它的输出将被上传到Codecov。

工作结构和工作流程

如上一节所述,有两个作业:buildfan-in_coverage 。我在这里将它们折叠起来,以便于回顾。

jobs: build:...
  fan-in_coverage:...
workflows:
  main:
    jobs:
      - build
      - fan-in_coverage:
          requires:
            - build

请注意fan-in_coverage 需要来自build 工作的输出,以及fan-in_coverage 如何将覆盖工件上传到codecov.io。使用两个作业允许你使用并行性,并且仍然允许上传工件。

平行性

为什么要使用并行性?答案很简单:并行可以加快构建的速度。

# turn on parallelism to speed up
parallelism: 4

为了允许并行,你需要拆分测试。为了拆分测试,CircleCI需要知道测试文件的完整列表。

通过拆分测试实现并行化

第一行试图找到所有相关的测试文件,跳过__init__factories.py 。然后,文件列表被写入circleci_test_files.txt

TESTFILES=$(circleci tests glob "*/tests/*.py" | sed 's/\S\+__init__.py//g' | sed 's/\S\+factories.py//g')
echo $TESTFILES | tr ' ' '\n' | sort | uniq > circleci_test_files.txt
TESTFILES=$(circleci tests split --split-by=timings circleci_test_files.txt | tr "/" "." | sed 's/\.py//g')

然后,CircleCI将使用coverage.py 帮助以并行模式运行测试。如果你需要调试,添加一个--verbosity=3 标志。

# add `--verbosity=3` between $TESTFILES --keepdb if need to debug
coverage run --parallel-mode manage.py test --failfast $TESTFILES --keepdb

存储工件和文件

最后,配置告诉CircleCI在哪里存储工件和其他覆盖率相关的文件,以便在fan-in_coverage

- store_test_results:
    path: test-results
- store_artifacts:
    path: test-results
    destination: tr1
# save coverage file to workspace
- persist_to_workspace:
    root: ~/repo
    paths:
      - .coverage*

提交所有的修改并更新你在GitHub上的项目库。

将您的项目连接到CircleCI

要将您的项目连接到CircleCI,请登录您的CircleCI账户。如果你用你的GitHub账户注册,你所有的仓库将在你的项目仪表板上可用。从列表中找到你的项目;在这种情况下,它是dockerized-django-demo 。点击Set Up Project

![项目设置]((/blog/media/2022-06-06-setup-project.png){: .zoomable }。

你将被提示在你的项目中选择.circleci/config.yml 文件。然后,输入分支的名称并点击设置项目

![项目设置-选择配置]((/blog/media/2022-06-06-select-config.png){: .zoomable }

你的工作流程将启动并成功构建。

将Docker Hub凭证作为环境变量添加到项目设置中

接下来,你需要将你的Docker Hub用户名和个人访问令牌作为环境变量添加到项目设置中。

进入GitHub repo的CircleCI项目页面,点击项目设置。从左边的菜单侧边栏选择环境变量

创建两个环境变量:DOCKERHUB_PATDOCKERHUB_USERNAME 。使用你的Docker Hub账户作为数值的来源。

![环境变量]((/blog/media/2022-06-06-env-var-for-dockerized-django.png) {: .zoomable }

当你完成后,每次推送到你的GitHub仓库或拉动请求都会导致在你的CircleCI上构建。

因为演示项目是一个公开的版本,CircleCI项目的构建页面和Codecov覆盖页面也是公开的。

查看演示项目的CircleCI项目构建页面,了解在完成所有步骤后,您的构建页面应该是什么样子。

总结

在这篇文章中,你克隆了Django 3.2演示项目的生产准备,Docker化的演示项目。现在你可以为你自己的Docker化Django 3.2项目做同样的事情。更重要的是,在文章的后半部分,我深入探讨了config.yml 文件中应包含的内容。

如前所述,我建议你第一次使用演示项目按照教程进行操作。第二次,当使用你自己的项目时,只需用your Django project 替换所有提到的demo Django project

我强调了解释的某些部分,以便你知道它们是如何帮助你加快CI/CD构建的。使用并行性可以加快构建速度,而不会牺牲像Docker和上传到代码覆盖供应商的关键部分。需要注意的一点是,这里的设置是为了持续交付,而不是持续部署。请访问这个页面,了解更多的区别。


KimSia是一个独立的软件工程师,为企业编写运营软件作为一种服务。他是一个狂热的Python开发者,其项目包括GreenDeployAutonomyFirst.app