Service Mesh介绍

微服务架构发展到今天,基本组件包括服务注册和发现、服务通信和治理、故障熔断恢复、配置、安全、监控等等,这些微服务组件和功能在业界已经深入人心,在各大互联网公司开花结果,业界知名开源的微服务框架有Netflix OSS、Spring Cloud及其国内的阿里Dubbo等等,各种框架百花齐放。但微服务框架自身组件繁多,本身的治理问题亟待解决。

1. 什么是Service Mesh?

Service Mesh是一个基础设施层,其独立运行在应用服务之外,提供应用服务之间安全、可靠、高效的通信,并为服务通信实现了微服务运行所需的基本组件功能,包括服务注册发现、负载均衡、故障恢复、监控、权限控制等等。Service Mesh的中文译为“服务网格”。

一个典型的Service Mesh部署网络结构图如下,

其中绿色方块为应用服务,蓝色方块为Sidecar Proxy,应用服务之间通过Sidecar Proxy进行通信,整个服务通信形成图中的蓝色网络连线,图中所有蓝色部分就形成了Service Mesh。

2. 为什么需要Service Mesh

最主要的理由来自于,Service Mesh在提供微服务框架功能的同时,它是一个独立运行在应用服务之外的模块,这带来的好处就是,应用服务不再需要为接入微服务框架而在代码和配置中添加繁多的依赖库和依赖配置项,实现了微服务框架和应用服务之间的解耦,让应用服务代码更加纯粹地去实现自己的业务逻辑,能够更加轻松地接入微服务框架,享受微服务框架带来的各种服务治理功能。

目前业界知名的微服务框架,比如Netflix OSS、Spring Cloud及其国内的阿里Dubbo等等,都在特定组件、特定语言上有偏向的支持,开发者在项目的开发过程中,都要在项目代码中和各个组件打交道,添加各种“胶水”代码。

请见如下一个Spring Boot项目,它通过注解方式打开各个组件开关,

@SpringBootApplication
@EnableConfigServer
@EnableEurekaServer
@EnableEurekaClient
@EnableHystrix
@EnableHystrixDashboard
@EnableTurbine
@EnableDiscoveryClient
@EnableFeignClients
@EnableZuulProxy
@EnableZuulServer
Public class Application {
   SpringApplication.run (Application.class, args);
}

可以看到,即使开发一个简单的项目功能,各个组件依赖库一个都不能少,而这些问题正是Service Mesh所要解决的。

3. Service Mesh的发展历程

Netflix OSS在微服务架构上有很早的探索,其开源出很多微服务框架所需的组件,但这些组件库主要使用的是Java开发语言,为了方便非Java语言的应用服务也能够对接这些组件,在2014年Netflix OSS开源了一款Prana的产品,其主要功能有,

  • 通过Eureka组件实现应用服务的注册和发现
  • 健康检查
  • 通过Ribbon组件实现应用服务的负载均衡
  • 通过Archaius组件实现应用服务的配置管理

这个Prana产品是一个基于RxNetty的HTTP代理服务,随应用服务一起部署,这个Prana已经有了Service Mesh中Sidecar功能原型。

当前两个比较重要的service mesh项目分别为Linkerd和Envoy Proxy,其中Linkerd在2016年1月发布了0.0.7公开版本,而Envoy Proxy在2016年9月发布它们的第一个版本v1.0.0,两者都提供HTTP/gRPC/Thrift的服务通信方式。

如下是Linkerd的功能图解,

Envoy Proxy的图解请见后续的Istio介绍。

在2017年云原生计算基金会(CNCF,Cloud Native Computing Foundation)分别在1月和9月先后接受了Linkerd和Envoy Proxy项目,正式由CNCF来负责维护开发,这也宣布了业界对Service Mesh的支持和拥抱。

4. Servcie Mesh的整体集成解决方案Istio

Service Mesh主要解决的是微服务之间的网络通信交互,随着业务服务增加,整个Service Mesh会变得庞大和复杂之后,这个时候需要对Service Mesh的管理功能进行抽象出来,从而满足丰富的微服务运营需求,而Istio就是提供这样一个整体集成解决方案的项目。

Istio由Google、IBM、Lyft Envoy联手开发,一开始就定位于实现Service Mesh模式的微服务框架,在2017年5月发布0.1版本,然后在10月发布了0.2版本。

