Flomesh-Bookinfo基于虚拟机的演示

关于Flomesh-bookinfo的例子的介绍,请参考这里"bookinfo简介"

这个演示里,我们采用5个虚拟机完成演示,每个虚拟机的配置都是1C2G。用途和配置如下:

主机名称 IP地址 用途 部署组件
flomesh 192.168.122.10 控制平面+核心网关 aiur/fort/piped/kong/mysql/prometheus
productpage 192.168.122.132 产品展示页 productpage服务+piped
ratings 192.168.122.11 ratings服务,通过9080端口提供REST ratings服务+piped
reviews 192.168.122.65 reviews服务,通过9080端口提供REST reviews服务+piped
details 192.168.122.79 details服务,通过9080端口提供REST details服务+piped

部署flomesh主机

在这个演示中,我们把flomesh控制平面和核心网关部署在同一个虚拟机上,部署过程可以参考这里具体采用rpm方式部署,还是采用docker容器,还是采用kubernetes方式,对于演示来说没有差异。

部署productpage主机

productpage服务是一个python的程序,对外提供web页面。如下是部署这个服务的过程,在一个全新安装的centos7虚拟机里,按照如下步骤部署:

部署productpage服务

# 关闭防火墙
systemctl stop firewalld
systemctl disable firewalld

# 安装epel和git和unzip
yum -y install epel-release
yum -y install git unzip

# 复制演示程序代码
cd /root/
git clone https://github.com/flomesh-io/flomesh-bookinfo.git

# 安装python3,构建和运行product程序
yum install centos-release-scl
yum -y install rh-python36
scl enable rh-python36 bash
cd flomesh-bookinfo/productpage
pip install --no-cache-dir -r requirements.txt
python productpage.py 9080

现在productpage运行起来了,从外部浏览器或者curl访问这个服务,验证安装:

curl http://192.168.122.132:9080/

部署inbound代理

接下来我们安装、部署和配置piped。piped是一个flomesh团队开发的proxy程序,采用c++编写,目标之一是用来做mesh里的sidecar。

