第10课:架构设计理念

第10课:架构设计理念

前言

前面几课的内容从分布式架构的一些常见场景出发,介绍了分布式的各种问题和解决方案以及目前一些比较成熟的技术实现。在实施微服务架构时,我们更多的是使用这些成熟的技术。作为一个架构师或者想在技术架构领域深耕的开发人员,对于分布式系统相关的技术不仅仅要会用。在做技术选型及方案时,更需要对这些技术的实现思路、算法以及优点和限制进行了解。本节课内容作为整个课程系列的收尾,会把微服务架构结合 Docker 的一些技术选型进行介绍,包括服务的持续集成、容器编排以及日志采集和安全技术做个简单的引申,希望能给各位读者一些参考。微服务架构的概念本身不是什么新颖或万能的技术,还是要在实践中不断深入了解和摸索尝试。

如果读者们读到这一篇,应该还记得本系列课程还有示例的代码工程 microservicecoffeeshop,以及 gatewaycoffeeshop。示例工程代码将会在 8.10 日更新完毕。

微服务架构的技术选型

第05课:服务发现和服务通信中介绍了基于 Docker 技术的一些服务发现方案。示例的工程是基于 Eureka 来做服务发现的,本课内容主要围绕使用 Docker 容器实现微服务实例的构建、部署的相关技术选型介绍。

服务的持续集成部署

微服务本身和敏捷开发流程可以很好地融合。目前也有很多技术方案可以做微服务架构的 CI/CD(Continuous Integration/Continuous Delivery)。一个灵活高效的 CI/CD 也是符合 DevOps 理念的。下面先简单介绍一些基础的技术设施,为需要搭建整个架构体系的 DevOps 提供一些方案和思路。

Docker 镜像仓库

用 Docker 部署微服务,需要将运行着微服务实例的应用代码打包成 Docker 镜像,就如同部署在 Web server 的代码打包 war 文件一样。只不过 Docker 镜像运行在 Docker 容器中,并且 Docker 容器是相对隔离和独立的环境。对于企业服务来说,为了能统一管理打包以及分发(pull/push)镜像,一般需要搭建自己的镜像私库,或者使用一些云服务平台提供的 CaaS(Containers as a Service)。

自己搭建私库只需依赖基础服务器,或者使用 IaaS 平台。一般负责镜像托管的一组服务器,需要先分别安装 Docker 环境,然后直接部署 Dockerhub 的镜像仓库 Registry2,目前最新的版本是 V2。Dockerhub 的镜像资源,一般 pull 比较慢(服务分布在国外),在国内可以使用一些平台提供的镜像加速器。

代码仓库

代码托管也是项目持续集成的基础一环,企业的代码仓库的私库建立可以使用 SVN、Gitlab 等代码版本管理工具。

Gitlab 除了可以代码托管,还有轻量级的 CI/CD 工具。通过 [Git 的 Docker 镜像](docker-hub gitlab)安装、部署操作也很便捷。具体安装步骤可以参考 Docker Gitlab install。为了能快速构建、打包,也可将同一微服务的 Git 和 Registry 部署在同一台服务器,作为基础的 SCM 机器组。

如果代码托管基于 Gitlab,那么 Gitlab 的 CI/CD 工具可以有效提升敏捷开发效率。Gitlab CI 工具的核心组件 Pipelines 包含了编译、测试、部署等步骤,只要在创建好 gitlab-ci.yml 文件,配置好 Runner 即可,并且 Gitlab 本身也有可视化的控制台可以管理集成的过程。除了 Gitlab CI,Jenkins 也是一个被广泛使用的持续集成方案,Jenkins 也有针对 Docker 的持续集成插件,但是还存在一些不完善。

项目构建

在 Springboot 项目中,构建工具可以用 Maven 或者 Gradle。Gradle 相比 Maven 更加灵活。Springboot 工程的整体风格也是去配置化的,用基于 Groovy 的 Gradle 会更加适合,DSL 本身也比 XML 更加简洁高效。

