vlambda博客
学习文章列表

读书笔记《hands-on-docker-for-microservices-with-python》管理工作流

Managing Workflows

在本章中,我们将把前面章节中描述的不同流程整合到一个通用的工作流程中,以便我们可以对单个微服务进行更改。我们将从获取新功能请求的过程继续进行到本地开发、审查、在演示环境中测试、批准更改并将其发布到实时集群。

这与我们在第4章中介绍的管道概念有关,< em>创建管道和工作流。然而,在本章中,我们将讨论任务的过程。管道和构建结构可确保任何提议的更改都符合质量标准。在本章中,我们将重点关注技术的团队合作方面,以及如何在跟踪不同变化的同时实现流畅的交互。

在本章中,我们将介绍以下主题:

  • Understanding the life cycle of a feature
  • Reviewing and approving a new feature
  • Setting up multiple environments
  • Scaling the workflow and making it work

在本章结束时,我们将清楚地了解为我们的一个微服务设置新功能所涉及的不同步骤,以及我们如何使用多个环境来测试并确保发布成功。

Understanding the life cycle of a feature

遵循敏捷原则,任何团队的主要目标都是能够快速实施新功能,而不会影响系统的质量或稳定性。改变的第一个元素是 功能请求。

功能请求是对系统更改的非技术性描述。功能请求通常由非工程师(产品所有者、经理和 CEO)出于与业务相关的原因(例如制造更好的产品或增加收入)而寻求改进系统。

功能请求可以很简单,例如 在主页更新公司的标志,或者大而复杂的,比如 增加对新 5G 网络的支持。功能请求可能包括错误报告。虽然他们通常不会,但为了本章的目的,他们会这样做。

复杂的功能请求可能需要细分为较小的自包含功能请求,以便我们可以小增量进行迭代。

Our focus is on the elements that need to be taken into account due to the microservices approaches and practices more than agile practices. Such practices deal with how to structure feature requests into tasks and estimations, but they are not specific to the underlying technologies.

Take a look at the Further reading section at the end of this chapter to find out more about agile practices and methodologies.

在一个整体中,我们将所有元素都放在同一个代码库下。因此,无论一个特定的功能请求多么复杂,只有一个系统会受到影响。单体中只有一个系统。但是,一旦我们迁移到微服务,情况就不是这样了。

在微服务架构中,我们需要根据它影响的微服务或微服务来分析任何传入的功能请求。如果我们正确设计我们的微服务,大多数请求只会影响单个微服务。然而,最终,某些功能请求将太大而无法整齐地放入单个微服务中,并且需要分成两个或更多步骤,每个步骤更改不同的微服务。

例如,如果我们有一个新功能请求允许我们提及用户in 想法的文本(类似于在 Twitter 上提及的方式) ,那么 这个提及 必须存储在 Thoughts Backend 中并显示在 Frontend 中。此功能影响两个微服务: 前端和Thoughts后端。

在本节中,我们指的是我们在前几章中介绍的概念,并将它们从全局的角度结合在一起。

在下一小节中,我们将研究影响多个微服务的特性。

Features that affect multiple microservices

对于多个微服务特性请求,您需要将特性划分为多个技术特性,每个技术特性影响一个微服务。

每个技术特性都应涵盖与其影响的微服务相关的一个方面。如果每个微服务都有明确的目的和目标,那么该功能就会被完善和泛化,以便用于以后的请求。

The basis for a successful microservice architecture is to have loosely coupled services. Ensuring that the API of each microservice makes sense on its own is important if we wish to avoid blurring the lines between services. Not doing so may mean that independent work and deployments aren't allowed.

还应考虑请求和微服务之间的依赖关系,以便可以将工作安排回前面。这意味着准备将添加额外数据或功能但默认保留旧行为的新功能。完成此操作后,可以部署使用此额外数据的新功能。这种工作方式可确保在任何给定时间向后兼容。

回到我们之前的示例,为了将用户的提及添加到他们的想法中,我们需要使想法后端能够处理对用户的可选引用。这是一个独立的任务,不会影响现有功能。它可以被部署和测试。

