单体应用拆分成多个微服务后,确实能够实现快速开发迭代。然而,随之而来的问题是测试和运维部署的成本会有所提升。相信你对拆分微服务的利弊早已熟知,下面我来讲一个具体的例子。

早期,微博业务是一个大的单体 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 和 Cgroups 这两种机制,可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至是自己的用户 ID 空间。这样的话,容器里的进程就像是运行在宿主机上的另外一个单独的操作系统内,从而实现与宿主机操作系统里运行的其他进程隔离。
Docker 也是基于 Linux 内核的 Cgroups、Namespace 机制来实现进程的封装和隔离的。那么,Docker 为何能把容器技术推向一个新的高度呢?这就要从 Docker 在容器技术上的一项创新 ——Docker 镜像说起。
虽然容器解决了应用程序运行时隔离的问题,但是要想实现应用能够从一台机器迁移到另外一台机器上还能正常运行,就必须保证另外一台机器上的操作系统是一致的,而且应用程序依赖的各种环境也必须是一致的。Docker 镜像恰恰就解决了这个痛点。具体来讲,就是 Docker 镜像不光可以打包应用程序本身,而且还可以打包应用程序的所有依赖,甚至可以包含整个操作系统。这样的话,你在自己本机上运行通过的应用程序,就可以使用 Docker 镜像把应用程序文件、所有依赖的软件以及操作系统本身都打包成一个镜像,可以在任何一个安装了 Docker 软件的地方运行。
Docker 镜像成功解决了 DevOps 中微服务运行环境在本地环境、测试环境以及线上环境难以保持一致的难题。这样一来,开发人员可以将在本地环境中运行测试通过的代码,以及其依赖的软件和操作系统本身打包成一个镜像,接着自动部署在测试环境中进行测试,在测试通过后再自动发布到线上环境。如此,整个开发、测试和发布的流程便得以打通。同时,无论是在内部物理机还是公有云的机器上部署服务,都能够利用 Docker 镜像将微服务运行环境封装起来,进而屏蔽内部物理机和公有云机器运行环境的差异,实现同等对待,降低了运维的复杂度。
微服务容器化实践

Docker 能够助力解决服务运行环境可迁移问题的关键在于对 Docker 镜像的使用。实际上,在使用 Docker 镜像时,通常并非直接将业务代码、依赖的软件环境以及操作系统本身全部打包成一个镜像,而是利用 Docker 镜像的分层机制,在每一层通过编写 Dockerfile 文件来逐层打包镜像。这是因为尽管不同的微服务依赖的软件环境有所不同,但仍存在大大小小的相同之处。所以,在打包 Docker 镜像时,可以进行分层设计并逐层复用,这样能够减少每一层镜像文件的大小。

