转载

使用Eureka的微服务如何上Istio

使用Eureka的微服务如何上Istio

由于当前公司的绝大多数应用都还是使用eureka来做服务发现,算法团队又提出一些灰度的需求。从不重复造轮子以及技术趋势的角度,自然想到是否能够将Istio整合到平台产品中。但是,据我所知,eureka2.0已经被放弃了,Istio也在早期版本中,直接废弃了对Eureka做服务发现的支持。因此,这可能需要对现有的脚手架做一定改造,这个后面具体再论。另一封面,听说istio在架构上发生了很大的变化,于是下载了1.5版本来亲自体验了一下。

v1.5架构变化

其实也不能够算是软件架构的大变化,更准确的说法应该是部署架构的变化。就是将之前控制面上多个微服务都变为模块的形势,统一放到istiod里面去运行。这样一个比较大的好处是,部署起来就方便多了。当然,提到部署的便捷性,istioctl这个工具还是相当不错的。基于 istioctl manifest apply 可以快速按照用户指定的profile拉起来一个istio环境。但是,除了istio自带的模块被打包到了istiod里面之外,如果你还启用了调用链、监控、遥测之类的功能的话,在istio-system中,依然还有一大堆pod。如果想要彻底变革,这些是不是也可以考虑整合一把,哪怕是放到一个pod里面去,比如说kiali其实是高度依赖于prometheus的数据的。

使用Istio的顾虑

早年openstack上neutron还不够成熟的时候,经常出现VM网络不通,然后又各种折腾排查,动不动就需要在某个接口上tcpdump。说实在的,这样的日子如履薄冰,天天神经脆弱。我所理解的Istio中traffic的部分,本质是在容器层面的SDN;比起VM网络,envoy上大量的XDS规则,一旦有问题,可能比VM网络更难排查。所以,长久以来,对这货都不敢轻易引入(当年v1.0似乎也是在赶鸭子上架,很快速的又推出了新版本)。

好在如今的版本中,通过 istioctl pc 等工具,极大的降低了登录到各个数据面节点上排查路由规则的复杂度,同时, istioctl ps 也提供了控制面板到数据面配置一致性的debug手法。所以,现在采用istio,在功能层面,我是没啥顾虑的;那么,唯一可能比较担心的还是在于性能层面。

但是,我们完全可以将该技术先只使用到开发测试环境,等待其数据面代理性能得到一个整体提升后,再最终用到生产中。

最适用的功能

在Istio提供的所有功能中,其实也没必要全都采纳,比如调用链APM这块,我就觉得比较二,对代码的入侵性太强,我更愿意在平台类产品中整合pinpoint或者skywalking之类专业的调用链。

另外,数据安全加密这块,对于绝大多数的应用场景,其实都是非必要的,关键它还对转发性能造成了较大影响。就像做管理决策,当我们排优先级的时候,哪些事情是能够为企业带来更大价值产出的,那么我认为这些事理应提高其优先级。

如前面所述,先在开发测试环境中,使用Istio的traffic控制相关的功能,同时,能够使用遥测数据来生成项目微服务全局的流量状况视图,应该算是比较稳妥的。

与eureka/consul兼容

我这里提到consul,其实并非是想将consul与Istio的pilot-discovery对接,因为之前在研究这块的时候,感觉不过是一个炫头,大家一般都不这么用。

这里有一个插曲,大概是2018年的时候,有一次我在团队里面提出过:“到底istio的数据面是如何转发流量的?”

这个问题是源于istio默认结合k8s的服务发现来工作,我们都知道,K8S的服务发现,是通过watch service上的cluster IP变化来刷新dns的(这个设计很巧妙,为啥不是直接刷新pod IP,而要使用cluster IP呢?因为dns client一般都有缓存,而cluster IP到pod ip的路由是由K8S实时控制的,因此…)。

我们回来讲Istio,因为Istio是基于K8S来做服务发现的,如果服务A要访问B,不管A和B的sidecar会对流量做怎么的操作。毕竟A的程序看不到sidecar,它要转流量到B之前,总是会查询B的地址,但是在DNS查询返回的IP列表里面,目的IP是cluster IP。这时,报文被封装,并基于网络协议栈发送出去,再被A的sidecar拦截。此时A的sidecar会基于路由规则来修改A的真实转发地址。那么,在这个过程中,A发送出去报文的目的IP地址,到底有啥“卵用”?

