手把手教你实践Service Mesh微服务架构 - 基础部署部分

litefish 2019-06-28

内容不断完善中,访问文档查看最新更新

当下,已经有很大一部分公司完成了单体架构向微服务架构的迁移改造,并在疲于应对大量微服务间通信问题时,开始考虑采用Service Mesh微服务架构作为服务与服务直接通信的透明化管理框架,以插件式的方式实现各种业务所需的高级管理功能。

而开源PaaS Rainbond提供了开箱即用的Service Mesh微服务架构,部署在Rainbond上的应用原生即是Service Mesh微服务架构应用。

接下来,我们将以 Rainbond v3.7.0 为基础平台,以开源商城项目 sockshop 为例,演示如何在源代码无入侵的情况下,将项目改造为具有服务注册与发现分布式跟踪A/B测试灰度发布限流熔断性能分析高可用日志分析等能力的高可靠性电商业务系统。

一键部署Rainbond请查看快速开始

sockshop是一个典型的微服务架构案例,具备用户管理、商品管理、购物车、订单流程、地址管理等完善的电商相关功能。sockshop主要由Spring bootGolangNodejs等多种语言开发,使用MySQLMongoDB等多种数据库,原方案采用单机环境下的部署方式,缺乏服务治理能力和分布式能力。

sockshop部署后的拓扑图总览

手把手教你实践Service Mesh微服务架构 - 基础部署部分

sockshop商城首页预览图

手把手教你实践Service Mesh微服务架构 - 基础部署部分

sockshop架构图

手把手教你实践Service Mesh微服务架构 - 基础部署部分

更多信息

sockshop在Rainbond的部署流程

STEP1 服务创建

Rainbond支持从源码、镜像、应用市场等多种方式进行应用部署,这里我们采用DockerCompose配置文件的创建方式,批量创建 sockshop 中包含的所有服务。

  • docker-compose 创建

手把手教你实践Service Mesh微服务架构 - 基础部署部分

需要注意的是,在检测和创建过程中,获取大量镜像需要一定时间,请耐心等待完成!
version: '2'