一个Istio Service Mesh在逻辑上可以分成两大区块,

  • 数据区(data plane):由通信代理组件(Envoy/Linkerd等)和组件之间的网络通信组成。
  • 控制区(control plane):负责对通信代理组件进行管理和配置

Istio的架构图如下,

在图中可以看到Istio Service Mesh的两大区块。

架构图中各个子模块功能如下,

  • Envoy:一个使用C++语言开发的高性能通信代理,负责各个应用服务之间通信
  • Pilot:管理和配置Envoy,提供服务发现、负载均衡和智能路由,保证弹性服务(服务超时次数、重试、熔断策略)。
  • Mixer:信息监控检查
  • Istio-Auth:提供服务和服务、用户和服务之间的认证服务,实现访问控制,解决是谁访问的是哪个API的问题

其中,图中的通信代理组件为Envoy,这是Istio原生引入的,但Linkerd也能够集成对接Istio。

5. 参考资料

Mesos架构和工作流程简介

1. 什么是Mesos

Mesos是Apache软件基金会维护的一个开源软件,它负责管理一批服务器集群,并将所有服务器集群的CPU、GPU、内存、存储、端口和其它相关计算资源进行了抽象统一,让用户进行动态配置和使用,提高整个系统的资源利用率,并以集群分布式的方式保证系统运行的高可用和弹性扩展。

2. Mesos架构和工作流程

官网中有个Mesos的架构图如下,

主要的模块有,

  • Mesos Master:管理所有机器资源信息,一般会部署多台来保证master的高可用,以resource offers的方式定时告知scheduler可用资源。
  • ZooKeeper:实现当前工作Mesos Master的选举,并实现数据一致性。
  • Mesos Agent:集群中每个机器都会部署一台Mesos Agent,定时汇报当前机器可用资源给Master,并从Master获取分配的任务信息,调用Executor来运行。
  • Scheduler:定时得到Master发送过来的resource offers,将任务列表中各个任务的资源需求进行一一匹配,一旦资源需求获得满足,则告诉Master使用匹配到的资源启动任务。
  • Executor:被Agent调用,根据指定的任务和资源信息,执行任务。

其中Scheduler和Executor是开发者根据自己需要进行开发,业界里各种Mesos Framework也就是实现这两部分,例如图中的Hadoop Mesos/MPI Mesos,还有Mesosphere公司的marathon,HudSpot公司的Singularity,国内数人云的Swan等。

Mesos提供两层调度,一层是Agent调用Executor执行Master分配的任务,另外一层是任务的资源匹配和调度放到Scheduler,由Scheduler推送任务到Master。由于Scheduler和Executor的可动态调整,这样也使得任务调度策略和执行变得可动态配置。

整个Mesos系统的主要工作流程如下,

  1. Mesos Agent定期上报各个机器的资源(CPU、Memory、磁盘、端口号)
  2. Mesos Master收集所有Agent可用的资源,定期推送resource offer给Scheduler,offer中描述了可用的资源信息。
  3. Scheduler启动时,会找到Mesos Master并注册,定期获取Master推送给的resource offer,以此了解可用的资源。
  4. 用户向Scheduler申请任务需要的资源,执行相关任务,例如申请2CPU 4G Mem来运行程序Demo。
  5. Scheduler在获取到Master推送的offer后,当offer中的资源满足用户申请的任务需求,就向Master申请执行。
  6. Mesos master根据Scheduler申请,在相应的资源上调用Agent执行任务。

整个流程图见如下,

需要注意的是,Mesos的四大组件(Master/Slave/Scheduler/Executor)之间的通信,是通过libprocess实现actor model模型的进程间消息异步通信,每个进程是一个actor。

见上图,在Mesos的master节点中,每个Framework以及Slave都是一个远程的actor。而slave节点上,每个executor是一个actor,只不过内置的executor是在同一个进程中的,而其他自定义的executor是独立的进程,executor和slave之间通过进程间通信方式(网络端口)交互。

Actor模型通信带来的好处是省去了对消息队列的依赖,但同时由于消息都是异步的,需要actor处理消息的丢失以及超时逻辑,Mesos无法保证消息的可靠投递,提供的投递策略是 at-most-once(至多一次,不会重试)。

3. 如何开发一个Mesos Framework

Mesos提供支持多种语言的Framework开发,包括Java/Python/Scala等,下面以Java语言为例介绍Mesos Framework的开发,其它语言类似。

