CI/CD 如何管理前端和后端之间的依赖关系?

In CI/CD how to manage dependency between frontend and backend?

我将描述我的设置以使问题不那么抽象,但它们似乎并不特别ci适合我的情况。

上下文

我们有 Python-Django 后端和一个 VueJS 前端,每个都在一个存储库中,使用 Portainer(使用堆栈)配置和部署 Gitlab-CI。 每个存储库的生产分支中的提交遵循此路径:

  1. 提交
  2. gitlab-ci管道:
    1. 构建docker图像
    2. 测试图像(针对部署的后端测试前端)
    3. 将图像标记为 production:latest
    4. 将映像推回 git实验室注册表
    5. webhook portainer中对应的服务(frontend/backend)更新部署的镜像
  3. 搬运工:
    1. 拉取镜像
    2. 部署

问题

部署同步

想象一下,我们正在对前端和后端进行重大更改,并且两者都将与以前的版本不兼容。所以必须同时部署新版本。

在我们当前的设置中,我们必须首先部署后端(这会破坏已部署的前端),然后部署新的前端,修复生产,但有一个 "down" 周期。

测试的分支依赖

有时候我们在前端开发branch feature-1的时候,需要在后台针对branch feature-1进行测试

在我们当前的设置中,前端中的所有提交都针对已部署的后端进行测试(以避免在 CI 中复制后端,仅使用生产 API 地址),导致错误在这种情况下测试结果。

后端集成测试

当提交到后端时,它可能会破坏前端。

目前没有针对前端测试后端(只有另一种方式)。

可能的解决方案

对于部署同步问题,我考虑创建另一个存储库,该存储库只有一个文件 specifying 应该部署的前端和后端版本。在此存储库中的提交将导致 Portanier 的服务 webhooks "curled" 用于更新(后端和前端)。这不能保证同时更新(在 Portainer 中可能会失败并且不会回滚),但它会比当前设置更好。

我不确定这里应该使用什么来 specify 版本:提交散列、git 标签、分支、docker 图像版本...最后也许可以避免重建和测试图像,但我认为图像名称和版本在 Portainer 的堆栈定义中是固定的,并且不容易自动更新。

对于分支依赖性测试,我考虑过在每个存储库(前端和后端)中都有一个文件 specifying backend/frontend 中的哪个分支进行测试。但是每个存储库的 CI 必须复制整个部署环境(例如,运行 一个新的后端和前端来测试每个前端提交)。这也将允许后端集成测试。由于我们使用 Docker,这不是很复杂,但每个 CI 管道将花费额外的时间...另外,当第一个存储库(前端或后端)被提交时,它会引用另一个存储库中仍然不存在的分支,并且失败...

这些解决方案对我来说似乎很尴尬,特别是ci如果这些是 CI/CD 和 Docker 的常见问题。当我们添加更多存储库时,它会变得更加丑陋。

备选方案?

感谢关注!

(编辑: 出于好奇,我当前的设置是基于此 article)

Deployment synchronization

Imagine we're doing a major change in both frontend and backend, and both will become incompatible with previous versions. So the new versions must be deployed simultaneously.

In our current setup we have to first deploy the backend (what will break the deployed frontend) and then deploy the new frontend, fixing production, but with a "down" period.

我不是 portainer 用户,但也许你可以依赖一些 docker-compose.yml 文件左右,收集后端和前端的版本?在这种情况下,它们可以同时更新……

确实根据 portainer/portainer#1963 and this doc page,portainer 似乎同时支持 docker-compose 和 swarm 堆栈。

此外,docker swarm 提供了一些功能,可以在不停机的情况下执行服务升级,如 this blog 中所述,但我不知道在 portainer 中可以配置到什么程度。

Possible Solutions

I'm not sure about what should be used to specify versions here: commit hash, git tag, branch, docker image version... The last maybe avoids having to rebuild and test images, but I think images name and versions are fixed in Portainer' stacks definition, and not easy to update automatically.

虽然提交哈希是精确的标识符,但它们可能不足以方便地识别不兼容的版本。因此,您可能希望 semantic versioning 在 Git 后端存储库上使用标签(and/or 分支)。

然后,您可以相应地标记相应的 Docker 图片,如果需要,可以引入一些同义标签。例如,假设后端已发布版本 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0, 1.2.1, 1.2.2,标准做法包括标记 Docker 图像,如下所示:

  • project/backend:2.0.2 = project/backend:2.0 = project/backend:2
  • project/backend:2.0.1
  • project/backend:2.0.0
  • project/backend:1.1.1 = project/backend:1.1 = project/backend:1
  • project/backend:1.1.0
  • project/backend:1.0.1 = project/backend:1.0
  • project/backend:1.0.0

(如果需要删除旧图像)

Backend integration tests

Currently the backend isn't tested against the frontend (only the other way).

好的,但我猜你的方法相当标准(前端取决于后端,而不是相反)。