services:
  front-end:
    image: weaveworksdemos/front-end:0.3.12
    hostname: front-end
    restart: always
    cap_drop:
      - all
    ports:
      - "8079:8079"
      - "9001:9001"
    depends_on:
      - catalogue
      - carts
      - payment
      - user
      - orders
  edge-router:
    image: weaveworksdemos/edge-router:0.1.1
    ports:
      - '80:80'
      - '8080:8080'
    cap_drop:
      - all
    cap_add:
      - NET_BIND_SERVICE
      - CHOWN
      - SETGID
      - SETUID
      - DAC_OVERRIDE
    tmpfs:
      - /var/run:rw,noexec,nosuid
    hostname: edge-router
    restart: always
    depends_on:
      - front-end
  catalogue:
    image: weaveworksdemos/catalogue:0.3.5
    hostname: catalogue
    restart: always
    cap_drop:
      - all
    cap_add:
      - NET_BIND_SERVICE
    depends_on:
      - catalogue-db
      - zipkin
  catalogue-db:
    image: rilweic/catalog-db
    hostname: catalogue-db
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_ALLOW_EMPTY_PASSWORD=true
      - MYSQL_DATABASE=socksdb
  carts:
    image: weaveworksdemos/carts:0.4.8
    hostname: carts
    restart: always
    cap_drop:
      - all
    cap_add:
      - NET_BIND_SERVICE
    tmpfs:
      - /tmp:rw,noexec,nosuid
    environment:
      - JAVA_OPTS=-Xms64m -Xmx128m -XX:+UseG1GC -Djava.security.egd=file:/dev/urandom -Dspring.zipkin.enabled=false
    ports:
      - "80:80"
    depends_on:
      - carts-db
      - zipkin
  carts-db:
    image: mongo:3.4
    hostname: carts-db
    restart: always
    cap_drop:
      - all
    cap_add:
      - CHOWN
      - SETGID
      - SETUID
    tmpfs:
      - /tmp:rw,noexec,nosuid
  orders:
    image: rilweic/orders
    hostname: orders
    restart: always
    cap_drop:
      - all
    cap_add:
      - NET_BIND_SERVICE
    tmpfs:
      - /tmp:rw,noexec,nosuid
    environment:
      - JAVA_OPTS=-Xms64m -Xmx128m -XX:+UseG1GC -Djava.security.egd=file:/dev/urandom -Dspring.zipkin.enabled=false
    ports:
      - "8848:8848"
    depends_on:
      - orders-db
      - zipkin
      - shipping
      - carts
      - payment
      - user
  orders-db:
    image: mongo:3.4
    hostname: orders-db
    restart: always
    cap_drop:
      - all
    cap_add:
      - CHOWN
      - SETGID
      - SETUID
    tmpfs:
      - /tmp:rw,noexec,nosuid
  shipping:
    image: Rainbond/shipping:0.4.8
    hostname: shipping
    restart: always
    cap_drop:
      - all
    cap_add:
      - NET_BIND_SERVICE
    tmpfs:
      - /tmp:rw,noexec,nosuid
    environment:
      - JAVA_OPTS=-Xms64m -Xmx128m -XX:+UseG1GC -Djava.security.egd=file:/dev/urandom -Dspring.zipkin.enabled=false
    ports:
      - "8080:8080"
    depends_on:
      - rabbitmq
      - zipkin
  queue-master:
    image: weaveworksdemos/queue-master:0.3.1
    hostname: queue-master
    restart: always
    cap_drop:
      - all
    cap_add:
      - NET_BIND_SERVICE
    tmpfs:
      - /tmp:rw,noexec,nosuid
    depends_on:
      - rabbitmq
  rabbitmq:
    image: rabbitmq:3.6.8
    hostname: rabbitmq
    restart: always
    cap_drop:
      - all
    cap_add:
      - CHOWN
      - SETGID
      - SETUID
      - DAC_OVERRIDE
  payment:
    image: weaveworksdemos/payment:0.4.3
    hostname: payment
    restart: always
    cap_drop:
      - all
    cap_add:
      - NET_BIND_SERVICE
    depends_on:
      - zipkin
  user:
    image: weaveworksdemos/user:0.4.4
    hostname: user
    restart: always
    cap_drop:
      - all
    cap_add:
      - NET_BIND_SERVICE
    environment:
      - MONGO_HOST=user-db:27017
    depends_on:
      - user-db
      - zipkin
  user-db:
    image: weaveworksdemos/user-db:0.4.0
    hostname: user-db
    restart: always
    cap_drop:
      - all
    cap_add:
      - CHOWN
      - SETGID
      - SETUID
    tmpfs:
      - /tmp:rw,noexec,nosuid
  zipkin:
    image: openzipkin/zipkin
    hostname: zipkin
    restart: always
    cap_drop:
      - all
    cap_add:
      - CHOWN
      - SETGID
      - SETUID
    tmpfs:
      - /tmp:rw,noexec,nosuid
    environment:
      - reschedule=on-node-failure
    ports:
      - "9411:9411"
源码、应用市场等其他创建方式请参考Rainbond文档:创建一个应用

服务创建完成后,我们需要对批量创建的服务进行注册和对部署内存的调整,根据服务之间的调用关系,分析出哪些服务是作为内部服务供给其它服务调用、哪个服务是对用户提供访问的,并进行接下来的操作:

STEP2 服务注册

在 Rainbond 平台,我们可以通过在服务的端口页打开端口来进行服务的注册。关于服务注册的详细文档可参考Rainbond 平台服务注册

各服务对应的端口和部署内存大小如下:

手把手教你实践Service Mesh微服务架构 - 基础部署部分

请注意,这里必须确定对每个服务组件的服务注册信息和资源分配信息设置正确。

STEP3 服务发现

sockshop通过内部域名来进行服务调用,也就是说,在完成服务的注册后,调用服务需要发现被调用服务。

在 Rainbond 平台,我们可以通过服务依赖来实现(详情参考文档服务发现)。

各服务依赖的详情可参考上图商城在Rainbond平台的概览

如果使用上面的 docker-compose 文件创建应用,无需手动添加依赖,在创建应用时系统已根据 docker-compose 文件内容自动配置了服务发现

STEP4 服务 Mesh 治理

