Istio 中业务开发需要关注的二三事

2020-09-21 11:36:33 浏览数 (1)

在应用开发过程中,虽然 Istio 号称是 0 入侵,但我们在开发过程中,还是会碰到一些问题,如在远程调用和调用链等方面还是会有一些改变,下面列举了一些 开发人员在开发过程中需要关注的问题。

临时暴露服务

在应用开发中,我们需要连接某个特定的远程服务( Provider),并且能够方便的查询调用链和日志。

通常我们会在测试环境中部署已经基本完善的微服务,这些微服务想要被开发者调用,需要“临时”放开,在 Istio 中,通常放开服务访问的方法有如下两种。

1、使用 NodePort 暴露服务

我们可以创建一个新的 NodePort 的服务类型来暴露,如下面的 yaml 配置。

代码语言:txt复制
apiVersion: v1
kind: Service
metadata:
  name: shopcart-dev
spec:
  type: NodePort
  selector:
    app: shopcart
  ports: 
    - port: 80
      targetPort: 80

或者,直接使用如下命令暴露:

代码语言:txt复制
kubectl expose deployment shopcart --type=NodePort --port=80 --target-port=80 --name=shopcart-dev

通过 kubectl get svc shopcart-dev 可以查看具体的端口,默认在 30000-32767 这个范围

这样,我们就可以使用 schema://node-ip:nodeport-ip 来访问 shopcart 这个应用了。

2 使用 Gateway 来暴露服务

下面的代码分别发布了一个 http 服务和一个 tcp 服务:

代码语言:txt复制
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: passport-tmp-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 7301
      name: http
      protocol: HTTP
    hosts:
    - "*"
  - port:
      number: 28888
      name: dubbo-dev
      protocol: TCP
    hosts:
    - "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: dev-vs
spec:
  hosts:
  - "*"
  gateways:
  - passport-tmp-gateway
  http:
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: passport
        port:
          number: 7301
  tcp:
  - match:
    - port: 28888
    route: 
    - destination:
        host: dubbo-app
        port:
          number: 28888

现在使用 schema://gateway-ip: port 就可以访问相应的应用了。

代码中的远程调用服务名/域名问题

我们在写远程调用的时候会自然地写出如下的代码:

代码语言:txt复制
User user = restTemplate.getForObject("http://passport:8080/user/info", User.class);
// 或者是
User user = restTemplate.getForObject("http://passport.namespace:8080/user/info", User.class);

虽然有了 K8S 的 service,我们可以把服务名作为域名来调用,但这么写有个显而易见但弊端:当端口,或者服务名发生变化但时候,噩梦便来了。

我们不妨写一个方法来处理此类问题:

代码语言:txt复制
// java 伪代码
public <T> T getRemote(String servieName, String apiUrl,  Class<T> type) {
    String url = Util.combin(Util.getHostFromConfig(servieName), apiUrl);
    return restTemplate.getForObject(url, type);
}

//调用
User user = getRemote("passport", "/user/info", User.class);

servieName 现在是一个变量了,而且是从配置文件中获取的,并且这个配置可以放到远程配置中心。以后再变换域名或者端口都雨女无瓜了。

调用链追踪

TCM/Istio 的 HTTP 调用链符合 opentracing 标准,所以想要被 Istio 追踪,需要在远程调用的时候加上相应的上下文 http headers。

代码语言:txt复制
x-request-id
x-b3-traceid
x-b3-spanid
x-b3-parentspanid
x-b3-sampled
x-b3-flags
x-ot-span-context

x-cloud-trace-context
traceparent
grpc-trace-bin

这些 headers 需要在开发过程中进行处理,下面是一段 Java 代码处理方法,这个方法“无脑“透传了上下文的 headers,有点儿暴力:

代码语言:txt复制
    public static HttpHeaders traceHeaders(HttpServletRequest request, String[] keys) {
        if (null == keys) {
            Enumeration<String> oriHeader = request.getHeaderNames();
            ArrayList<String> oriHeaderList = Collections.list(oriHeader);
            keys = oriHeaderList.toArray(new String[0]);
        }
        HttpHeaders headers = new HttpHeaders();
        for (String s : keys) {
            String v = request.getHeader(s);
            if (v != null && !v.isEmpty()) {
                headers.add(s, v);
            }
        }
        return headers;
    }

    public static String restGet(String url, RestTemplate restTemplate, HttpServletRequest request) {
        HttpHeaders headers = traceHeaders(request, null);
        HttpEntity<Object> entity = new HttpEntity<>(headers);
        ResponseEntity<String> result = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
        return result.getBody();
    }