然后,我们可以在前端进行相应的更改,以允许外部用户通过 HTML 界面与之交互。

正如我们在 第 1 章中所讨论的,采取行动 - 设计、计划, 和 Execute,对于任何微服务架构来说,我们可以独立部署服务是至关重要的。这使我们能够独立地测试服务并避免任何需要复杂部署的开销,这些开销使我们在发生错误时难以调试和回滚。

如果不同的团队在不同的微服务上独立工作,那么他们也需要协调。

在下一节中,我们将学习如何在单个微服务中实现一个特性。

Implementing a feature

一旦我们定义了一个独立的技术特征,就可以实现它。

Defining a technical feature in a clear manner can be challenging. Remember that a single feature may need to be further subdivided into smaller tasks. However, as we mentioned previously, the objective here is not to structure our tasks.

通过创建一个新的 Git 分支开始您的任务。可以更改代码以反映此分支中的新功能。正如我们在第 2 章中看到的,使用 Python 创建 REST 服务< /em> 和 第 3 章构建、运行和测试您的服务 使用 Docker,可以运行单元测试以确保这项工作不会破坏构建。

As we described in Chapter 3 , Build, Run, and Test Your Service Using Docker, in the Operating with an immutable container section, we can use pytest arguments to run subsets of tests to speed up development, thereby enabling quick feedback when running tests. Make sure you use it.

可以通过本地集群的部署来检查此功能在整个系统中的工作方式。这会启动可能受此分支中工作影响的其他微服务,但它有助于确保当前工作不会中断任何影响其他微服务的现有调用。

基于管道,任何推送到 Git 的提交都将运行它的所有测试。这将及早发现问题并确保构建在与主分支合并之前是正确的。

在此过程中,我们可以使用拉取请求来查看主分支和新功能之间的更改。我们可以在合并之前检查我们的 GitHub 配置以确保代码处于良好状态。

一旦功能准备就绪并与主分支合并,应该创建一个新标签以允许其部署。作为配置管道的一部分,此标记将触发生成,该生成在注册表中生成图像并使用相同的标记标记图像。 标签和图片是不可变的,所以我们可以确保代码不会在不同的环境之间发生变化。您可以放心地前滚和后退,代码将与标记中定义的代码完全相同。

正如我们在第 8 章中看到的,使用 GitOps 原则 ,标签可以按照 GitOps 原则进行部署。部署在 Kubernetes 配置文件中描述,在 Git 控制下,并在需要批准的拉取请求中进行审查。一旦拉取请求与主分支合并,它将由 Flux 自动部署,正如我们在 第 8 章使用 GitOps 原则,在设置 Flux 以控制 Kubernetes 集群部分。那时,该功能在集群中可用。

让我们回顾一下这个生命周期,从技术请求的描述到部署到集群中的时间:

这是我们介绍的 Flow 的更完整版本 第 4 章创建管道和工作流
  1. The technical request is ready to be implemented into a single microservice.
  2. A new feature branch is created.
  3. The microservice's code is changed in this branch until the feature is ready.
  4. A pull request, which is used to merge the feature branch into the main branch, is created. This, as described in Chapter 4, Creating a Pipeline and Workflow, in the Understanding the continuous integration practices section, runs the CI process to ensure that it is of a high quality.
  5. The pull request is reviewed, approved, and merged into the main branch.
  6. A new tag is created.
  7. A deployment branch is created in the GitOps repository that changes the version of the microservice to the new tag.
  8. A pull request, which is used to merge this deployment branch, is created. Then, it's reviewed and merged.
  9. Once the code has been merged, the cluster automatically releases the new version of the microservice.
  10. Finally, the new feature is available in the cluster!
这是生命周期的简化版本;实际上,情况可能更复杂。在本章后面,我们将研究生命周期需要部署到多个集群的情况。

在下一节中,我们将看看在审查和批准拉取请求时的一些建议。

Reviewing and approving a new feature