在sockshop案例中,front-endNodejs项目,该服务会调用其他 5 个服务来获取数据,如图所示:

手把手教你实践Service Mesh微服务架构 - 基础部署部分

front-end 在调用其他服务时,会使用域名+端口的调用方式(该项目所有调用均为此方式)
front-end 调用 orders 时,内部访问地址为 http://orders/xxx.

Rainbond 平台在服务进行调用时,会默认将顶级域名解析到127.0.0.1,如果调用的服务对应的端口都不冲突没有任何问题,而在此案例中,front-end调用的其他 5 个服务的端口均为 80。因此这里需要第一个治理功能:端口复用。

在不安装 7 层网络治理插件的情况下,平台默认使用 4 层网络治理插件,无法提供端口复用的机制。因此,我们为服务front-end orders 分别安装网络治理插件。

STEP5 安装网络治理插件

我的插件中选择服务网络治理插件进行安装。

手把手教你实践Service Mesh微服务架构 - 基础部署部分

特别注意

工作在 7 层的 Mesh 插件默认会占用 80 端口,因此需要安装此插件的服务本身不能占用 80 端口。因此我们推荐服务尽量监听非 80 端口。插件内存使用量需要根据流量大小调节。

STEP6 应用安装插件

在应用详情页面选择插件标签,然后开通指定的插件。

手把手教你实践Service Mesh微服务架构 - 基础部署部分

Rainbond默认提供的服务网络治理插件是基于Envoy制作,Rainbond ServiceMesh架构为Envoy提供了标准的运行支持。安装插件后需重启应用生效。

STEP7 Mesh 插件配置

配置域名路由,实现端口复用。为了front-end服务能根据代码已有的域名调用选择对应的服务提供方,我们需要根据其调用的域名来进行配置。将应用进行依赖后,服务网络治理插件能够自动识别出其依赖的应用。我们只需在插件的配置的域名项中进行域名配置即可。如下图:

手把手教你实践Service Mesh微服务架构 - 基础部署部分

详细配置

手把手教你实践Service Mesh微服务架构 - 基础部署部分

更新插件相关的配置后进行保存并重启相关应用即可。此处暂时先只用到基于域名的路由配置,关于网络治理插件的更对详情可参考 服务路由,灰度发布,A/B 测试

STEP8 服务性能分析

微服务是一个分布式的架构模式,它一直以来都会有一些自身的问题。当一个应用的运行状态出现异常时,对于运维和开发人员来说,即时发现应用的状态异常并解决是非常有必要的。我们可以通过监控手段对服务进行衡量,或者做一个数据支撑。

Rainbond 平台为我们提供了服务监控与性能监控,可以简单直观的了解服务当前的状态和信息。

目前支持 HTTP 与 mysql 协议的应用
  • 安装插件

手把手教你实践Service Mesh微服务架构 - 基础部署部分

  • 应用安装插件

    同上应用网络治理插件安装

  • 安装完成效果图

    安装完成性能分析插件,可以在安装该插件的应用概览页面查看应用的平均响应时间吞吐率

手把手教你实践Service Mesh微服务架构 - 基础部署部分

除此以外,我们也可以在该组应用的组概览中看到应用的访问情况。

手把手教你实践Service Mesh微服务架构 - 基础部署部分

  • 案例上的性能测试工具服务

    sockshop 商城案例自带性能测试的服务,但是与该项目不是持续运行,而是运行一次后程序便会退出。在这里,我们根据源码进行了一点小的修改。主要是将程序变为不退出运行。源码地址

    我们可以通过源码方式来创建项目——

手把手教你实践Service Mesh微服务架构 - 基础部署部分

创建完成后,我们需要在 sockshop 商城创建一个账号为user、密码为password 的用户,负载测试需要使用该用户名来和密码进行模拟请求。

完成以上步骤,接下来我们对sockshop的分布式跟踪进行处理。

微服务分布式跟踪

zipkin 简介

随着业务越来越复杂,系统也随之进行各种拆分,特别是随着微服务架构和容器技术的兴起,看似简单的一个应用,后台可能有几十个甚至几百个服务在支撑;一个前端的请求可能需要多次的服务调用最后才能完成;当请求变慢或者不可用时,我们无法得知是哪个后台服务引起的,这时就需要解决如何快速定位服务故障点,Zipkin 分布式跟踪系统就能很好的解决这样的问题。

