距离2017年的见闻技术架构调整接近2年,随着业务线的发展,见闻技术部的项目数量、项目架构类型、基础设施规模、服务变动频率都在不断地增加,带给SRE的挑战是如何能更快地助力于开发人员更快更稳定地部署服务,保障线上服务的稳定。前端
咱们的后端开发团队仍然以Golang为主,不一样业务线的技术选型不尽相同,同时存在Python,Java服务,这就须要SRE提供更易接入的微服务基础组件,常见的方案就是为每种语言提供适配的微服务基础组件,但痛点是基础组件更新维护的成本较高。git
为了解决痛点,咱们将目光放到服务网格,它能利用基础设施下沉解决多语言基础库依赖问题,不一样的语言不须要再引入各类不一样的服务发现、监控等依赖库,只需简单的配置并运行在给定的环境下,就能享有以上功能,同时网络做为最重要的通讯组件,能够基于它实现不少复杂的功能,譬如根据不一样可用区进行的智能路由、服务熔断降级等。github
为此,咱们调研了一些服务网格方案,包括Istio、Linkerd,基于咱们的当前的后端架构特色:golang
对比下来,Istio拥有更多活跃的开源贡献者,迭代速度快,以及Istio架构可行性讨论,咱们选择Istio做为实践方案。docker
这张图介绍了见闻典型的服务网格架构,左半图介绍了一个用户请求是如何处理,右半图介绍运维系统是如何监控服务,若无特殊说明,服务都是部署在腾讯云托管Kubernetes。数据库
Istio(1.0.0)
服务网格开源方案,支持与Kubernetes集成。apache
Service A所在Pod接收Ingress请求后端
Proxy进程发起对Service B所在Pod的请求api
Service B所在Pod接收请求缓存
Service B Proxy接收请求并路由到Service B所在进程
以上的流程能够观察到,服务之间通讯彻底依靠Proxy进程完成,Proxy进程接管同一个Pod中服务的出入流量,完成请求的路由。
经过架构图以及以上流程,咱们拆分出如下关键组件,观察其性能、可用性、拓展性。
Istio Ingress用来处理用户入流量,使用Envoy实现,转发性能高。挂载在负载均衡后,经过增长实例实现可拓展。
Istio Proxy以Sidecar形式随应用一块儿部署,增长2次流量转发,存在性能损耗。
性能: 4核8G服务器,上面运行Proxy服务和API服务,API服务只返回ok字样。(此测试只测试极限QPS)
单独测试API服务的QPS在59k+,平均延时在1.68ms,CPU占用4核。
经过代理访问的QPS7k+,平均延时14.97ms,代理CPU占用2核,API服务CPU占用2核。
CPU消耗以及转发消耗下降了QPS,增长了延时,经过增长机器核数并增长服务部署数量缓解该问题,通过测试环境测试,延时能够接受。
可用性:基于Envoy,咱们认为Envoy的可用性高于应用。依赖Pilot Discovery进行服务路由,可用性受Pilot Discovery影响。
拓展性:Sidecar形式,随应用数拓展
Istio Policy须要在服务调用前访问,是同步请求,会增长服务调用延时,经过拓展服务数量增长处理能力。属于可选服务,见闻生产未使用该组件。
性能: 未测试
可用性:若开启Policy,必须保证Policy高可用,不然正常服务将不可用
拓展性:增长实例数量进行拓展
性能: 从监控上观察Report 5000qps,使用25核,响应时间p99在72ms。异步调用不影响应用的响应时间。
可用性:Telemetry不影响服务可用性
拓展性:增长实例数量进行拓展
性能: 服务发现组件1.0.5版本通过监控观察,300个Service,1000个Pod,服务变动次数1天100次,平均CPU消耗在0.01核,内存占用在1G之内。
可用性: 在服务更新时须要保证可用,不然新建立的Pod没法获取最新路由规则,对于已运行Pod因为Proxy存在路由缓存不受Pilot Discovery关闭的影响。
拓展性:增长实例数量能够增长处理量。
能够看到各个组件的可用性、拓展性都有相应的策略达到保障,咱们认为Istio是具备可实施性的。
Pilot Discovery负责Istio服务发现,支持在Kubernetes里部署,它读取K8S资源配置,并生成Proxy可用的路由表。如下面的Service A服务为例,介绍Istio如何进行精细路由。
要知道若是在Istio访问一个服务,必须得声明K8S Service。
Istio经过K8S CRD拓展K8S已有的服务访问能力,咱们列举网络相关经常使用的配置:
控制Istio Ingress的路由转发及TLS证书绑定。
服务流量控制,实现如A/B测试、错误注入、服务保护等。
用于目标服务的版本管理,根据Pod的Label区分目标服务的版本,联合VirtualService进行流量控制。
如下举个例子介绍如何利用它们配置一样大小流量到服务的不一样版本,
# serviceA.yaml kind: Service apiVersion: v1 metadata: name: serviceA labels: app: serviceA spec: ports: - name: http-8080 protocol: TCP port: 8080 targetPort: 8080 selector: app: serviceA type: ClusterIP # virtualServiceA.yaml apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: serviceA spec: hosts: - serviceA http: - route: - destination: host: serviceA subset: v1 - route: - destination: host: serviceA subset: v2 --- # destinationRule apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: serviceA spec: host: serviceA subsets: - labels: version: v1 name: v1 - labels: version: v2 name: v2
以上实现了Istio服务调用serviceA时,会随机地50%几率到serviceA的v1版本,50%几率到serviceA的v2版本。
能够看到,VirtualService经过hosts关联serviceA,在http区域有两个route,分别是subset v1, subset v2,v1,v2依赖DestinationRule来定义,一样用host来标注该DestinationRule控制哪一个host的访问,以及经过pod label中version来划分不一样版本。
流量控制方面,Istio有至关丰富的功能支持,同时也带来了至关的复杂度,建议用户根据平常的使用频率在后台实现相应的前端控制台,利用自动化来完成流量控制。
在K8S 1.9以后的版本,Istio利用K8S提供的MutatingAdmissionWebhook在K8S建立Pod前回调Istio提供的istio-sidecar-injector动态修改Pod配置,添加以Sidecar形式运行的Proxy。这里开启自动注入有两个维度,一是namespace,namespace须要添加istio-injection : enabled
标签,这样实现该namespace下的全部Pod自动注入Proxy;二是deployment能够设置annotation关闭自动注入。
若是K8S版本不够,能够利用命令行工具修改Deployment的配置。
Service A所在Pod至少运行Service A应用容器以及用于代理的Envoy容器,建立Pod时proxy-init命令负责获取Pod监听的端口和具体协议,以此初始化网络,利用iptables将容器网络出入流量都转发到Proxy监听的localhost端口。
若Service A的Pod声明servicePort为8080:HTTP,最终Proxy将会接收8080端口的Pod入流量和所有的Pod出流量。
Proxy基于Envoy,与Pilot Discovery链接,动态同步Kubernetes集群中全部的服务信息:服务与Pod IP、端口之间的映射表,经过路由信息实现智能路由,从而使服务发现从业务代码中剥离。
Proxy支持设置Zipkin URL,异步上报链路追踪数据。
Proxy将属性包上报给Telemetry服务,Telemetry根据用户的配置生成指标数据并由Prometheus收集。
咱们目前的服务部署在腾讯云托管Kubernetes,节点使用16核32G的网络加强型机器,全部的后端服务都以Docker部署,K8S集群外部署高可用ETCD支持集群内服务发现,数据库以MySQL、Cassandra、MongoDB为主,消息队列采用Kafka、NSQ。在应用Istio的过程当中,咱们对基础库进行了修改,删减了Istio已提供的功能并完成了对Istio的适配。
见闻旧后端服务架构,全部Golang服务以打包成Docker镜像,以"gRPC"协议通讯。
见闻Golang后端使用go-micro框架,一个支持多插件的Golang微服务框架,做者将组件分红transport,server,client,registry,codec等,经过组合不一样类型的组件很是灵活地配置微服务技术栈。对于有定制需求的微服务架构,是值得推荐的选择。
通讯协议做为服务互通的基石,Istio对gRPC和HTTP很是友好,根据协议Istio能解析HTTP头中的信息,支持提取指标以供分析。go-micro只是利用HTTP和gRPC做为通讯协议,可是协议的解析逻辑是协议无关的,因此能够说它只是用了这些通讯协议的外壳,传输的报文内容是"micro方言",这就致使了Golang暴露的服务没法被其它语言其它框架调用。为了将协议能在多语言中彻底统一,也为了更好地使用Istio的监控统计功能,这个时候咱们开始对go-micro的存留有一些新的思考,咱们是否还须要go-micro?通过近2年的生产实践,咱们是否是能够更精简咱们的框架?
通过这些思考事后,咱们的决定是去go-micro框架,拥抱更轻量级的基础框架,这个框架只要支持:
纯原生便可
支持Istio的基础功能,譬如一些HTTP header转发等
咱们已经存在上百个Golang项目,避免改动Golang项目代码,将改动放到基础库为佳
go-micro经过定义自制protobuf插件的方式在stub代码中集成框架功能,通过对逻辑的梳理,咱们决定复写protobuf插件,生成兼容micro的stub代码,经过对micro接口的向后兼容,开发人员不须要修改代码,只须要在CI阶段运行protoc即时生成新版代码。
详情可见再见,micro
右半图描述运维人员如何利用运维后台运维Kubernetes集群,Istio的运维必须有自动化的工具来减小人工配置带来的错误,见闻的旧运维后台基于腾讯云容器平台暴露的开放API,在引入Istio后,功能依赖于更细节的label以及CRD(Custom Resource Definition),因而得依托更细粒度的Kubernetes API,新的后台须要能完成基本的Kubernetes运维,并且结合Istio的实际进行平常更新,通过选型,见闻基于Kubernetes Dashboard二次开发了Istio部分的一些功能(APP部署、更新,Istio配置更新等),利用Istio Dashboard实现APP建立、部署接口,并由此重构原有的运维后台。
最终,SRE提供两个后台,精细控制的Istio Dashboard;提供给开发人员平常更新使用的简化版后台。
平常最重要、最高频的功能,服务版本变动。
服务建立包括对老服务的改造,一个K8S服务要通过一些配置上的更新才能成为Istio APP。一个Kubernetes服务须要知足如下要求来得到Istio的功能支持:
这里的服务端口声明中name字段值须要以协议名为起始值,譬如grpc、http、tcp、udp等,istio识别前缀,用于初始化Proxy,譬如grpc-svc
,http-svc
,不正确的端口命名会引发Proxy的异常行为,以及监控服务没法捕获该协议下的指标。
每一个后端服务提供一个HTTP探活接口,这样服务启动时,不至于让其它服务访问到未就绪的状态。
对于HTTP探活接口的定义包括Proxy以及APP是否初始化完成,见闻的实践是在基础镜像中打入一个探活程序:
经过Proxy的配置同步时间与Pilot Discovery的配置更新时间对比,相同时认为其就绪。
APP能够在指定端口提供就绪API。
# serviceA.yaml kind: Service apiVersion: v1 metadata: name: serviceA namespace: default labels: app: serviceA spec: ports: - name: grpc protocol: TCP port: 10088 targetPort: 10088 selector: app: serviceA type: ClusterIP
Deployment资源要求
# deploymentA.yaml kind: Deployment apiVersion: extensions/v1beta1 metadata: name: serviceA-v1 labels: app: serviceA version: v1 spec: replicas: 1 selector: matchLabels: app: serviceA version: v1 template: metadata: labels: app: serviceA version: v1 spec: containers: - name: serviceA image: 'some-image' ports: - containerPort: 10088 protocol: TCP resources: requests: cpu: 1000m livenessProbe: httpGet: path: /health port: 54321 scheme: HTTP initialDelaySeconds: 1 timeoutSeconds: 2 periodSeconds: 5 successThreshold: 1 failureThreshold: 3 terminationMessagePath: /dev/termination-log terminationMessagePolicy: File imagePullPolicy: Always securityContext: privileged: false restartPolicy: Always terminationGracePeriodSeconds: 30 dnsPolicy: ClusterFirst
符合以上要求,服务能正确接入Istio系统并得到服务发现和监控的能力。
Istio提供流量控制,给运维带来方便的A/B测试,用于根据指定规则迁移流量。
见闻的服务更新依靠Istio流量迁移功能,发布服务的新版本,并将流量经过Istio规则迁移到新版本,实际细节以下:
更新流量控制将流量指向已有版本
如下实例利用VirtualService将ServiceA的服务流量所有指向已存在的v1版本
# virtualService apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: serviceA spec: hosts:
http: - route: - destination: host: serviceA subset: v1 --- # destinationRule apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: serviceA spec: host: serviceA subsets: - labels: version: v1 name: v1 ```
查找符合app label的deployment,运维人员基于该deployment建立v2版本的deployment,并向destinationRule中增长v2版本。
# destinationRule apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: serviceA spec: host: serviceA subsets: - labels: version: v1 name: v1 - labels: version: v2 name: v2
如下实例利用VirtualService将ServiceA的服务流量所有指向v2版本
# virtualService apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: serviceA spec: hosts: - serviceA http: - route: - destination: host: serviceA subset: v2
使用Istio Dashboard来实现上述流程
为何使用Istio Ingress做为新的Ingress方案?
过去咱们使用腾讯云托管的Kubernetes Ingress,为了对Ingress流量控制而引入Istio Ingress。咱们以前提到Istio Ingress是基于Envoy,它读取Istio控制的配置进行路由,与其它内部服务同样方便地接入Istio全部功能。
除了VirtualService和DestinationRule,Istio定义了Gateway来控制实例支持的Host和证书。具体的流程是:
建立Deployment ingressgateway时,以ConfigMap的形式挂载Ingress须要的证书。
配置Ingress接收具体域名(如wallstreetcn.com)的流量,以及对应的TLS证书位置,这里的证书路径已经挂在到Ingress的Deployment上。如下是一个典型的Gateway配置。
apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: wallstreetcn-com namespace: istio-system spec: selector: istio: ingressgateway servers: - hosts: - wallstreetcn.com port: name: http number: 80 protocol: HTTP - hosts: - wallstreetcn.com port: name: https number: 443 protocol: HTTPS tls: mode: SIMPLE privateKey: /etc/istio/ingressgateway-certs/tls.key serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
配置完成后,再配合VirtualService的路由控制,控制Ingress的反向代理到default命名空间下的gateway服务80端口,以下所示:
apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: wallstreetcn-com namespace: istio-system spec: gateways: - wallstreetcn-com hosts: - wallstreetcn.com http: - route: - destination: host: gateway.default.svc.cluster.local port: number: 80
Istio支持Prometheus拉取集群指标,并提供Grafana看板展现。这里建议初期使用Istio自带的Grafana看板配置,而且注意Kubernetes主机的类型划分,Prometheus服务适合放在内存型机器。能够与Dashboard集成,在发布服务过程当中即时查看指标。
Istio自带一些默认的Grafana面板,统计全部能够被访问的HTTP/gRPC服务的返回码以及延时状况。
对于返回码,认为5xx为错误,并在面板上使用label_join((sum(rate(istio_requests_total{reporter="destination", response_code!~"5.*"}[1m])) by (destination_workload, destination_workload_namespace) / sum(rate(istio_requests_total{reporter="destination"}[1m])) by (destination_workload, destination_workload_namespace)), "destination_workload_var", ".", "destination_workload", "destination_workload_namespace")
计算服务错误率。
对于延时状况采用histogram_quantile
获取多维度p50、p90、p9五、p99的延时分布。
以前提到Proxy由Envoy实现,Envoy支持设置Zipkin上报API,Proxy在收发请求时将链路指标上报到Zipkin,为了实现链路追踪,Proxy在流量转发中解析协议中的HTTP或gRPC请求头,找出其中的追踪头,组装成指标。
因此应用端须要在收到调用方请求时解析出请求头,并持续携带该请求头向后传递。
因为见闻在Ingress以后映射一个HTTP gateway,请求从Ingress转发到HTTP gateway,再发送到后续的gRPC服务,因此HTTP gateway有段代码生成gRPC请求头。
import ( "github.com/labstack/echo" gmeta "google.golang.org/grpc/metadata" ) // Create a gRPC context from Echo. func NewContextFromEcho(ec echo.Context) context.Context { md := gmeta.MD{} for _, header := range []string{ "x-request-id", "x-b3-traceid", "x-b3-spanid", "x-b3-parentspanid", "x-b3-sampled", "x-b3-flags", "x-ot-span-context", } { md.Set(header, ec.Request().Header.Get(header)) } md.Set("x-b3-parentspanid", ec.Request().Header.Get("x-b3-spanid")) return gmeta.NewOutgoingContext(context.Background(), md) }
在后续的gRPC服务调用中使用该Context,至于gRPC服务之间的调用,咱们发现会自动将context传递到下一个服务,因此没有作相似处理。
这里追踪的数据若是全量捕获将会是很是大的,而且对于监控来讲也没必要要,因此能够设置抽样率,Istio提供ConfigMap中设置抽样率,通常来讲设置成1%便可。
在Istio实践过程当中,有哪些须要注意的问题。
Istio对Kubernetes的API有很强的依赖,诸如流量控制(Kubernetes资源)、集群监控(Prometheues经过Kubernetes服务发现查找Pod)、服务权限控制(Mixer Policy)。因此须要保障API server的高可用,咱们曾遇到Policy组件疯狂请求Kubernetes API server使API server没法服务,从而致使服务发现等服务没法更新配置。
* 为避免这种请求,建议使用者了解与API server直接通讯组件的原理,并尽可能减小直接通讯的组件数量,增长必要的Rate limit。 * 尽可能将与API server通讯的服务置于能够随时关闭的环境,这是考虑若是部署在同一Kubernetes集群,若是API server挂掉,没法关闭这些有问题的服务,致使死锁(又想恢复API server,又要依靠API server关闭服务)
服务配置是Istio部署后的重头戏,避免使用手动方式更改配置,使用代码更新配置,将经常使用的几个配置更新操做作到运维后台,相信手动必定会犯错的事实。
Pilot Discovery 1.0.0版本有很大的性能问题,1.0.4有很大的性能提高,但引入了一个新bug,因此请使用1.0.5及以上的版本,该版本在见闻的平均CPU负载从10核降到了0.5核,大大下降了Proxy同步配置的延时。
这个组件曾致使API server负载太高(很高的list pods请求),因此咱们暂时束之高阁,慎用。
在使用Proxy、Telemetry时,默认它们会打印访问日志,咱们选择在生产上关闭该日志。
时刻观察Istio社区的最新版本,查看新版本各个组件的性能优化以及bug修复状况,将Istio当作高度模块化的系统,单独升级某些组件。上面就提到咱们在Istio1.0的基础上使用了1.0.5版本的Policy、Telemetry、Pilot Discovery等组件。
Istio依靠Proxy来帮助APP进行路由,考虑几种状况会出现意外的状态:
* APP启动先于Proxy,并开始调用其它服务,这时Proxy还没有初始化完毕,APP调用失败。 * Service B关闭时,调用者Service A的Proxy还没有同步更新Service A关闭的状态,向Service B发送请求,调用失败。
第一种状况要求APP有重试机制,能适当重试请求,避免启动时的Proxy初始化与APP初始化的时差。
第二种状况,一种是服务更新时,咱们使用新建新服务,再切流量;一种是服务异常退出,这种状况是在客户端重试机制。但愿使用Istio的开发人员有更好的解决方案。
见闻Istio化已于去年10月份完成并上线,咱们的线上集群中Istio和非Istio的APP混合部署,上千的Pod数量曾对不够健壮的服务发现组件形成巨大的压力,在这期间曾遇到Istio的一些惊喜,并不断总结经验,但愿给以后使用Istio的同窗一些借鉴。以后的过程当中,SRE的目标依然是保障线上服务的健壮性。
优化APP部署流程,考虑自动部署的功能,经过服务指标自动完成灰度发布和流量迁移。
Prometheus的高可用、可拓展方案的探索。