随着Python、Node.js等语言的快速发展,多语言微服务应用越来越多。EDAS能够通过服务网格支持部署多语言微服务应用,且提供应用托管和服务发现、链路追踪、负载均衡等服务治理能力。

背景信息

应用从最初的单体架构演变到目前的微服务架构,在带来便利的同时也大大增加了服务部署、运维的复杂度。而微服务本身可以是任意语言开发的,在部署多语言服务后,如何对多语言的微服务提供通用的链路追踪、服务发现、负载均衡等能力,一种做法是提供多语言的SDK,一种是服务网格。SDK对应用有侵入性,而服务网格在无侵入性的同时也能提供服务发现、负载均衡、链路追踪等能力,EDAS对多语言的支持正是采用的后者——服务网格。

服务网格是致力于解决服务间通讯的基础设施层。它负责在现代云原生应用程序的复杂服务拓扑中可靠地传递请求,通常是通过一组轻量级网络代理,与应用程序部署在一起来实现,而无需感知应用程序本身。

示例场景说明

示例应用BookInfo模仿在线书店的一个分类,显示一本书的信息。页面上会显示一本书的描述,书籍的细节(ISBN、页数等),以及关于这本书的一些评论。

BookInfo是一个异构应用,几个微服务应用是由不同的语言编写的。这些服务构成了一个有代表性的服务网格的例子:由多个服务、多个语言构成,并且Reviews服务具有多个版本。

图 1. 多语言服务架构
EDAS示例场景的微服务架构

BookInfo应用包含四个单独的服务:

  • Productpage:为Python服务,会调用Details和Reviews两个服务,用来生成页面。同时,Productpage还包含登录和登出功能。
  • Details:为Ruby服务,包含了书籍的信息。
  • Reviews:为Java服务,包含了书籍相关的评论,还会调用Ratings服务。Reviews包含3个版本:
    • v1版本不会调用Ratings服务。
    • v2版本会调用Ratings服务,并使用1到5个黑色星形图标来显示评分信息。
    • v3版本会调用Ratings服务,并使用1到5个红色星形图标来显示评分信息。
  • Ratings:为Node.js服务,包含了由书籍评价组成的评级信息。

前提条件

将示例应用制作成镜像,并上传到阿里云镜像仓库。上传镜像的具体操作,请参见构建仓库与镜像

示例应用下载地址:BookInfo Sample

步骤一:创建容器服务Kubernetes集群

登录容器服务Kubernetes版控制台,创建容器服务Kubernetes集群。具体操作,请参见快速创建Kubernetes托管版集群

如果创建ASK集群,专有网络请选择自动创建(选择已有网络,创建完毕后请查看集群资源是否包含VPC和交换机资源),服务发现请选择PrivateZone,以便ASK集群在导入EDAS后可以使用服务网格。

创建ASK集群

步骤二:为用户授予开通服务网格的操作权限