因为 Gradle 支持自定义 task,所以微服务的 Dockerfile 写好之后,就可以用 Gradle 的 task 脚本来进行构建、打包成 Docker Image。本课程的示例工程代码是基于 Sprigboot 的,在 checkout 下来代码之后,可以通过以下一行简单的代码就可以打包好一个 docker 镜像,并 push 到指定的 Registry 镜像仓库。示例是上传到本地仓库,在企业应用中,一般需要 Tag、Push 到企业的私有仓库域名下。

$./gradlew buildDocker  

...
BUILD SUCCESSFUL

Total time: 2 mins 4.374 secs
...

$ docker images
REPOSITORY                         TAG                 IMAGE ID            CREATED             SIZE
localhost:5000/user                1.0.0               42c4cb474806        1 second ago        223MB
localhost:5000/trade               1.0.0               234c73515f87        17 seconds ago      223MB
localhost:5000/item                1.0.0               977c63029e2e        41 seconds ago      223MB

而 Gradle 的 task 代码也很简单:

task buildDocker(type: Docker, dependsOn: build) {

    tagVersion = "1.0.0"
    applicationName = "item"
    tag = "localhost:5000/${applicationName}"
    push = true
    dockerfile = ('../docker/Dockerfile')
    doFirst {
        copy {
            from jar
            rename { 'app.jar' }
            into stageDir
        }
    }
}

在创建工程时,需要引入 classpath('se.transmode.gradle:gradle-docker:1.2') 依赖,示例工程中使用的是开源的构建工具:Transmode-Gradlew 插件,其还支持对子项目(单个微服务)进行独立构建 Docker 镜像。运行命令如下:

$  ./gradlew service-item:buildDocker 

在生产环境中,Gitlab 的机器最好和 Docker 私有仓库,以及用来二方、三方 lib 管理的 Nexus 部署在同一内网环境,这样构建、打包、上传镜像的速度会很快。Nexus 也可以用 Docker 进行部署,这里细节不再展开,目前 Nexus 的最新版本是 Nexus3

容器编排技术

代码托管、镜像私库、持续集成的问题解决之后,还要处理集群的容器管理,包括 Docker 容器的部署、升级、回滚;集群的负载均衡、扩容缩容等。

Docker 镜像构建好之后,因为每个容器运行着不同的微服务实例,容器之间也是隔离部署的。通过容器编排技术,可以使 DevOps 高效地管理容器的部署、监控等。

目前一些通用的编排工具比如 Ansible、Chef、Puppet,也可以做容器的编排,但他们都不是专门针对 Docker 容器的编排工具,所以使用时需要自己编写一些 shell 脚本,结合 Docker 命令。Ansible 针对自己研发的容器 Ansible Container 也提供了集成、部署以及容器管理方案。

围绕 Docker 容器的调度、编排,比较成熟的技术有 Google 的 Kubernetes(下文会简写 k8s)Apache Mesos + Marathon 管理 Docker 集群;Docker 1.12.0 以上的官方版本内置的 Docker Swarm (目前 Swarm 处于很尴尬的地位,之前写过一篇介绍 swarm 的博客基于 docker-swarm 搭建持续集成集群服务,感兴趣的读者可以简单看看,不是很建议在企业集群中引入 swarm),编排技术是容器技术的重点之一,选择一个适合自己团队的容器编排技术也可以使运维更高效、自动化。

Kubernetes

Kubernetes 是 Google 开源的容器集群管理系统,使用 Go 语言实现,其提供了 Docker 容器的部署、维护、 扩展等功能,目前可以在 GCE、vShpere、CoreOS、OpenShift、Azure 等平台使用 K8s。国内目前 Aliyun 也提供了基于 K8s 的服务治理平台。如果企业自己基于物理机、虚拟机搭建 Docker 集群的话,也可以直接部署、运行 K8s。在微服务的集群环境下,Kubernetes 可以很方便管理跨机器的微服务容器实例。