在实际开发中,我们可以借用现成的类库,如:https://github.com/opentracing-contrib/java-spring-web

调用链中的日志:错误堆栈和业务信息

在开发过程中,当例外发生时,我们希望通过调用链顺藤摸瓜,查看远程 Provider 中出现的错误详细日志。

此时,需要取出 opentracing 的那些 header,并在记录 Exception 的时候加入这些信息。

Java 语言下也有现成类库可以使用,如在 log4j 的 MDC 中可以加入上下文信息和业务信息。可以参考这篇文章实现:https://www.baeldung.com/mdc-in-log4j-2-logback。在实际应用中,也可以通过切面的方式修改log记录方式而无需修改业务代码。

当把 trace 信息写入日志系统后,我们就可以通过日志系统(如 ELK)的调用链的 id 查询到“额外的”日志,通过这些日志,可以洞察到微服务的更多细节。

我们也可以使用 jaeger client 的 SDK 来对接更多的调用链日志,稍后会撰写文章进行专门讨论。

远程日志查看/管理集群

当下,在腾讯云 TKE/TCM 中开发,普通开发者如何查看远程日志?运维人员如何管理集群?如果不希望普通开发者对整个集群有修改权限,只想给日志的读取权限,有些开发者也不愿意登录云控制台。可考虑如下实现方法:

1、通过 TKE 控制台通过授权管理对子账号进行授权

在腾讯云建立一个子账号,通过容器集群的授权管理,将某个命名空间的只读权限赋给这个子账号,开发者通过这个子账号进行日志查询。

TKE 后台进入容器集群,点击授权管理(当前需要申请开放白名单),就可以进行可视化的授权操作。

授权完成后,使用子账号登陆控制台,则只能进行只读操作了,同时,这个子账号当前的 kubeconfig 文件也具有了相应权限。

授权菜单入口授权菜单入口
选择子用户选择子用户
给子账号某个命名空间的相应权限给子账号某个命名空间的相应权限
使用子账号的 kubeconfig使用子账号的 kubeconfig

使用子账号登录之后,将只有有限的权限,使用这个新的 Kubeconfig 作为客户端访问凭据,亦可以控制权限。

2、安装本地 WebUI 或者使用客户端工具

除了使用 Kubernates Dashboard(TKE 中并未默认安装),下面这些工具也可使用,有些可以在本地 docker 中安装,对集群“无入侵”。

Lens

https://github.com/lensapp/lens

无入侵:客户端界面,本地安装。

Octant

https://github.com/vmware-tanzu/octant

无入侵:VMware 出品,Web 界面,功能全面,本地运行Web服务。**

Weave Scope

https://github.com/weaveworks/scope

Web 界面,本地安装,支持本地 docker 管理; K8S 管理需要在集群安装。

Kubernetes Web View

https://github.com/hjacobs/kube-web-view

无入侵:Web 界面,支持集群内安装和本地启动。本地安装脚本:

代码语言:txt复制
docker run -it --rm -p 8080:8080 -u $(id -u) -v $HOME/.kube:/.kube hjacobs/kube-web-view

K8Dash

https://github.com/indeedeng/k8dash

Web 界面,需要在集群内安装。

Kubernator

https://github.com/smpio/kubernator

无入侵:Web 界面,集群内安装,本地安装。但 TKE 测试失败

代码语言:txt复制
kubectl proxy 
docker run -it --rm --name=kubernator -p 3000:80 smpio/kubernator

Kubernetes Opertional View

https://github.com/hjacobs/kube-ops-view

无入侵:Web 界面,本地安装,这是个只读的系统,不提供交互。但 TKE 测试失败

代码语言:txt复制
kubectl proxy 
docker run -it --rm --net=host hjacobs/kube-ops-view

Konstellate

https://github.com/containership/konstellate

Web 界面,需要下载并安装 Clojure(JVM 的 Lisp 方言) 命令行,然后在本地启动服务:clojure -m figwheel.main -b dev -r。很久未更新,不推荐。

3、使用日志系统

更推荐但方法是使用日志系统,上述方法只能查看有限的日志,适合即时开发。在 TKE 集群中,可以使用 CLS 进行日志的收集,TKE 已经对 CLS 进行了集成。

0 人点赞