Zipkin 分布式跟踪系统;它可以帮助收集时间数据,解决在 microservice 架构下的延迟问题;它管理这些数据的收集和查找;Zipkin 的设计是基于谷歌的 Google Dapper 论文。
每个应用程序向 Zipkin 报告定时数据,Zipkin UI 呈现了一个依赖图表来展示多少跟踪请求经过了每个应用程序;如果想解决延迟问题,可以过滤或者排序所有的跟踪请求,并且可以查看每个跟踪请求占总跟踪时间的百分比。

  • zipkin 架构

手把手教你实践Service Mesh微服务架构 - 基础部署部分

装配了 zipkin 跟踪器的服务可以将服务的每次调用(可以是 http 或者 rpc 或数据库调用等)延时通过Transport(目前有 4 总共发送方式,http,kafka,scribe,rabbitmq)发送给zipkin服务。

zipkin 主要包含 4 个模块

collector: 接收或收集各应用传输的数据。
storage: 存储接受或收集过来的数据,当前支持 Memory,MySQL,Cassandra,ElasticSearch 等,默认存储在内存中。
API(Query): 负责查询 Storage 中存储的数据,提供简单的 JSON API 获取数据,主要提供给 web UI 使用
Web: 提供简单的 web 界面

  • zipkin 服务追踪流程

手把手教你实践Service Mesh微服务架构 - 基础部署部分

从上图可以简单概括为一次请求调用,zipkin 会在请求中加入跟踪的头部信息和相应的注释,并记录调用的时间并将数据返回给 zipkin 的收集器 collector。

zipkin 安装

在 Rinbond 平台,我们可以直接通过 docker run 方式运行 zipkin.

手把手教你实践Service Mesh微服务架构 - 基础部署部分

注意开启对外访问端口和调整应用内存大小

此时创建的 zipkin 的数据存在于内存中,服务关闭或重启数据都会丢失。因此在生产环境中,我们需要将数据存入存储。

zipkin 支持 MySQL,Cassandra,ElasticSearch 三种存储。我们以 Mysql 为例说明。目前 zipkin 至此的 mysql 版本为 5.6 和 5.7 版本。

在 Rainbond 平台应用市场创建版本为 5.7 的 mysql 应用,如图。

手把手教你实践Service Mesh微服务架构 - 基础部署部分

创建完成 mysql 以后,我们需要进行数据库的初始化操作,zipkin 需要使用到 zipkin 数据和相应的表结构,需要我们自行创建。

在应用的详情页面,我们可以选择管理容器进入到容器进行操作,如图。

手把手教你实践Service Mesh微服务架构 - 基础部署部分

进入容器后,使用命令登录 mysql 命令行。

mysql -uusername -ppassword

mysql 的用户和密码可以在应用的依赖里看到
如图

手把手教你实践Service Mesh微服务架构 - 基础部署部分

进入 mysql 命令行后,创建数据库 zipkin

CREATE DATABASE zipkin ;

创建 zipkin 相关的表:下载

CREATE TABLE IF NOT EXISTS zipkin_spans (
  `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
  `trace_id` BIGINT NOT NULL,
  `id` BIGINT NOT NULL,
  `name` VARCHAR(255) NOT NULL,
  `parent_id` BIGINT,
  `debug` BIT(1),
  `start_ts` BIGINT COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL',
  `duration` BIGINT COMMENT 'Span.duration(): micros used for minDuration and maxDuration query'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;

ALTER TABLE zipkin_spans ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `id`) COMMENT 'ignore insert on duplicate';
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`, `id`) COMMENT 'for joining with zipkin_annotations';
ALTER TABLE zipkin_spans ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTracesByIds';
ALTER TABLE zipkin_spans ADD INDEX(`name`) COMMENT 'for getTraces and getSpanNames';
ALTER TABLE zipkin_spans ADD INDEX(`start_ts`) COMMENT 'for getTraces ordering and range';