正如我们在 Chapter 4 中描述的管道模型所指定的,创建一个流水线和工作流,候选代码会经过一系列阶段,如果出现问题就会停止。

正如我们之前提到的,如果我们希望在我们的微服务代码中引入新功能,以及如果我们希望通过 GitOps 实践将这些更改部署到集群中,那么使用 GitHub 拉取请求进行审查是可行的。

在这两种情况下,我们都可以通过自动化测试和流程自动检查这一点。然而,最后一步需要人工干预:知识转移和额外的一双眼睛。一旦审阅者认为新功能准备就绪,他们就可以批准它。

这些工具是相同的,但审核过程的工作方式略有不同。这是因为目标不一样。对于功能代码,在被批准并合并到主分支之前,审查更容易讨论。另一方面,审核和批准版本通常更容易直接和快速。

让我们从学习如何查看功能代码开始。

Reviewing feature code

可以在开发功能并打开合并它的请求时启动代码审查。正如我们已经看到的,在 GitHub 中,可以在 pull request 阶段查看代码。

代码审查基本上是关于代码和新功能的有形讨论;也就是说,我们在将代码引入主分支之前对其进行检查。这为我们提供了在开发过程中以及在它成为系统组件之前改进功能的机会。

在这里,团队成员可能会阅读尚未合并的代码并给作者一些反馈。这可以反复进行,直到审阅者认为代码已准备好合并并批准它。本质上,除功能作者之外的其他人需要同意新代码符合要求的标准。

Code bases grow over time and their components can help each other out. Merging code into the main branch states that you fully accept that the new code will be maintained by the team as part of the code base.

代码可能需要由一个或多个人或特定人员批准。

在 GitHub 中,您可以启用代码所有者。这些是负责批准存储库或存储库部分的工程师。查看 GitHub 文档以获取更多信息: https://help.github.com/en/articles/about-code-owners

如今,代码审查是一个相当普遍的过程,并且在 GitHub 中使用拉取请求的流行度和易用性已经传播开来。大多数开发人员都熟悉这个过程。

不过,实施良好的反馈文化比看起来要困难得多。编写代码是一种深刻的个人体验;没有两个人会编写相同的代码。对于开发人员而言,除非有明确的规则,否则让他人批评您的代码可能是一种困难的经历。

以下是一些建议:

  • Tell your reviewers what they should look for. Make a point of following a checklist. This helps develop a culture within the team so that they care about shared core values. This also helps junior developers know what to look for. This may change from team to team, but here are some examples:
    • There are new tests.
    • Error conditions are tested.
    • The documentation is properly updated.
    • Any new endpoints comply with standards.
    • Architectural diagrams are updated.
  • Reviewing code is not the same thing as writing the code. There will always be discrepancies (for example, this variable name could be changed), but what needs to be reviewed is whether such changes need to be implemented. Nitpicking will erode the trust between team members.
  • The bigger the code to review, the more difficult it is to do. It's better to work in small increments, which works well with the principles of continuous integration.
  • All of the code should be reviewed on equal grounds. This includes the code of senior developers, and junior developers should be encouraged to leave honest feedback. This helps ownership of the code and its fairness to grow.
  • A code review is a conversation. A comment doesn't necessarily mean that the reviewer's feedback has to be implemented without you questioning it first. It opens a conversation about improving the code, and making clarifications and pushing back is totally fine. Sometimes, the proper way of handling a request, that is, to change a part of the code, is to leave a comment explaining why this was done in a particular way.
  • Reviews help spread knowledge about the code base. This isn't a silver bullet, though. Code reviews tend to fall into tunnel vision, where only small issues such as typos, and local snippets of code, are looked at, and not the bigger elements. This is why it's important to implement features in small increments: to help those around you digest change.
  • It's important to leave appreciative comments. Create a culture that appreciates well-written code. Only highlighting what's bad makes the review process miserable for the author.
  • Criticism should be addressed to the code, not to the coder. Ensure that your review is civil. In this step, we want to ensure that the code is of a high quality; you don't want to make yourself, as the reviewer, look superior.