目前 K8s 基本是公认的最强大开源服务治理技术之一,其主要提供以下功能:

  • 自动化对基于 Docker 对服务实例进行部署和复制。
  • 以集群的方式运行,可以管理跨机器的容器以及滚动升级、存储编排。
  • 内置了基于 Docker 的服务发现和负载均衡模块。
  • K8s 提供了强大的自我修复机制,会对崩溃的容器进行替换(对用户,甚至开发团队都无感知),并可随时扩容、缩容,让容器管理更加弹性化。

简单介绍下 K8s 的几个重要组件:

  • Pod 是 Kubernetes 的最小的管理元素,一个或多个容器运行在 pod 中。pod 的生命周期很短暂,会随着调度失败,节点崩溃,或者其他资源回收时消亡。
  • Label 是 key/value 存储结构的,可以关联 pod,主要用来标记 pod,给服务分组。微服务之间通过 label 选择器(Selectors)来识别 pod。
  • Replication Controller 是 K8s Master 节点的核心组件。用来确保任何时候 Kubernetes 集群中有指定数量的 pod 副本(replicas)运行,提供了自我修复机制的功能,并且对缩容扩容、滚动升级也很有用。
  • Service 是对一组 pod 的策略的抽象,也是 K8s 管理的基本元素之一。Service 通过 Label 识别一组 pod,创建时也会创建一个本地集群的 DNS(存储 Service 对应的 pod 的服务地址)。所以客户端的请求,会先通过本地集群的 DNS,来获取一组当前可用的 pods 的 IP 地址。之后通过每个 Node 中运行的 kube-proxy 将请求转发给其中一个 pod。这层负载均衡是透明的,但是目前的 K8s 的负载均衡策略还不是很完善,默认是随机策略的路由方式。

Swarm、K8s、Mesos 各有各的特性,他们对于容器的持续部署、管理以及监控都提供了支持。Mesos 还支持数据中心的管理。Docker swarm mode 扩展了现有的 Docker API,通过 Docker Remote API 的调用和扩展,可以调度容器运行到指定的节点。Kubernetes 是目前市场规模最大的容器编排服务,目前很多大公司也都加入到了 K8s 家族,K8s 应对集群应用的扩展、维护和管理更加灵活,但是负载均衡策略比较粗糙。而 Mesos 更专注于通用调度,提供了多种调度器。对于容器编排,还是要选择最适合自己团队的,如果初期机器数量很少,集群环境不复杂,可以用 Ansible + Docker Compose,再加上 Gitlab CI 来做持续集成,节省成本、快速上线。

日志采集

基于容器技术的微服务的监控体系面临着更复杂的网络、服务环境。日志采集、监控如何能对微服务减少侵入性、对开发者更透明,也是很多微服务的 DevOps 需要不断思考和实践的。

1. 微服务日志的采集

微服务的 API 层的监控,需要从 API-Gateway 的请求开始,一直到每个微服务的调用路径的跟踪、采集以及分析。使用 Rest API 的话,为了对所有请求进行采集,可以使用 Spring Web 的 OncePerRequestFilter 对所有请求进行拦截,在采集日志的时候,也最好对请求的 RT(response time)进行记录。

服务日志除了要记录 access、request 等信息,还需要对 API 调用链路进行跟踪。如果单纯记录每个服务以及 Gateway 的日志,那么当 Gateway Log 出现异常的时候,就不知道其具体是微服务的哪个容器实例出现了问题。如果容器达到一定数量,也不可能排查所有容器以及服务实例的日志。比较简单的解决方式就是对日志信息都 append 一段含有容器信息(Container Id)的、唯一可标识的 Trace 串。

日志采集之后,还需要对其进行分析。目前比较成熟的在线实时日志分析,可以使用 E.L.K 技术体系,ELK 中的 E 是指 Elasticsearch,基于 Lucene 实现的高性能可扩展的搜索引擎,使用 Docker 也可以搭建 Elasticsearch 集群,详细的搭建步骤可以阅读下这篇文章:用 Docker 搭建 Elasticsearch 集群 。如果应用架构本身使用了 Elasticsearch 作为搜索集群,搭建 ELK 会更加容易。Logstash 的作用是进行日志收集、分析,并将数据同步到 Elasticsearch。Kibana 可以为 Elasticsearch 提供可视化平台,增强日志数据的可视化管理。