EDAS通过服务网格支持多语言应用,所以需要为用户授予开通服务网格ASM的操作权限。

  1. 使用云账号登录RAM控制台
  2. 创建服务网格的权限策略。
    1. 在左侧导航栏选择权限管理 > 权限策略管理
    2. 权限策略管理页面单击创建权限策略
    3. 新建自定义权限策略页面输入策略名称配置模式选择脚本配置,在策略内容区域输入服务网格的自定义权限策略,然后单击确定

      测试服务的自定义权限策略内容如下:

      {
          "Version": "1",
          "Statement": [
              {
                  "Action": [
                      "ecs:CreateSecurityGroup",
                      "ecs:CreateSecurityGroupPermissions",
                      "ecs:DeleteSecurityGroup",
                      "ecs:DescribeAccountAttributes",
                      "ecs:DescribeSecurityGroups",
                      "ecs:AuthorizeSecurityGroup",
                      "ecs:RevokeSecurityGroup",
                      "ecs:AuthorizeSecurityGroupEgress",
                      "ecs:JoinSecurityGroup",
                      "ecs:LeaveSecurityGroup",
                      "ecs:UnassociateEipAddress",
                      "ecs:ReleaseEipAddress",
                      "ecs:RevokeSecurityGroupEgress",
                      "ecs:DescribeInstances",
                      "ecs:DescribeNetworkInterfaces"
                  ],
                  "Resource": "*",
                  "Effect": "Allow"
              },
              {
                  "Action": [
                      "vpc:DescribeVpcs",
                      "vpc:DescribeVSwitches",
                      "vpc:DescribeEipAddresses",
                      "vpc:DescribeNetworkQuotas",
                      "vpc:AllocateEipAddress",
                      "vpc:AssociateEipAddress",
                      "vpc:UnassociateEipAddress",
                      "vpc:ReleaseEipAddress",
                      "vpc:DeletionProtection",
                      "vpc:DescribeVpcAttribute"
                  ],
                  "Resource": "*",
                  "Effect": "Allow"
              },
              {
                  "Action": [
                      "slb:DescribeLoadBalancerAttribute",
                      "slb:CreateLoadBalancer",
                      "slb:DeleteLoadBalancer",
                      "slb:RemoveBackendServers",
                      "slb:StartLoadBalancerListener",
                      "slb:StopLoadBalancerListener",
                      "slb:CreateLoadBalancerTCPListener",
                      "slb:AddBackendServers",
                      "slb:CreateVServerGroup",
                      "slb:CreateLoadBalancerHTTPSListener",
                      "slb:CreateLoadBalancerUDPListener",
                      "slb:ModifyLoadBalancerInternetSpec",
                      "slb:SetBackendServers",
                      "slb:AddVServerGroupBackendServers",
                      "slb:DeleteVServerGroup",
                      "slb:ModifyVServerGroupBackendServers",
                      "slb:CreateLoadBalancerHTTPListener",
                      "slb:RemoveVServerGroupBackendServers",
                      "slb:DeleteLoadBalancerListener",
                      "slb:AddTags",
                      "slb:RemoveTags",
                      "slb:SetLoadBalancerDeleteProtection",
                      "slb:DescribeLoadBalancers",
                      "slb:DescribeHealthStatus",
                      "slb:DescribeLoadBalancerTCPListenerAttribute"
                  ],
                  "Resource": [
                      "*"
                  ],
                  "Effect": "Allow"
              },
              {
                  "Action": "xtrace:GetToken",
                  "Resource": "*",
                  "Effect": "Allow"
              },
              {
                  "Action": [
                      "cen:DescribeCenAttachedChildInstances",
                      "cen:DescribeCens"
                  ],
                  "Resource": "*",
                  "Effect": "Allow"
              },
              {
                  "Action": [
                      "arms:ListClusterFromGrafana",
                      "arms:GetPrometheusApiToken",
                      "arms:Get*"
                  ],
                  "Resource": "*",
                  "Effect": "Allow"
              },
              {
                  "Action": [
                      "log:GetProject",
                      "log:GetDashboard"
                  ],
                  "Resource": "*",
                  "Effect": "Allow"
              }
          ]
      }
  3. 创建RAM角色,修改其信任策略并为RAM角色授权。
    1. 在RAM控制台左侧导航栏单击RAM角色管理
    2. RAM角色管理页面单击创建RAM角色
    3. 创建RAM角色面板中的选择类型页面选择阿里云账号,单击下一步
    4. 配置角色页面角色名称文本框中输入AliyunServiceMeshDefaultRole,在选择云账号下方选择当前云账号,然后单击完成
    5. 返回RAM角色管理页面,单击刚刚创建的RAM角色名称。
    6. 在RAM角色详情页面单击信任策略管理页签,在信任策略管理页签中单击修改信任策略,输入以下自定义策略内容,单击确定
      {
          "Statement": [
              {
                  "Action": "sts:AssumeRole",
                  "Effect": "Allow",
                  "Principal": {
                      "Service": [
                          "servicemesh.aliyuncs.com"
                      ]
                  }
              }
          ],
          "Version": "1"
      }
  4. 为RAM角色授予开通服务网格的操作权限,请参见为RAM用户授权
  5. 配置完成后,在RAM控制台确认授权是否成功。
    1. 使用云账号登录RAM控制台
    2. 在左侧导航栏单击RAM角色管理
    3. RAM角色管理页面创建RAM角色右侧的文本框中输入AliyunServiceMeshDefaultRole,然后在RAM角色名称列中单击AliyunServiceMeshDefaultRole
    4. AliyunServiceMeshDefaultRole页面的权限策略列表中查看是否已包含AliyunServiceMeshRolePolicy
      服务网格角色详情

步骤:在EDAS控制台导入容器服务K8s集群