版权与免责: 本文所述的piped程序为Flomesh团队开发并拥有版权(并非open source software或者freeware),这里作为测试提供。此文档及安装介质可以免费下载用于测试、学习、非盈利目的使用。对于非原文转载,请注明出处(http://flomesh.cn)。piped程序不得作为盈利目的使用。Flomesh团队会维护该文档确保有效性,维护piped程序确保稳定、可靠,但是Flomesh团队不对此文档和该程序的使用负有责任。

# 安装piped
yum -y install http://repo.polaristech.io/piped/piped-0.1.0-57.el7_pl.x86_64.rpm

编辑/etc/piped/inbound.ini,内容如下:

[pipeline.proxy]
listen = 0.0.0.0:8080

[module.http-decode]
name = http-request-decoder

[module.http-encode]
name = http-request-encoder

[module.7]
name = proxy
upstream = 127.0.0.1:9080

启动处理inbound请求的piped:

[root@productpage ~]# cd /etc/piped/
[root@productpage piped]# pwd
/etc/piped
[root@productpage piped]# piped inbound.ini 
Loaded config file /etc/piped/inbound.ini
Thu Jan 30 21:31:41 2020 [info] Listening on 0.0.0.0:8080

相比与istio的通过iptables的方法拦截inbound请求,然后经过envoy再转发给实际服务的方式,flomesh采用了不同的方式和思路:

  • flomesh同时保留了服务的原生端口,在这个例子里边是9080
  • 同时采用sidecar代理开通了另外的端口,这里是piped监听8080端口
  • 这个sidecar会代理请求到实际服务端口,也就是9080,就是8080->9080

这样做的目的和好处是:

  • 同时保留了原生端口和代理端口,在调试和分析问题时候很方便
  • 在实际使用中,未必所有的流量在到达服务提供方时候都必须经过inbound代理,无论是在vm还是在pod模式下
  • 采用iptables会约束使用环境,虽然多数目标环境都是基于linux的,支持iptables,但是考虑到仍然有其他不同的环境,flomesh选择了更为兼容的方式
  • istio采用的iptables的办法,会增加不少的iptables规则,考虑到每个计算节点有可能运行上百个pod,这个规则的数量对性能的影响不能忽略;尽可能少的使用iptables有利于控制性能开销

现在inbound proxy运行起来了,从外部浏览器或者curl访问这个服务,验证安装:

curl http://192.168.122.132:8080/

可以看到这个和访问9080端口没有差异。实际上我们并没有在inbound proxy上加任何实际功能的模块。感兴趣的读者可以试着用ab分别测试8080端口和9080端口,作为对比。在我自己的对比测试里,经过proxy和不经过proxy的tps分别为850和950;经过代理时候,piped占用了2M内存,cpu使用峰值不超过20%。对于一个1C2G的虚拟机,一个python程序,这个数值还是不错的~

部署outbound代理

我们采用另外一个piped来做outbound的代理。外行流量大致的数据流是这样的:应用发出对外的请求,被iptables拦截,转发到outbound proxy(也就是outbound piped),然后piped对请求进行必要的处理,然后或者转发到中央代理,或者直接转发到服务提供方。

在这个例子中,flomesh对inbound和outbound采用两个独立的proxy进程,一方面是为了便于演示,另一方面在实际生产部署我们也建议采用inbound/outbound分离的办法。这样做的好处我们认为有这样几个:

  • 整体的配置会更简单。简单的东西易于调试和维护
  • 分离inbound和outbound在一些控制方面更容易,比如带宽控制
  • piped被设计成非常小巧,在配置了常用的功能后,piped运行内存不到10M,容器镜像不到10M。资源的低占用,在大规模部署下有更好的经济效益

Flomesh团队早期尝试过基于istio的mesh方案。但是istio对iptables的依赖,以及难于监视和调试代理对数据流的改变,为实施带来了很大的挑战。所以在重新设计和实现mesh的时候,我们采用了更简单的sidecar设计;并且努力维护sidecar是可以独立运行和配置部署的,而不用依赖kubernetes,这样可以降低开发过程中的复杂度,提高效率

配置/etc/piped/outbound.ini,内容如下:

[root@productpage ~]# cd /etc/piped/
[root@productpage piped]# cat outbound.ini 
[pipeline.proxy]
listen = 127.0.0.1:9090

[module.http-decode]
name = http-request-decoder

[module.http-add-header]
name = http-request-headers
X-App-Key = productpage

[module.http-encode]
name = http-request-encoder

[module.proxy]
name = proxy
upstream = 192.168.122.10:8000

在这个例子中,注意module.http-add-header模块的使用,这个模块在经过piped proxy的流上,增加一个http header,内容是“X-App-Key: productpage”,这个增加的header被后续用于跟踪和访问控制。在实际中,这个key可以是从统一认证中心获取的token。演示中采用服务名称,主要是为了可读性。

注意最后一个module.proxy,这个是把请求转发到192.168.122.10:8000,这个位置运行一个kong作为核心代理。关于sidecar+中央代理的模式,我们在本文最后做一些讨论。这个位置在实际使用中,推荐采用域名来替代ip。比如采用ingress.[name space].svc。

接下来我们需要增加一条iptables的规则,用途是拦截这个vm内所有进程向外部9080端口发出的请求,然后转发到这个outbound proxy里(127.0.0.1的9090端口)。productpage这个程序里会访问bookinfo的另外几个服务,这些服务默认都运行在9080端口,所以productpage进程对外的访问,都会被这个iptables规则拦截,然后转发给outbound proxy,也就是监听在9090端口的piped,然后piped再转发给Kong。

iptables -t nat -A OUTPUT -p tcp ! -d 127.0.0.1  --dport 9080 -j REDIRECT --to-ports 9090

这条iptables的规则意思是:对于所有向外发送的请求,如果目标端口是9080,目标地址不是127.0.0.1,那么把请求重定向到本地的9090端口

Flomesh尽可能降低对iptables的依赖。主要是三个原因:

  1. iptables对底层系统有依赖,包括操作系统和版本。这降低了mesh对于老的应用以及可能新增的环境的兼容性(虽然有可能这些不兼容环境是少数)
  2. iptables规则难于调试和管理,当iptables规则出现问题时候,分析问题非常困难
  3. 一个节点内iptables规则的增加对性能有影响。考虑到在k8s的环境里,通常人们的期望值是一个计算节点运行100个pod,累加后性能损失不得不考虑

在拦截outbound请求,并且转发到proxy这个环节,除了iptables方法,还有其他的方法,包括使用http代理和socks代理等。Flomesh也支持这些方式

验证:在配置好outbound proxy和iptables规则以后,可以随便curl一个外部ip地址的9080端口,可以看到请求被iptables拦截,通过outbound proxy转发给了kong。

[root@productpage ~]# curl -i http://1.1.1.1:9080/
HTTP/1.1 404 Not Found
Date: Thu, 30 Jan 2020 15:21:09 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
Content-Length: 23

{"message":"Not Found"}[root@productpage ~]# 

message: Not Found这个404错误来自kong。因为我们还没有添加任何路由规则。我们稍后添加规则。

部署ratings主机

这里整体步骤和productpage主机部署类似,需要注意的是:需要自己安装jdk和maven。

在这个演示里,ratings主机的ip是192.168.122.11

部署ratings服务

# 关闭防火墙
systemctl stop firewalld
systemctl disable firewalld

# 安装epel和git和unzip
yum -y install epel-release
yum -y install git unzip

# 复制演示程序代码
cd /root/
git clone https://github.com/flomesh-io/flomesh-bookinfo.git

修改/root/flomesh-bookinfo/ratings/src/main/resources/applications.yml文件18行,把监听端口修改成9080。

编译和启动ratings服务:

cd /root/flomesh-bookinfo/ratings
mvn clean package
java -jar target/bookinfo-ratings-1.0.0-SNAPSHOT.jar

验证服务可用:

[root@ratings ratings]# curl http://localhost:9080
{"timestamp":1580452354099,"status":404,"error":"Not Found","message":"No message available","path":"/"}

部署inbound代理

部署inbound代理的步骤和方法,和productpage服务里方法一样。

部署outbound代理

部署outbound代理的步骤和方法,和productpage服务里方法一样。

部署reviews服务

部署reviews服务的过程和ratings过程一样,包括三个部分:

  1. 部署服务。需要注意监听端口改成9080。启动服务之前,记得export RATINGS_URL=http://192.168.122.11:9080,服务需要这个环境变量作为参数
  2. 部署inbound代理
  3. 部署outbound代理,注意添加iptables规则

reviews服务虚机的ip为192.168.122.65

部署details服务

部署details服务过程和ratings过程一样,包括三个部分:

  1. 部署服务。需要注意监听端口改成9080
  2. 部署inbound代理
  3. 部署outbound代理,注意添加iptables规则

配置flomesh规则和策略

当四个服务(productpage、ratings、reviews、details)都部署好以后,我们配置flomesh来处理这些流量。首先我们看下在没有使用mesh的情况下服务调用关系: 服务调用关系

在加入了flomesh以后,对于每个服务,都变成这个样子:访问的请求,经过中央网关(Kong)到达服务;服务发出的请求,首先经过sidecar的outbound proxy(piped),然后达到kong,然后再到达目标服务。也就是这样:

 inbound request                              |---------|  
-------------->kong-------->(可选)piped------>|         |
                                              |         |
 outbound request                             | Service |
<-------------kong<----------piped<-----------|         |
                                              |---------|

举个例子,对于上边没有采用mesh的调用链:

-->productpage-->reviews-->ratings

在采用了flomesh之后,会变成这样(大写的部分是插入的mesh的proxy):

-->KONG-->productpage-->OUTBOUND PIPED-->KONG-->reviews-->OUTBOUND PIPED-->KONG-->ratings

接下来讲解如何完成代理的配置,都是图形界面,会简单很多。

配置productpage代理

productpage服务是一个python的程序,同时提供了基本的静态页面内容。我们把productpage服务放在核心网关后边,这样当外部访问productpage的时候,需要经过kong。配置的过程可以参考“101反向代理”。如下的截图是演示了如何在网关上配置productpage服务,需要注意几点:

  1. 在“服务提供者(provider)”里边,我们使用了"服务模式(service)",所以需要先创建一个productpage的服务
  2. 在"访问端点(endpoint)"里边,我们采用了域名匹配的方式:所有请求域名productpage.bookinfo.svc的,都会由productpage服务处理请求
  3. 作为这个例子的小技巧,我们开启了cache。这是一个类似cdn的功能,就是可以把一些静态内容,缓存在网关,当后续访问的时候,可以提高访问速度,很有用的东西

API配置

Cache的配置: Response Cache配置

服务的配置: Service 配置

这里,对于不熟悉Flomesh概念的,做一些讲解。在Flomesh里:

  • "服务"是具体的应用服务,比如spring boot的fatjar,比如kubernetes的服务,比如dubbo的服务
  • "服务"在运行状态下是”实例“,多个实例运行的是同一个程序,流量在这些实例上由Flomesh提供”负载均衡“
  • ”API“是一个抽象的概念,就像编程里边的interface。API可以三种类型的”提供者(Provider)“:
  • 主机(Host)。是直接由API转发到指定的主机,可以是IP也可以是域名
  • 服务(Service)。请求会被转发给具体的”服务“
  • 分区(Zone)。请求会转发给其他的网关

让我们用一个例子加深理解:比如这个productpage的程序,它是一个python的web服务,如果我在两个vm里部署,那么就是创建了一个”服务(Service)“,这个”服务“的”负载均衡(Load Balance)“有两个”目标(Target)“--这两个目标就是两个虚拟机的IP和端口。然后我们创建一个API,这个API的提供者是这个服务

为什么需要”主机(Host)“和"分区(Zone)"作为Provider呢?假设这个productpage服务,除了运行在当前数据中心,还运行在公有云上,我们希望对特定的请求,路由到这个公有云的productpage服务上,这个时候可以用Host模式,就是只要填写域名(productpage所在公有云的入口)就可以。假设这个productpage服务,还有一个副本,作为容灾的实例,运行在另外一个数据中心,那个数据中心流量入口在Flomesh里是另外一个分区(Zone),对于特定的流量,我们希望路由到这个灾备实例上,就可以选择Zone模式,然后只要把请求转发到这个Zone就可以。

会不会觉得这样配置起来很麻烦?Flomesh可以和很多微服务体系做集成,集成后可以自动完成上述多种资源的创建,不用一个一个手动填写。目前Flomesh支持kubernetes、openshift/okd、springboot eureka、Dubbo zookeeper、Nacos

验证这个配置,从任意主机,访问proxy主机(192.168.122.10)的8000端口,记得带Host参数:

root@localhost:~# curl -i -vvvv -H "Host: productpage.bookinfo.svc" http://192.168.122.10:8000/
*   Trying 192.168.122.10...
* TCP_NODELAY set
* Connected to 192.168.122.10 (192.168.122.10) port 8000 (#0)
> GET / HTTP/1.1
> Host: productpage.bookinfo.svc
> User-Agent: curl/7.52.1
> Accept: */*
> 
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Content-Type: text/html; charset=utf-8
Content-Type: text/html; charset=utf-8
< Content-Length: 1683
Content-Length: 1683
< Connection: keep-alive
Connection: keep-alive
< X-Cache-Key: 88b034cfc02c0460e662399834860eac
X-Cache-Key: 88b034cfc02c0460e662399834860eac
< X-Cache-Status: Bypass
X-Cache-Status: Bypass
< Server: Werkzeug/0.15.5 Python/3.6.9
Server: Werkzeug/0.15.5 Python/3.6.9
< Date: Fri, 31 Jan 2020 14:42:50 GMT
Date: Fri, 31 Jan 2020 14:42:50 GMT

配置ratings、reviews、details的代理

这三个服务的配置过程和productpage的配置很接近,不同之处在于:

  1. 这三个服务的”访问端点(endpoint)“配置采用了基于路径的路由。实际上,Flomesh推荐在微服务系统里采用基于域名的配置(就像productpage服务一样),这也是kubernetes的通用配置之一
  2. 没有采用cache。cache对于前端页面有直观意义,但是对于REST API,需要谨慎使用;当然,恰当使用也可以大幅提升性能
  3. 配置了基于key的访问控制,这个部分在下一个段落里单独介绍

ratings的配置看起来是这样的: Ratings REST服务配置

reviews的配置看起来是这样的: Reviews REST服务配置

details的配置看起来是这样的: Details REST服务配置

当这几个服务的代理都配置好了以后,验证办法如下(这个可以参考https://github.com/flomesh-io/flomesh-bookinfo/blob/master/readme.md):

  • 验证ratings服务,从任意的主机访问:
root@localhost:~# curl -d '{"reviewerId":"9bc908be-0717-4eab-bb51-ea14f669ef20","productId":"a071c269-369c-4f79-be03-6a41f27d6b5f","rating":3}' -H "Content-Type: application/json" -X POST http://192.168.122.10:8000/ratings | jq
{
  "id": "2cb6c0ad-0f37-4371-9f69-66cc424185dd",
  "reviewerId": "9bc908be-0717-4eab-bb51-ea14f669ef20",
  "productId": "a071c269-369c-4f79-be03-6a41f27d6b5f",
  "rating": 3
}
  • 验证reviews服务,从任意主机访问:
root@localhost:~# curl -d '{"reviewerId":"9bc908be-0717-4eab-bb51-ea14f669ef20","productId":"a071c269-369c-4f79-be03-6a41f27d6b5f","review":"This was OK.","rating":3}' -H "Content-Type: application/json" -X POST http://192.168.122.10:8000/reviews | jq
{
  "id": "3c8ae96f-ce34-4e28-925c-a718268e4e82",
  "reviewerId": "9bc908be-0717-4eab-bb51-ea14f669ef20",
  "productId": "a071c269-369c-4f79-be03-6a41f27d6b5f",
  "review": "This was OK.",
  "rating": 3
}

注意:reviews服务会访问ratings服务。所以如果需要确认是否正常访问ratings服务,可以在reviews的vm里,执行上一步对ratings服务的验证

  • 验证details服务,从任意主机访问:
root@localhost:~# curl http://192.168.122.10:8000/details/1234567890 | jq
{
  "id": "e807f1fc-f82d-332f-9bb0-18ca6738a19f",
  "title": "No Name",
  "description": "Bala bala...",
  "author": "NoName Author",
  "printType": "paperback",
  "year": 1595,
  "pageCount": 999,
  "publisher": "PublisherA",
  "language": "English",
  "isbn": "1234567890"
}

配置访问控制

在productpage、ratings、reviews、details这四个服务中,存在如下的访问关系:

  1. productpage --> reviews
  2. productpage --> details
  3. reviews --> ratings 如下我们演示Flomesh如何控制服务之间的访问权限:

首先配置第一条访问控制,productpage-->reviews。首先打开reviews API的设置,在”认证“的tab里选择"key认证",如图: 配置reviews认证

注意其中的"key names"是"X-App-Key",这个是我们在outbound proxy的配置中设置的一个HTTP Header

保存配置,然后部署新的配置;然后我们再次尝试访问这个服务,会看到401错误:

root@localhost:~# curl -i -d '{"reviewerId":"9bc908be-0717-4eab-bb51-ea14f669ef20","productId":"a071c269-369c-4f79-be03-6a41f27d6b5f","review":"This was OK.","rating":3}' -H "Content-Type: application/json" -X POST http://192.168.122.10:8000/reviews 
HTTP/1.1 401 Unauthorized
Date: Sat, 01 Feb 2020 06:12:07 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
WWW-Authenticate: Key realm="flomesh"
Content-Length: 41

{"message":"No API key found in request"}

到这里,说明对reviews服务的访问控制生效了。接下来我们配置productpage来访问reviews。在左侧菜单中选择”服务“,然后打开”productpage服务“,选择"身份标识"tab,创建一个key,内容为”productpage“,如图:

配置服务的key

然后我们创建productpage服务对reviews API的”订阅“。

当一个API配置了认证保护以后,需要访问这个API的服务或者外部App,需要先”订阅“这个API,然后API管理者需要”审批“这个订阅并完成部署,客户端的服务和App才能正常访问该API

还是在productpage的服务配置页面上,选择”订阅“tab,然后创建一个对reviews服务的”订阅“,如图: 配置服务订阅

这个时候,到reviews API的配置页面,"订阅"tab,就可以看到这个订阅请求了,如图: 查看订阅申请

点击这个申请,然后审批: 审批订阅申请

这个时候,我们用curl去访问reviews服务,然后提供一个Header,"X-App-Key: productpage",可以看到访问通过了身份验证:

root@localhost:~# curl -d '{"reviewerId":"9bc908be-0717-4eab-bb51-ea14f669ef20","productId":"a071c269-369c-4f79-be03-6a41f27d6b5f","review":"This was OK.","rating":3}' -H "Content-Type: application/json" -X POST http://192.168.122.10:8000/reviews -H "X-App-Key: productpage" | jq
{
  "id": "d7e89bde-7f41-445e-94ee-d42e7419be6b",
  "reviewerId": "9bc908be-0717-4eab-bb51-ea14f669ef20",
  "productId": "a071c269-369c-4f79-be03-6a41f27d6b5f",
  "review": "This was OK.",
  "rating": 3
}

参考如上的步骤,配置另外两个访问控制:

  • productpage --> details
  • reviews --> ratings

最后,我们再创建一个App,名字是”iphone“,然后订阅到productpage的API上。然后用浏览器或者curl去模拟一些访问,在Flomesh控制台的"网格"链接里,可以看到这些服务之间的访问关系(服务拓扑),如图: 服务拓扑

需要注意的是,在Flomesh里,”服务拓扑“是依赖”认证和访问控制“计算出来的;多数其他的Service Mesh软件依赖服务跟踪(Tracing)来计算服务拓扑。这样做的原因和理由如下:

  • Flomesh想要兼容那些存量的、没有办法加入Tracing能力的应用,通过简单的sidecar(piped proxy)就可以为存量服务带来服务拓扑能力
  • Flomesh认为在微服务体系内,所有的服务都应该采用某种访问控制机制来保护,所以认证是普遍存在的
  • 基于”认证和访问控制“的服务拓扑计算和Tracing并不冲突。在Flomesh中,可以同样配置Tracing,目前支持的包括Zipkin/Jaeger和Skywalking

性能验证与分析

基于这个演示,我们做一下性能验证与分析,考虑到篇幅,过程和结论请参考这个Blog