Code reviews can be stressful for those who aren't used to them. Some companies are creating principles and ideas to make this process less painful. A good example can be found at https://www.recurse.com/social-rules. Don't be afraid to define and share your own principles.
  • It's important that code can be approved at all times, even when someone in the team is on holiday or sick. Ensure that you grant approval to multiple members of the team so that the approval process itself isn't a bottleneck.

当您开始进行代码审查时,请确保团队领导者牢记这些注意事项,并强调为什么要审查所有代码。

It is worth emphasizing how code reviews are not a technological solution, but a people-related one. As such, they can suffer from people-related problems such as big egos, adversarial discussions, or non-productive debates.

The microservice architecture is fit for big systems that have multiple people working on them. Teamwork is crucial. Part of that is ensuring that the code doesn't belong to a single person, but to a whole team. Code reviews are a great tool to that end, but be sure to actively look for healthy ones.

Over time, a consensus will develop and a lot of code will be developed consistently. In a healthy team, the amount of time that's spent on reviews should reduce.

随着时间的推移,团队会定期进行代码审查,但一开始设置这些基础可能会很复杂。确保您有时间介绍他们。正如我们之前提到的,一旦功能准备就绪,我们需要继续批准它。批准新功能的代码并将其合并到主分支是功能审查的最后阶段,但仍需要发布。版本受代码控制,也需要审查。

Approving releases

使用 GitOps 原则允许我们启用相同的审查和批准方法,以便我们可以在 Kubernetes 基础架构中进行更改。正如我们之前提到的,基础设施是由 Kubernetes 中的 YAML 文件定义的这一事实使我们能够控制这些更改。

对 Kubernetes 集群所做的任何更改都可以通过拉取请求和审查方法进行。 这使得批准发布到集群成为一个简单的过程。

这有助于最大限度地减少问题,因为更多的团队成员参与了更改,并且他们对基础架构的了解更好。这与允许团队控制自己的部署和基础架构的 DevOps 原则非常吻合。

但是,GitOps 中的基础架构更改往往比常规代码审查更容易审查。一般而言,它们是以非常小的增量完成的,并且大多数更改都非常简单,以至于产生辩论的可能性很小。

作为一般规则,尽量使任何基础架构更改尽可能小。基础设施变更风险更大,因为错误可能会破坏其中的重要部分。更改越小,风险越小并且更容易诊断任何问题

我们对代码审查提出的所有建议也可以发挥作用。最重要的是包含一些参考基础设施关键部分的指南。

Some sections of the infrastructure may be under the GitHub code owner's protection. This makes it mandatory for certain engineers to approve changes to critical parts of the infrastructure. Take a look at the documentation for more information: https://help.github.com/en/articles/about-code-owners.

由于基础设施被定义为存储在 GitHub 中的代码,这也使得复制基础设施变得容易,从而大大简化了多环境的生成。

Setting up multiple environments

在 Kubernetes 下创建、复制和删除命名空间的便利性大大减轻了之前保留多个环境副本以复制底层基础设施的负担。您可以利用它来发挥自己的优势。

基于我们前面提到的 GitOps 原则,我们可以定义新的命名空间来生成新的集群。我们可以使用另一个分支(例如,对生产集群使用 master 分支和 demo 对于演示集群)或复制包含集群定义的文件并更改命名空间。

You can use different physical Kubernetes clusters for different purposes. It's better to leave the production cluster as not being shared with any other environment to reduce risks. However, every other environment could live in the same cluster, which won't affect external customers.

一些功能请求足以证明开发团队将确切地知道要做什么,例如错误报告。但是,其他人在开发过程中可能需要更多的测试和沟通,以确保满足要求。当我们检查新功能是否对预期的外部用户真正有用时,可能就是这种情况,或者可能是更具探索性的功能。在这种情况下,我们需要调用外部方,即功能的最终批准者:利益相关者

利益相关者是项目管理中的一个术语,它指定第三方,即产品的最终用户或受产品影响的用户。在这里,我们使用该术语来指定对某个功能感兴趣但在团队外部的人,因此他们无法从内部定义功能需求。例如,利益相关者可以是经理、客户、公司 CEO 或内部工具的用户。