从本质上来说,这货确实没啥用,因为都被替换了。但是,也不是绝对的,假设A、B之间发送的报文是基于HTTP协议。我们知道HTTP协议有一个header,header里面有一个叫做“host”的一级公民。其实envoy就是依据这个host来首先过滤流量的。比如说,A要发送请求个B服务,如果它使用的是 service-b 这个域名的话,虽然协议栈会解析出service-b的IP来做底层IP报文的destination IP。但是顶层的HTTP协议封装的时候,依然会使用“host:service-b”来封装header。另一方面,所有TCP报文送出pod的时候,都会被其sidecar拦截并基于转发逻辑转发报文;所以,都还没有等IP报文中的destination IP发挥作用,就被sidecar替换了。因此,这个只要我们保障HTTP的“host”字段匹配了envoy中的规则,目的IP不重要。

比如,A要发送报文给B,当我们配置好了Istio规则之后,A在封装报文的时候,只需要确保HTTP的header中包含“host:B”,而目的IP可以随便填写。

所以,结论就是:A怎么知道B的目的IP这个事情,真的不重要!A是通过consul,eureka还是基于K8S dns查询到B的地址的,都一样。唯一重要的是是否满足envoy的转发规则。

但是,HTTP的很多SDK库都会忽略掉我们在上层直接设置的host值,而采用所访问域名或者目的IP地址。这就是一个坑,所以,需要对代码脚手架做一些改造,目标就是强制设置该host值。

实践

以下是一个调用链的例子,我写了一个程序,通过环境变量传入微服务名称和工作模式,其中 stub 模式为叶子节点, ingress 模式为流量入口(树根节点),需要指定其下一跳节点(各分支或叶子)。所有的服务起来之后,都会将自己注册到consul上面,consul会基于注册的心跳时间来对各个服务发起健康检查。当服务A要访问服务B的时候,会在consul上查询存活的B列表,并访问B服务。

调用链拓扑为:

a - > c
  - > b - > d

我们实际部署后,在kiali上看到的流量转发图是这样的:

使用Eureka的微服务如何上Istio

除了业务的调用链之外,我们看到consul有往各个service做健康检查的流量。由于consul发往各个service的流量的host我们没法做修改,因此看到了匹配passthrough cluster的力量。这个是Istio上如果流量没法匹配对应的路由,就会发送到passthrough cluster,最终基于报文的目标IP来转发(当然也可以配置成黑洞模式,这样没法匹配istio中路由规则的流量就会全部被干掉)。

下面是k8s上,启动整个应用各微服务的yaml文件和istio config文件:

  • services.yaml
apiVersion: v1
items:
- apiVersion: extensions/v1beta1
  kind: Deployment
  metadata:
    labels:
      run: centos
    name: centos
    namespace: default
  spec:
    replicas: 1
    selector:
      matchLabels:
        run: centos
    template:
      metadata:
        labels:
          run: centos
      spec:
        containers:
        - args:
          - sleep
          - "999999"
          image: centos:7
          imagePullPolicy: IfNotPresent
          name: centos
- apiVersion: extensions/v1beta1
  kind: Deployment
  metadata:
    labels:
      app: consul
      version: v1
    name: consul
    namespace: default
  spec:
    replicas: 1
    selector:
      matchLabels:
        app: consul
        version: v1
    template:
      metadata:
        labels:
          app: consul
          version: v1
      spec:
        containers:
        - env:
          - name: CONSUL_BIND_INTERFACE
            value: eth0
          image: consul
          imagePullPolicy: Always
          name: consul
          ports:
          - containerPort: 8500
            protocol: TCP
- apiVersion: extensions/v1beta1
  kind: Deployment
  metadata:
    labels:
      app: service-a
      version: v1
    name: service-a
    namespace: default
  spec:
    replicas: 1
    selector:
      matchLabels:
        app: service-a
        version: v1
    template:
      metadata:
        labels:
          app: service-a
          version: v1
      spec:
        containers:
        - env:
          - name: CONSUL_ADDR
            value: consul
          - name: CONSUL_PORT
            value: "8500"
          - name: app
            value: service-a
          - name: mode
            value: ingress
          - name: next_services
            value: service-b,service-c
          image: ljchen/istio-demo
          imagePullPolicy: Always
          name: service-a
          ports:
          - containerPort: 9090
            name: http
            protocol: TCP