无论如何,我记得即使被测系统是前端,实施单元测试(开发和 运行 比集成测试成本更低)也可能是值得的,这样在触发必要的集成测试之前,管道中的第一阶段快速 运行 进行这些单元测试。

Branch dependency for tests

In our current setup all the commits in the frontend are tested against the deployed backend (to avoid replicating the backend in CI, only the production API address is used), resulting in false tests results in such cases.

这可能不够灵活:一般来说,CI/CD 假设集成测试 运行 使用专用后端实例(“开发”服务器或“预生产”服务器),并且如果所有集成测试和系统测试都通过,则图像将部署到“prod”服务器(并监控等)

我从你的 post 那里看到你正在使用 GitLab CI,其中有一些 native Docker support,所以也许这可以很容易地实现。

一些提示:

  • 假设后端已在非向后兼容版本中进行了修改,并且相应的 Docker 映像在注册表中可用(例如 GitLab CI).然后您可以在前端配置中更改该图像的规范(例如,在 GitLab CI 配置文件中将 project/backend:1 替换为 project/backend:2 左右)。

  • 您的后端可能实现为 REST Web 服务,在这种情况下,您可能还想在 URL 中添加版本前缀,以便当您从 [=19] 切换时=] 到 project/backend:2(具有不兼容的更改),如果需要,可以同时部署两个版本,到 URLs https://example.com/api/v1/…https://example.com/api/v2/…

此外,除了 CI/CD 只有两个 repos 的解决方案(后端分开测试,前端针对后端的相关版本进行测试),您首先建议的解决方案也可以考虑:

For the deployment synchronization problem I thought about creating another repository that would have only one file specifying the versions for frontend and backend that should be deployed. A commit in this repository would result in both Portanier' services webhooks being "curled" for update (backend and frontend). This doesn't guarantee the simultaneous update (one may fail in Portainer and there would be no rollback), but it would be better than current setup.

您可以稍微修改此方法以避免此类部署失败:您可以向第三个存储库添加一些 CI 设置,它只包含一个 docker-compose.yml 左右的文件,然后移动从前端 CI 到“撰写”CI…

的集成测试

(仅供参考,此方法类似于此 DigitalOcean tutorial 中建议的方法,其中集成测试是通过某些 docker-compose.test.yml 文件实现的。)

Branch dependency for tests

Sometimes when we develop branch feature-1 in the frontend, it must be >tested against branch feature-1 from the backend.

In our current setup all the commits in the frontend are tested against the deployed backend (to avoid replicating the backend in CI, only the production API address is used), resulting in false tests results in such cases.

Backend integration tests

When a commit is done to the backend, it can break the frontend.

Currently the backend isn't tested against the frontend (only the other way).

在我现在的公司,我们有用于前端 (FE) 和后端 (BE) 的 Django,每个都在一个存储库中。 我们正在关注基于主干的开发。 CI/CD 我们也使用 gitlab。 我推出了你在这里提到的,一点也不觉得尴尬。

环境与此分支模型之间的关系。

|分支机构|示例|环境|

|大师|大师|分期|

|release-v*|release-v1.1.10|preprod|

标签:

|标签|示例|环境|

|v<主要>.<次要>.<补丁>| v.1.1.10|生产|

一旦创建 branches/tags 或对定义的分支进行任何提交,gitlab 将触发自动 build/deployment.

前端必须针对后端进行测试。我们用功能分支来做到这一点。

特征/<分支摘要>

开发者需要确保FE和BE上存在相同的特性分支名称

为每个部署生成每个 frontend/backend 的 URL 看起来像 -fe.<域>.com 用于前端 -be.< domain >.com 作为后端

例如:两个 FE/BE 存储库中都有一个 feature/mytask。 FE URL 是 mytask-fe.< domain >.com 和 BE URL 是 mytask-be.< domain >.com

您可以使用 docker-compose 但在我的例子中,我们的应用程序是使用 helm 部署到 kubernetes 的。 这个实现的更进一步,我的 FE 和 BE 有一个由 traefik 管理的 k8s 入口。 DNS 记录(对于每个 URL)自动创建(由 k8s DNS 控制器),后端使用 DB 和 Redis,每次创建或更改功能分支时都会创建它们。按照特性分支命名的约定,FE知道如何连接BE,BE知道如何使用自己的DB和Redis。

例如: helm upgrade --install ${RELEASE_NAME} ...

RELEASE_NAME 是从 feature/< branch-summary > 中提取的(不超过 63 个字符)

其他,您可以考虑为功能分支部署初始化数据。 就我而言

*)开发人员将设法填充数据(可能 运行 脚本作为 k8s 中的初始化容器)。 如果开发人员向同一功能分支推送提交,这将触发重新部署并重新初始化 DB / Redis 的所有数据。开发人员可能需要重新填充数据,QC 可能需要从该功能的开头重新开始测试。

*) 避免在 k8s 中创建太多资源和在 gitlab 存储库中创建分支。 我在 gitlab CI 中设置了一个删除函数,以便删除 BE/FE 存储库中的一个功能分支,这将分别触发 k8s 中的部署删除。