任何必须处理来自利益相关者的模糊定义请求(例如允许按名称搜索)的开发人员都必须对其进行调整:不,不是按名字,而是按姓< /em>。

Ensure that you define a proper end to these kinds of tasks. Stakeholder feedback can be endless if it's allowed to run without limits. Define what is and is not included in it, as well as any deadlines, beforehand.

要运行测试并确保正在开发的功能朝着正确的方向发展,您可以创建一个或多个演示环境,您将在其中部署正在进行的工作,然后再将其合并到主分支中。这将帮助我们与利益相关者分享这项工作,以便他们可以在功能完成之前给我们反馈,而无需我们在生产环境中发布它。

正如我们在前几章中看到的,在 Kubernetes 中生成一个新环境很容易。我们需要创建一个新的命名空间,然后复制集群的生产定义,从而改变命名空间。这将创建环境的副本。

更改正在开发的微服务的特定版本将允许我们创建它的工作版本。可以在此演示环境中照常部署较新的版本。

This is a simplified version. You may need to make changes between the production environment and demo environments, such as the number of replicas, and database setup. In such cases, a template environment could be used as a reference so that it's ready to be copied.

其他环境(例如登台)可以以类似的方式创建,目的是创建测试以确保部署到生产中的代码将按预期工作。这些测试可以是自动的,但如果我们想检查用户体验是否足够,它们也可以是手动的。

暂存环境是作为副本工作的设置,它尽可能忠实于生产环境,这意味着我们可以运行测试以确保生产中的部署能够正常工作。分阶段通常可以帮助我们验证部署过程以及任何最终测试是否正确。

暂存环境的运行通常非常昂贵。毕竟,它们是生产环境的副本。使用 Kubernetes,您可以轻松复制生产环境并减少所需的物理基础设施。您甚至可以在不使用时启动和停止它以降低成本。

您可以使用多个环境以类似的方式创建级联部署结构。这意味着需要将标签部署到暂存环境中并在进入生产环境之前获得批准。

现在让我们从开发人员的角度来看看如何处理这种结构。

Scaling the workflow and making it work

实施这种工作方式的一些挑战包括创建一种提供足够反馈循环的文化,以及在快速审查新代码的同时仔细检查新代码。等待审核是阻止开发人员实施正在审核的功能的阻止状态。

虽然这段等待时间可以用于其他目的,但无法取得进展会很快降低生产力。要么开发人员将并行保留一些功能,这从上下文切换的角度来看是非常有问题的,要么他们需要等待并滚动他们的拇指,直到审查完成。

The context switch is probably the most serious killer of productivity. One of the keys to keeping your team's productivity high is being able to start and finish a task. If the task is small enough, it will be finished quickly, so swapping between projects is easier. However, working on two or more tasks at the same time is a very bad practice.

If this happens often, try to divide your tasks into smaller chunks.

为了能够在彻底审查代码和减少阻塞时间之间取得平衡,需要牢记一些元素。

Reviewing and approving is done by the whole team

任何时候都需要有足够的审稿人。如果只是开发人员有经验,那么评审可能最终只能由团队中最资深的人完成,例如团队负责人。尽管原则上这个人可能是更好的审稿人,但从长远来看,这种结构会损害团队,因为审稿人将无能为力。如果审阅者因任何原因(例如生病或休假)无法在开发和发布阶段取得进展,也将被阻止。

相反,让整个团队能够审查他们同行的代码。尽管高级贡献者在教团队其他成员如何审查方面发挥了更积极的作用,但一段时间后,大多数审查不应该需要他们的帮助。

最初,虽然实施此过程需要积极的指导,但这通常由团队的高级成员领导。 审查代码是一种可训练的能力,目标是在一段时间后,每个人都能够运行审查并允许批准拉取请求。

部署拉取请求遵循相同的过程。最终,团队中的每个人,或者至少是相当数量的成员,都应该能够部署一个版本。不过,最初的主要审查者可能是不同的人。