- apiVersion: extensions/v1beta1
  kind: Deployment
  metadata:
    labels:
      app: service-b
      version: v1
    name: service-b
    namespace: default
  spec:
    replicas: 1
    selector:
      matchLabels:
        app: service-b
        version: v1
    template:
      metadata:
        labels:
          app: service-b
          version: v1
      spec:
        containers:
        - env:
          - name: CONSUL_ADDR
            value: consul
          - name: CONSUL_PORT
            value: "8500"
          - name: app
            value: service-b
          - name: next_services
            value: service-d
          image: ljchen/istio-demo
          imagePullPolicy: Always
          name: service-b
          ports:
          - containerPort: 9090
            name: http
            protocol: TCP
- apiVersion: extensions/v1beta1
  kind: Deployment
  metadata:
    labels:
      app: service-c
      version: v1
    name: service-c
    namespace: default
  spec:
    replicas: 1
    selector:
      matchLabels:
        app: service-c
        version: v1
    template:
      metadata:
        labels:
          app: service-c
          version: v1
      spec:
        containers:
        - env:
          - name: CONSUL_ADDR
            value: consul
          - name: CONSUL_PORT
            value: "8500"
          - name: app
            value: service-c
          - name: mode
            value: stub
          image: ljchen/istio-demo
          imagePullPolicy: Always
          name: service-c
          ports:
          - containerPort: 9090
            name: http
            protocol: TCP
- apiVersion: extensions/v1beta1
  kind: Deployment
  metadata:
    labels:
      app: service-d
      version: v1
    name: service-d-v1
    namespace: default
  spec:
    replicas: 1
    selector:
      matchLabels:
        app: service-d
        version: v1
    template:
      metadata:
        labels:
          app: service-d
          version: v1
      spec:
        containers:
        - env:
          - name: CONSUL_ADDR
            value: consul
          - name: CONSUL_PORT
            value: "8500"
          - name: app
            value: service-d
          - name: mode
            value: stub
          - name: version
            value: v1
          image: ljchen/istio-demo
          imagePullPolicy: Always
          name: service-d-v1
          ports:
          - containerPort: 9090
            name: http
            protocol: TCP
- apiVersion: extensions/v1beta1
  kind: Deployment
  metadata:
    labels:
      app: service-d
      version: v2
    name: service-d-v2
    namespace: default
  spec:
    progressDeadlineSeconds: 600
    replicas: 1
    selector:
      matchLabels:
        app: service-d
        version: v2
    strategy:
      rollingUpdate:
        maxSurge: 25%
        maxUnavailable: 25%
      type: RollingUpdate
    template:
      metadata:
        labels:
          app: service-d
          version: v2
      spec:
        containers:
        - env:
          - name: CONSUL_ADDR
            value: consul
          - name: CONSUL_PORT
            value: "8500"
          - name: app
            value: service-d
          - name: mode
            value: stub
          - name: version
            value: v2
          image: ljchen/istio-demo
          imagePullPolicy: Always
          name: service-d-v2
          ports:
          - containerPort: 9090
            name: http
            protocol: TCP
        dnsPolicy: ClusterFirst
        restartPolicy: Always
- apiVersion: v1
  kind: Service
  metadata:
    labels:
      app: consul
      version: v1
    name: consul
    namespace: default
  spec:
    externalTrafficPolicy: Cluster
    ports:
    - name: http
      nodePort: 31057
      port: 8500
      protocol: TCP
      targetPort: 8500
    selector:
      app: consul
      version: v1
    sessionAffinity: None
    type: NodePort
- apiVersion: v1
  kind: Service
  metadata:
    labels:
      component: apiserver
      provider: kubernetes
    name: kubernetes
    namespace: default
  spec:
    ports:
    - name: https
      port: 443
      protocol: TCP
      targetPort: 6443
    sessionAffinity: None
    type: ClusterIP
