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

时间:2018-11-21 12:27:24

标签: docker continuous-integration gitlab-ci continuous-deployment portainer

我将描述我的设置以使问题不太抽象,但问题似乎并非我所要解决的。

上下文

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

  1. 提交
  2. gitlab-ci管道:
    1. 构建docker映像
    2. 测试映像(针对部署的后端测试了前端)
    3. 将图像标记为生产:最新
    4. 将图像推回gitlab注册表
    5. web挂接portainer中的相应服务(前端/后端)以更新已部署的映像
  3. 门框:
    1. 拉动图像
    2. 部署

问题

部署同步

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

在当前设置中,我们必须首先部署后端(将会破坏已部署的前端),然后部署新的前端,以固定生产,但周期为“停工”。

测试的分支依赖性

有时候我们在前端开发分支功能1时,必须从后端对分支功能1进行测试。

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

后端集成测试

对后端的提交完成后,可能会破坏前端。

当前后端尚未针对前端进行测试(仅相反)。

可能的解决方案

对于部署同步问题,我考虑过创建另一个存储库,该存储库只有一个文件,该文件指定应部署的前端和后端的版本。在此存储库中的提交将导致Portanier的服务Web挂钩都被“卷曲”以进行更新(后端和前端)。这不能保证同时进行更新(Portainer中的更新可能会失败,并且不会回滚),但是比当前设置要好。

我不确定在此处指定版本时应该使用什么:提交哈希,git标签,分支,docker映像版本...最后一个可以避免不必重建和测试映像,但是我认为映像的名称和版本已在Portainer的堆栈定义中固定,并且不容易自动更新。

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

这些解决方案对我来说似乎很尴尬,特别是如果这些是Docker在CI / CD中常见的问题。当我们向组合中添加更多存储库时,它可能变得更加难看。

替代品?

感谢您的关注!

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

2 个答案:

答案 0 :(得分:1)

  

部署同步

     

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

     

在当前设置中,我们必须首先部署后端(将会破坏已部署的前端),然后部署新的前端,以固定生产,但周期为“停工”。

我不是portainer用户,但是也许您可以依靠某个docker-compose.yml文件,同时收集后端和前端的版本?在这种情况下,它们可以同时更新...

根据portainer/portainer#1963this doc page的定义,portainer似乎支持docker-compose和swarm堆栈。

this blog中也记录了docker swarm提供的一些功能,可以在不停机的情况下执行服务升级,但是我不知道在portainer中可以在多大程度上进行配置。

  

可能的解决方案

     

我不确定在此处指定版本时应该使用什么:提交哈希,git标签,分支,docker映像版本...最后一个可以避免不必重建和测试映像,但是我认为映像的名称和版本已在Portainer的堆栈定义中固定,并且不容易自动更新。

尽管提交哈希是精确的标识符,但它们可能不足以识别不兼容的版本。因此,您可能想依靠semantic versioning在Git后端存储库上使用标签(和/或分支)。

然后,您可以相应地标记相应的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

(如果需要,删除旧图像)

  

后端集成测试

     

当前后端尚未针对前端进行测试(仅相反)。

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

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

  

测试的分支依赖性

     

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

这可能不够灵活:通常,CI / CD假定集成测试是使用专用后端实例(“ dev”服务器或“ pro-prod”服务器)运行的,并且所有集成测试和系统测试都通过了,将映像部署到“产品”服务器(并进行监控等)

我从您的帖子中看到您正在使用GitLab CI,它具有一些native Docker support,因此也许可以轻松实现。

一些提示:

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

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

此外,除了只有两个带有CI / CD的存储库的解决方案(分开测试后端,并针对后端的相关版本测试前端)之外,还可以考虑您首先建议的解决方案:

  

对于部署同步问题,我考虑过创建另一个存储库,该存储库只有一个文件,该文件指定应部署的前端和后端的版本。在此存储库中的提交将导致Portanier的服务Web挂钩都被“卷曲”以进行更新(后端和前端)。这不能保证同时进行更新(Portainer中的更新可能会失败,并且不会回滚),但是比当前设置要好。

您可以略微修改此方法,以免发生这样的部署失败:您可以在第三个存储库中添加一些配置项设置,该配置项仅包含一个docker-compose.yml文件,然后从前端配置项移动集成测试那个“组成” CI ...

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

答案 1 :(得分:0)

  

测试的分支依赖性

     

有时候我们在前端开发分支功能1时,必须>从后端对分支功能1进行测试。

     

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

  

后端集成测试

     

对后端的提交完成后,可能会破坏前端。

     

当前后端尚未针对前端进行测试(仅相反)。

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

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

|分支|示例|环境|

|主|主|分期|

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

标签:

|标签|示例|环境|

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

一旦创建了分支/标签或对已定义分支的任何提交,gitlab将触发自动构建/部署。

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

功能/ <分支摘要>

开发人员需要确保FE和BE上都存在相同的功能分支名称。

为每个部署生成每个前端/后端的URL,如下所示 -fe。 .com for frontend -be。 .com for backend

例如:FE / BE库中都有一个功能/我的任务。 FE URL是mytask-fe。<域> .com,BE URL是mytask-be。<域> .com

您可以使用docker-compose,但就我而言,我们的应用程序是使用helm部署到kubernetes的。 在此实现的基础上,My FE和BE具有k8s入口,由traefik管理。 DNS记录(针对每个URL)是通过k8s DNS控制器自动创建的,后端使用的是DB和Redis,它们在每次创建或更改功能分支时都会创建。按照功能分支命名的约定,FE知道如何连接到BE,BE知道如何使用其自己的DB和Redis。

例如: 安全升级-安装$ {RELEASE_NAME} ...

RELEASE_NAME是从功能/ <分支摘要>中提取的(不超过63个字符)

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

*)开发人员将设法填充数据(也许在k8s中将脚本作为init容器运行)。 如果开发人员将提交推送到同一功能分支,则将触发重新部署,并且将重新初始化DB / Redis的所有数据。开发人员可能需要重新填充数据,而质量控制人员可能需要从此功能开始重新开始测试。

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