CREATE TABLE IF NOT EXISTS zipkin_annotations (
  `trace_id_high` BIGINT NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
  `trace_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.trace_id',
  `span_id` BIGINT NOT NULL COMMENT 'coincides with zipkin_spans.id',
  `a_key` VARCHAR(255) NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1',
  `a_value` BLOB COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB',
  `a_type` INT NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation',
  `a_timestamp` BIGINT COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp',
  `endpoint_ipv4` INT COMMENT 'Null when Binary/Annotation.endpoint is null',
  `endpoint_ipv6` BINARY(16) COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address',
  `endpoint_port` SMALLINT COMMENT 'Null when Binary/Annotation.endpoint is null',
  `endpoint_service_name` VARCHAR(255) COMMENT 'Null when Binary/Annotation.endpoint is null'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;

ALTER TABLE zipkin_annotations ADD UNIQUE KEY(`trace_id_high`, `trace_id`, `span_id`, `a_key`, `a_timestamp`) COMMENT 'Ignore insert on duplicate';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`, `span_id`) COMMENT 'for joining with zipkin_spans';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id_high`, `trace_id`) COMMENT 'for getTraces/ByIds';
ALTER TABLE zipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT 'for getTraces and getServiceNames';
ALTER TABLE zipkin_annotations ADD INDEX(`a_type`) COMMENT 'for getTraces';
ALTER TABLE zipkin_annotations ADD INDEX(`a_key`) COMMENT 'for getTraces';
ALTER TABLE zipkin_annotations ADD INDEX(`trace_id`, `span_id`, `a_key`) COMMENT 'for dependencies job';

CREATE TABLE IF NOT EXISTS zipkin_dependencies (
  `day` DATE NOT NULL,
  `parent` VARCHAR(255) NOT NULL,
  `child` VARCHAR(255) NOT NULL,
  `call_count` BIGINT,
  `error_count` BIGINT
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8 COLLATE utf8_general_ci;

ALTER TABLE zipkin_dependencies ADD UNIQUE KEY(`day`, `parent`, `child`);

在 zipkin 服务中添加环境变量 STORAGE_TYPEMySQL,此变量标志 zipkin 使用的存储方式。可选择值为 MySQL,elasticsearchcassandra

将 zipkin 与 mysql 建立依赖关系后,zipkin 服务便安装完成。

zipkin 内部会默认调用环境变量 MYSQL_USER(用户名),MYSQL_PASS(密码),MYSQL_HOST(连接地址),MYSQL_PORT(端口)。刚好与 Rainbond 平台默认设置的变量一致,所以无需做任何修改。

其他服务如果连接的变量与 Rainbond 平台默认提供的不一致,我们可以在应用的设置也添加相应的环境变量来达到访问的目的。

sockshop 中的 zipkin 案例

sockshop 案例集成了zipkin做分布式跟踪。集成的组件为 userscartsorderspaymentcatalogueshipping

其中 cartsordersshippingspring-boot项目,只需在设置中将环境变量JAVA_OPTS-Dspring.zipkin.enabled改为true即可。

如图

手把手教你实践Service Mesh微服务架构 - 基础部署部分

paymentcatalogueusersGolang项目,项目已在内部集成了 zipkin 组件,我们需要添加环境变量zipkinhttp://zipkin:9411/api/v1/spans 来明确服务调用 zipkin 的地址。

如图

手把手教你实践Service Mesh微服务架构 - 基础部署部分

设置完成后,可以做直接访问 zipkin 应用对外提供的访问地址。访问详情如图

手把手教你实践Service Mesh微服务架构 - 基础部署部分

我们可以在该图中查看各个服务调用的延时详情。

小结

至此,我们已经完成了基础部署,可以看到完整的业务拓扑图,sockshop也已经可以正常工作了。毫不夸张得说,项目与实际电商系统也只是差一些业务逻辑了:)

接下来的进阶部分,我们会完成每一个服务的水平伸缩持续集成与部署数据备份灰度发布等,敬请关注。

关于Rainbond

Rainbond是一款以应用为中心的开源PaaS,由好雨基于Docker、Kubernetes等容器技术自主研发,可作为公有云或私有云环境下的应用交付平台、DevOps平台、自动化运维平台和行业云平台,或作为企业级的混合云多云管理工具、Kubernetes容器管理工具或Service Mesh微服务架构治理工具。

相关推荐