- apiVersion: v1
  kind: Service
  metadata:
    labels:
      app: service-a
      version: v1
    name: service-a
    namespace: default
  spec:
    ports:
    - name: http
      port: 9090
      protocol: TCP
      targetPort: 9090
    selector:
      app: service-a
    sessionAffinity: None
    type: ClusterIP
- apiVersion: v1
  kind: Service
  metadata:
    labels:
      app: service-b
      version: v1
    name: service-b
    namespace: default
  spec:
    ports:
    - name: http
      port: 9090
      protocol: TCP
      targetPort: 9090
    selector:
      app: service-b
    sessionAffinity: None
    type: ClusterIP
- apiVersion: v1
  kind: Service
  metadata:
    labels:
      app: service-c
      version: v1
    name: service-c
    namespace: default
  spec:
    ports:
    - name: http
      port: 9090
      protocol: TCP
      targetPort: 9090
    selector:
      app: service-c
    sessionAffinity: None
    type: ClusterIP
- apiVersion: v1
  kind: Service
  metadata:
    labels:
      app: service-d
      version: v1
    name: service-d
    namespace: default
  spec:
    ports:
    - name: http
      port: 9090
      protocol: TCP
      targetPort: 9090
    selector:
      app: service-d
    sessionAffinity: None
    type: ClusterIP
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""
  • istio-config.yaml
apiVersion: v1
items:
- apiVersion: networking.istio.io/v1beta1
  kind: VirtualService
  metadata:
    name: consul
    namespace: default
  spec:
    hosts:
    - consul
    http:
    - route:
      - destination:
          host: consul
          subset: v1
- apiVersion: networking.istio.io/v1beta1
  kind: VirtualService
  metadata:
    name: service-a
    namespace: default
  spec:
    gateways:
    - gateway
    hosts:
    - ljchen.net
    http:
    - route:
      - destination:
          host: service-a
          subset: v1
- apiVersion: networking.istio.io/v1beta1
  kind: VirtualService
  metadata:
    name: service-b
    namespace: default
  spec:
    hosts:
    - service-b
    http:
    - route:
      - destination:
          host: service-b
          subset: v1
- apiVersion: networking.istio.io/v1beta1
  kind: VirtualService
  metadata:
    name: service-c
    namespace: default
  spec:
    hosts:
    - service-c
    http:
    - route:
      - destination:
          host: service-c
          subset: v1
- apiVersion: networking.istio.io/v1beta1
  kind: VirtualService
  metadata:
    name: service-d
    namespace: default
  spec:
    hosts:
    - service-d
    http:
    - route:
      - destination:
          host: service-d
          subset: v1
      - destination:
          host: service-d
          subset: v2
        weight: 100
- apiVersion: networking.istio.io/v1beta1
  kind: Gateway
  metadata:
    name: gateway
    namespace: default
  spec:
    selector:
      istio: ingressgateway
    servers:
    - hosts:
      - ljchen.net
      port:
        name: http
        number: 80
        protocol: HTTP
- apiVersion: networking.istio.io/v1beta1
  kind: DestinationRule
  metadata:
    name: consul
    namespace: default
  spec:
    host: consul
    subsets:
    - labels:
        version: v1
      name: v1
- apiVersion: networking.istio.io/v1beta1
  kind: DestinationRule
  metadata:
    name: service-a
    namespace: default
  spec:
    host: service-a
    subsets:
    - labels:
        version: v1
      name: v1
- apiVersion: networking.istio.io/v1beta1
  kind: DestinationRule
  metadata:
    name: service-b
    namespace: default
  spec:
    host: service-b
    subsets:
    - labels:
        version: v1
      name: v1
- apiVersion: networking.istio.io/v1beta1
  kind: DestinationRule
  metadata:
    name: service-c
    namespace: default
  spec:
    host: service-c
    subsets:
    - labels:
        version: v1
      name: v1
- apiVersion: networking.istio.io/v1beta1
  kind: DestinationRule
  metadata:
    name: service-d
    namespace: default
  spec:
    host: service-d
    subsets:
    - labels:
        version: v1
      name: v1
    - labels:
        version: v2
      name: v2
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""
原文  http://ljchen.net/2020/04/06/使用Eureka的微服务如何上Istio/
正文到此结束
Loading...