审核发布的最佳人选可能是 Kubernetes 基础架构配置方面的专家,而不是微服务代码方面的专家。

Understanding that not every approval is the same

请记住,功能的不同阶段并不同样重要。代码审查的早期过程是确保代码可读并保持质量标准。在早期阶段,代码会有相对较多的注释,并且会有更多的讨论,因为需要调整更多的元素。

A big part of reviews is creating code that is understandable enough that other members of the team understand it. Although some people claim that code reviews make everyone aware of the changes that other members of the team are implementing, in my experience, reviewers are not that aware of specific features.

A good review, however, will ensure that nothing cryptic is being introduced into the code base and that the core elements are respected (elements such as introducing tests, keeping documentation up to date, and keeping code readable). As we suggested previously in this chapter, try to create an explicit list of things to check. It will help you make the reviews and code more consistent.

新功能的部署阶段只需要我们检查微服务的版本是否发生变化以及基础设施的其余部分是否完好无损。这些通常非常小;他们中的大多数人会仔细检查没有错别字,并且要更改的微服务是正确的。

Defining a clear path for releases

拥有一个简单而清晰的流程可以帮助所有相关人员清楚地了解一个特性是如何从开发到发布到生产环境中的。例如,根据我们讨论过的想法,我们最终会得到一个类似于下图所示的部署路径:

读书笔记《hands-on-docker-for-microservices-with-python》管理工作流

对于这些步骤中的每一个,我们都需要验证该步骤是否正确。正如我们在 第 4 章中看到的,创建管道和工作流 em>,自动测试确保合并到主分支中的任何内容都不会破​​坏现有构建。这涵盖了前面的图表直到 创建标签 步骤。

同样,可能有一种方法可以在应用部署后验证部署是否成功。以下是对此的一些想法:

  • Manual tests, to check that the deployed microservice works as expected
  • Automated tests, such as the ones described in Chapter 4, Creating a Pipeline and Workflow
  • Check that the image to be deployed has been correctly deployed using Kubernetes tools or a version API

一旦一个部署阶段成功完成,就可以开始下一个部署阶段。

在非生产环境中执行部署可以最大程度地降低中断生产的风险,因为这将确保部署过程是正确的。 该过程需要足够快以允许快速部署,从而使它们尽可能小。

The full process from merging into the main branch until the new version is released into the production environment should take less than a few hours, but ideally less than that.

If more time is required, the process is probably too heavy.

小型、频繁的部署将破坏生产环境的风险降到最低。在某些特殊情况下,常规程序可能会很慢,应使用紧急程序。

Emergency releases

让我们想象一下,生产中有一个严重的错误,需要尽快解决。对于这些特殊情况,可以事先定义一个紧急流程。

这个紧急过程可能涉及加快审查甚至完全跳过审查。这可能包括跳过中间版本(例如事先不部署到演示环境)。确保您明确定义何时需要此过程,并确保仅在紧急情况下使用它。

If your regular deployment process is fast enough, then there's no need for an emergency process. This is an excellent reason to try to increase deployment times.

回滚就是这种情况的一个很好的例子。要还原在上一个版本中引入了严重错误的微服务的部署,仅在生产中回滚并返回到以前的版本,而不影响其他任何内容,是一个合理的过程。

请注意,在这里,我们如何通过确保已回滚的版本之前已经部署过来降低进行快速更改的风险。这是紧急程序如何运作和降低风险的一个很好的例子。

在发现异常情况时使用您的常识,并事先与您的团队讨论如何处理它们。我们将在 第 12 章中讨论回顾,跨团队协作和沟通.

Releasing frequently and adding feature flags

虽然回滚是可能的,正如我们刚刚看到的,但普遍的共识应该是每个新部署都会向前推进。新版本的代码包含上一版本的代码,以及一些小的更改。遵循 Git 的操作方式,我们在一个被推进的分支(主分支)上工作。

这意味着必须避免几个活跃的长寿分支。此模型称为基于主干的开发,它是旨在实现持续集成的推荐工作方式。在基于主干的开发中,功能分支是短暂的,并且总是与主分支(或主干)合并,在 Git 中通常称为 master

