This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
<audio id="audio" title="25 | 微服务为什么要容器化?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ff/cf/ff12b96b00c5a9a358fd221b789706cf.mp3"></audio>
专栏前面的文章我主要给你讲解了微服务架构的基础组成以及在具体落地实践过程中的会遇到的问题和解决方案这些是掌握微服务架构最基础的知识。从今天开始我们将进一步深入微服务架构进阶的内容也就是微服务与容器、DevOps之间的关系。它们三个虽然分属于不同领域但却有着千丝万缕的关系可以说没有容器的普及就没有微服务架构的蓬勃发展也就没有DevOps今天的盛行其道。
之后我还会具体分析它们三者之间是如何紧密联系的,今天我们先来看微服务为什么要容器化。
## 微服务带来的问题
单体应用拆分成多个微服务后能够实现快速开发迭代但随之带来的问题是测试和运维部署的成本的提升。相信拆分微服务的利弊你早已耳熟能详我讲个具体的例子。微博业务早期就是一个大的单体Web应用在测试和运维的时候只需要把Web应用打成一个大的WAR包部署到Tomcat中去就行了。后来拆分成多个微服务之后有的业务需求需要同时修改多个微服务的代码这时候就有多个微服务都需要打包、测试和上线发布一个业务需求就需要同时测试多个微服务接口的功能上线发布多个系统给测试和运维的工作量增加了很多。这个时候就需要有办法能够减轻测试和运维的负担我在上一讲给出的解决方案是DevOps。
DevOps可以简单理解为开发和运维的结合服务的开发者不再只负责服务的代码开发还要负责服务的测试、上线发布甚至故障处理等全生命周期过程这样的话就把测试和运维从微服务拆分后所带来的复杂工作中解放出来。DevOps要求开发、测试和发布的流程必须自动化这就需要**保证开发人员将自己本地部署测试通过的代码和运行环境,能够复制到测试环境中去,测试通过后再复制到线上环境进行发布**。虽然这个过程看上去好像复制代码一样简单,但在现实时,本地环境、测试环境以及线上环境往往是隔离的,软件配置环境的差异也很大,这也导致了开发、测试和发布流程的割裂。
而且还有一个问题是拆分后的微服务相比原来大的单体应用更加灵活经常要根据实际的访问量情况做在线扩缩容而且通常会采用在公有云上创建的ECS来扩缩容。这又给微服务的运维带来另外一个挑战因为公有云上创建的ECS通常只包含了基本的操作系统环境微服务运行依赖的软件配置等需要运维再单独进行初始化工作因为不同的微服务的软件配置依赖不同比如Java服务依赖了JDK就需要在ECS上安装JDK而且可能不同的微服务依赖的JDK版本也不相同一般情况下新的业务可能依赖的版本比较新比如JDK 8而有些旧的业务可能依赖的版本还是JDK 6为此服务部署的初始化工作十分繁琐。
而容器技术的诞生恰恰解决了上面这两个问题为什么容器技术可以解决本地、测试、线上环境的隔离解决部署服务初始化繁琐的问题呢下面我就以业界公认的容器标准Docker为例来看看Docker是如何解决这两个问题的。
## 什么是Docker
Docker是容器技术的一种事实上已经成为业界公认的容器标准要理解Docker的工作原理首先得知道什么是容器。
容器翻译自英文的Container一词而Container又可以翻译成集装箱。我们都知道**集装箱的作用就是,在港口把货物用集装箱封装起来,然后经过货轮从海上运输到另一个港口,再在港口卸载后通过大货车运送到目的地。这样的话,货物在世界的任何地方流转时,都是在集装箱里封装好的,不需要根据是在货轮上还是大货车上而对货物进行重新装配**。同样在软件的世界里容器也起到了相同的作用只不过它封装的是软件的运行环境。容器的本质就是Linux操作系统里的进程但与操作系统中运行的一般进程不同的是容器通过[Namespace](https://en.wikipedia.org/wiki/Linux_namespaces)和[Cgroups](https://zh.wikipedia.org/wiki/Cgroups)这两种机制可以拥有自己的root文件系统、自己的网络配置、自己的进程空间甚至是自己的用户ID空间这样的话容器里的进程就像是运行在宿主机上的另外一个单独的操作系统内从而实现与宿主机操作系统里运行的其他进程隔离。
Docker也是基于Linux内核的Cgroups、Namespace机制来实现进程的封装和隔离的那么Docker为何能把容器技术推向一个新的高度呢这就要从Docker在容器技术上的一项创新Docker镜像说起。虽然容器解决了应用程序运行时隔离的问题但是要想实现应用能够从一台机器迁移到另外一台机器上还能正常运行就必须保证另外一台机器上的操作系统是一致的而且应用程序依赖的各种环境也必须是一致的。Docker镜像恰恰就解决了这个痛点具体来讲就是**Docker镜像不光可以打包应用程序本身而且还可以打包应用程序的所有依赖甚至可以包含整个操作系统**。这样的话你在你自己本机上运行通过的应用程序就可以使用Docker镜像把应用程序文件、所有依赖的软件以及操作系统本身都打包成一个镜像可以在任何一个安装了Docker软件的地方运行。
Docker镜像解决了DevOps中微服务运行的环境难以在本地环境、测试环境以及线上环境保持一致的难题。如此一来开发就可以把在本地环境中运行测试通过的代码以及依赖的软件和操作系统本身打包成一个镜像然后自动部署在测试环境中进行测试测试通过后再自动发布到线上环境上去整个开发、测试和发布的流程就打通了。
同时无论是使用内部物理机还是公有云的机器部署服务都可以利用Docker镜像把微服务运行环境封装起来从而屏蔽机器内部物理机和公有云机器运行环境的差异实现同等对待降低了运维的复杂度。
## 微服务容器化实践
Docker能帮助解决服务运行环境可迁移问题的关键就在于Docker镜像的使用上实际在使用Docker镜像的时候往往并不是把业务代码、依赖的软件环境以及操作系统本身直接都打包成一个镜像而是利用Docker镜像的**分层机制**在每一层通过编写Dockerfile文件来逐层打包镜像。这是因为虽然不同的微服务依赖的软件环境不同但是还是存在大大小小的相同之处因此在打包Docker镜像的时候可以分层设计、逐层复用这样的话可以减少每一层镜像文件的大小。
下面我就以微博的业务Docker镜像为例来实际讲解下生产环境中如何使用Docker镜像。正如下面这张图所描述的那样微博的Docker镜像大致分为四层。
<li>
基础环境层。这一层定义操作系统运行的版本、时区、语言、yum源、TERM等。
</li>
<li>
运行时环境层。这一层定义了业务代码的运行时环境比如Java代码的运行时环境JDK的版本。
</li>
<li>
Web容器层。这一层定义了业务代码运行的容器的配置比如Tomcat容器的JVM参数。
</li>
<li>
业务代码层。这一层定义了实际的业务代码的版本比如是V4业务还是blossom业务。
</li>
<img src="https://static001.geekbang.org/resource/image/3d/7d/3df442f8c8eaec6184826028ad5a5f7d.png" alt="" />
这样的话每一层的镜像都是在上一层镜像的基础上添加新的内容组成的以微博V4镜像为例V4业务的Dockerfile文件内容如下
```
FROM registry.intra.weibo.com/weibo_rd_content/tomcat_feed:jdk8.0.40_tomcat7.0.81_g1_dns
ADD confs /data1/confs/
ADD node_pool /data1/node_pool/
ADD authconfs /data1/authconfs/
ADD authkey.properties /data1/
ADD watchman.properties /data1/
ADD 200.sh /data1/weibo/bin/200.sh
ADD 503.sh /data1/weibo/bin/503.sh
ADD catalina.sh /data1/weibo/bin/catalina.sh
ADD server.xml /data1/weibo/conf/server.xml
ADD logging.properties /data1/weibo/conf/logging.properties
ADD ROOT /data1/weibo/webapps/ROOT/
RUN chmod +x /data1/weibo/bin/200.sh /data1/weibo/bin/503.sh /data1/weibo/bin/catalina.sh
WORKDIR /data1/weibo/bin
```
FROM代表了上一层镜像文件是“tomcat_feed:jdk8.0.40_tomcat7.0.81_g1_dns”从名字可以看出上一层镜像里包含了Java运行时环境JDK和Web容器Tomcat以及Tomcat的版本和JVM参数等ADD就是要在这层镜像里添加的文件 这里主要包含了业务的代码和配置等RUN代表这一层镜像启动时需要执行的命令WORKDIR代表了这一层镜像启动后的工作目录。这样的话就可以通过Dockerfile文件在上一层镜像的基础上完成这一层镜像的制作。
## 总结
今天我给你讲解了微服务拆分后相比于传统的单体应用所带来的两个问题一个是测试和发布工作量的提升另一个是在弹性扩缩容时不同微服务所要求的软件运行环境差异带来的机器初始化复杂度的提升而Docker利用Docker镜像对软件运行环境的完美封装正好解决了这两个问题。
正是因为Docker可以做到一处通过、到处运行所以对业务的价值极大解决了以前应用程序在开发环境、测试环境以及生产环境之间的移植难的问题极大提高了运维自动化的水平也为DevOps理念的流行和业务上云提供了基础。
可见容器化改造对微服务是十分必要的但Docker也不是“银弹”同样会产生新的复杂度问题比如引入Docker后旧的针对物理机的运维模式就无法适应了需要一种新的针对容器的运维模式。所以接下来我将分三期给你详细讲解微服务容器化后该如何运维。
## 思考题
Docker的概念乍一看与虚拟机有些类似你认为它们有什么不同之处吗分别适合什么应用场景
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@@ -0,0 +1,85 @@
<audio id="audio" title="26 | 微服务容器化运维:镜像仓库和资源调度" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f0/f9/f0d362dcc2e7198888ba1c3ae40b21f9.mp3"></audio>
专栏上一期我给你讲解了容器化技术解决了单体应用拆分为微服务后,所带来的服务测试和发布运维复杂度提升的问题,可以说容器化技术天生就是为微服务而生。但微服务容器化后又带来了一个新的挑战,那就是容器如何运维的问题。
为什么微服务容器化的运维又成了新问题?
对于大部分业务团队来说在进行容器化以前服务都是部署在物理机或者虚拟机上运维往往有一套既有的运维平台来发布服务。我就以微博的运维平台JPool来举例当有服务要发布的时候JPool会根据服务所属的集群一般一个业务线是一个集群运行在哪个服务池一般一个业务线有多个服务池找到对应的物理机或者虚拟机IP然后把最新的应用程序代码通过Puppet等工具分批逐次地发布到这些物理机或者虚拟机上然后重新启动服务这样就完成一个服务的发布流程。
但是现在情况变了,**业务容器化后运维面对的不再是一台台实实在在的物理机或者虚拟机了而是一个个Docker容器它们可能都没有固定的IP**,这个时候要想服务发布该怎么做呢?
这时候就需要一个面向容器的新型运维平台,它能够在现有的物理机或者虚拟机上创建容器,并且能够像运维物理机或者虚拟机一样,对容器的生命周期进行管理,通常我们叫它“容器运维平台”。
根据我的经验一个容器运维平台通常包含以下几个组成部分镜像仓库、资源调度、容器调度和服务编排。所以关于微服务容器化运维的内容我会分为3期今天先来看容器运维平台的镜像仓库和资源调度后面两期会介绍容器调度、服务编排和微博容器运维平台的建设。
## 镜像仓库
Docker容器运行依托的是Docker镜像也就是说要发布服务首先必须把镜像发布到各个机器上去这个时候问题就来了这个镜像该放在哪如何把镜像发布到各个机器上去这时候你就要依靠**镜像仓库**了。
镜像仓库的概念其实跟Git代码仓库类似就是有一个集中存储的地方把镜像存储在这里在服务发布的时候各个服务器都访问这个集中存储来拉取镜像然后启动容器。
Docker官方提供了一个镜像仓库地址[https://hub.docker.com/](https://hub.docker.com/),对于测试应用或者小规模的业务可以直接使用。但对于大部分业务团队来说,出于安全和访问速度的需要,都会搭建一套私有的镜像仓库。那么具体该如何搭建一套私有的镜像仓库呢?下面我就结合微博的实践,和你聊聊这里面的门道。
**1.权限控制**
镜像仓库首先面临的第一个问题就是权限控制的问题,也就是说哪些用户可以拉取镜像,哪些用户可以修改镜像。
一般来说,镜像仓库都设有两层权限控制:一是必须登录才可以访问,这是最外层的控制,它规定了哪些人可以访问镜像仓库;二是对镜像按照项目的方式进行划分,每个项目拥有自己的镜像仓库目录,并且给每个项目设置项目管理员、开发者以及客人这三个角色,只有项目管理员和开发者拥有自己镜像仓库目录下镜像的修改权限,而客人只拥有访问权限,项目管理员可以给这个项目设置哪些人是开发者。
这个权限控制就跟大厦办公楼的管理类似,你要进入大厦里的一个办公室,首先必须具备进入大厦的权限,这个权限是在大厦里所有办公的人都有的。然后你还得具备大厦里你办公室所在楼层的门禁,这样才能进入办公室。不同楼层的人权限不同,只能进入自己楼层的办公室。如果某个办公室有新来的员工,首先要给他分配大厦的进入权限,然后还要这个办公室的管理员给他分配办公室的权限。是不是这样讲权限控制就好理解一些了呢。
**2.镜像同步**
在实际的生产环境中,往往需要把镜像同时发布到几十台或者上百台集群节点上,单个镜像仓库实例往往受带宽原因限制无法同时满足大量节点的下载需求,这个时候就需要配置多个镜像仓库实例来做负载均衡,同时也就产生镜像在多个镜像仓库实例之间同步的问题了。显然通过手工维护十分繁琐,那有什么好的办法吗?
一般来说,有两种方案,一种是一主多从,主从复制的方案,比如开源镜像仓库[Harbor](https://github.com/goharbor/harbor)采用了这种方案另一种是P2P的方案比如阿里的容器镜像分发系统[蜻蜓](https://alibaba.github.io/Dragonfly/)采用了P2P方案。微博的镜像仓库是基于Harbor搭建的所以这里我就以Harbor为例介绍镜像同步机制。
Harbor所采取的主从复制的方案是把镜像传到一个主镜像仓库实例上去然后其他从镜像仓库实例都从主镜像仓库实例同步它的实现就像下图所描述的一样。
<img src="https://static001.geekbang.org/resource/image/0a/17/0a85d5cda10eef1a24d84fe0100b9917.png" alt="">
除此之外Harbor还支持层次型的发布方式如果集群部署在多个IDC可以先从一个主IDC的镜像仓库同步到其他从IDC的镜像仓库再从各个从IDC同步给下面的分IDC它的实现就像下图所描述的一样。
<img src="https://static001.geekbang.org/resource/image/9f/be/9fb40d4513cd3a3cce2c8af4bc5b80be.png" alt="">
**3.高可用性**
既然Docker镜像是Docker容器运行的基础那么镜像仓库的高可用性就不言而喻了。一般而言高可用性设计无非就是把服务部署在多个IDC这样的话即使有IDC出问题也可以把服务迁移到别的正常IDC中去。同样对于镜像仓库的搭建也可以采用多IDC部署那么需要做到的就是不同IDC之间的镜像同步。以微博的镜像仓库为例就像下图所描述的那样镜像仓库会部署在永丰、土城两个内网IDC内两个IDC内的镜像同步采用Harbor的双主复制策略互相复制镜像这样的话即使有一个IDC出现问题另外一个IDC仍然能够提供服务而且不丢失数据。
<img src="https://static001.geekbang.org/resource/image/55/9e/55fce15167cc03a452ae4a58646c779e.png" alt="">
## 资源调度
解决了Docker镜像存储和访问的问题后新问题又随之而来了Docker镜像要分发到哪些机器上去这些机器是从哪里来的这其实涉及的是**资源调度**的问题。
根据我的经验,服务部署的集群主要包括三种:
1.物理机集群。大部分中小团队应该都拥有自己的物理机集群,并且大多按照集群 - 服务池 - 服务器这种模式进行运维。物理机集群面临的问题主要是服务器的配置不统一尤其对于计算节点来说普遍存在的一种情况就是几年前采购的机器的配置可能还是12核16G内存的配置而近些年采购的机器都至少是32核32G内存的配置对于这两种机器往往要区别对待比如旧的机器用于跑一些非核心占用资源量不大的业务而新采购的机器用于跑一些核心且服务调用量高的业务。
2.虚拟机集群。不少业务团队在使用物理机集群之后发现物理机集群存在使用率不高、业务迁移不灵活的问题因此纷纷转向了虚拟化方向构建自己的私有云比如以OpenStack技术为主的私有云集群在国内外不少业务团队都有大规模的应用。它的最大好处就是可以整合企业内部的服务器资源通过虚拟化技术进行按需分配提高集群的资源使用率节省成本。
3.公有云集群。现在越来越多的业务团队,尤其是初创公司,因为公有云快速灵活的特性,纷纷在公有云上搭建自己的业务。公有云最大的好处除了快速灵活、分钟级即可实现上百台机器的创建,还有个好处就是配置统一、便于管理,不存在机器配置碎片化问题。
为了解决资源调度的问题Docker官方提供了[Docker Machine](https://github.com/docker/machine)功能通过Docker Machine可以在企业内部的物理机集群或者虚拟机集群比如OpenStack集群又或者公有云集群比如AWS集群等上创建机器并且直接部署容器。Docker Machine的功能虽然很好但是对于大部分已经发展了一段时间的业务团队来说并不能直接拿来使用。
这主要是因为**资源调度最大的难点不在于机器的创建和容器的部署,而在于如何对接各个不同的集群,统一管理来自不同集群的机器权限管理、成本核算以及环境初始化等操作,这个时候就需要有一个统一的层来完成这个操作**。这个对有历史包袱的团队,比如公司内网的物理机集群已经有一套运维体系来说,挑战不小,需要针对新的模式重新开发这套运维平台。以微博的业务为例,为了满足内部三种不同集群资源的统一管理,专门研发了容器运维平台[DCP](https://github.com/weibocom/opendcp)来实现对接多个不同的集群。它的难点在于不仅对外要对接不同的云厂商针对不同云厂商提供的ECS创建的API统一封装一层API来实现机器管理对内也要针对私有云上不同集群的机器进行管理进行上下线和配置初始化等操作。
以DCP配置初始化操作为例在创建完主机后还需要在主机上进行安装NTP服务、修改sysctl配置、安装Docker软件等操作这时候就需要借助配置管理软件来向主机上进行分发。因为微博内网的主机之前都是通过[Puppet](https://puppet.com/)进行分发的,考虑到稳定性并没有对这一部分进行修改;而针对阿里云上创建的主机,则使用的是编程功能更为强大的[Ansible](https://www.ansible.com/)进行分发。
更多有关DCP的内容我会在容器化运维系列的第三期跟你仔细聊聊。
## 总结
今天我给你讲解了容器运维平台的两个关键组成,镜像仓库和资源调度。
镜像仓库帮我们解决的是Docker镜像如何存储和访问的问题在业务规模较大时各个业务团队都需要搭建自己的私有镜像仓库。类似Harbor这种开源解决方案能很好地解决权限控制、镜像同步等基本问题关于高可用性的要求以及上云支持等业务场景你可以参考我给出的解决方案它是经过微博实际线上业务验证过的。
资源调度帮我们解决的是如何整合来自不同的集群的资源的问题,如果你的业务不止在内部私有云上部署,在公有云上也有部署,甚至是采用了多家公有云,那么资源的调度将会是非常复杂的问题,尤其是在公司内部已经存在一套对接内部集群的运维管理平台的情况下,是升级已有的运维平台以支持公有云,还是直接开发另外一套新的能够实现多云对接,这是一个很现实的问题。我的建议是单独开发一套新的运维平台先来接管公有云,然后逐步迁移内部集群的管理工作到新的运维平台中。
今天就讲到这里,关于容器运维平台的另外两个关键组成:容器调度和服务编排,我们下期再聊。
## 思考题
在讲解镜像仓库解决方案时除了Harbor这种主从镜像复制的方案以外我还提到了P2P的方案你觉得这两种方案有何区别分别适用哪种业务场景
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@@ -0,0 +1,98 @@
<audio id="audio" title="27 | 微服务容器化运维:容器调度和服务编排" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/71/30/710156dfa2483af3b52da23251ebe530.mp3"></audio>
专栏上一期我给你讲解了容器运维平台的两个关键组成镜像仓库和资源调度。复习一下镜像仓库解决的是Docker镜像存储和访问的问题资源调度决定了Docker镜像可以分发到哪些机器上的问题。这两个问题解决后你就该考虑如何在集群中创建容器也就是容器如何调度的问题容器创建后如何运作才能对外提供服务也就是服务如何编排的问题。下面我们就一起看看容器调度和服务编排都是如何解决的。
## 容器调度
容器调度的问题,说的是现在集群里有一批可用的物理机或者虚拟机,当服务需要发布的时候,该选择哪些机器部署容器的问题。
比如集群里只有10台机器并且已经有5台机器运行着其他容器剩余5台机器空闲着如果此时有一个服务要发布但只需要3台机器就行了这个时候可以靠运维人为的从5台空闲的机器中选取3台机器然后把服务的Docker镜像下载下来再启动Docker容器服务就算完成发布。但如果集群机器的规模扩大到几十台或者上百台时要发布的服务也有几十个或者上百个的时候由于每个服务对容器的要求以及每台机器上正在运行的容器情况变得很复杂就不太可能靠人肉运维了。
这时就需要有专门的容器调度系统了为此也诞生了不少基于Docker的容器调度系统比如Docker原生的调度系统[Swarm](https://docs.docker.com/engine/swarm/)、Mesosphere出品的[Mesos](http://mesos.apache.org/)以及Google开源的大名鼎鼎的[Kubernetes](https://kubernetes.io/)。下面我就结合微博的实践经验,给你讲讲容器调度要解决哪些问题。
**1.主机过滤**
主机过滤是为了解决容器创建时什么样的机器可以使用的问题,主要包含两种过滤。
<li>
存活过滤。也就是说必须选择存活的节点,因为主机也有可能下线或者是故障状态。
</li>
<li>
硬件过滤。打个比方现在你面对的集群有Web集群、RPC集群、缓存集群以及大数据集群等不同的集群硬件配置差异很大比如Web集群往往用作计算节点它的CPU一般配置比较高而大数据集群往往用作数据存储它的磁盘一般配置比较高。这样的话如果要创建计算任务的容器显然就需要选择Web集群而不是大数据集群。
</li>
上面这两种过滤方式都是针对主机层次的过滤方式除此之外Swarm还提供了容器层次的过滤可以实现只有运行了某个容器的主机才会被加入候选集等功能。
**2.调度策略**
调度策略主要是为了解决容器创建时选择哪些主机最合适的问题一般都是通过给主机打分来实现的。比如Swarm就包含了两种类似的策略spread和binpack它们都会根据每台主机的可用CPU、内存以及正在运行的容器的数量来给每台主机打分。spread策略会选择一个资源使用最少的节点以使容器尽可能的分布在不同的主机上运行。它的好处是可以使每台主机的负载都比较平均而且如果有一台主机有故障受影响的容器也最少。而binpack策略恰恰相反它会选择一个资源使用最多的节点好让容器尽可能的运行在少数机器上节省资源的同时也避免了主机使用资源的碎片化。
具体选择哪种调度策略,还是要看实际的业务场景,通常的场景有:
<li>
各主机的配置基本相同,并且使用也比较简单,一台主机上只创建一个容器。这样的话,每次创建容器的时候,直接从还没有创建过容器的主机当中随机选择一台就可以了。
</li>
<li>
在某些在线、离线业务混布的场景下为了达到主机资源使用率最高的目标需要综合考量容器中跑的任务的特点比如在线业务主要使用CPU资源而离线业务主要使用磁盘和I/O资源这两种业务的容器大部分情况下适合混跑在一起。
</li>
<li>
还有一种业务场景主机上的资源都是充足的每个容器只要划定了所用的资源限制理论上跑在一起是没有问题的但是某些时候会出现对每个资源的抢占比如都是CPU密集型或者I/O密集型的业务就不适合容器混布在一台主机上。
</li>
所以实际的业务场景对调度策略的要求比较灵活如果Swarm提供的spread和binpack满足不了的话可能就需要考虑自行研发容器调度器了。
## 服务编排
**1.服务依赖**
大部分情况下微服务之间是相互独立的在进行容器调度的时候不需要考虑彼此。但有时候也会存在一些场景比如服务A调度的前提必须是先有服务B这样的话就要求在进行容器调度的时候还需要考虑服务之间的依赖关系。
为此Docker官方提供了[Docker Compose](https://github.com/docker/compose)的解决方案。它允许用户通过一个单独的docker-compose.yaml文件来定义一组相互关联的容器组成一个项目从而以项目的形式来管理应用。比如要实现一个Web项目不仅要创建Web容器比如Tomcat容器还需要创建数据库容器比如MySQL容器、负载均衡容器比如Nginx容器等这个时候就可以通过docker-compose.yaml来配置这个Web项目里包含的三个容器的创建。
Docker Compose这种通过yaml文件来进行服务编排的方式是比较普遍的算法以微博的业务为例也是通过类似yaml文件的方式定义了服务扩容的模板模板除了定义了服务创建容器时的镜像配置、服务池配置以及主机资源配置以外还定义了关联依赖服务的配置。比如微博的Feed服务依赖了user服务和card服务假如user服务扩容的模板ID为1703271839530000card服务扩容的模板ID为1707061802000000那么Feed服务的扩容模板里就会像下面这样配置它代表了每扩容10台Feed服务的容器就需要扩容4台user服务的容器以及3台card服务的容器。
```
{&quot;Sid&quot;:1703271839530000,&quot;Ratio&quot;:0.4}
{&quot;Sid&quot;:1707061802000000,&quot;Ratio&quot;:0.3}
```
**2.服务发现**
容器调度完成以后,容器就可以启动了,但此时容器还不能对外提供服务,服务消费者并不知道这个新的节点,所以必须具备服务发现机制,使得新的容器节点能够加入到线上服务中去。
根据我的经验比较常用的服务发现机制包括两种一种是基于Nginx的服务发现一种是基于注册中心的服务发现。
- 基于Nginx的服务发现
这种主要是针对提供HTTP服务的当有新的容器节点时修改Nginx的节点列表配置然后利用Nginx的reload机制会重新读取配置从而把新的节点加载进来。比如基于Consul-Template和Consul把Consul作为DB存储容器的节点列表Consul-Template部署在Nginx上Consul-Template定期去请求Consul如果Consul中存储的节点列表发生变化就会更新Nginx的本地配置文件然后Nginx就会重新加载配置。
- 基于注册中心的服务发现
这种主要是针对提供RPC服务的当有新的容器节点时需要调用注册中心提供的服务注册接口。注册中心的服务发现机制在[专栏第5](http://time.geekbang.org/column/article/14603)期我有过详细讲解你可以再回顾一下它的原理。在使用这种方式时如果服务部署在多个IDC就要求容器节点分IDC进行注册以便实现同IDC内就近访问。以微博的业务为例微博服务除了部署在内部的两个IDC还在阿里云上也有部署这样的话内部机房上创建的容器节点就应该加入到内部IDC分组而云上的节点应该加入到阿里云的IDC。
**3.自动扩缩容**
容器完成调度后,仅仅做到有容器不可用时故障自愈还不够,有时候还需要根据实际服务的运行状况,做到自动扩缩容。
一个很常见的场景就是大部分互联网业务的访问呈现出访问时间的规律性。以微博业务为例白天和晚上的使用人数要远远大于凌晨的使用人数而白天和晚上的使用人数也不是平均分布的午高峰12点半和晚高峰10点半是使用人数最多的时刻。这个时候就需要根据实际使用需求在午高峰和晚高峰的时刻增加容器的数量确保服务的稳定性在凌晨以后减少容器的数量减少服务使用的资源成本。
常见的自动扩缩容的做法是根据容器的CPU负载情况来设置一个扩缩容的容器数量或者比例比如可以设定容器的CPU使用率不超过50%,一旦超过这个使用率就扩容一倍的机器。
## 总结
今天我给你讲解了容器运维平台的另外两个关键组成:容器调度和服务编排,并给出了常用的解决方案。你的业务团队在选择解决方案时,要根据自己的需要选择合适的方案,而不是理论上最好的。
比如Kubernetes解决方案在容器调度、服务编排方面都有成熟的组件并且经过大业务量的实际验证。但是要考虑到Kubernetes本身的复杂性以及概念理解的门槛对于大部分中小业务团队来说在生产环境上使用Kubernetes都会显得大材小用并且还需要部署并运维Kubernetes周边的一些基础设施比如etcd等。
相比之下Docker原生自带的解决方案Swarm和Compose就要简单得多但是功能也比较有限如果不能满足你的业务需求的话也不好再二次开发。
在了解了镜像仓库、资源调度、容器调度、服务编排后你会发现,微服务容器化后最大的挑战其实来自于原有运维设施如何支持容器的运维,是在原有运维平台上升级还是完全采用新的容器运维平台,这才是关键,往往不能一蹴而就,需要逐步按照业务进行替换升级。但是考虑到微服务容器化后所带来的种种好处,采用新的运维模式势在必行。
## 思考题
容器调度方面业界最有名的莫过于Swarm、Mesos和Kubernetes了你认为它们的优缺点是什么分别适合什么业务场景
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@@ -0,0 +1,124 @@
<audio id="audio" title="28 | 微服务容器化运维微博容器运维平台DCP" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/30/b5/30183d1c6f67d8d36df733d81ff708b5.mp3"></audio>
微服务容器化运维系列的前两期,我给你详细介绍了微服务容器化后如何运维的几个关键问题:镜像仓库、资源调度、容器调度、服务编排,这些问题的产生都是因为微服务部署的节点从一台台物理机或者虚拟机变成了一个个容器,运维模式发生了根本性的变化。此时,容器运维平台也就应运而生。
微博的业务从2013年就开始进行容器化2015年为了应对春晚以及突发热点事件带来的峰值流量开始引入阿里云同时也为了适应业务的发展和运维方式的变化在2015年底开始研发新的容器运维平台DCP。今天我就和你聊聊微博容器运维平台DCP我会讲讲一个真实的容器运维平台是如何建设的在建设过程中面临了哪些问题以及对应的解决方案希望可以让你对容器运维平台的架构有所了解并提供一些经验可供借鉴。
## DCP整体架构
首先我们先来看看DCP的架构设计从下面这张架构图你可以看到DCP的架构主要分为四个部分基础设施层、主机层、调度层、编排层对应的分别解决前面提到的容器运维平台建设的几个关键问题基础设施层用于解决镜像仓库的问题主机层主要解决如何进行资源调度的问题调度层主要解决容器如何在资源上创建的问题编排层主要解决容器如何运作以对外提供服务的问题。下面我们来看各层的详细设计。
<img src="https://static001.geekbang.org/resource/image/10/64/101e6beb60f0bc482ef6cb0e793d5864.png" alt="">
## 基础设施层
DCP中基础设施层主要用于提供各种基础设施以保证其他层功能的正常运行。通常来讲主要包括以下几个基础组件用于存放容器镜像的镜像仓库、提供监控服务的监控中心、实时监控系统容量以便于自动扩缩容的容量评估系统以及容器创建后如何加入线上服务的服务发现组件其中**镜像仓库是DCP最核心的基础组件**。
正如[专栏第26期](http://time.geekbang.org/column/article/42167)我讲的那样DCP以开源镜像仓库Harbor为基础搭建了私有的镜像仓库不过由于微博业务的特征为了应对随时可能到来的突发峰值流量的冲击需要随时随地能够扩容服务池。但在内网冗余度不足的时候也不得不借助公有云来实现因此服务不仅在内网私有云上有部署在阿里云上也有部署这样的话从阿里云申请的主机也需要从镜像仓库中拉取镜像。此时如果镜像仓库只在内网部署的话就需要跨专线去拉取镜像但如果上百台服务器同时拉取镜像带宽占用很可能达到上百G由于专线带宽是有限的显然这样不可取。为此正确的做法就像下图中那样在阿里云机房也部署一套镜像仓库并且通过Harbor的主从复制机制与内网的镜像仓库保持同步。同时为了做到负载均衡每个机房内部都部署了多个Harbor节点内网节点访问内网镜像仓库会通过LVS进行负载均衡阿里云上节点访问阿里云镜像仓库会通过SLB进行负载均衡以满足镜像仓库的带宽需求。
<img src="https://static001.geekbang.org/resource/image/d3/d2/d3bbd465ed4b053082b011d12be9acd2.png" alt="">
## 主机层
DCP中主机层的功能主要是为了完成资源的调度也就是针对不同的集群完成主机的创建、成本的管理以及配置初始化工作也叫Pluto层。前面提到过微博业务不仅在内网私有云上有部署而且在阿里云上也有部署为此Pluto需要适配不同底层提供的创建主机的API进行成本核算并且进行配置初始化操作。Pluto层的架构你可以参看下图我来详细讲一下。
<img src="https://static001.geekbang.org/resource/image/fc/e7/fc32ede78147d2c19cd51fb4f597b3e7.png" alt="">
1.主机创建
Pluto在创建主机时主要有两个来源一个是内部物理机组成的共享池一个是调用阿里云API创建ECS。其中共享池内的资源主要来源于两部分一部分是冗余度高的服务池缩容部分主机加入到共享池一部分是在线业务和离线计算互相补充比如白天在线业务需要的机器多而离线计算的任务主要运行在凌晨这时候就可以在白天把离线计算的集群的部分机器加入到共享池给在线业务使用而在晚上业务低峰期把在线业务的部分机器加入到共享池给离线计算任务使用。而使用阿里云创建ECS主要是在共享池内的资源不足的情况下比如有突发热点事件到来各个服务池都需要紧急扩容这时候共享池内的资源就不足以应对了。而使用阿里云API创建ECS会受到阿里云API的各种限制下面我列举几个微博在使用阿里云创建机器时所遇到的问题你就可以理解主机创建的复杂性所在了。
- 由于阿里云API对单账户的调用有并发限制所以实际业务在创建阿里云ECS上时不能上百台同时创建一般要控制在几十台的规模左右如果这个时候业务需要创建上百台机器该怎么做呢那就需要采取队列机制来控制机器创建的速度。下面这张图就描述了微博在使用阿里云创建ECS时的解决方案在实际创建ECS时不会立即调用阿里云API而是把节点创建任务先放到一个DB队列中然后再通过一个线程定时从DB队列中获取创建任务每次只创建几十台这样的话就不会触发阿里云API对单账号调用的并发限制。
<img src="https://static001.geekbang.org/resource/image/ec/c0/ec5e29cf71a8b55ed136f18ce786dac0.png" alt="">
- 除了有单账户调用的并发限制还会有可用区的库存限制、安全组库存限制以及vSwitch库存限制所以在实际使用阿里云API创建ECS时当机器规模较大如果直接指定使用某个可用区、安全组和vSwitch就可能因为库存原因导致创建失败。微博一开始就使用了这种方案但在突发峰值流量来临时往往要创建几百台甚至上千台的阿里云ECS为此经常会因为以上限制导致创建失败。后来针对可用区、安全组以及vSwitch都做了多可用区、多安全组以及多vSwtich配置在出现库存不够时就自动切换到别的地方来创建极大提高了大规模ECS创建的成功率。
<img src="https://static001.geekbang.org/resource/image/56/3c/5650102ded876d26c6abde1b97678e3c.png" alt="">
2.成本管理
无论是从共享池内创建的机器还是调用阿里云API创建的ECS都是有成本的为此必须对机器的数量以及使用时长进行记录以便进行成本管理。
以阿里云的ECS为例又分为按量付费、按月付费以及按年付费可以按照以下方式来进行管理。
<li>
按量付费。按照使用时长以秒为单位计费适合突发流量到来临时需要扩容部分机器时使用所以需要记录每台ECS从调用API创建成功到销毁所使用的时长。
</li>
<li>
按月付费。这种比较适合短期业务需要使用机器的场景,比如微博曾经在奥运会期间扩容过大量包月付费的机器,以应对奥运会期间带来的流量上涨。需要注意的是,这种机器到了月底会自动销毁,所以如果还有使用需要的话,需要及时续费。
</li>
<li>
按年付费。这种比较适合需要长期在阿里云上部署的业务,比如有一些新的业务因为业务发展比较快,采用传统自采机器部署的话,由于采购周期比较长不适合业务发展,所以使用公有云更为合适。
</li>
3.配置初始化
主机创建完成后还要进行一些基础软件的安装以及配置修改等工作这就是配置初始化的过程。以阿里云创建的ECS为例如果短时间内创建了上千台ECS这个时候配置初始化的工作量会非常大需要同时给上千台ECS下发配置文件并安装基础软件同时还需要记录每台ECS的初始化状态到DB以便查询是否初始化成功。下图描述了初始化的过程DCP在进行主机配置初始化时会通过Ansible向所有主机下发配置文件和基础软件并通过自定义callback queue把每台主机的初始化状态异步写入到DB中避免上百台机器同时并发写入DB造成死锁。
<img src="https://static001.geekbang.org/resource/image/98/c3/98dd72c57190af3502e037e32fc4b8c3.png" alt="">
## 调度层
DCP中调度层的主要功能是在可用的主机上创建容器。由于微博业务早在2013年就开始进行容器化基于当时的背景考虑就选择了Swarm作为容器调度的工具并根据自己的业务特点在Swarm基础上进行二次封装定制了自己的调度层Roam使其具备支持跨IDC、高可用以及可扩展的特性。下面是Roam的架构其主要工作原理是
<li>
Swarm Manager和Swarm Client节点都向Consul中注册并且有一个Active Manager和Standby Manager。任何一个IDC内的Active Manager如果down掉的话Standby Manager就会注册到Consul中成为新的Active Manager以保证高可用性。
</li>
<li>
当发起容器调度时Roam根据IDC参数请求Consul得到该IDC的Swarm Manager信息。
</li>
<li>
Roam访问该IDC内的Swarm ManagerSwarm Manager再访问Consul获取Swarm Client信息并根据Roam传递的调度策略从Swarm Client中选择节点创建容器。
</li>
<img src="https://static001.geekbang.org/resource/image/68/bb/68d80a9dac56519d38730c7359e93bbb.png" alt="">
## 编排层
DCP中编排层的主要作用是对服务进行整合以对外提供服务主要包括服务依赖、服务发现以及自动扩缩容下面我来详细介绍每一部分的具体实现。
1.服务依赖
DCP通过模板来管理容器的创建一个服务如果需要进行扩容、创建容器就必须按照模板里定义的参数来执行以下图描述的DCP里的一个扩容任务创建模板为例通常来讲模板里定义的参数主要包括几个部分任务的名称、机器的配置、任务依赖、任务详细配置包括调用阿里云API创建ECS时的可用区、安全组参数等其中任务依赖的配置项是
```
{&quot;Sid&quot;:1707061842070000,&quot;Ratio&quot;:0.2,&quot;ElasticCount&quot;:0}
{&quot;Sid&quot;:1703271821000000,&quot;Ratio&quot;:0.3,&quot;ElasticCount&quot;:0}
```
它的含义是执行这个扩容任务时会自动执行ID为1707061842070000和1703271821000000的扩容任务并且按照每扩容10台容器分别扩容2台和3台依赖容器的比例来执行。
<img src="https://static001.geekbang.org/resource/image/20/7e/20e3c0c7d1eca4738979a675a866d87e.png" alt="">
2.服务发现
微博的业务场景主要包含两种服务一种是HTTP服务一种是Motan RPC服务他们分别使用了不同的服务发现方式。
<li>
HTTP服务。考虑到传统的基于Nginx的配置Reload机制实现的服务发现方式在高并发访问的情况下会导致吞吐量下降10%左右如果业务频繁变更的话就会受到影响。为此DCP在实际业务中基于Nginx和Consul研发了一种可行的解决方案[nginx-upsync-module](https://github.com/weibocom/nginx-upsync-module),并且已经开源。
</li>
<li>
Motan RPC服务。Motan RPC服务在启动时会向注册中心Config Service注册服务并且注册中心支持多IDC部署。像下图所描述的那样正常情况下服务消费者会访问同一个IDC内的服务提供者并且支持在故障的时候可以切换到其他IDC。
</li>
<img src="https://static001.geekbang.org/resource/image/95/3b/9519bcc735da020dd24b64ba74a41a3b.png" alt="">
3.自动扩缩容
DCP系统实现自动扩缩容主要依靠的是容量决策支持系统由容量决策支持系统来实时监控系统的容量。如下图所示一旦容量决策支持系统检测到某个服务需要进行扩容就会创建扩容任务Config Watcher会监控到扩容任务并通知CronTrigger有调度策略变更。CronTrigger接到扩容任务就会调用Scheduler来具体执行扩容。同时还可以通过API来修改、查询扩缩容的信息也可以通过UI来操作。
<img src="https://static001.geekbang.org/resource/image/7d/54/7d20839ac42bc38fc887875a1397b054.png" alt="">
## 总结
今天我给你讲解了微博容器运维平台DCP的架构主要包括基础设施层、主机层、调度层以及编排层并详细介绍了每一层的功能实现以及各自承担的不同职能。下面这张图是一次完整扩容流程包括了资源评估、配额评估、初始化、容器调度、部署服务、服务依赖、服务发现以及自动扩缩容等DCP正是通过把这些过程串联起来实现容器运维的。
<img src="https://static001.geekbang.org/resource/image/54/ed/5499780f1f12d9b3940988377dae80ed.png" alt="">
## 思考题
在讲到服务编排时我提到服务之间会存在依赖关系比如服务A依赖服务B假如此时服务A的流量上涨需要对服务A进行扩容这时候有两种方案一种方案是通过自动扩缩容服务A和服务B的扩容完全独立分别按需自动扩缩容一种方案是通过服务依赖扩容服务A之前先扩容服务B你认为这两种方案哪种更好为什么
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@@ -0,0 +1,93 @@
<audio id="audio" title="29 | 微服务如何实现DevOps" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/fa/a6/fa44b6094f0cc50da7dd42a3de0678a6.mp3"></audio>
把一个大的单体应用拆分成多个微服务之后,每个服务都可以独立进行开发、测试和运维。但当拆分的微服务足够多时,却又仿佛陷入一个新的泥沼,无论是业务代码的开发还是测试和运维,工作量都比之前提升了很多。
采单体应用架构时一个业务需求只需要修改单体应用的代码然后针对这个单体应用进行测试测试通过后再把单体应用的代码发布到线上即可。而拆分为微服务之后一个大的系统被拆分为多个小的系统一个业务需求可能要同时修改多个微服务的代码这样的话多个微服务都需要进行测试测试通过了都需要把代码发布到线上显然工作量成倍增加。这时候就迫切需要一种新的开发、测试和运维模式来解决这个问题这就是今天我要给你讲的微服务与DevOps。
## 什么是DevOps
在介绍DevOps之前我先来带你回顾一下传统的业务上线流程开发人员开发完业务代码后把自测通过的代码打包交给测试人员然后测试人员把代码部署在测试环境中进行测试如果测试不通过就反馈bug给开发人员进行修复如果通过开发就把测试通过的代码交给运维人员打包然后运维人员再发布到线上环境中去。可见在传统的开发模式下开发人员、测试人员和运维人员的职责划分十分明确他们往往分属于不同的职能部门一次业务上线流程需要三者之间进行多次沟通整个周期基本上是以天为单位。你肯定会想假如能够把开发、测试和发布流程串联起来就像生产流水线上那样每个步骤完成后就自动执行下一个步骤无须过多的人为干预业务的迭代效率不就能提升很多吗。
没错DevOps的思想正是如此。在我看来DevOps是一种新型的业务研发流程业务的开发人员不仅需要负责业务代码的开发还需要负责业务的测试以及上线发布等全生命周期真正做到掌控服务全流程。DevOps就是下图中心的部分集开发、测试和运维三者角色于一体。
<img src="https://static001.geekbang.org/resource/image/ad/7e/ad0ca1f08ca2c5fa6abc15741b01597e.png" alt="">
而要实现DevOps就必须开发完成代码开发后能自动进行测试测试通过后能自动发布到线上。对应的这两个过程就是CI和CD具体来讲就是
<li>
CIContinuous Integration持续集成。开发完成代码开发后能自动地进行代码检查、单元测试、打包部署到测试环境进行集成测试跑自动化测试用例。
</li>
<li>
CDContinuous Deploy持续部署。代码测试通过后能自动部署到类生产环境中进行集成测试测试通过后再进行小流量的灰度验证验证通过后代码就达到线上发布的要求了就可以把代码自动部署到线上。
</li>
其中CD还有另外一个解释就是持续交付Continuous Delivery它与持续部署不同的是持续交付只需要做到代码达到线上发布要求的阶段就可以了接下来的代码部署到线上既可以选择手动部署也可以选择自动部署。实际服务发布时代码能否自动部署到线上本身并不是难点关键在于是否需要人为判断整个发布过程是否正常毕竟有些异常只有在真正的线上发布过程中才能被发现人为介入相对来说要保险一些所以只做到持续交付也可以算是实现了DevOps。
DevOps的关键是如何实现代码开发自测通过自动部署到测试环境验证通过后再自动部署到生产环境小流量验证后再自动发布到线上去。在传统的采用物理机部署服务的时代这个流程的很难自动化执行的最大原因就是代码环境的可移植性差这是因为开发自己的环境跟测试环境以及生产环境的软件配置往往存在很大差异经常会出现开发在自己的环境中运行通过的代码部署到测试环境就运行不了的问题。而容器化正好解决了代码环境的可移植性的问题使得DevOps取得了突飞猛进的发展并成为业界推崇的开发模式。那么具体该如何实现DevOps呢下面我就以微博的业务实践为例来给你详细讲解。
## 微博的DevOps实践
目前业界比较通用的实现DevOps的方案主要有两种一种是使用[Jenkins](https://jenkins.io/),一种是使用[Git](https://gitlab.com/)[L](https://gitlab.com/)[ab](https://gitlab.com/)。微博就主要使用的是GitLab来实现DevOps下面我就从微博一个服务的开发、测试到上线的具体流程看看是如何实现DevOps的。
<img src="https://static001.geekbang.org/resource/image/a6/52/a6dc7759695e8e685b2e1de1653ec952.png" alt="">
从上面图中你可以看到,一个服务的发布流程主要包含了三个步骤。
1.持续集成这个步骤的主要作用是确保每一次代码的Merge Request都测试通过可随时合并到代码的Develop分支主要包括四个阶段build阶段开发分支代码的编译与单元测试、package阶段开发分支代码打包成Docker镜像、deploy阶段开发分支代码部署到测试环境、test阶段开发分支代码集成测试
2.持续交付这个步骤的主要作用是确保所有代码合并Merge Request到Develop分支后Develop分支的代码能够在生产环境中测试通过并进行小流量灰度验证可随时交付到线上。主要包括五个阶段build阶段Develop分支的代码编译与单元测试、package阶段Develop分支的代码打包成Docker镜像、deploy阶段Develop分支的代码部署到测试环境、test阶段Develop分支的代码集成测试、canary阶段Develop分支的代码的小流量灰度验证
3.持续部署这个步骤的主要作用是合并Develop分支到Master主干并打包成Docker镜像可随时发布到线上。主要包括四个阶段build阶段Master主干的代码编译与单元测试、package阶段Master主干的代码打包成Docker镜像、clear阶段Master主干的代码Merge回Develop分支、production阶段Master主干的代码发布到线上
那么上面这些流程是如何实现自动化的呢在GitLab中可以通过一个叫“.gitlab-ci.yml”的文件来定义自动化流程都包含哪些阶段以及每个阶段所具体执行的脚本这样的话在提交代码Merge Request后会自动触发gitlab-ci.yml文件中定义的各个流程按顺序执行。
## 实现DevOps的关键点
上面我讲了具体业务中如何使用GitLab来实现DevOps在具体实施时每个阶段都有关键问题只有解决了这些关键问题才能真正实现DevOps。
1.持续集成阶段
持续集成阶段的主要目的是保证每一次开发的代码都没有问题,即使合并到主干也能正常工作,这里主要依靠三部分的作用。
<li>
代码检查。通过代码检查可以发现代码潜在的一些bug比如Java对象有可能是null空指针等实际执行时可以在持续集成阶段集成类似[Sonarqube](https://www.sonarqube.org/)之类的工具来实现代码检查。
</li>
<li>
单元测试。单元测试是保证代码运行质量的第二个关卡。单元测试是针对每个具体代码模块的单元测试的覆盖度越高各个代码模块出错的概率就越小。不过实际业务开发过程中为了追求开发速度许多开发者并不在意单元测试的覆盖度而是把大部分测试工作都留在了集成测试阶段这样可能会造成集成测试阶段返工的次数太多需要多次修复bug才能通过集成测试。尤其对于业务复杂度比较高的服务来说在单元测试阶段多花费一些功夫其实从整个代码开发周期角度来看收益还是要远大于付出的。
</li>
<li>
集成测试。集成测试就是将各个代码的修改集成到一起,统一部署在测试环境中进行测试。为了实现整个流程的自动化,集成自测阶段主要的任务就是跑每个服务的自动化测试用例,所以自动化测试用例覆盖的越全,集成测试的可靠性就越高。这里就要求开发和测试能及时沟通,在新的业务需求确定时,就开始编写测试用例,这样在跑自动化测试用例时,就不需要测试的介入了,省去了沟通成本。当然,业务开发人员也可以自己编写测试用例,这样的话就不需要专职的业务测试人员了。
</li>
除此之外还有一个值得关注的问题就是集成测试阶段业务代码部署的测试机器从何而来。在单体应用的时候一般是开发把代码打包交给测试测试人员再分配给自己的测试机中部署业务然后进行集成测试。但是现在问题来了由于拆分成了微服务需要测试的服务变多了如果同时有多个需求在测试测试人员的测试机可能就不够用了而出于成本考虑一般公司都不会花费采购大量的测试机器。一个好的办法就是通过Kubernetes之类的容器平台对测试集群进行管理当有业务代码正在执行集成测试时就从测试集群中创建一个容器部署服务完成测试后再销毁容器及时进行资源回收。这样测试机器不需要分配给某个具体的个人实现按需使用提高了测试集群的资源使用率。
2.持续交付阶段
持续交付阶段的主要目的是保证最新的业务代码,能够在类生产环境中可能够正常运行,一般做法都是从线上生成环境中摘掉两个节点,然后在这两个节点上部署最新的业务代码,再进行集成测试,集成测试通过后再引入线上流量,来观察服务是否正常。通常需要解决两个问题:
- 如何从线上生产环境中摘除两个节点。这就需要接入线上的容器管理平台比如微博的容器管理平台DCP就提供了类似下面的API能够从线上生产环境中摘除某个节点然后部署最新的业务代码。
```
curl -s http://raptor.api.weibo.com/extension/v1/preview/run/ -d action=503&amp;ip=11.75.21.155&amp;service_pool=openapi_friendship-yf-docker&amp;user=weibo_rd_user
```
- 如何观察服务是否正常。由于这两个节点上运行的代码是最新的代码在引入线上流量后可能会出现内存泄露等在集成测试阶段无法发现的问题所以这个阶段这两个节点上运行最新代码后的状态必须与线上其他节点一致。实际观察时主要有两个手段一个是观察节点本身的状态如CPU、内存、I/O、网卡等一个是观察业务运行产生的warn、error的日志量的大小尤其是error日志量有异常时往往就说明最新的代码可能存在异常需要处理后才能发布到线上。
3.持续部署阶段
持续部署阶段的主要目的把在类生产环境下运行通过的代码自动的发布到线上所有节点中去这里的关键点就在于实际的线上发布阶段并不是想象中的那么直接。以微博API的业务为例同样的服务也分为核心池和非核心池核心池提供给移动端和PC调用非核心池提供给其他内部业务调用并且还按照机房分为不同的服务池比如永丰机房服务池和土城机房服务池。实际发布的时候考虑到线上服务的稳定性并不是说按照一定的步长自动把所有服务池都发布了而是先发布非核心池以及土城机房的核心池然后验证观察一段时间线上服务一切正常后再继续发布永丰机房的核心池以防止某些问题在服务发布的过程中才暴露出来但又不至于影响线上所有的服务节点。所以这个阶段持续部署一般并不要求那么完美许多公司在这个阶段都采用了手动发布的方式以控制风险或者只做到持续交付阶段对于持续部署并不要求自动化。
## 总结
今天我给你介绍了DevOps对于微服务的意义它通过将开发、测试和运维流程自动化以减轻微服务拆分后带来的测试和运维复杂度的提升同时还提高了业务研发的效率。为了实现DevOps需要实现持续集成、持续交付以及持续部署可以采用Jenkins或者GitLab这些开源DevOps工具来搭建你自己的CI/CD流程关键点在于如何把已有的自动化测试用例以及现有容器管理平台集成到CI/CD流程当中去以完成自动化的CI/CD流水线处理。
实际上DevOps你可以理解为一种新型的业务研发流程也可以理解为一种新的技术思维它摒弃了传统的开发、测试和运维严格区分的观念把三者的角色融为一体让服务的开发者负责从开发、测试到发布的整个生命周期真正的承担起服务负责人的角色。更广义的DevOps除了包括CI/CD流程的自动化处理还包括智能监控决策、在线自动扩缩容等甚至还引入了人工智能技术走向另外一个新方向AIOps关于自动扩容的内容咱们下期再聊。
## 思考题
在DevOps的持续集成阶段有两个测试一个是单元测试一个集成测试你觉得它们的作用有何区别是不是做好了集成测试就不需要单元测试了
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@@ -0,0 +1,91 @@
<audio id="audio" title="30 | 如何做好微服务容量规划?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a8/32/a8f551c631cb89eb179e8cd5ba901f32.mp3"></audio>
专栏上一期我给你讲解了单体应用拆分为微服务后带来的开发、测试和运维复杂度的提升可以通过DevOps实现CI/CD流程的自动化来解决。除此之外单体应用拆分为微服务还带来另外一个问题也就是拆分出来后的多个**微服务容量如何规划**的问题。在单体应用时,只需要针对这个单体应用的访问量和实际接口性能来决定要不要给单体应用扩容,而拆分为众多的微服务之后,需要考虑每个服务的容量规划,它的复杂度主要来自下面几个方面。
<li>
服务数量众多纯靠人肉运维难以管理比如微博Feed业务仅仅RPC服务就有将近40个。
</li>
<li>
服务的接口表现差异巨大有的接口属于访问量比较大但接口响应时间比较短的轻接口有的接口属于访问量比较小但接口响应时间比较长的重接口。比如微博Feed业务中计数接口的平均耗时只有23ms而微博Feed业务中Feed接口的平均耗时要超过200ms。
</li>
<li>
服务部署的集群规模大小不同需要扩容的机器数量差异很大。比如微博的AB测试服务集群只有大约20台机器扩容只需要几台机器就满足了而Feed服务则有上千台机器往往扩容需要上百台机器。
</li>
<li>
服务之间还存在依赖关系在服务扩容的时候还需要考虑依赖服务的容量是否足够。比如微博Feed业务扩容还依赖用户关系服务和Card服务扩容时还需要考虑依赖的用户关系服务和Card服务容量是否有问题。
</li>
由此可见单体应用拆分为微服务后微服务的容量规划难度一下子增加了很多再靠传统的人肉运维模式显然难以为继。延续上期DevOps的话题其实微服务的容量规划也是广义DevOps要解决的问题之一那么DevOps是如何解决的呢答案就是容量规划系统下面我就来聊聊容量规划系统该如何实现。
容量规划系统的作用是**根据各个微服务部署集群的最大容量和线上实际运行的负荷,来决定各个微服务是否需要弹性扩缩容,以及需要扩缩容多少台机器**。
可见,容量规划系统实施的关键在于两点:一是如何评估集群的最大容量和线上实际运行的负荷,也就是**如何做好容量评估**;二是如何确定弹性扩缩容的时机以及机器数,也就是如**何做好调度决策**。下面我们分别来看这两个关键点,逐个击破。
## 容量评估
一般集群的容量评估都是通过线上实际压测来确定的,那么该如何进行线上压测呢?都需要注意哪些关键点呢?
1.选择合适的压测指标
一般在选取压测指标时主要有两类一类是系统类指标比如机器的CPU使用率、内存占用量、磁盘I/O使用率以及网卡带宽等一类是服务类指标比如接口响应的平均耗时、P999耗时、错误率。但这些指标在实际压测时都会存在一些问题。系统类指标比如CPU使用率并不能直接反映出服务压测时的健康状况有时候CPU使用率不高的时候接口耗时也可能有问题而有时候CPU使用率较高时接口耗时表现依然很正常。而服务类的指标比如接口响应的平均耗时也不能精确的反映服务的实际健康状态一个最典型的场景就是在压测时已经出现一定比例的慢请求而在平均耗时上并不能看出有多大变化这时候实际服务已经处于不健康的状态了应该停止压测了。
根据我的经验在压测时除了观察以上这些指标以外还可以观察接口的慢速比也就是接口响应时间高于某个阈值的比例。比如微博在进行Feed接口压测时选择的压测指标就是Feed接口响应时间大于1s的比例压测的终止条件是Feed接口响应时间大于1s的比例超过1%。我的主要考虑是当99%以上接口请求都在1s以内返回时反馈到用户的直接使用感受是不容易感知到Feed刷新慢反之则不然。对于大部分在线服务来说接口慢速比不超过1%都是服务质量保证的底线了,因此可以作为一个通用的压测指标。
2.压测获取单机的最大容量
集群的最大容量就是单机的最大容量 × 集群内的机器数量,所以要获得集群的最大容量,就必须获得单机的最大容量。通常有两种方式来获取单机的最大容量,一种是单机压测,一种是集群压测。
<li>
单机压测一般有两种方式一种是通过日志回放等手段模拟线上流量来对单机进行压测一种是通过TCP-Copy的方式把线上机器的流量拷贝过来对单机进行压测。
</li>
<li>
集群压测是对整个集群进行压测,以获取单机的最大容量。一般做法是通过不断把线上集群的节点摘除,以减少机器数的方式,来增加线上节点单机的流量,从而达到压测的目的。
</li>
从我的经验来看,采用集群压测的方式要更合理一些,因为它是完全使用线上真实流量进行压测,获取的单机最大容量数值更精确。如果采用单机压测,通常为了避免产生“脏数据”,往往需要去掉一些上行的修改请求,所以不能完全模拟线上真实情况。不过使用集群压测的方式也有一个缺点,就是压测的时候会对线上用户的实际请求产生影响,如果压测出问题了,会直接影响线上服务,所以一般会选择在业务低峰期进行压测,最大限度减少对线上服务造成的影响。还有一点是,通常会在工作日进行压测,以便出现问题时,也能人为快速介入。
假设我们采用集群压测不断地缩减线上节点的数量并观察服务的慢速比指标当慢速比达到1%时就停止压测这个时候就可以计算单机的最大容量了一般做法是用压测停止时刻的单机平均QPS作为单机的最大容量。但是采用QPS就真的合理吗实际上并非如此这是因为QPS并不能准确衡量单机的消耗就像下面这两张图所展示的左图的请求响应时间主要集中在100ms以下没有超过500ms的而右图的请求响应时间主要集中在50ms以上没有低于10ms的。这两种请求分布对单机消耗差异很大显然右边要对单机的消耗更大一些。在单机QPS都是100的情况下左边的单机还能继续加大QPS而右边的单机已经出现超过500ms以上的慢请求了。
<img src="https://static001.geekbang.org/resource/image/c9/1c/c9a544b649bfba7d4d760147cb9a301c.png" alt="">
所以一个更合理的计算单机容量的方式是采用区间加权来计算也就是把请求按照响应时间分成多个区间每个区间分别赋予不同的权重响应时间越长权重越高比如010ms区间的权重是11050ms区间的权重是250100ms区间的权重是4100200ms区间的权重是8200500ms区间的权重是16500ms以上的权重是32那么上面两张图所描述的情况的单机容量分别是8×1+50×2+30×4+10×8+2×16=340和2×2+10×4+50×8+20×16+8×32=1020。因此单机的最大容量也就是压测停止时刻采用区间加权方式计算得出。
3.实时获取集群的运行负荷
通过压测能够获取到单机的最大容量,再乘以集群内的机器数量就是集群的最大容量了,下一步获取集群实际运行的负荷,就可以判断集群是否需要扩容了。跟刚才计算单机容量的方式类似,集群的运行负荷也需要通过采用区间加权的方式来计算,但是因为集群的规模可能很大,超过上千台机器,显然通过计算每台单机运行的负荷再加在一起的方式效率不高。我在线上实际使用的方法是统计每台单机在不同耗时区间内的请求数,推送到集中处理的地方进行聚合,将同一个集群内的单机位于不同耗时区间内的请求进行汇总,就得到整个集群的请求在不同耗时区间内的分布了,再利用区间加权的方式就可以计算整个集群的运行负荷。
## 调度决策
在容量评估阶段,你可以获取集群的最大容量和集群的实际运行负荷,有了这两个数据后该如何做调度策略呢?我在实际线上业务使用的是**水位线**来进行调度决策。就像水库的水位线一样,要实时观测水库的蓄水量,如果因为长时间降水导致水库蓄水量超过警戒水位线就需要开闸泄洪;如果长时间干旱降水量太少,就需要关闸蓄水,以保持水库中的蓄水量始终在一个合理的水位线上。这样的话,任意时刻的水位线就是集群的最大容量除以集群的实际运行负荷,可以实时监控集群的水位线。
<img src="https://static001.geekbang.org/resource/image/f5/4e/f51ace4e71f40600920331d1a7d7fe4e.png" alt="">
在调度决策时候,就可以根据水位线来做决定。你可以看到下面图中划分了两条线,一条是安全线,一条是致命线。当集群的水位线位于致命线以下时,就需要立即扩容,在扩容一定数量的机器后,水位线回到安全线以上并保持一段时间后,就可以进行缩容了。
<img src="https://static001.geekbang.org/resource/image/ff/7d/ff14c602b1d2a5df8949c620f01aee7d.png" alt="">
那具体在执行扩缩容时,机器数量该如何决定呢?
1.扩容
在决定扩多少机器时一般有两种方式一种是按数量一种是按比例。因为不同的集群内机器数量差别可能很大所以一般采取按比例的方式举个例子比如每一次扩容都增加30%的机器数量,再看扩容后的水位线是否处于致命线以上了。
2.缩容
在扩容完成后集群的水位线保持在安全线以上一段时间后就需要缩容以节省机器成本。可以根据实际业务特点来决定多久后可以缩容比如微博的业务一般突发流量维持在1个小时以内因此集群的水位线在安全线以上超过1个小时之后就可以缩容。而在缩容时也不是一次把所有扩容的机器都缩掉而是采用逐步缩容的方式每隔5分钟判断一次集群的水位线是否还在致命线以上然后按照10%、30%、50%、100%的比例进行缩容,这样可以避免缩容太快导致集群水位线又降到致命线以下又得再扩容机器。
在实际根据水位线决定是否扩缩容时还需要防止网络抖动等原因造成的水位线瞬间抖动这个时候集群的运行负荷会突然变大导致水位线异常此时如果加以处理的话就会触发扩容而实际上并不需要扩容。为了防止瞬间抖动可以每分钟采集一次系统的水位线一共采集5个点只有5个点里有3个点满足扩容条件才真正触发扩容。
## 总结
今天我从两个方面具体给你讲解了微服务如何做好容量规划的问题,即做好容量评估和调度决策。容量评估方面,首先要通过压测获取集群的最大容量,并实时采集服务调用的数据以获取集群的实时运行负荷,这样就可以获取集群的实时水位线。而调度决策方面,主要是通过水位线与致命线和安全线对比来决定什么时候该扩缩容。而扩缩容的数量也是有讲究的,扩容的机器数一般按照集群机器数量的比例来,而缩容一般采取逐步缩容的方式以免缩容太快导致反复扩容。
在单体应用拆分为多个微服务后,如果不做好容量规划是很危险的事情,尤其是在微服务的调用量出现突发峰值流量时,再靠人为判断决策扩缩容往往为时已晚。根据我在微博的实践,也证明了通过容量规划实现微服务的自动扩缩容才是解决这个问题的最佳途径。
## 思考题
在计算集群的水位线时,经常会遇到集群内有些单机问题导致整个集群的实时运行负荷偏大,对此你有什么解决方案吗?
欢迎你在留言区写下你的思考,与我一起讨论。

View File

@@ -0,0 +1,103 @@
<audio id="audio" title="31 | 微服务多机房部署实践" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/44/f6/44bd579263aa61f5d455cda45dd297f6.mp3"></audio>
专栏前面我在讲服务治理时提到过,为了实现高可用性,微服务一般要部署在多个机房,保证有一个机房因为各种不可抗力因素导致不可用时,可以把流量切换到其他可用机房来避免故障。但是,是不是只要部署到多个机房就万事大吉了呢?你有没有想过这几个问题呢?
<li>
一切正常时用户请求该访问哪个机房?
</li>
<li>
多个机房之间的数据如何同步?
</li>
<li>
多个机房之间的数据如何确保持一致性?
</li>
你看多机房部署并非看似那么轻松,里面还有不少门道。接下来,我就以微博业务实践为例,跟你聊聊微服务实际进行多机房部署时是如何解决这些关键问题的。
## 多机房负载均衡
当服务部署在多个机房时最简单的就是遵循用户就近访问的原则比如北方用户访问联通机房南方用户访问电信机房。微博的服务也是同时部署在联通和电信机房你可以看下面这张图访问时根据用户访问的IP通过DNS解析到不同的机房如果是北方用户就访问联通机房南方用户就访问电信机房。并且为了实现负载均衡还会在每个机房分别部署四层负载均衡器VIP以及七层负载均衡器Nginx。比如来自北方用户的请求通过DNS解析到联通机房下任意一个VIP然后通过VIP把请求转发给联通机房下任意一个NginxNginx再把请求转发给联通机房下任意一个Tomcat容器通过这种方式来实现各个机房内高并发访问下的负载均衡。
<img src="https://static001.geekbang.org/resource/image/75/5d/75c4ef7b74fa445515c9150a0c353d5d.png" alt="">
当然这是最理想的情况,在实际部署时经常会遇到下面的情况:
<li>
某个机房的流量比较大,但是该机房的服务器规模有限并不足以支撑线上流量。
</li>
<li>
某个机房服务有问题,需要切一部分流量到另外一个机房。
</li>
因此在实际部署时,有时候并不能完全遵循就近访问的原则,而是要**根据需要调配流量,达到各个机房流量均衡的目的**。在实践中可以通过两种方法来切换流量一种是在DNS解析时把一部分北方用户的请求解析到电信机房的VIP或者把一部分南方用户的请求解析到联通机房的VIP另一种是在Nginx转发请求时把一部分电信机房的Tomcat容器配置到联通机房的Nginx的upstream里或者把一部分联通机房的Tomcat容器配置到电信机房的Nginx的upstream里。这两种方法的示意你可以看下面这张图。
<img src="https://static001.geekbang.org/resource/image/21/e4/218a70f6d54e030978542c823de4bbe4.png" alt="">
## 多机房数据同步
想要实现服务部署到多机房供用户访问是有前提的这个前提是每个机房的数据都是一样的用户访问哪个机房都可以获取到一样的数据这就要求多个机房之间的数据必须保持同步。对于微博这种高并发访问的服务来说数据通常都会有两层存储即缓存层和数据库层就像下图所展示的。缓存层是为了存储用户经常访问的数据尤其是在高并发访问下可以用缓存cache住绝大多数用户请求减少对数据库层的压力这是因为数据库层要直接访问磁盘相比缓存层直接访问内存来说响应要慢得多。
<img src="https://static001.geekbang.org/resource/image/fe/79/fe9d8cb2f9f8cf6842a7d6070b4ce179.png" alt="">
如此一来,要保证多个机房的数据一致,不仅要保证数据库层的数据一致,还需要保证缓存层的数据一致,应该如何实现呢?
1.主从机房架构
主从机房数据同步方案如下图所示。主从机房架构是以一个机房为主机房所有的写请求都只发给主机房的处理机由主机房的处理机来更新本机房的缓存和数据库其他机房的缓存也通过主机房的处理机来更新而数据库则通过MySQL的binlog同步机制的方式实现数据同步。
<img src="https://static001.geekbang.org/resource/image/e3/d0/e3e9c22939637a21f04b4187876bf2d0.png" alt="">
上面这种架构把所有的写请求都发给主机房由主机房来负责写所有机房的缓存和本机房的数据库而其他机房的数据库则通过MySQL的binlog同步机制实现数据同步。显然这样做有一个很大的风险那就是如果主机房出现问题就没法更新缓存和数据库了所以就有了第二种方案。
2.独立机房架构
这种架构的数据同步方案如下图所示联通和电信机房都有写请求并通过一个叫WMB的消息同步组件把各自机房的写请求同步一份给对方机房这样的话相当于每个机房都有全量的写请求。每个机房的处理机接收到写请求后更新各自机房的缓存只有一个机房会更新数据库其他机房的数据库通过MySQL的binlog同步机制实现数据同步。
<img src="https://static001.geekbang.org/resource/image/4d/33/4d9406553f68198f99b47d291c62a633.png" alt="">
独立机房架构相比于主从机房架构的优势在于任意一个机房出现问题都不影响别的机房的数据更新因为每个机房的写消息都是全量的所以每个机房可以更新自己的缓存并从数据库主库同步数据。其实独立机房架构的关键点在于WMB消息同步组件它可以把各个机房之间的写请求进行同步。下面我就详细讲讲WMB消息同步组建是如何实现的。
WMB消息同步组件的功能就是把一个机房的写请求发给另外一个机房它的实现原理可以用下面这张图来描述分为两个部分
<li>
reship负责把本机房的写请求分发一份给别的机房。
</li>
<li>
collector负责从别的机房读取写请求然后再把请求转发给本机房的处理机。
</li>
<img src="https://static001.geekbang.org/resource/image/e9/11/e9147143867af6c73ff72d68e284c211.png" alt="">
那么该如何实现WMB的消息同步功能呢根据我的实践经验主要有两种方案一种是通过MCQ消息队列一种是通过RPC调用。
- MCQ消息队列实现
下面这张图是采用MCQ消息队列的实现方案从图中你可以看到联通机房的写请求写入到联通机房的MCQ里然后联通机房的reship就从联通机房的MCQ里读取再写入到电信机房的MCQ里电信机房的collector就可以从电信机房的MCQ里读取到写请求再写入到电信机房的另外一个MCQ里电信机房的队列机就会从这个MCQ里读取写请求然后更新缓存。可见采用这种方案的一个缺点是流程比较长需要多次与MCQ消息队列打交道当有大量写请求到来时不仅要扩容reship和collector确保有足够的处理能力还需要扩容MCQ消息队列以确保能够承受大量读取和写入一种更加简单的方案是采用RPC调用来实现。
<img src="https://static001.geekbang.org/resource/image/26/40/26dac20f6b3000e54b0f0e50525ac440.png" alt="">
- RPC调用实现
下面这张图是采用RPC调用的实现方案从图中你可以看到联通机房的写请求会调用联通机房的reship RPC然后联通机房的reship RPC就会调用电信机房的collector RPC这样电信机房的collector RPC就会调用电信机房的处理机RPC从而实现把联通机房的写请求同步给电信机房的处理机进行处理。
<img src="https://static001.geekbang.org/resource/image/40/12/40491bab3074044c0e6683486ef3b012.png" alt="">
## 多机房数据一致性
解决了多机房数据同步的问题之后,还要确保同步后的数据是一致的,因为在同步过程中,会因为各种原因导致各机房之间的数据不一致,这就需要有机制能确保数据的一致性。而且考虑到不同业务的特征对数据一致性的要求也不相同,类似金融类的业务要求多机房之间的数据必须是强一致的,也就是一个机房的数据必须时刻同另外一个机房的数据完全一致;而社交媒体类的业务则要求没那么高,只需要能达到最终一致即可。微博的服务主要是通过**消息对账机制**来保证最终一致性,下面我们来看下如何通过消息对账机制来保证最终一致性。
你可以先看下面这张图系统会给每一次写请求生成一个全局唯一的requestId联通机房的写请求一方面会调用联通机房的处理机RPC来修改缓存和数据库另一方面还会调用联通机房的reship RPCreship RPC再调用电信机房的collector RPC来同步写请求电信机房的collector RPC最后会调用电信机房的处理RPC来更新缓存。在这整个过程的每一个环节requestId始终保持向下传递无论是处理成功或者失败都记录一条包含requestId和机房标记的处理日志并写到Elasticsearch集群上去。然后通过一个定时线程每隔1分钟去扫描Elasticsearch集群上的日志找出包含同一个requestId的不同机房的处理日志然后验证是否在各个机房请求都处理成功了如果有的机房某一阶段处理失败则可以根据日志信息重试该阶段直到成功从而保证数据的最终一致性。
<img src="https://static001.geekbang.org/resource/image/aa/a3/aa111c36681abc3eb9beb46182488da3.png" alt="">
## 总结
今天我给你讲解了微服务多机房部署时要面临的三个问题,一是多机房访问时如何保证负载均衡,二是多机房之间的数据如何保证同步,三是多机房之间的数据如何保证一致性,并给出了微博在多机房部署微服务时所采取的解决方案,对于大部分中小业务团队应该都有借鉴意义。可以说多机房部署是非常有必要的,尤其是对可用性要求很高的业务来说,通过多机房部署能够实现异地多活,尤其可以避免因为施工把光缆挖断导致整个服务不可用的情况发生,也是业务上云实现混合云部署的前提。下一期我再来聊聊微服务混合云部署的实践,你可以对多机房部署的重要性有更深的认识。
## 思考题
在讲解多机房数据同步实践的时候我提到了微博采用了WMB消息同步组件的方案除了这种方案你是否有了解过其他多机房数据同步的方案它们是如何实现的
欢迎你在留言区写下自己的思考,与我一起讨论。

View File

@@ -0,0 +1,107 @@
<audio id="audio" title="32 | 微服务混合云部署实践" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/39/df/39145bfde5e0107254f9a482626038df.mp3"></audio>
专栏上一期我给你讲解了微服务多机房部署的实践以及需要解决的三个问题。大多数业务在发展到一定阶段要么出于高可用性的需要把业务部署在多个机房以防止单个机房故障导致整个服务不可用要么出于访问量大的需要把业务流量分散到多个机房以减少单个机房的流量压力。此时服务一般是部署在企业内部机房的机器上并利用私有云技术把内部机房的机器管理起来。然而有些业务经常还有弹性需求比如微博就经常因为热点事件带来突发的峰值流量需要扩容成倍的机器但内部机房的资源有限并且从成本因素考虑也不会预留太多机器这个时候就会自然想到公有云。类似AWS和阿里云这样的公有云厂商一般都会采购上万台机器专门对外售卖这样公有云的用户就不用预留这么多的机器了可以按需弹性使用节省机器成本。
我们今天要聊的混合云部署,就是既在企业内部的私有云部署服务,又使用企业外部公有云部署服务的模式。和多机房一样,混合云部署同样要考虑几个关键的问题。
<li>
跨云服务如何实现负载均衡?
</li>
<li>
跨云服务如何实现数据同步?
</li>
<li>
跨云服务如何实现容器运维?
</li>
下面我就结合微博的微服务混合云部署实践,帮你找到解决上面问题的答案。
## 跨云服务的负载均衡
上一期我们聊了多机房的负载均衡它主要考虑用户的就近访问把用户的请求分别路由到不同的机房。同样的道理当服务上云后还需要考虑把一定比例的用户请求路由到云上部署的服务就像下图那样微博的服务不仅在私有云的两个机房永丰和土城有部署在阿里云上也部署了服务。为了做到负载均衡把用户的访问按照DNS解析到不同的机房私有云机房部署了VIP和Nginx分别用作四层和七层的负载均衡阿里云机房部署了SLB和Nginx分别用作四层和七层的负载均衡。
<img src="https://static001.geekbang.org/resource/image/a6/0a/a6112452dbd6f5afa7824f1ee2870d0a.png" alt="">
## 跨云服务的数据同步
复习一下上一期我给你讲解的多机房之间的数据同步。为了做到高可用性一般采用独立机房的部署架构每个机房的写请求都通过WMB同步给别的机房以保证任意一个机房都有全量的写请求从而使得任意一个机房的处理机都会全量更新数据。那么当服务跨云部署后该如何实现数据同步呢根据我的经验在公有云部署服务和内部私有云部署服务还是有一些不同的主要体现在下面两个方面。
1.私有云与公有云之间的网络隔离
一般来讲出于安全的需要企业内部机房同公有云机房之间的网络是隔离的为了实现互通需要架设专门的VPN网络或者专线就像下图描述的微博在内部私有云和阿里云之间搭建了两条跨云专线分别打通了内部的联通、电信机房与阿里云的联通、电信可用区这样的话不仅实现了私有云和公有云之间的网络互动双专线也保证了高可用性即使一条专线断了也可以通过另外一条专线实现数据同步。不过这样做需要保证专线的冗余度充足任何一根专线的带宽能够承担所有跨云的流量否则就很危险了因为一旦一根专线断了所有流量都通过另外一根专线的话就会把专线打满出现网络延迟影响服务。
<img src="https://static001.geekbang.org/resource/image/7a/30/7a397e8880c97b2d2db12a9479ba6330.png" alt="">
2.数据库能否上云
数据库能否上云的关键取决于数据的隐私性。一般而言,企业都会考虑数据库里的数据放在公有云上是否安全,因为企业内部私有云部署的数据库与外网隔离,再加上有种种防护措施,一般情况下不会出现数据库数据外泄情况。而公有云厂商普遍采用了虚拟化技术,不同公司的虚拟机有可能部署在同一台物理机上,所以能否实现有效的数据隔离非常关键,尤其对于企业的核心业务数据,往往会出于安全隐私的考虑,并不敢直接放到云上部署。考虑到这一点,微博的服务在阿里云部署时,并没有部署数据库,只部署了缓存,当缓存穿透时需要访问内网数据库,你可以参考下面这张图。
<img src="https://static001.geekbang.org/resource/image/3a/e3/3abf7ad443b574928e7fd0ac3e72eee3.png" alt="">
综合上面两点考虑微博在做跨云数据同步的时候把内部的永丰机房和土城机房的写消息通过WMB同步给阿里云机房的WMB阿里云机房的WMB把写消息转发给阿里云机房的处理机处理进而更新阿里云机房的缓存整个流程可见下图。其中阿里云机房主要用于承担下行的读请求部署的缓存也不是跟内网机房完全一致而是只部署了最核心的服务所依赖的缓存这样可以将大部分阿里云机房的请求都在内部消化减少到内网数据库的穿透从而节省跨云专线的带宽使用。
<img src="https://static001.geekbang.org/resource/image/f6/09/f62a16e9384579110f558df14f815509.png" alt="">
## 跨云服务的容器运维
前面我讲过,微服务容器化后,便具备了可移植性,不仅可以在内部私有云上部署,还可以在外部公有云上部署,这就要求有一套统一的容器运维平台不仅能对接内部私有云的基础设施,也能对接外部的公有云,这部分内容你可以在[第28期](http://time.geekbang.org/column/article/42604)容器运维平台DCP中找到。服务实现了混合云部署后DCP在实施跨云的容器运维时又多了哪些关键点呢
1.跨云的主机管理
跨云主机管理的关键点在于如何对内部私有云的机器和公有云的ECS进行管理在DCP里是按照“主机-服务池-集群”的模式进行管理的,这三个概念的含义分别是:
<li>
主机某一台具体的服务器可能是私有云内创建的虚拟机也有可能是公有云创建的ECS。
</li>
<li>
服务池:针对具体某个服务而言,由这个服务部署的主机组成,可能包含私有云的主机,也可能包含公有云的主机,规模可能是几台也可能是上百台。
</li>
<li>
集群针对具体某个业务线而言可能包含多个服务池比如微博的内容业务线包含了Feed服务池也包含了评论服务池等。
</li>
在实际扩容时,如下图所示,可能有三种情况。
<li>
私有云内弹性扩容:当某个服务池的容量不足需要进行扩容时,如果该服务池所在的集群内的主机数量充足,则只需要在私有云内弹性扩容加入服务池即可。
</li>
<li>
公有云弹性扩容:当某个服务池的容量不足需要进行扩容时,如果该服务池所在的集群内没有多余的主机可用时,就需要在公有云上弹性扩容,然后加入服务池。
</li>
<li>
私有云和公有云同时弹性扩容:当某个服务池的容量不足需要进行扩容时,如果该服务池所在的集群内的主机数量不足时,就需要在同时在私有云和公有云上进行弹性扩容,最后都加入到服务池中去。
</li>
<img src="https://static001.geekbang.org/resource/image/ef/03/ef3f58941ce5608addf24fe795dd8f03.png" alt="">
2.跨云服务发现。
在[第28期](http://time.geekbang.org/column/article/42604)我讲过DCP的服务发现主要有两种方式一种是针对HTTP服务采用的[nginx-upsync-module](https://github.com/weibocom/nginx-upsync-module)一种是针对RPC服务的Config Service。除此之外阿里云上部署的服务还可以直接使用SLB来做服务发现。比如下面这张图红包飞依赖了用户关系服务当阿里云上用户关系服务扩容后可以直接添加容器的IP到SLB下这样红包飞服务访问SLB就可以获得最新的用户关系服务的节点列表。
<img src="https://static001.geekbang.org/resource/image/40/9d/401ca348976668385d67644b9813bc9d.png" alt="">
3.跨云弹性扩容。
当有流量上涨超出了内部私有云机房部署所能承受的范围时可以扩容阿里云机房的机器然后把流量切换到阿里云机房这个过程请看下面这张图。切流量也有两种方案一是在DNS层切换把原先解析到私有云机房VIP的流量解析到阿里云机房的SLB这时候阿里云机房部署的SLB、Nginx和Java Web都需要扩容一种是在Nginx层切换把原先转发到私有云机房Nginx的流量转发到阿里云机房的Java Web这个时候只需要扩容阿里云的Java Web。
**这两种方案应对的业务场景不同**DNS层的切换主要是针对大规模流量增长的情况这个时候一般四层VIP、七层Nginx和Java Web的容量都不足以应对就需要在DNS层就把流量切到阿里云机房在阿里云扩容SLB、Nginx和Java Web而Nginx层的切换主要是针对私有云内某个机房的Java Web容量不足或者服务有问题的时候需要把这个机房的一部分流量切换到其他机房这个时候就可以只扩容阿里云机房的Java Web然后从Nginx层把流量切换到阿里云机房。
<img src="https://static001.geekbang.org/resource/image/33/9c/33de6e4eee9b98cf980956f4522d279c.png" alt="">
4.跨云服务编排。
在进行服务编排时如果服务跨云部署就要考虑跨机房访问的问题了。就像下图所描述的那样微博的Feed服务不仅依赖User RPC还依赖Card RPC这样的话如果Feed服务需要扩容的话就需要先扩容User RPC和Card RPC。由于Feed服务在永丰、土城、阿里云三个机房内都有部署任意一个机房内部署的Feed服务需要扩容时就需要扩容同一个机房内的User RPC和Card RPC。
<img src="https://static001.geekbang.org/resource/image/8c/c9/8c5fe22862244b239a567ded5ac3f3c9.png" alt="">
## 总结
今天我给你讲解了微服务混合云部署必须解决的三个问题:跨云服务的负载均衡、跨云服务的数据同步、跨云服务的容器运维,以及微博在微服务混合云部署时的实践方案,可以说正是由于采用了混合云部署,才解决了微博在面对频繁爆发的热点事件带来突发流量时,内部资源冗余度不足的问题。虽然云原生应用现在越来越流行,但对于大部分企业来说,完全脱离内部私有云并不现实,因为云也不是完全可靠的,一旦云厂商出现问题,如果没有内部私有云部署的话,那么服务将完全不可用。如果你的服务对高可用性要求很高,那么混合云的方案更加适合你。
## 思考题
微服务采用混合云部署的时候,如果公有云和私有云都都部署数据库的话,数据该如何保持同步?
欢迎你在留言区写下自己的思考,与我一起讨论。