对于数据量大的日志的采集,为了提升采集性能,一般还需要在 ELK 的基础上增加一个高性能的消息队列。优化后的日志采集架构如下(Kafka 作为消息队列为例,也可以使用其他消息队列):

ELK + Kafka 日志分析采集

2. 底层服务的日志采集

除了要对 Gateway 以及微服务实例的所有 Rest API 请求的日志进行采集,也有必要对于底层依赖的存储服务、中间件(包括 Redis、MySQL、Elasticsearch、MQ 等)的调用日志进行采集和分析。

对于中间件服务的日志采集,一般需要埋点在应用代码中,更好的方式是通过动态代理实现。对于服务调用的如 cache 层、repository 层(包括搜索和 DB)的基础方法,进行拦截及回调日志记录。具体的实现方式可以采用字节码生成框架 ASM,关于方法的逻辑注入,可以参考之前写的一篇 ASM(四) 利用 Method 组件动态注入方法逻辑,如果觉得 ASM 代码不太好维护,也可以使用相对 API 友好的 Cglib,性能稍差一些但是实现更快的方式还可以用 AspectJ 框架。

安全技术

安全性是架构的基础。互联网的环境复杂,保护好服务的安全,也是对用户的基本承诺。安全技术涉及到的范围比较广,本文选几个常用方式做简单介绍。

服务实例安全

分布式集群本身就是对于服务实例安全的一种保障,一台服务器或者某一个服务实例出现问题的时候,负载均衡可以将请求转发到其他可用的服务实例。但很多企业是自建机房,而且是单机房的,这种布局其实比较危险,因为服务器的备份容灾也得不到完整的保障。对于底层依赖的存储服务,尤其是数据库,尽量保证可以有多数据中心,做好网络分区。所有的机器需要注意配置防火墙安全策略。

如果可以,尽量使用一些高可用、高可伸缩的稳定性 IaaS、PaaS 平台。

网络安全

1. 预防网络攻击

目前主要的网络攻击有一下几种:

  • SQL 注入:根据不同的持久层框架,应对策略不同。如果使用 JPA,则只要遵循 JPA 的规范,基本不用担心。
  • XSS 攻击:做好参数的转义处理和校验,具体参考 XSS 预防
  • CSRF 攻击:做好 Http 的 Header 信息的 Token、Refer 验证,具体参考 CSRF 预防
  • DDOS 攻击:大流量的 DDoS 攻击,一般是采用高防 IP,也可以接入一些云计算平台的高防 IP。

以上只是列举了几种常见的攻击,想要深入了解的可以多看看 REST 安全防范表。安全领域,一般很容易被初创企业忽视,如果没有专门的运维团队,需要开发工程师们有能力处理好 RESTful API 的安全。

2. 使用安全协议

这个不用多说,无论是对于使用 RESTful API 的微服务通信,还是使用的 CDN 服务、DNS 服务,涉及到 Http 协议的,建议都统一使用 Https。无论是什么规模的应用,都要防范流量劫持,否则将会给用户带来很不好的使用体验。

3. 鉴权

在微服务架构中,API-Gateway 作为系统的统一入口,基于微服务的所有鉴权,都可以围绕 Gateway 容器去做。在 SpringBoot 应用中,基础的授权可以使用 spring-boot-starter-security 以及 Spring Security(Spring Security 也可以集成在 Spring MVC 项目中)。

Spring Security 主要通过 AOP 对资源请求进行拦截,其内部维护了一个不同角色(Roles)的 Filter Chain。因为微服务都是通过 Gateway 请求的,所以微服务的 @Secured 可以根据 Gateway 中不同的资源的角色级别进行设置。