Trunk-based development avoids issues when we have long-lived branches that diverge from the main one, thus making the integration of several components complicated. The basis for continuous integration is to be able to always have code that can be released in small increments. This model takes "trunk" as the reference for the releases.

在下图中,我们可以看到 feature A w如何合并到ma​​ster strong> 分支以及 功能 B 如何仍在进行中。任何版本都来自 ma​​ster 分支:

读书笔记《hands-on-docker-for-microservices-with-python》管理工作流

如果 feature A 引入了一个 bug,一个新的 bugfix 分支将从 ma​​ster 分支出来并合并回来。注意结构是如何继续前进的。

为了使这个系统正常工作,特性分支需要是短暂的-,通常只有几天。这使得合并变得容易,并允许小的增量更改,这是持续集成的关键。

Using feature flags

有时,根据设计,有些功能需要一次性进行大/剧烈的更改,例如新的 UI 界面。持续集成提倡的那种缓慢添加小功能的短而快速的迭代周期在这些频繁的发布情况下不起作用。新界面需要一次性包含所有元素,否则看起来会很奇怪。

当您希望以小幅增量方式继续工作时,您可以使用功能标志,同时延迟激活功能直到它准备就绪

功能标志是启用或禁用特定功能的配置元素。这允许您通过配置更改来更改微服务的行为,该配置更改充当开关。

In Kubernetes, we use the deployment.yaml file to describe the environment variables, as well as ConfigMaps. We will discuss ConfigMaps in Chapter 11, Handling Change, Dependencies, and Secrets in the System.

配置与每个单独的环境相关联。这使我们可以在特定环境中而不是在另一个环境中呈现功能,而代码库保持不变。

例如,一个新的接口可以慢慢开发,并在一个特性标志下得到保护。某些环境(例如演示环境)仍可以处于活动状态,以便收集内部反馈,但这不会在生产环境中显示。

新界面准备就绪后,可以进行一些小改动;例如,我们可以更改配置参数以启用它。这在外部看起来可能是一个很大的变化,但如果我们换回参数,它可以很容易地恢复。

Feature flags are useful when we're dealing with externally accessible services. Internal services can add more features without any issue since they'll only be called by other microservices in the system.

Internal microservices are normally okay with adding new features. Here, backward compatibility is respected. Externally accessible features sometimes require us to replace a feature with another for reasons including interface changes or the deprecation of products.

一种相关的方法是将功能滚动到用户子集。这可以是一组预定义的用户,例如已注册 Beta 计划以获得早期访问功能的用户或随机样本,以便他们可以在全球发布之前及早发现问题。

Some big companies use regional access as well, where some features are enabled in certain countries first.

一旦功能标志被激活,任何不推荐使用的功能都可以被删除和清理,所以没有旧代码不会被使用。

Dealing with database migrations

数据库迁移是对存储在特定环境(通常是一个或多个数据库)中的持久数据所做的更改。大多数情况下,这意味着更改数据库模式,但还有其他一些。

生产环境中的数据是运行系统中最重要的资产。建议对数据库迁移格外小心。

在某些情况下,迁移可能会将表锁定一段时间,从而导致系统无法使用。确保正确测试迁移,以避免或至少为这些情况做好准备。

尽管数据库迁移在技术上可能是可逆的,但这样做在开发时间方面非常昂贵。例如,添加和删除可能很简单,但是一旦该列开始运行,该列将包含不应删除的数据。

为了能够在数据迁移的情况下无缝工作,您需要将其与将要调用它的代码分离并按照以下步骤操作:

  1. Design a database migration in a way that doesn't interfere with the current code. For example, adding a table or column to the database is safe since the old code will ignore it.
  2. Perform database migration. This makes the required changes while the existing code keeps operating without interruption.
  3. Now, the code can be deployed. Once it has been deployed, it will start using the advantages of the new database definition. If there's a problem, the code can be rolled back to a previous version.

这意味着我们需要创建两个部署:

  • One for the migration
  • Another for the code that uses this migration