一个Mesos Framework主要实现两个模块,

  1. Scheduler
  2. Executor

其中Scheduler会是单独可运行的项目程序,比如一个Java程序或者一个Java Web后端服务;Executor则会是Jar包项目,打出Jar包后由Mesos Agent引用并启动。

在两个项目中各自引入依赖包,

<dependency>
<groupId>org.apache.mesos</groupId>
<artifactId>mesos</artifactId>
</dependency>

在包org.apache.mesos中提供如下主要抽象接口,

  • Scheduler:这是Framework要实现的Scheduler接口,用于Mesos Master回调。
  • Executor:这是Framework要实现的Executor接口,用于Agent回调。
  • SchedulerDriver:这是Scheduler和Mesos进行通信的抽象接口。
  • ExecutorDriver:这是Executor和Mesos进行通信的抽象接口。

在两个项目中需实现上述的Scheduler和Executor抽象接口,各个接口方法的实现需求请参见Apache Mesos项目源代码中对其的接口描述,可以通过如下git命令下载代码,
git clone git@github.com:apache/mesos.git

在Apache Mesos项目代码中有对Mesos Framework开发提供的样例实现,在其文件目录中,

  • mesos\src\examples\java\TestFramework.java
  • mesos\src\examples\java\TestExecutor.java

前一个实现了org.apache.mesos.Scheduler,是一个可单独运行的Java程序;后一个实现了org.apache.mesos.Executor,是一个Jar包。

注:在包org.apache.mesos中还有两个抽象接口:SchedulerDriver和ExecutorDriver,Apache Mesos提供两个Driver的默认具体实现MesosSchedulerDriver和MesosExecutorDriver,所以Mesos Framework可以直接使用,用于和Mesos Master通信,不用自己实现这两个Driver。

4. 参考资料

Json Web Token的介绍和最佳实践

1. 什么是JSON Web Token

JSON Web Token是一个开放的、行业规范(RFC7519)的方法,用于通信双方进行可靠的数据传输。它规定了一种紧凑的数据格式,将要传输的数据以JSON对象方式的组织,然后进行编码和签名,转化成为Token。

JSON Web Token包含三部分信息数据,

Header.Payload.Signature

其中,

  • Header:数据头部,声明Token类型和加密算法(比如HMAC SHA256或者RSA)
  • Payload:数据的信息体,包含了要传输的信息体
  • Signature:数据的数字签名,用于对数据进行校验来源可靠性和不被篡改

在三部分中,header/payload都是公开信息,JSON对象数据格式,使用Base64方式进行编码,这意味着header/payload可以被任何人进行解码和阅读,所以不要在header/payload放入敏感信息。

一个Token样例为,
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

其中,

Header Base64编解码 {"alg":"HS256","typ":"JWT"}
Payload Base64编解码 {"sub":"1234567890","name":"John Doe","admin":true}
Signature HS256签名算法 HS256(eyJhb……RydWV9, secret)

注:签名算法中的eyJhb……RydWV9为Base64编码后的Header.Payload

2. 应用场景

JWT一般用在如下两个场景,

  • 认证:这是最常用JWT应用场景,用于用户登录后,后端服务器颁发一个带有时效性的JWT token给前端应用,然后前端应用在后续的请求中使用该Token进行访问后端资源。
  • 信息交换:由于JWT对数据进行了签名,信息不能被篡改,可以可靠地在通信双方进行数据信息交换。

下图简单描绘了JWT在认证场景的使用流程,

使用方法

JWT的使用方法包括:签发、解码、校验,

  • 签发:用于对信息进行签名,然后颁发给使用方,供后续进行认证和信息交换
  • 解码:对JWT的信息体解码,获取信息,一般用在不进行校验的场合,比如在浏览器端
  • 校验:对JWT的校验,确认信息的来源可靠性和不被篡改、时效不过期

目前已经有如下语言的支持,

  • Java
  • Node.js
  • JavaScript
  • Microsoft .NET、.NET RT(Windows 8.1 and Windows Phone 8.1)
  • Python
  • Perl
  • Ruby
  • Go
  • Lua
  • Scala
  • Object-C
  • Swift
  • PHP

等等。

下面主要介绍JavaScript/Node.Js/Java三种语言对Jwt的签发、校验、解码的代码实现,这三种语言分别代表着web客户端/web服务器端/后端服务。

1) JavaScript