Spring Security 提供了基础的角色的校验接口规范,但客户端请求的 Token 信息的加密、存储以及验证,需要应用自己完成。对于 Token 加密信息的存储可以使用 Redis,这里再多提一点,为了保证一些加密信息的可变性,最好在一开始设计 Token 模块的时候就考虑到支持多个版本密钥,以防止万一内部密钥被泄露可以及时更换。至于加密算法,以及具体的实现在此就不再展开。

除了微服务本身之外,我们使用的一些如 MySQL、Redis、Elasticsearch、服务发现等基础服务,也需要设置好鉴权,并且尽量通过内网访问,不要对外暴露过多的端口。前端请求最好通过 Nginx 反向代理再请求到 API-Gateway 服务。

架构要素分析

无论我们采用微服务或者 SOA 架构,也无论使用什么服务来实现集群的管理,在分布式系统中,没有一蹴而就的完美的架构,并且每位在分布式环境从事开发的工程师们也需要了解到,时刻存在着不可靠的网络和随时可能挂掉的节点。在做技术选型或者拟定技术方案时,可以多想想架构的几个要素,在考量时也不能过于追求完美,只有适合的才是最好的。

最后,结合架构核心的五要素来列举一些搭建 Docker 微服务集群时可以使用的技术体系:

  1. 高性能
    • 尽量使用异步的方式,比如完成某业务逻辑后,上传资源或者发送短信,可以通过消息队列如 Kafka、RocketMQ、ActiveMQ 等替代同步调用。
    • 在 Gateway 中使用类似 RxJava 框架,可异步并发请求多个(数据无关联和依赖的)微服务的API。
    • 对于读高频并且实时性依赖不高的请求可以使用分布式缓存、本地缓存、Http 的 Etag 缓存。
    • 对于查询条件复杂,排序场景多的情况,可以引进分布式搜索引擎技术,如 Elasticsearch、Solr。
    • 图片、样式等静态资源可以使用 CDN 加速。
  2. 可用性
    • 使用高可用的服务发现技术,如 Consul、Eureka、Etcd。
    • 在 Gateway 层做好熔断处理,可以使用 Hytrix 熔断器或者通过 RxJava 的 onErrorReturn 自定义 ErrorHandler。
    • 适当针对特殊场景做服务降级。
    • 在 Gateway 以及微服务之间做好超时机制、重试机制、并对 POST、PUT 等提交数据请求做好幂等处理。
    • 降低对线性一致性的追求,更多使用最终一致性。
  3. 伸缩性
    • 服务器集群的伸缩,主要是基于硬件和服务器层面可以使用一些弹性云平台,方便进行水平和垂直扩展。
    • 容器层面,可以使用容器编排技术 Kubernetes。
    • 服务实例本身,针对不同微服务的特性,在打包 Docker 镜像时,对服务依赖资源做好分配(比如 JVM 层面的堆、栈空间,GC 策略等)。
    • 存储方面,可以做数据库的数据复制、数据分区(第03、04课中有介绍)。
    • 使用一些支持线性伸缩的 NoSQL 技术如 Redis、MongoDB 等。
  4. 扩展性
    • 微服务本身就是一种扩展性架构
  5. 安全性
    • 鉴权框架的引入,如 SpringSecurity、OAuth 等
    • 日志采集、监控
    • Https 协议、HTTP/2.0
    • 高防 IP、防火墙策略等
    • 使用 Nginx 做反向代理

小结

本课程系列的前面内容重点介绍了分布式中的一些常见场景的问题以及解决方案,通过对一些原理和算法的介绍,帮助读者可以更好地理解分布式架构相关的技术原理。本篇课程主要介绍了微服务架构的技术选型。微服务的开源社区目前也越来越成熟,实施微服务的过程中也需要做更多的调研。对企业的服务架构来说,技术实施以及架构要素的权衡是一方面,更重要的是结合业务和团队风格,适合的才是最好的。

读者们对于本课程内容,或者示例代码工程如有疑问,随时可以在 Github 提 issues 或者在课程的读者圈提问。

最后,感谢我的好朋友荣博,帮助我仔细检查每一篇的内容,让课程可以更易读、更丰富。

上一篇
下一篇
目录