在EDAS控制台导入容器服务K8s集群时,会默认安装应用防护(限流降级)组件(ack-ahas-sentinel-pilot)、ARMS监控组件(ack-arms-pilot)以及Prometheus监控组件(ack-arms-prometheus)。

  1. 登录EDAS控制台
  2. 在左侧导航栏选择资源管理 > 容器服务K8s集群
  3. 在顶部菜单栏选择地域,选择要导入集群的目标命名空间,然后单击同步容器服务Kubernetes集群
  4. 在导入的容器服务K8s集群的操作列单击导入
  5. 导入Kubernetes集群对话框选择命名空间,并打开服务网格开关,然后单击导入
    说明
    • 如果您未创建命名空间,在此步骤可不选择命名空间,使用默认命名空间。
    • 如果您的集群已经导入且未开启服务网格,可在集群列表的服务网格列单击开关来开启。
    • 开启服务网格时,默认会创建两个私网规格SLB实例(slb.s1.small)实现管控,并暴露两个SLB实例的端口(6443端口和15011端口)。更多信息,请参见背景信息

      默认生成的两个小规格SLB实例(slb.s1.small)会产生费用。具体收费标准,请参见SLB收费标准

    当容器服务K8s集群状态显示为运行中,并且导入状态显示为导入成功时,表示容器服务K8s集群已成功导入到EDAS。

步骤:添加链路追踪功能

EDAS控制台部署多语言应用采用的是基于Istio的服务网格链路追踪的监控方案。Istio代理虽能够自动发送Span信息,但应用程序仍然需要携带HTTP表头信息,以实现在发送Span信息时将Span信息正确地关联到单个跟踪中。

应用程序需要携带以下HTTP表头,并将其从传入请求传播到任意传出请求。
  • x-request-id
  • x-b3-traceid
  • x-b3-spanid
  • x-b3-parentspanid
  • x-b3-sampled
  • x-b3-flags
  • x-ot-span-context

此处仅介绍示例多语言应用中部分服务的应用程序中携带的HTTP表头信息。

以Python语言实现的Productpage服务的应用程序中携带的HTTP表头如下:
def getForwardHeaders(request):
    headers = {}

    # x-b3-*** headers can be populated using the opentracing span
    span = get_current_span()
    carrier = {}
    tracer.inject(
        span_context=span.context,
        format=Format.HTTP_HEADERS,
        carrier=carrier)

    headers.update(carrier)

    # ...

    incoming_headers = ['x-request-id']

    # ...

    for ihdr in incoming_headers:
        val = request.headers.get(ihdr)
        if val is not None:
            headers[ihdr] = val

    return headers