下面使用jwt-decode类库在web客户端(浏览器端)对token进行解码操作。

点击这里下载jwt-token.js脚本。

在HTML页面中引入上面的JavaScript脚本,下面为简单的解码演示,

<script type="text/javascript" src="jwt-token.js"></script>
<script>

    let jwtToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ";
    let payload = jwtToken.split('.')[1];
    let payloadInfo = JSON.parse(jwt.base64urldecode(payload));

    for (let key in payloadInfo) {
        console.log(key + ": " + payloadInfo[key]);
    }

</script>
2) Node.js

下面使用Node Auth0类库在nodejs的web服务器端进行token的签发和校验
安装命令,从npm中安装jsonwebtoken
npm install jsonwebtoken

签发

let jwt = require('jsonwebtoken');
let token = jwt.sign({name: 'John'}, 'secret', { algorithm: 'HS256', expiresIn: '1d', issuer: 'pphh'});

校验

let jwt = require('jsonwebtoken');
let info = jwt.verify(token, 'secret');
console.log(info.name) // John

解码

let info = jwt.decode(token, {complete: true});
console.log(info.header);
console.log(info.payload);
3) Java

下面使用Java Auth0类库来进行token的签发和校验。

在Maven项目中添加依赖项,

<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>

签发

try {
    Algorithm algorithm = Algorithm.HMAC256("secret");
    String token = JWT.create()
                      .withIssuer("auth0")
                      .sign(algorithm);
} catch (UnsupportedEncodingException exception){
    //UTF-8 encoding not supported
} catch (JWTCreationException exception){
    //Invalid Signing configuration / Couldn't convert Claims.
}

校验

String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
try {
    Algorithm algorithm = Algorithm.HMAC256("secret");
    JWTVerifier verifier = JWT.require(algorithm)
                              .withIssuer("auth0")
                              .build(); //Reusable verifier instance
    DecodedJWT jwt = verifier.verify(token);
} catch (UnsupportedEncodingException exception){
    //UTF-8 encoding not supported
} catch (JWTVerificationException exception){
    //Invalid signature/claims
}

解码

String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
try {
    DecodedJWT jwt = JWT.decode(token);
} catch (JWTDecodeException exception){
    //Invalid token
}

更多语言和JWT类库使用,请参考官方网站和样例。

最佳实践

  1. 在JWT信息体Payload中不要包括敏感信息,若一定要,可以尝试使用JSON Web Encryption 。
  2. 一定要添加Token过期时间expiration time (exp claim) ,用于时效性检查。
  3. 不建议以无签名算法的方式(签名算法为none)来颁发JWT token,这个有安全风险,会导致攻击者绕过某些JWT类库的校验,具体可以查看该资料
  4. 建议使用HTTPS来传输JWT token信息,在最大程度保证信息在传输过程中的安全性,防止被中间人截取。
  5. 确保签名密钥只能让颁发者和使用者知道,尽可能使用非对称加密算法来颁发Token(比如RSA算法)。
  6. 如果担心重放攻击(replay attacks),可以尝试设置jti声明(JWT ID Claim),该声明将使得每个JWT token拥有唯一的ID,使其可以只使用一次,可以用来防止重放攻击。
  7. 在浏览器端,token可以存放到sessionStorage/localStorage,但注意XSS(跨站点脚本)攻击;还有一种方法是token放到了Cookie中,建议为其加上HttpOnly和Secure标记,HttpOnly保证JavaScript无法获取该Cookie,而Secure标记将保证该Cookie信息只能通过Https传输,但注意Cookie的方式可能会引起CSRF(跨站请求伪造)攻击。

演示项目

admin demo项目代码中有一一使用上述的JWT token颁发、校验、解码方法,

  1. 在前端项目admin-front中,通过jwt-decode解析JWT token来获取登录用户账号。
  2. 在前端项目admin-front中,在配置文件webpack.config.js中有提供/test/auth接口,通过jsonwebtoken颁发JWT token。
  3. 在后端项目admin-server中,在UserService中,通过com.auth0.jwt.JWT来颁发JWT token。
  4. 在后端项目admin-server中,在JwtFilter中,通过com.auth0.jwt.JWT来校验用户登录token。

具体可以获取项目代码查看。

admin demo代码仓库地址:https://gitee.com/pphh/simple-admin,可以通过如下git clone命令获取仓库代码,

git clone git@gitee.com:pphh/simple-admin.git

参考资料