Migration deployment may be similar to code deployment. Maybe there's a microservice running the migrations, or maybe it's a script doing all the work. Most frameworks will have a way of making migrations to ensure that a migration isn't applied twice.

For example, for SQLAlchemy, there's a tool called Alembic ( https://alembic.sqlalchemy.org/en/latest/) that we can use to generate and run migrations.

但是,还有另一种操作:尝试将迁移应用到将使用它们的微服务。在处理生产环境时,这是一个坏主意,因为无论是否发生迁移,这都会在所有情况下减慢启动时间。此外,它不会检查代码是否可以安全回滚并与以前版本的数据库一起使用。

使用两个独立的部署显然比随意更改数据库更具限制性,但它确保了每一步都是可靠的,并且服务不会中断。它更刻意。例如,要重命名列,我们将按照以下步骤操作:

  1. First, we would deploy a migration that creates a new column with the new column name, thereby copying the data from the old column. The code reads and writes from the old column.
  2. Then, we would deploy the new code that reads from the old column and writes to both. During the release process, any writes from the old code to the old column will be read correctly.
  3. After, we would create another migration that copies the data from the old one to the new one. This ensures that any transient copy is correctly applied. At this point, any new data still goes to both columns.
  4. Then, we would deploy code that reads and writes to the new column, ignoring the old one.
  5. Finally, we would implement a migration to drop the old column. At this point, the old column doesn't contain relevant data and can be safely deleted. It won't affect the code.

这是一个经过深思熟虑的长流程示例,但在大多数情况下,不需要这么长的流程。但是,在这些步骤中的任何一点都没有任何不一致之处。如果其中一个阶段出现问题,我们可以恢复到上一个​​阶段-它仍然可以工作,直到修复到位。

主要目标是避免出现数据库无法与当前部署的代码一起工作的瞬态。

Summary

在本章中,我们讨论了团队的流程,从启动新功能到将其部署到生产环境中。

当我们在微服务架构中工作时,我们首先讨论了功能请求的关键点。我们介绍了影响多个微服务的请求,并学习了如何构建工作以使服务不被中断。

我们讨论了构成良好审核和批准流程的要素,以及 GitHub 拉取请求如何帮助我们做到这一点。使用 GitOps 实践来控制基础架构可以直接审查部署。

然后,我们讨论了使用 Kubernetes 和 GitOps 如何帮助我们创建多个环境,以及在处理演示和暂存 环境时如何利用它们来测试部署并在受控环境中呈现功能,然后再投入生产

在此之后,我们讨论了如何让团队拥有整个生命周期的全局视图,从功能请求到部署,并能够快速遵循完整路径。我们学会了如何阐明这些步骤,以及如何让团队负责审查和批准自己的代码,从而使开发人员能够完全掌控开发周期。

我们还讨论了在处理数据库迁移时可能出现的问题,并解释了如何进行这种不容易回滚的特殊部署。

在下一章中,我们将讨论实时系统以及如何启用指标和日志等元素,以便我们能够检测生产环境中发生的问题和错误,并拥有足够的信息来尽可能快速主动地修复它们。

Questions

  1. As a new business feature is received, what analysis do we need to perform in a system working under a microservice architecture?
  2. If a feature requires two or more microservices to be changed, how do we decide which one should be changed first?
  3. How does Kubernetes help us set up multiple environments?
  4. How does a code review work?
  5. What is the main bottleneck for code reviews?
  6. Under GitOps principles, are the reviews for deployment different from code reviews?
  7. Why is it important to have a clear path to deployment once a feature is ready to be merged into the main branch?
  8. Why are database migrations different from regular code deployments?

Further reading

要了解有关敏捷实践并将其介绍给团队的更多信息,请查看以下书籍:

如果您在组织中使用 JIRA,请阅读使用 JIRA 进行敏捷软件开发实践 (https://www.packtpub.com/eu/application-development/hands-agile-software-development-jira) 可以帮助您更好地使用当您使用敏捷实践时,请不要使用该工具。