以Java语言实现的Reviews服务的应用程序中携带的HTTP表头如下:
@GET
@Path("/reviews/{productId}")
public Response bookReviewsById(@PathParam("productId") int productId,
                            @HeaderParam("end-user") String user,
                            @HeaderParam("x-request-id") String xreq,
                            @HeaderParam("x-b3-traceid") String xtraceid,
                            @HeaderParam("x-b3-spanid") String xspanid,
                            @HeaderParam("x-b3-parentspanid") String xparentspanid,
                            @HeaderParam("x-b3-sampled") String xsampled,
                            @HeaderParam("x-b3-flags") String xflags,
                            @HeaderParam("x-ot-span-context") String xotspan) {

  if (ratings_enabled) {
    JsonObject ratingsResponse = getRatings(Integer.toString(productId), user, xreq, xtraceid, xspanid, xparentspanid, xsampled, xflags, xotspan);

步骤五:在容器服务K8s版集群中部署多语言应用

您需要将场景示例中的几个服务以应用的形式分别部署到EDAS中。下面介绍单个服务的部署过程。

说明 多语言应用目前仅支持使用镜像部署。
  1. 登录EDAS控制台
  2. 在左侧导航栏单击应用列表,在顶部菜单栏选择地域,在页面中选择目标命名空间,然后单击创建应用
  3. 应用基本信息页面中设置应用的集群类型和应用运行环境,然后单击下一步
    参数 描述
    集群类型 选择Kubernetes集群
    应用运行环境 托管应用类型区域选择多语言Node.js、C++、Go…
  4. 应用配置页签中设置应用的环境信息、基本信息及镜像相关配置,设置完成后单击下一步
    参数 描述
    命名空间 选择您导入的K8s集群所在的命名空间,如果您未创建命名空间或不做选择,将默认选择默认命名空间。
    集群 在右侧下拉列表内选择导入的容器服务K8s集群。
    K8s Namespace 此处以选择default为例。
    应用名称 输入应用名称,必须以字母开头,允许数字、字母、短划线(-)组合。最大长度为36个字符。
    应用描述 输入应用描述 ,最大长度为128个字符。
    版本 可在右侧单击生成版本号来生成版本,也可按照页面提示自定义版本号。
    镜像类型 选择配置镜像,然后选择镜像所属地域和目标镜像,并设置镜像版本。
    Pod总数 设置该应用要部署的Pod个数。
    单Pod资源配额 设置单个Pod的CPU和内存,如果您需要限额,请填写具体的数字,使用默认值0则代表不限额。
  5. 配置应用高级设置,完成配置后单击创建应用
    1. 配置服务网格。
      参数 描述
      协议 在下拉列表选择协支持的协议。
      服务名 请输入应用提供的服务名,要和应用代码中的服务名一致,以保证服务能成功注册和被调用。
      服务端口 请输入应用提供的服务端口,要和应用代码中的服务端口一致,以保证服务能成功注册和被调用。
    2. 可选:配置以下高级设置。
  6. 完成设置后单击创建应用,然后在应用创建完成页签单击确定创建应用
    应用创建可能需要几分钟,创建过程中,可以在页面上方单击查看详情,跳转到应用的变更记录页面查看部署进度及相关日志。创建完成后,返回应用总览页面查看实例Pod的运行状态若为运行中则说明应用发布成功,单击Pod的运行状态可以查看应用实例的工作负载容器组(Pod)启动命令等高级配置信息。
  7. 重复执行上述步骤,部署示例应用的其他服务。

结果验证

服务部署完成后,访问主服务,页面上会显示一本书的描述,书籍的细节(ISBN、页数等),以及关于这本书的一些评论。同时还能进行登录、登出操作。

  1. 应用总览页面的访问方式配置区域单击负载均衡(公网)右侧的图标。
  2. 负载均衡(公网)对话框中设置SLB和监听参数,然后单击确认
    1. 选择SLB右侧的下拉列表中选择新建SLB
      如果您有SLB实例,可以在列表中选择已有的SLB实例。
    2. 选择协议,然后在协议右侧单击添加新的监听,然后将SLB端口和容器端口分别设置为8018082
      说明 如果您选择了HTTPS协议,则还需要选择SSL证书
      添加公网SLB大概需要30秒。添加完成后,访问方式配置区域的负载均衡(公网)右侧会显示公网SLB的地址,格式为SLB实例IP:端口
  3. 复制公网SLB地址,在浏览器中粘贴该公网地址进入到示例应用(在线书店)首页。
    访问客户端应用
  4. 在客户端应用首页单击Normal user或者Test user
  5. 在页面顶部单击Sign in,输入User NamePassword,然后单击Sign in登录示例应用。
    本示例的User NamePassword均为adminSign in
    在该示例多语言应用里,您可看到书籍的详细信息和相关评论。书籍的详细信息和书评

应用监控

示例多语言应用部署完成后,您可以查看应用的健康状况关键指标,包括请求总量、平均响应时间等总体指标,应用所提供的服务、所依赖的服务的相关指标;以及CPU使用量、内存使用量等系统信息。

  1. 登录EDAS控制台
  2. 在左侧导航栏中单击应用列表,在顶部菜单栏选择地域并在页面上方选择命名空间,然后在应用列表页面单击具体的应用名称。
  3. 查看应用的健康状况指标。
    1. 在左侧导航栏单击应用总览
    2. 应用总览页面右侧区域单击概览分析页签。
      您可在概览分析页签查看该应用的请求总量、平均响应时间等总体指标和应用所提供的服务、所依赖的服务的相关指标。更多信息,请参见概览分析概览分析-健康指标
    3. 应用总览页面右侧区域单击拓扑图页签。
      您可在拓扑图页签查看上下级组件及调用关系,以及请求数、响应时间和错误率等指标。更多信息,请参见应用总览拓扑图-性能指标
  4. 查看应用的系统使用量情况。
    1. 在左侧导航栏选择监控 > Prometheus
    2. Prometheus页面左上角选择目标namespacePod,然后在右上角选择目标时间段。
      您可在该页面查看该应用的Pod IP地址、Pod状态、Pod容器,以及Pod CPU使用率、Pod内存使用率等系统信息。更多信息,请参见查看监控指标Prometheus监控-系统信息

EDAS多语言应用交流群

如果您在部署EDAS多语言微服务应用过程中有任何疑问或建议,请提交工单,或使用钉钉扫描下面的二维码或搜索钉钉群号23307994加入钉钉群进行反馈。

EDAS多语言应用交流群