vlambda博客
学习文章列表

读书笔记《hands-on-docker-for-microservices-with-python》采取行动--设计、规划和执行

Making the Move – Design, Plan, and Execute

随着 Web 服务变得越来越复杂,以及软件服务公司的规模不断扩大,我们需要新的工作方式来适应和加快变化的速度,同时制定高质量的标准。微服务架构已成为控制大型软件系统的最佳工具之一,由容器和编排器等新工具提供支持。我们将首先介绍传统单体架构和微服务架构之间的差异,以及转向后者的优势。我们将介绍如何构建架构迁移以及如何计划在这个困难的过程中取得成功。

在本书中,我们将处理 web server 服务,尽管其中一些想法可以用于其他类型的软件应用程序,显然是通过调整它们。单体/微服务架构与操作系统设计中的单体/微内核讨论有一些相似之处,包括 著名的辩论(https://www.oreilly.com/openbook/opensources/book/appa.html) 在 Linus Torvalds 和 Andrew S. Tanenbaum,早在 1992 年。本章对工具相对不可知,而接下来的章节将介绍具体的工具。

本章将涵盖以下主题:

  • The traditional monolith approach and its problems
  • The characteristics of a microservices approach
  • Parallel deployment and development speed
  • Challenges and red flags
  • Analyzing the current system
  • Preparing and adapting by measuring usage
  • Strategic planning to break the monolith
  • Executing the move

在本章的最后,您将熟悉我们将在整本书中使用的基本概念、在迁移到微服务期间如何进行和构建工作的不同策略,以及我们将处理的一个实际示例在其余章节中。

Technical requirements

The traditional monolith approach and its problems

开发系统时软件的传统方法是创建一个整体。这是一个花哨的词,可以说单个元素,包含所有内容,而且几乎每个项目都是这样开始的。在 Web 应用程序的上下文中,这意味着创建可以复制的可部署代码,以便可以将请求定向到任何已部署的副本:

读书笔记《hands-on-docker-for-microservices-with-python》采取行动--设计、规划和执行

毕竟,每个项目都会从小做起。早期进行严格的划分很不方便,甚至没有意义。新创建的项目很小,可能由单个开发人员处理。虽然该设计可以适应少数人的头脑,但在系统的各个部分之间建立严格的界限会适得其反。

运行 Web 服务有很多选择,但通常由一个或多个运行 Web 服务器应用程序(如 NGINX 或 Apache)的服务器(物理机、虚拟机和云实例,如 EC2 等)组成将请求定向到 HTTP 端口 80 或 HTTPS 端口 443 向一个或多个 Python 工作者(通常通过 WSGI 协议)运行,由 mod_wsgi—— https://github.com/GrahamDumpleton/mod_wsgi(仅限 Apache)、uWSGI、GNUnicorn 等.

如果使用多台服务器,则会有一个负载均衡器在它们之间分配负载。我们将在本章后面讨论它们。服务器(或负载均衡器)需要可在 Internet 上访问,因此它将具有专用 DNS 和公共 IP 地址。

在其他编程语言中,结构将类似:前端 Web 服务器以 HTTP/HTTPS 公开端口,后端在专用 Web Worker 中运行整体代码。

但是事情发生了变化,成功的软件不断增长,一段时间后,拥有一大堆代码可能不是构建大型项目的最佳方式。

在任何情况下,单体都可以具有内部结构,这意味着它们不一定会进入意大利面条代码的领域。它可能是结构完美的代码。单体应用的定义是要求将系统作为一个整体进行部署,而不能进行部分部署。

意大利面条式代码是指缺乏任何结构且难以阅读和遵循的代码的常用方式。

随着单体的增长,它的一些限制将开始显现:

  • The code will increase in size: Without strict boundaries between modules, developers will start having problems understanding the whole code base. While good practices can help, the complexity naturally tends to increase, making it more difficult to change the code in certain ways and increasing subtle bugs. Running all tests will become slow, decreasing the speed of any Continuous Integration system.
  • Inefficient utilization of resources: Each individual deployed web worker will require all the resources required for the whole system to work, for example, the maximum amount of memory for any kind of request, even if a request that demands a lot of memory is rare and just a couple of workers will be sufficient. The same may happen with the CPU. If the monolith connects to a database, each individual worker will require a connection to it, whether that's used regularly or not, and so on.
  • Issues with development scalability: Even if the system is perfectly designed to be horizontally scalable (unlimited new workers can be added), as the system grows and the development team grows, development will be more and more difficult without stepping on each other's toes. A small team can coordinate easily, but once several teams are working on the same code base, the probability of clashing will increase. Imposing boundaries for teams in terms of ownership and responsibility can also become blurry unless strict discipline is enforced. In any case, teams will need to be actively coordinated, which reduces their independence and speed.
  • Deployment limitations: The deployment approach will need to be shared across teams, and teams can't be individually responsible for each deployment, as deployment will probably involve work for multiple teams. A deployment problem will bring down the whole system.
  • Interdependency of technologies: Any new tech needs to fit with the tech in use in the monolith. A new technology, for example, a tool that's perfect for a particular problem, may be complicated to add to the monolith, due to a mismatch of technologies. Updating dependencies can also cause issues. For example, an update to a new version of Python (or a submodule) needs to operate with the whole code base. Some required maintenance tasks, such as a security patch, can cause a problem just because the monolith already uses a specific version of a library, which will break if changed. Adapting to these changes requires extra work too.
  • A bug in a small part of the system can bring down the whole service: As the service is a whole, any critical issue that affects the stability affects everything, making it difficult to generate quality service strategies or causing degraded results.

正如您在示例中看到的,大多数单体问题都是日益严重的问题。除非系统有相当大的代码库,否则它们并不重要。有些事情在单体应用中运行良好,例如,由于代码中没有边界,因此可以非常快速有效地更改代码。但是随着团队的成长和越来越多的开发人员在系统中工作,边界有助于定义目标和职责。从长远来看,过多的灵活性会成为一个问题。

The characteristics of a microservices approach

整体方法一直有效,直到它不起作用。但是,有什么选择呢?这就是微服务架构进入场景的地方。

遵循微服务架构的系统是松散耦合的专业服务的集合,它们协同工作以提供全面的服务。让我们更具体地划分一下定义:

  1. A collection of specialized services, meaning that there are different, well-defined modules.
  2. Loosely coupled, meaning that each of the microservices can be independently deployed.
  3. That work in unison—each microservice is capable of communicating with others.
  4. To provide a comprehensive service, because our microservice system will need to replicate the same functionalities that were available using a monolith approach. There is an intent behind its design.

与上图相比,微服务架构将如下所示:

读书笔记《hands-on-docker-for-microservices-with-python》采取行动--设计、规划和执行

每个外部请求都将被引导到 微服务 A 或 微服务 B,每个请求都专门处理一种特定类型的请求。在某些情况下,微服务 B 与 微服务 C 通信,而不是直接在外部可用。请注意,每个微服务可能有多个工作人员。

这种架构有几个优点和含义:

  1. If the communication between microservices is done through a standard protocol, each microservice can be programmed in different languages.
在整本书中,我们将使用带有 JSON 编码数据的 HTTP 请求在微服务之间进行通信。尽管有更多选项,但这绝对是最标准和最广泛使用的选项,因为几乎所有广泛使用的编程语言都对它有很好的支持。

这在专业语言非常适合解决专业问题的情况下非常有用,但限制了它的使用以便包含它,而不需要对公司进行重大改变。

  1. Better resource utilization—if Microservice A requires more memory, we can reduce the number of worker copies. While on a monolith, each worker requires the maximum resource allocation, now each microservice uses only the resources required for its part of the whole system. Maybe some of them don't need to connect to the database, for example. Each individual element can be tweaked, potentially even at the hardware level.
  2. Each individual service is smaller and can be dealt with independently. That means fewer lines of code to maintain, faster builds, and a simpler design, with less technical debt to maintain. There are no dependency issues between services, as each can define and move them at their own pace. Performing refactors can be done in a more controlled way, as they won't affect the totality of the system. Furthermore, each microservice can change the programming language it's written in, without affecting other microservices.
从某种角度来说,微服务架构类似于UNIX的哲学,应用于Web服务:写每个程序(服务)做一件事并做好 ,编写程序(服务)协同工作,编写程序(服务)处理文本流(HTTP调用),因为这是一个通用接口。
  1. Some services can be hidden from external access. For example, Microservice C is only called by other services, not externally. In some scenarios, that can improve security, reducing the attack surface area for sensitive data or services.
  2. As the systems are independent, a stability problem in one won't completely stop the system. This reduces critical responses and limits the scope of a catastrophic failure.
  3. Each service can be maintained independently by different developers. This allows for parallel development and deployment, increasing the amount of work that can be done by the company. This requires the exposed APIs to be backward compatible, as we will describe later.

Docker containers

微服务架构对于支持它的平台是非常不可知的。它可以部署在专用数据中心、公共云或容器化形式的旧物理盒子上。

不过,有一种趋势是使用容器来部署微服务。容器是一个打包的软件包,它封装了运行所需的所有内容,包括所有依赖项。它只需要兼容的操作系统内核即可自主运行。

Docker 是 Web 应用程序容器的主要参与者。它有一个非常活跃的社区支持它,以及用于各种操作的强大工具。我们将学习如何使用 Docker 工作和操作。

我第一次使用 Docker 容器时,它们在我看来就像是一种 轻型虚拟机;一个不需要模拟硬件即可运行的小型操作系统。但过了一会儿,我意识到这不是正确的方法。

描述容器的最佳方式是考虑一个被自己的文件系统包围的进程。您运行一个进程(或几个相关进程),它们看到整个文件系统,不被任何人共享。

这使得容器非常便携,因为它们与底层硬件和运行它们的平台分离;它们非常轻量级,因为需要包含最少量的数据,并且它们是安全的,因为容器的暴露攻击面非常小。您不需要像在传统服务器(如 sshd 服务器或 Puppet 等配置工具)上那样管理它们的应用程序。它们是专门的,设计为小型且单一用途。

In particular, try to keep your containers small and single-purpose. If you end up adding several daemons and a lot of configuration, it's likely that you are trying to include too much; maybe you need to split it into several containers.

使用 Docker 容器有两个步骤。首先,我们构建容器,对文件系统进行一层又一层的更改,例如添加将要执行的软件和配置文件。然后,我们执行它,启动它的主命令。我们将在 第 3 章 中了解如何做到这一点,Dockerizing the服务

微服务架构非常符合 Docker 容器的一些特征——通过 HTTP 调用进行通信的小型、单一用途的元素。这就是为什么,尽管这不是一个硬性要求,但现在它们通常会一起呈现。

Twelve-Factor App 原则(https://12factor .net/) 是一组已被证明在开发 Web 应用程序方面取得成功的实践,它们也非常符合 Docker 容器和微服务架构。 Docker 中的一些原则非常容易遵循,我们将在本书后面深入评论它们。

An important factor for dealing with containers is that containers should be stateless (Factor VI— https://12factor.net/processes). Any state needs to be stored in a database and each container stores no persistent data. This is one of the key elements for scalable web servers that, when dealing with a couple of servers, may not be done. Be sure to keep it in mind.

Docker 的另一个优势是大量现成容器的可用性。 Docker Hub (https://hub.docker.com/) 是一个充满趣味的公共注册中心在开发或生产中继承或直接使用的容器。 这可以帮助您为自己的服务提供示例,并快速创建需要很少配置的小型服务。

Container orchestration and Kubernetes

尽管 Docker 介绍了如何处理每个单独的微服务,但我们需要一个编排器来处理整个服务集群。为此,我们将在整个过程中使用 Kubernetes (https://kubernetes.io/)书。这是主要的编排项目,得到了主要云厂商的大力支持。我们将在第5章中详细讨论,使用Kubernetes协调微服务

Parallel deployment and development speed

最重要的一个要素是独立部署的能力。创建成功的微服务系统的第一条规则是确保每个微服务可以尽可能独立独立于其他微服务运行。这包括开发、测试和部署。

这是允许不同团队之间并行开发的关键元素,允许他们扩展工作。这加快了复杂系统的变化速度。

负责特定微服务的团队需要能够在不中断任何其他团队或服务的情况下部署新版本的微服务。目标是增加部署的数量和每个部署的速度。

The microservice architecture is strongly related to Continuous Integration and Continuous Deployment principles. Small services are easy to keep up to date and to continuously build, as well as to deploy without interruption. In that regard, a CI/CD system tends to be microservices due to the increase in parallelization and the speed of delivery.

由于部署微服务应该对依赖服务透明,因此应特别注意向后兼容性。一些更改需要升级并与其他团队协调,以在不中断系统的情况下删除旧的、不正确的功能。

虽然从理论上讲,服务完全断开是可能的,但在实践中这是不现实的。一些服务之间会有依赖关系。微服务系统将迫使您在服务之间定义强大的边界,任何需要跨服务通信的功能都会带来一些开销,甚至可能需要协调不同团队之间的工作。

当迁移到微服务架构时,迁移不仅仅是技术层面的,还意味着公司工作方式的巨大变化。微服务的开发将需要自主和结构化的通信,这需要在规划系统的总体架构时预先付出额外的努力。在单体系统中,这可能是临时性的,并且可能已经演变成一个不那么分离的内部结构,从而增加了纠结代码和技术债务的风险。

The need to clearly communicate and define owners cannot be stressed enough. Aim to allow each team to make their own decisions about their code and formalize and maintain the external APIs where other services depend on them.

但是,这种额外的计划增加了长期交付带宽,因为团队有权做出更多自主决策,包括使用哪种操作系统或哪种编程语言等大决策,以及无数较小的决策,例如使用第三方包、框架或模块结构。这加快了日常运营的开发速度。

微服务还可能影响组织中团队的结构。作为一般规则,应该尊重现有团队。他们将拥有非常有用的专业知识,而引发一场全面革命将破坏这一点。但可能需要进行一些调整。每个微服务都需要一些概念,例如理解 Web 服务和 RESTful 接口,以及如何部署自己的服务的知识。

划分团队的传统方式是创建一个负责基础设施和任何新部署的运营团队,因为他们是唯一允许访问生产服务器的人。微服务方法会干扰这一点,因为它需要团队能够控制自己的部署。在 第五章使用 Kubernetes 来协调微服务,我们将看到使用 Kubernetes 在这种情况下的帮助,将基础设施的维护与服务的部署分离。

它还可以创造一种强烈的主人翁感,因为鼓励团队在自己的王国以自己喜欢的方式工作,同时他们与其他团队在明确定义和结构化的边界内玩游戏。微服务架构可以允许在系统的小部分进行实验和创新,一旦得到证实,就可以在整个系统中传播。

Challenges and red flags

我们已经讨论了微服务架构相对于单体架构的许多优势,但迁移是一项不容小觑的艰巨任务。

系统从整体开始,因为它更简单,并且允许在小型代码库中进行更快的迭代。在任何新公司中,调整和更改代码,寻找成功的商业模式至关重要。这优先于清晰的结构和架构分离——这是它应该的方式。

但是,一旦系统成熟,公司就会发展壮大。随着越来越多的开发人员参与进来,单体架构的优势开始变得不那么明显,对长期战略和结构的需求变得更加重要。更多的结构并不一定意味着转向微服务架构。使用良好架构的单体可以实现很多目标。

迁移到微服务也有其自身的问题。其中一些如下:

  1. Migrating to microservices requires a lot of effort, actively changing the way an organization operates, and a big investment until it starts to pay off. The transition will probably be painful, as a pragmatic approach is required and compromises will need to be made. It will also involve a lot of designing documents and meetings to plan the migration—all while the business continues to operate. This requires full commitment and an understanding of what's involved.
  2. Do not underestimate the cultural change—organizations are made of people, and people do not like change. A lot of the changes in microservices are related to different ways of operating and doing things in different ways. While this empowers different teams, it also forces them to clarify their interfaces and APIs and to formalize communication and boundaries. This can lead to frustration and resistance by members of the teams.
There's an adage called Conway's law ( http://www.melconway.com/Home/Conways_Law.html) that states that organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations. For microservices, this means that divisions between teams should reflect the different services. Having multiple teams working in the same microservice will blur the interfaces. We will discuss Conway's law in detail in Chapter 12, Collaborating and Communicating across Teams.
  1. There's also a learning curve in learning the tools and procedures. Managing clusters is done differently than a single monolith, and developers will need to understand how to interoperate different services for testing locally. In the same way, that deployment will be different from traditional, local development as well. In particular, learning Docker takes some time to adapt. Plan accordingly and give support and training to everyone involved.
  1. Debugging a request that moves across services is more difficult than a monolithic system. Monitoring the life cycle of a request is important and some subtle bugs can be difficult to replicate and fix in development.
  1. Splitting a monolith into different services requires careful consideration. A bad division line can make two services tightly coupled, not allowing independent deployment. A red flag in that means almost any change to one service requires a change in the other, even if, normally, it could be done independently. This creates duplication of work, as routinely working on a single feature requires changing and deploying multiple microservices. Microservices can be mutated later and boundaries redefined, but there's a cost associated with that. The same care should be taken when adding new services.
  2. There's an overhead in creating microservices, as there's some work that gets replicated on each service. That overhead gets compensated by allowing independent and parallel development. But, to fully take advantage of that, you need numbers. A small development team of up to 10 people can coordinate and handle a monolith very efficiently. It's only when the size grows and independent teams are formed that migrating to microservices starts to make sense. The bigger the company, the more it makes sense.
  3. A balance between freedom and allowing each team to make their own decisions and standardize some common elements and decisions is necessary. If teams have too little direction, they'll keep reinventing the wheel over and over. They'll also end up creating knowledge silos where the knowledge in a section of the company is totally nontransferable to another team, making it difficult to learn lessons collectively. Solid communication between teams is required to allow consensus and the reuse of common solutions. Allow controlled experimentation, label it as such, and get the lessons learned across the board so that the rest of the teams benefit. There will be tension between shared and reusable ideas and independent, multiple-implementation ideas.
Be careful when introducing shared code across services. If the code grows, it will make services dependent on each other. This can reduce the independence of the microservices.
  1. Following the Agile principles, we know that working software is more important than extensive documentation. However, in microservices, it's important to maximize the usability of each individual microservice to reduce the amount of support between teams. That involves some degree of documentation. The best approach is to create self-documenting services. We'll look at some examples later in the book on how to use tools to allow documenting how to use a service with minimal effort.
  2. Each call to another service, such as internal microservices calling each other, can increase the delay of responses, as multiple layers will have to be involved. This can produce latency problems, with external responses taking longer. They will also be affected by the performance and capacity of the internal network connecting the microservices.

转向微服务时应谨慎并仔细分析其优缺点。在一个成熟的系统中完成迁移可能需要数年时间。但是对于一个大型系统,最终的系统将更加灵活且易于更改,从而使您能够有效地解决技术债务问题,并使开发人员能够完全掌握和创新、构建通信并提供高质量、可靠的服务。

Analyzing the current system

正如我们之前定义的,从单体架构迁移到微服务集合的第一步是了解当前系统。这个阶段不应该被低估。很可能没有一个人对单体应用的不同组件有很好的理解,特别是如果某些部分是遗留的。

此阶段的目标是确定对微服务的更改是否真的有益,并初步了解迁移将导致哪些微服务。正如我们所讨论的,采取行动是一项重大投资,不应掉以轻心。在此阶段无法详细估算所需的工作量;此时的不确定性很大,但千里之行始于一步。

所涉及的工作量将在很大程度上取决于单体应用程序的结构。这可能会有所不同,从没有太多方向的有机增长的意大利面条代码,到结构良好和模块化的代码库。

我们将在本书中使用一个示例应用程序——一个名为 MyThoughts 的微博站点,这是一个允许我们发布和阅读短消息或想法的简单服务。该网站允许我们登录、发布新想法、查看我们的想法以及在系统中搜索想法。

读书笔记《hands-on-docker-for-microservices-with-python》采取行动--设计、规划和执行

作为第一步,我们将绘制单体架构图。将当前系统简化为相互交互的块列表。

我们示例的代码可在此处获得: https://github.com /PacktPublishing/Hands-On-Docker-for-Microservices-with-Python/tree/master/Chapter01/Monolith。它是一个使用 Bootstrap 作为其 HTML 界面的 Django 应用程序。见 README 以获取有关如何运行它的说明。

在我们的示例中,MyThoughts 模型如下图所示:

读书笔记《hands-on-docker-for-microservices-with-python》采取行动--设计、规划和执行

如您所见,整体似乎遵循模型视图控制器结构 (https://www.codecademy. com/articles/mvc):

Django 使用一种称为模型模板视图的结构,它遵循与 MVC 相似的模式。阅读文章 https://medium.com/shecodeafrica/understanding-the-mvc -pattern-in-django-edda05b9f43f 了解更多信息。是否是 100% MCV 是有争议的。我们不要拘泥于语义,而是以定义为起点来描述系统。
  • There are three entities stored in a database and accessed through the models: the user, the thoughts, and the session models. The session is used for keeping track of logins.
  • A user can log in and out to access the site through the code in login.py. If the user logs in, a session is created that allows the user to see the rest of the website.
请注意,此示例中对身份验证和密码的处理仅用于演示目的。使用 Django 中的默认机制以获得更安全的访问。对于不使用本机会话管理的会话也是如此。
  • A user can see their own thoughts. On the same page, there's a new form that creates a new thought. This is handled by the thoughts.py file, which retrieves and stores the thoughts through ThoughtModel.
  • To search other users' thoughts, there's a search bar that connects to the search.py module and returns the obtained values.
  • The HTML is rendered through the login.html, search.html, list_thoughts.html, and base.html templates.
  • On top of that, there are static assets that style the website.

这个例子非常简单,但我们可以看到一些相互依赖关系:

  • The static data is very isolated. It can be changed at any point without requiring any changes anywhere else (as long as the templates are compatible with Bootstrap).
  • The search functionality is strongly related to list down thoughts. The template is similar, and the information is displayed in the same way.
  • Login and logout don't interact with ThoughtModel. They edit the session, but the rest of the application only reads the information there.
  • The base.html template generates the top bar and it's used for all pages.

经过这个分析,我想到了一些关于如何进行的想法:

  1. Just leave it the way it is, investing in structuring it, but without splitting it into several services. It has a certain structure already, though some parts could be improved. For example, the handling of whether the user is logged in or not could be better. This is obviously a small example, and, in real life, splitting it into microservices would have a big overhead. Remember that sticking with a monolith may be a viable strategy, but if you do, please invest time in cleaning up code and paying technical debt.
  1. Searching for thoughts is pretty basic. At the moment, we directly search the database. If there are millions of thoughts, this won't be a viable option. The code in search.py could call a specific search microservice, backed by a search engine such as Solr (https://lucene.apache.org/solr/) or Elasticsearch (https://www.elastic.co/products/elasticsearch). This will scale the searches and could add capabilities like searching between dates or displaying the text matches. Search is also read-only, so it may be a good idea to detach calls creating new thoughts from calls searching them.
  2. Authentication is also a different problem from reading and writing thoughts. Splitting it will allow us to keep on track for new security issues and have a team specifically dealing with those issues. From the point of view of the rest of the application, it only requires you to have something available to check whether a user is logged or not, and that can be delegated in a module or package.
  3. The frontend is pretty static at the moment. Maybe we want to create a single-page application that calls a backend API to render the frontend in the client. To do that, a RESTful API microservice that is able to return elements for thoughts and searches will need to be created. The frontend could be coded in a JavaScript framework, such as Angular (https://angular.io) or React (https://reactjs.org/). In this case, the new microservice will be the frontend, which will be served as static, precompiled code, and will pull from the backend.
  4. The RESTful API backend will also be available to allow external developers to create their own tools on top of the MyThoughts data, for example, to create a native phone app.

这些只是一些想法,需要讨论和评估。您的单体应用程序的具体痛点是什么?路线图和战略未来是什么?现在或未来最重要的点和特点是什么?也许,对于一家公司来说,拥有强大的安全性是首要任务,第 3 点至关重要,但对于另一家公司来说,第 5 点可能是与合作伙伴合作的扩展模型的一部分。

团队的结构也很重要。第 4 点需要一个具有良好前端和 JavaScript 技能的团队,而第 2 点可能涉及后端优化和数据库工作,以允许高效搜索数百万条记录。

Do not jump too quickly to conclusions here; think about what capacity is viable and what your teams can achieve. As we discussed before, the change to microservices requires a certain way of working. Check with the people involved for their feedback and suggestions.

经过一番考虑,对于我们的示例,我们提出了以下潜在架构:

读书笔记《hands-on-docker-for-microservices-with-python》采取行动--设计、规划和执行

系统将分为以下模块:

  1. Users backend: This will have the responsibility for all authentication tasks and keep information about the users. It will store its data in the database.
  2. Thoughts backend: This will create and store thoughts.
  3. Search backend: This will allow searching thoughts.
  4. A proxy that will route any request to the proper backend. This needs to be externally accessible.
  5. HTML frontend: This will replicate the current functionality. This will ensure that we work in a backward-compatible way and that the transition can be made smoothly.
  6. Allowing clients to access the backends will allow the creation of other clients than our HTML frontend. A dynamic frontend server will be created, and there are talks with an external company to create a mobile app.
  7. Static assets: A web server capable of handling static files. This will serve the styling for the HTML frontend and the index files and JavaScript files for the dynamic frontend.

这种架构需要适应现实生活中的使用;为了验证它,我们需要测量现有的使用情况。

Preparing and adapting by measuring usage

显然,任何现实世界的系统都会比我们的示例更复杂。仅通过仔细查看代码分析可以发现的内容是有限的,并且计划通常无法在与现实世界的接触中幸存下来。

任何部门都需要经过验证,以确保它会产生预期的结果并且付出的努力是值得的。因此,请仔细检查系统是否按照您认为的方式运行。

了解实时系统如何工作的能力称为可观察性。它的主要工具是指标和日志。您会发现的问题是它们通常会被配置为反映外部请求并且不提供有关内部模块的信息。 我们将在第 10 章监控日志和指标。您可以参考它以获取更多信息并应用此处描述的技术。

If your system is a web service, by default, it will have activated its access log. This will log each HTTP request that comes into the system and store the URL, result, and time when it happens. Check with your team where these logs are located, as they will provide good information on what URLs are being called.

不过,这种分析可能只会提供有关被调用的外部端点的信息,但不会过多地说明将根据我们的计划拆分为不同微服务的内部模块。请记住,迁移到微服务的长期成功最重要的因素是允许团队独立。如果您拆分需要不断统一更改的模块,部署将不会真正独立,并且在转换之后,您将被迫使用两个紧密耦合的服务。

Be careful, in particular, about making a microservice that's a dependency for every other service. Unless the service is extremely stable, that will make frequent updates likely when any other service requires a new feature.

为了验证新的微服务不会紧密耦合,让团队了解这些部门以及他们必须多久更改一次围绕它们的接口。监视这些变化几周,以确保分割线是稳定的,不需要不断变化。如果微服务之间的接口被非常积极地改变,任何功能都需要在多个服务中进行多次更改,这将减慢交付新功能的速度。

在我们的示例中,在分析了提议的架构之后,我们决定简化设计,如下图所示:

读书笔记《hands-on-docker-for-microservices-with-python》采取行动--设计、规划和执行

在监督并与团队交谈后,我们决定了一些更改:

  1. The teams don't have good knowledge of JavaScript dynamic programming. The change to the frontend, at the same time as making the move to microservices, is seen as too risky.
  2. The external mobile application, on the other hand, is seen as a strategic move for the company, making the externally accessible API a desirable move.
  3. Analyzing the logs, it seems like the search functionality is not often used. The growth in the number of searches is small, and splitting search into its own service will require coordination with the Thoughts Backend, as it's an area of active development, with new fields being added. It is decided to keep search under the Thoughts Backend, as both work with the same thoughts.
  4. The Users Backend has been received well. It will allow improving the security of authentication by having clear ownership of who's responsible for patching security vulnerabilities and improving the services. The rest of the microservices will have to work independently with verification by the Users Backend, which means the team responsible for this microservice will need to create and maintain a package with information on how to validate a request.

一旦我们决定了最终状态,我们仍然需要决定如何从一个状态转移到另一个状态。

Strategic planning to break the monolith

正如我们之前所讨论的,从初始状态转移到所需状态将是一个缓慢的过程。不仅因为它涉及新的做事方式,还因为它将与“一切照旧”的其他功能和开发并行发生。实事求是,公司的经营活动不会停止。这就是为什么应该制定计划以允许一种状态和另一种状态之间的平稳过渡。

这被称为 扼杀者模式 ( https://docs.microsoft.com/en-us/azure/architecture /patterns/strangler)——逐渐更换系统的一部分,直到旧系统被“扼杀”并且可以安全地移除。

对于采取何种技术方法进行迁移以及如何划分每个元素以迁移到新系统,有几种选择:

  • The replacement approach, which replaces the older code with new code written from scratch the new service
  • The divide approach, which cherry-picks existing code and moves it into its own new service
  • A combination of the two

让我们更好地了解它们。

The replacement approach

服务被大块替换,只考虑到它们的外部接口或效果。这种黑盒方法完全用从头开始的替代方法替换了现有的功能编码。一旦新代码准备就绪,它就会被激活,旧系统中的功能将被弃用。

请注意,这并不是指替换整个系统的单个部署。这可以部分地、逐块地完成。这种方法的基础是它创建了一个新的外部服务,旨在替换旧系统。

这种方法的优点是它极大地有助于构建新服务,因为它不会继承技术债务,并允许事后重新审视旧问题。

新服务还可以使用新工具,并且不需要继续使用任何与公司技术未来方向的战略观点不一致的旧堆栈。

这种方法的问题在于它可能成本高昂并且可能需要很长时间。对于未记录的旧服务,替换它们可能需要付出很多努力。此外,这种方法只能应用于稳定的模块;如果它们被积极开发,试图用其他东西代替它们一直在移动球门柱。

这种方法对于小型的旧遗留系统最有意义,或者至少有一小部分执行有限的功能,并且是在一个很难或不再被认为需要维护的旧技术堆栈中开发的。

The divide approach

如果系统结构良好,也许它的某些部分可以干净地拆分成自己的系统,保持相同的代码。

在这种情况下,创建一个新服务更像是一种复制粘贴的练习,并用最少的代码包装它,以允许它独立执行并与其他系统互操作,换句话说,围绕 HTTP 构建其 API要求有一个标准的接口。

如果可以使用这种方法,则意味着代码已经非常结构化,这是一个了不起的消息。

被调用到这部分的系统也必须适应进行调用,而不是内部代码,而是通过 HTTP 调用。好的部分是这可以通过几个步骤完成:

  1. Copy the code into its own microservice and deploy it.
  2. The old calling system is using the old embedded code.
  3. Migrate a call and check that the system is working fine.
  4. Iterate until all old calls are migrated to the new system.
  5. Delete the divided code from the old system.

如果代码结构不那么清晰,我们需要先对其进行更改。

Change and structured approach

如果单体一直在有机地增长,那么它的所有模块就不太可能结构清晰。可能存在一些结构,但可能它们不是我们想要的微服务部门的正确结构。

为了调整服务,我们需要进行一些内部更改。这些内部更改可以迭代完成,直到可以干净地划分服务。

这三种方法可以结合起来产生完全迁移。每一项所涉及的工作量都不相同,因为一个易于分割的服务将能够比替换记录不良的遗留代码更快。

在项目的这个阶段,目标是有一个清晰的路线图,应该分析以下要素:

  • An ordered plan of what microservices will be available first, taking into account how to deal with dependencies.
  • An idea of what the biggest pain points are, and whether working on them is a priority. Pain points are the elements that are worked with frequently and the current way of dealing with the monolith makes them difficult.
  • What are the difficult points and the cans of worms? It's likely that there'll be some. Acknowledge that they exist and minimize their impact on other services. Note that they may be the same as the pain points, or not. The difficult points may be old systems that are very stable.
  • A couple of quick wins that will keep the momentum of the project going. Show the advantages to your teams and stakeholders quickly! This will also allow everyone to understand the new mode of operation you want to move to and start working that way.
  • An idea of the training that teams will require and what the new elements are that you want to introduce. Also, whether there are any skills lacking in your team – it's possible that you may plan to hire.
  • Any team changes and ownership of the new services. It's important to consider feedback from the teams, so they can express their concerns over any oversights during the creation of the plan.

对于我们的具体示例,生成的计划如下:

  • As a prerequisite, a load balancer will need to be in front of the operation. This will be responsible for channeling requests to the proper microservice. Then, changing the configuration of this element, we will be able to route the requests toward the old monolith or any new microservice.
  • After that, the static files will be served through their own independent service, which is an easy change. A static web server is enough, though it will be deployed as an independent microservice. This project will help in understanding the move to Docker.
  • The code for authentication will be replicated in a new service. It will use a RESTful API to log in and generate a session, and to log out. The service will be responsible for checking whether a user exists or not, as well as adding them and removing them:
    • The first idea was to check each session retrieved against the service, but, given that checking a session is a very common operation, we decided to generate a package, shared across the externally faced microservices, which will allow checking to see whether a session has been generated with our own service. This will be achieved by signing the session cryptographically and sharing the secret across our services. This module is expected not to change often, as it's a dependency for all the microservices. This makes the session one that does not need to be stored.
    • The Users Backend needs to be able to allow authentication using OAuth 2.0 schema, which will allow other external services, not based on web browsers, to authenticate and operate, for example, a mobile app.
  • The Thoughts Backend will also be replicated as a RESTful API. This backend is quite simple at the moment, and it will include the search functionality.
  • After both backends are available, the current monolith will be changed, from calling the database directly, to use the RESTful APIs of the backends. After this is successfully done, the old deployment will be replaced with a Docker build and added to the load balancer.
  • The new API will be added externally to the load balancer and promoted as externally accessible. The company making the mobile app will then start integrating their clients.

我们的新架构模式如下:

读书笔记《hands-on-docker-for-microservices-with-python》采取行动--设计、规划和执行

请注意,HTML 前端将使用与外部可用的相同 API。这将验证调用是否有用,因为我们将首先将它们用于我们自己的客户端。

该行动计划可以有可衡量的时间和时间表。也可以采用一些技术选项——在我们的例子中,如下:

团队可以继续使用这些技术堆栈,并期待学习一些新技巧!

Executing the move

最后一步是执行精心设计的计划,开始从过时的单体架构迁移到微服务的新乐土!

但这个阶段的旅程实际上可能是最长和最困难的——特别是如果我们想保持服务运行并且不出现中断业务的中断。

此阶段最重要的一个想法是向后兼容。这意味着从外部角度来看,该系统的行为仍与旧系统相同。如果我们能够做到这一点,我们就可以透明地改变我们的内部运营,同时我们的客户能够不间断地继续他们的运营。

这显然说起来容易做起来难,有时被称为以福特 T 开始比赛,以法拉利结束比赛,不停地更换每一件。好消息是软件非常灵活和可延展,它实际上是可能的。

Web services' best friend – the load balancer

负载均衡器是一种允许在多个后端资源之间分配 HTTP 请求(或其他类型的网络请求)的工具。

负载均衡器的主要操作是允许将流量定向到单个地址,以便在多个相同的后端服务器之间分配,从而分散负载并实现更好的吞吐量。通常,流量将通过循环分配,即按顺序在所有这些之间分配:

读书笔记《hands-on-docker-for-microservices-with-python》采取行动--设计、规划和执行

第一个工人,然后另一个工人,连续:

读书笔记《hands-on-docker-for-microservices-with-python》采取行动--设计、规划和执行

那是正常的操作。但它也可以用来替换服务。负载均衡器确保每个请求都干净利落地发送给一个工作人员或另一个工作人员。工作池中的服务可以不同,因此我们可以使用它来干净地在一个版本的 Web 服务和另一个版本之间进行转换。

出于我们的目的,负载均衡器后面的一组旧 Web 服务可以添加一个或多个向后兼容的替换服务,而不会中断操作。替换旧服务的新服务将少量添加(可能是一两个工人),以合理配置分配流量,并确保一切按预期工作。验证后,通过停止向旧服务发送新请求、耗尽它们并只留下新服务器来完全替换它。

如果在快速移动中完成,例如在部署新版本的服务时,这称为滚动更新,因此工作人员会被一个接一个地替换。

但对于从旧的单体架构迁移到新的微服务,放慢速度更为明智。服务可以在 5%/95% 的拆分中存活数天,因此任何意外错误只会出现二十分之一的时间,然后迁移到 33/66,然后是 50/50,然后是 100% 迁移。

A highly loaded system with good observability will be able to detect problems very quickly and may only need to wait minutes before proceeding. Most legacy systems will likely not fall into this category, though.

任何能够以反向代理模式运行的 Web 服务器,例如 NGINX,都能够作为负载均衡器工作,但是,对于这个任务,可能最完整的选项是 HAProxy (http://www.haproxy.org/)。

HAProxy 专门用于在高可用性和高需求的情况下充当负载均衡器。它是非常可配置的,并在必要时接受从 HTTP 到较低级别 TCP 连接的流量。它还有一个很棒的状态页面,有助于监控通过它的流量,以及采取快速行动,例如禁用失败的工作人员。

AWS 或 Google 等云提供商也提供集成负载均衡器产品。它们在我们的网络边缘工作非常有趣,因为它们的稳定性使它们很棒,但它们不会像 HAProxy 那样可配置且易于集成到您的操作系统中。例如,Amazon Web Services 提供的产品称为 Elastic Load Balancing (ELB)—https://aws.amazon.com/elasticloadbalancing/

要从具有 DNS 引用的外部 IP 的传统服务器迁移并在前面放置负载均衡器,您需要遵循以下过程:

  1. Create a new DNS to access the current system. This will allow you to refer to the old system independently when the transition is done.
  2. Deploy your load balancer, configured to serve the traffic to your old system on the old DNS. This way, accessing either the load balancer or the old system, the request will ultimately be delivered in the same place. Create a DNS just for the load balancer, to allow referring specifically to it.
  3. Test that sending a request to the load balancer directed to the host of the old DNS works as expected. You can send a request using the following curl command:
$ curl --header "Host:old-dns.com" http://loadbalancer/path/
  1. Change the DNS to point to the load balancer IP. Changing DNS registries takes time, as caches will be involved. During that time, no matter where the request is received, it will be processed in the same way. Leave this state for a day or two, to be totally sure that every possible cache is outdated and uses the new IP value.
  2. The old IP is no longer in use. The server can (and should) be removed from the externally facing network, leaving only the load balancer to connect. Any request that needs to go to the old server can use its specific new DNS.

请注意,像 HAProxy 这样的负载均衡器可以使用 URL 路径,这意味着它可以将不同的路径定向到不同的微服务,这在从单体应用迁移时非常有用。

Because a load balancer is a single point of failure, you'll need to load balance your load balancer. The easiest way of doing it is creating several identical copies of HAProxy, as you'd do with any other web service, and adding a cloud provider load balancer on top.

因为 HAProxy 用途广泛且速度快,如果配置正确,您可以将其用作重定向请求的中心点——以真正的微服务方式!

Keeping the balance between new and old

计划只是计划,转向微服务是为了内部利益,因为它需要投资,直到外部改进可以以更快的创新步伐显示出来。

这意味着开发团队将面临在公司正常运营之上添加新功能和要求的外部压力。即使我们让这种迁移速度更快,在初始阶段你也会变慢。毕竟,改变事情是困难的,你需要克服最初的惯性。

迁移将经历三个粗略的阶段。

The pilot phase – setting up the first couple of microservices

在看到第一次部署之前,可能需要很多基础设施。这一阶段可能很难克服,这是需要最大推动力的阶段。一个好的策略是在新的微服务架构中组建一个专门的爱好者团队,让他们领导开发。他们可以是参与过设计的人,也可以是喜欢新技术的人,或者曾与 Docker 和 Kubernetes 合作过的业余项目。并非您团队中的每个开发人员都会对改变您的运营方式感到兴奋,但其中一些人会。用他们的热情来启动项目并在最初的步骤中处理它:

  1. Start small—there'll be enough work to set up the infrastructure. The objective in this phase is to learn the tools, set up the platform, and adjust how to work with the new system. The aspect of teamwork and coordination is important and starting with a small team allows us to test a couple of approaches and iterate to be sure that they work.
  2. Choose non-critical services. At this stage, there are a lot of things that can go wrong. Be sure that a problem does not have a huge impact on operations or revenue.
  3. Be sure to maintain backward compatibility. Substitute parts of the monolith with new services, but do not try to change the behavior at the same time, unless they are obvious bugs.

如果有一个新功能可以作为新的微服务实现,请抓住机会直接采用新方法,但要确保额外交付时间或错误的风险是值得的。

The consolidation phase – steady migration to microservices

在初始设置之后,其他团队开始使用微服务方法。这增加了处理容器和新部署的人数,因此最初的团队需要为他们提供支持和培训。

Training will be a critical part of the migration project—be sure to allocate enough time. While training events such as workshops and courses can be very useful to kickstart the process, constant support from experienced developers is invaluable. Appoint developers as a point of contact for questions, and tell them explicitly that their job is to ensure that they answer questions and help other developers. Make the supporting team meet up regularly to share concerns and improvements on the knowledge transfer.

传播知识是此阶段的主要重点之一,但还有另外两个重点:澄清和标准化流程以及保持足够的微服务迁移速度。

记录标准将有助于提供清晰和方向。创建检查点以全面提出非常明确的要求,因此微服务何时可以投入生产非常明确。为反馈创建足够的渠道,以确保可以改进流程。

在此期间,可以加快迁移步伐,因为很多不确定性和问题已经解决;并且因为开发将并行进行。您应该尝试以微服务方式处理任何新功能,尽管可能需要做出妥协。一定要保持动力并遵循计划。

The final phase – the microservices shop

单体已经被拆分,架构现在是微服务。可能存在被认为具有较低优先级的单体。任何新功能都以微服务风格实现。

While desirable, it may not be realistic to migrate absolutely everything from the monolith. Some parts may take a long time to migrate because they are especially difficult to migrate or they deal with strange corners of your company. If that's the case, at least clearly define the boundaries and limit their action radius.

在这个阶段,团队可以完全拥有他们的微服务并开始进行测试和创新,例如更改编程语言。架构也可以改变,微服务可以拆分或合并。有明确的界限来定义微服务的商定要求是什么,但在其中允许自由。

团队将建立完善,过程将顺利进行。 关注来自不同团队的好想法,并确保传播信息。

恭喜!你做到了!

Summary

在本章中,我们看到了传​​统单体方法和微服务架构之间的区别,以及微服务如何使我们能够跨多个团队扩展开发并改进高质量软件的交付。

我们讨论了从单体架构到微服务的过渡过程中面临的主要挑战以及如何在不同阶段执行更改:分析当前系统,测量以验证我们的假设,制定以可控方式拆分单体架构的计划,以及成功执行移动的策略。

虽然本章是以与技术无关的方式编写的,但我们已经了解了为什么 Docker 容器是实现微服务的好方法,这将在接下来的章节中进行探讨。您现在还知道使用负载平衡器如何有助于保持向后兼容性并以不间断的方式部署新服务。

您学习了如何制定计划,将单体架构划分为更小的微服务。我们描述了这样一个过程的一个例子和一个整体的例子,以及它是如何划分的。我们将在接下来的章节中详细了解如何做到这一点。

Questions

  1. What is a monolith?
  2. What are some of the problems of monoliths?
  3. Describe the microservice architecture.
  4. Which is the most important property of microservices?
  5. What are the main challenges to overcome in a migration from a monolith to microservices?
  6. What are the basic steps to make such a migration?
  7. Describe how to use a load balancer to migrate from an old server to a new one without interrupting the system.