Springcloud应用与监控链路日志体系
SpringCloud
SpringCloud是一套完整的微服务框架,有多个组件组成,用于解决应用架构微服务化后的通信、性能、稳定性等问题。
微服务架构
微服务架构基于传统单体应用架构发展而来,传统单体应用架构中所有的功能集成在一个项目工程中,使项目开发交付部署都不够灵活、可靠性和伸缩性差、技术限制多。
后来垂直应用架构和SOA面向服务架构分别实现项目垂直拆分和业务复用,但仍然存在服务接口协议不固定、系统与服务的界限模糊等问题。
微服务架构特点
- 将系统服务层完全独立出来,并将服务层抽取为一个一个的微服务。
- 微服务中每一个服务都对应唯一的业务能力,遵循单一原则。
- 微服务之间采用 RESTful 等轻量协议传输。
微服务架构优点
- 团队独立:每个服务都是一个独立的开发团队,这个小团队可以是 2 到 5 人的开发人员组成;
- 技术独立:采用去中心化思想,服务之间采用 RESTful 等轻量协议通信,使用什么技术什么语言开发,别人无需干涉;
- 前后端分离:采用前后端分离开发,提供统一 Rest 接口,后端不用再为 PC、移动端开发不同接口;
- 数据库分离:每个微服务都有自己的存储能力,可以有自己的数据库。也可以有统一数据库;
- 服务拆分粒度更细,有利于资源重复利用,提高开发效率;
- 微服务中单个服务更小,更易于易于被开发人员学习、理解、修改和维护
- 开发迭代更快,可以更加精准的制定每个服务的优化方案(比如扩展),提高系统可维护性;
微服务架构问题
-
客户端如何访问服务
——服务拆分后,客户端需要与多个服务通信,增加复杂度,可以通过服务网关解决
-
服务之间如何通信
——服务间需要相互通信而非直接调用,可用同步通信方案有REST、RPC,异步通信方案有RabbitMQ、Kafka
-
服务间如何互相查找
——服务以集群形式部署,且随时上线下线、IP变换,可以通过注册中心解决
-
服务出现问题如何处理
——某个服务出现问题时,会阻塞调用它的服务,若有大量的请求涌入,容器的线程资源会被消耗完毕,导致服务瘫痪。因为微服务间相互依赖,会造成连锁反应,引发服务故障“雪崩”效应。
微服务架构生态
-
服务网关:Zuul、Spring Cloud Gateway
API 网关将所有 API 调用统一接入到 API 网关层,由网关层统一接入和输出。
一个网关的基本功能有:统一接入、安全防护、协议适配、流量管控、长短连接支持、容错能力。
有了网关之后,各个 API 服务提供团队可以专注于自己的的业务逻辑处理,而 API 网关更专注于安全、流量、路由等问题。
-
服务调用:Feign、OpenFeign、Dubbo
目前主流的远程调用技术有基于 HTTP 的 RESTful 接口和基于 TCP 的 RPC 协议。
比较项 REST RPC 通讯协议 HTTP 一般使用 TCP 性能 略低 较高 灵活度 高 低 -
服务注册与发现:Eureka、Zookeeper、Consul、Nacos
- 「服务注册」:服务实例将自身服务信息注册到注册中心。
- 「服务发现」:服务实例通过注册中心,获取注册到其中的服务实例的信息,通过这些信息去请求它们提供的服务。
- 「服务剔除」:服务注册中心将出问题的服务自动剔除到可用列表之外,使其不会被调用到。
-
负载均衡:Ribbon
服务高可用的保证手段,为了保证高可用,每一个微服务都需要部署多个服务实例来提供服务,此时就需要根据不同的负载均衡策略对服务进行调用。
常用负载均衡策略:轮询、加权轮询、随机、最小并发等
-
服务容错:Hystrix、Sentinel
没法预防雪崩效应的发生,只能尽可能去做好容错。服务容错的三个核心思想是:
- 不被外界环境影响(避免服务本身不可用,如硬件故障,程序 BUG,缓存击穿等)
- 不被上游请求压垮(避免服务故障后,上游服务依旧做出大量请求)
- 不被下游响应拖垮(避免因为服务消费者不可用,同步等待造成的资源耗尽)
-
链路追踪:Sleuth
需要对一次请求涉及的多个服务链路进行日志记录,性能监控等等。单纯的理解链路追踪,就是指一次任务的开始到结束,期间调用的所有系统及耗时(时间跨度)都可以完整记录下来。
-
配置中心:Config、Consul、Nacos
集中式得管理每个服务的配置信息
-
安全认证:Spring Cloud Security
面对数十个甚至上百个微服务之间的调用,如何保证高效安全的身份认证?面对外部的服务访问,该如何提供细粒度的鉴权方案?
-
单点登录SSO(每个面向用户的服务都与认证服务交互,会产生大量非常琐碎的网络流量和重复的工作)
-
分布式Session(将关于用户认证的信息存储在共享存储中,且通常由用户会话作为 Key 来实现的简单分布式哈希映射。当用户访问微服务时,用户数据可以从共享存储中获取。缺点在于共享存储需要一定保护机制,复杂度高)
-
客户端Token(令牌在客户端生成,由身份验证服务进行签名,并且必须包含足够的信息,以便可以在所有微服务中建立用户身份。令牌会附加到每个请求上,为微服务提供用户身份验证。缺点是身份注销问题,需要使用短期令牌和频繁检查认证服务)
-
客户端Token+API网关(在请求时,网关将原始用户令牌转换为内部会话 ID 令牌。在这种情况下,注销就不是问题,因为网关可以在注销时撤销用户的令牌。)
-
SpringCloud组件
Spring Cloud 第一代 | Spring Cloud 第二代 | |
---|---|---|
网关 | Spring Cloud Zuul | Spring Cloud Gateway |
注册中心 | Eureka,Consul,ZooKeeper | 阿里 Nacos,拍拍贷 Radar 等 |
配置中心 | Spring Cloud Config | 阿里 Nacos,携程 Apollo,随行付 Config Keeper 等 |
客户端负载均衡 | Ribbon | spring-cloud-commons 的 Spring Cloud LoadBalancer |
熔断器 | Hystrix | spring-cloud-r4j(Resilience4J),阿里 Sentinel 等 |
链路追踪 | Sleuth + Zipkin | Apache Skywaling,OpenTracing 等 |
部分SpringCloud组件功能详解:
Eureka
注册中心,负责服务注册、服务续约、服务下线、获取服务注册列表、集群数据复制与同步。
特殊功能:
- 自我保护:当 Eureka Server 节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么就会把这个微服务节点进行保护。一旦进入自我保护模式,Eureka Server 就会保护服务注册表中的信息,不删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该 Eureka Server 节点会再自动退出自我保护模式。所以,自我保护模式是一种应对网络异常的安全保护措施。
- 安全认证:配置访问Eureka时需要用户名密码的安全认证体系,微服务配置注重中心时要携带安全认证
defaultZone: http://root:123456@localhost:8761/eureka/,http://root:123456@localhost:8762/eureka/
Ribbon
客服端负载均衡,基于 HTTP 和 TCP ,Ribbon不像注册中心、配置中心、API 网关那样独立部署,但是它几乎存在于每个 Spring Cloud 微服务中,包括 Feign 提供的声明式服务调用也是基于该 Ribbon 实现的。
不同于F5或Nginx等集中式负载均衡(服务器负载均衡),Ribbon属于进程内负载均衡,将负载均衡逻辑集成到 consumer,consumer 从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的 provider。
Feign
Feign 是 Spring Cloud Netflix 组件中的一个轻量级 RESTful 的 HTTP 服务客户端,实现了负载均衡和 Rest 调用的开源框架,封装了 Ribbon 和 RestTemplate,实现了 WebService 的面向接口编程,进一步降低了项目的耦合度。
Feign 是声明式服务调用组件,其核心就是:像调用本地方法一样调用远程方法,无感知远程 HTTP 请求。
特殊功能:
- Gzip压缩:网络数据经过压缩后降低了网络传输的字节数,加快网页加载的速度。可以通过配置feign实现Consumer 通过 Feign 到 Provider 的请求与相应的 Gzip 压缩,或直接配置Consumer,对客户端浏览器的请求以及 Consumer 对 Provider 的请求与响应都实现 Gzip 压缩。
- HTTP连接池:HTTP连接池通过复用HTTP连接,极大地优化了网络请求的性能和资源利用率。HTTP连接池的核心思想是在客户端维护一个连接池,池中的连接可以被多个请求复用。当发起一个新的HTTP请求时,连接池会尝试从池中获取一个可用的连接。如果池中有空闲连接,则直接使用该连接发起请求;如果没有空闲连接,则根据配置的策略(如阻塞等待、拒绝请求等)处理该请求。连接池中的连接在完成请求后会被放回池中,以供后续请求复用。连接池会定期检查并关闭那些长时间未使用的连接,以避免资源泄露。Feign使用HTTP连接池只需要Http 客户端工具修改为 HttpClient即可(添加httpclient 依赖,配置开启httpclient )。
- 请求超时:分布式项目中,服务压力比较大的情况下,可能处理服务的过程需要花费一定的时间,而默认情况下请求超时的配置是 1s 需要调整该配置延长请求超时时间。Feign 的负载均衡底层用的就是 Ribbon,所以请求超时配置其实就是配置 Ribbon。可以在Consumer中配置Ribbon的请求连接和处理时间实现全局配置,或配置某个被调用服务的请求连接和处理时间实现局部配置。
Hystrix
在分布式环境中,不可避免地会有许多服务依赖项中的某些服务失败而导致雪崩效应。Hystrix 是一个库,可通过添加等待时间容限和容错逻辑来帮助您控制这些分布式服务之间的交互。Hystrix 通过隔离服务之间的访问点,停止服务之间的级联故障并提供后备选项来实现此目的,所有这些都可以提高系统的整体稳定性。
请求缓存:Hystrix 为了降低访问服务的频率,支持将一个请求与返回结果做缓存处理。如果再次请求的 URL 没有变化,那么 Hystrix 不会请求服务,而是直接从缓存中将结果返回。这样可以大大降低访问服务的压力。可以利用Redis、MemCache等缓存数据库进行结果缓存,需修改业务代码。
请求合并:在高并发情况下,通信次数的增加会导致总的通信时间增加,同时,线程池的资源也是有限的,高并发环境会导致有大量的线程处于等待状态,进而导致响应延迟;通过请求合并,将多个请求合并后通过批处理调用,减少通信次数和资源消耗。引入hystrix依赖并修改业务代码进行请求合并。
线程池隔离:没有线程池隔离的项目所有接口都运行在一个 ThreadPool
中,当某一个接口压力过大或者出现故障时,会导致资源耗尽从而影响到其他接口的调用而引发服务雪崩效应。
通过每次都开启一个单独线程运行。它的隔离是通过线程池,即每个隔离粒度都是个线程池,互相不干扰。线程池隔离方式,等于多了一层的保护措施,可以通过 hytrix 直接设置超时,超时后直接返回。
线程池隔离前:
线程数隔离后:
引入Hystrix依赖,并在服务消费者业务层代码添加线程隔离规则
// 声明需要服务容错的方法
// 线程池隔离
@HystrixCommand(groupKey = "order-productService-listPool",// 服务名称,相同名称使用同一个线程池
commandKey = "selectProductList",// 接口名称,默认为方法名
threadPoolKey = "order-productService-listPool",// 线程池名称,相同名称使用同一个线程池
commandProperties = {
// 超时时间,默认 1000ms
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",
value = "5000")
},
threadPoolProperties = {
// 线程池大小
@HystrixProperty(name = "coreSize", value = "6"),
// 队列等待阈值(最大队列长度,默认 -1)
@HystrixProperty(name = "maxQueueSize", value = "100"),
// 线程存活时间,默认 1min
@HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),
// 超出队列等待阈值执行拒绝策略
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "100")
}, fallbackMethod = "selectProductListFallback")
特点:
- 请求线程和调用 Provider 线程不是同一条线程;
- 支持超时,可直接返回;
- 支持熔断,当线程池到达最大线程数后,再请求会触发
fallback
接口进行熔断; - 隔离原理:每个服务单独用线程池;
- 支持同步和异步两种方式;
- 资源消耗大,大量线程的上下文切换、排队、调度等,容易造成机器负载高;
- 无法传递 Http Header。
信号量隔离:每次调用线程,当前请求通过计数信号量进行限制,当信号量大于了最大请求数 maxConcurrentRequests
时,进行限制,调用 fallback
接口快速返回。信号量的调用是同步的,也就是说,每次调用都得阻塞调用方的线程,直到结果返回。
引入Hystrix依赖,并在服务消费者业务层代码添加信号量隔离规则
@HystrixCommand(commandProperties = {
// 超时时间,默认 1000ms
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",
value = "5000"),
// 信号量隔离
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY,
value = "SEMAPHORE"),
// 信号量最大并发,调小一些方便模拟高并发
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS,
value = "6")
}, fallbackMethod = "selectProductListFallback")
特点:
- 请求线程和调用 Provider 线程是同一条线程;
- 不支持超时;
- 支持熔断,当信号量达到
maxConcurrentRequests
后。再请求会触发fallback
接口进行熔断; - 隔离原理:通过信号量的计数器;
- 同步调用,不支持异步;
- 资源消耗小,只是个计数器;
- 可以传递 Http Header。
服务熔断:服务熔断一般是指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施,所以很多地方把熔断亦称为过载保护。
引入Hystrix依赖,并在服务消费者业务层代码添加服务熔断规则。
// 声明需要服务容错的方法
// 服务熔断
@HystrixCommand(commandProperties = {
// 当请求符合熔断条件触发 fallbackMethod 默认 20 个
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD,
value = "10"),
// 请求错误率大于 50% 就启动熔断器,然后 for 循环发起重试请求,当请求符合熔断条件触发 fallbackMethod
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE,
value = "50"),
// 熔断多少秒后去重试请求,默认 5s
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS,
value = "5000"),
}, fallbackMethod = "selectProductByIdFallback")
服务降级:服务降级是一种应对整体负荷超载的主动性控制策略。由于爆炸性的流量冲击,对一些服务进行有策略的放弃,以此缓解系统压力,保证目前主要业务的正常运行。它主要是针对非正常情况下的应急服务措施:当此时一些业务服务无法执行时,给出一个统一的返回结果。
引入Hystrix依赖,并在服务消费者业务层代码添加服务降级规则。
// 声明需要服务容错的方法
// 服务降级
@HystrixCommand(fallbackMethod = "selectProductByIdFallback")
熔断可以看做一种特殊的服务降级,由Provider故障自动引发,跟服务降级一样给出一个统一的返回结果。
Gateway
网关具有身份认证与安全、审查与监控、动态路由、负载均衡、缓存、请求分片与管理、静态响应处理等功能。当然最主要的职责还是与“外界联系”。
路由是网关的基础功能,Spring Cloud Gateway通过配置文件实现。
spring:
application:
name: gateway-server # 应用名称
cloud:
gateway:
# 路由规则
routes:
- id: product-service # 路由 ID,唯一
uri: http://localhost:7070/ # 目标 URI,路由到微服务的地址
predicates: # 断言(判断条件)
- Path=/product/** # 匹配对应 URL 的请求,将匹配到的请求追加在目标 URI 之后
路由的predicates可以根据path路径、Query请求Token、Method方法如GET、Datetime时间、RemoteAdd地址等设置。
Spring Cloud Gateway 支持与 Eureka 整合开发,根据 serviceId 自动从注册中心获取服务地址并转发请求,这样做的好处不仅可以通过单个端点来访问应用的所有服务,而且在添加或移除服务实例时不用修改 Gateway 的路由配置。
网关过滤器:网关过滤器用于拦截并链式处理 Web 请求,可以实现横切与应用无关的需求,比如:安全、访问超时的设置等。修改传入的 HTTP 请求或传出 HTTP 响应。Spring Cloud Gateway 包含许多内置的网关过滤器工厂一共有 22 个,包括头部过滤器、 路径类过滤器、Hystrix 过滤器和重写请求 URL 的过滤器, 还有参数和状态码等其他类型的过滤器。
如Path路径过滤器,实现 URL 重写,通过重写 URL 可以实现隐藏实际路径提高安全性,易于用户记忆和键入,易于被搜索引擎收录等优点。包含多种方法FilterFactory,如RewritePath 网关过滤器工厂采用路径正则表达式参数和替换参数,使用 Java 正则表达式来灵活地重写请求路径。
spring:
application:
name: gateway-server # 应用名称
cloud:
gateway:
# 路由规则
routes:
- id: product-service # 路由 ID,唯一
uri: lb://product-service # lb:// 根据服务名称从注册中心获取服务请求地址
predicates: # 断言(判断条件)
# 匹配对应 URI 的请求,将匹配到的请求追加在目标 URI 之后
- Path=/product/**, /api-gateway/**
filters: # 网关过滤器
# 将 /api-gateway/product/1 重写为 /product/1
- RewritePath=/api-gateway(?<segment>/?.*), $\{segment}
网关限流:限流是控制系统QPS从而保护系统的有力方式。常用的限流算法包括计数器、漏桶和令牌桶。
以令牌桶限流为例,Spring Cloud Gateway 内部使用的就是该算法,大概描述如下:
- 所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;
- 根据限流大小,设置按照一定的速率往桶里添加令牌;
- 桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
- 请求到达后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
- 令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流。
基于URL限流
spring:
application:
name: gateway-server # 应用名称
cloud:
gateway:
# 路由规则
routes:
- id: product-service # 路由 ID,唯一
uri: lb://product-service # lb:// 根据服务名称从注册中心获取服务请求地址
predicates: # 断言(判断条件)
# 匹配对应 URI 的请求,将匹配到的请求追加在目标 URI 之后
- Path=/product/**
filters: # 网关过滤器
# 限流过滤器
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充速率
redis-rate-limiter.burstCapacity: 2 # 令牌桶总容量
key-resolver: "#{@pathKeyResolver}" # 使用 SpEL 表达式按名称引用 bean
# redis 缓存
redis:
timeout: 10000 # 连接超时时间
host: 192.168.10.101 # Redis服务器地址
port: 6379 # Redis服务器端口
password: root # Redis服务器密码
database: 0 # 选择哪个库,默认0库
lettuce:
pool:
max-active: 1024 # 最大连接数,默认 8
max-wait: 10000 # 最大连接阻塞等待时间,单位毫秒,默认 -1
max-idle: 200 # 最大空闲连接,默认 8
min-idle: 5 # 最小空闲连接,默认 0
Config
Spring Cloud Config 在微服务分布式系统中,采用 「Server 服务端」和 「Client 客户端」的方式来提供可扩展的配置服务。服务端提供配置文件的存储,以接口的形式将配置文件的内容提供出去;客户端通过接口获取数据、并依据此数据初始化自己的应用。
k8s监控体系
Kubernetes 项目的监控体系曾经非常繁杂,在社区中也有很多方案。但这套体系发展到今天,已经完全演变成了以 Prometheus 项目为核心的一套统一的方案。Prometheus 项目与 Kubernetes 项目一样,也来自于 Google 的 Borg 体系,它的原型系统,叫作 BorgMon,是一个几乎与 Borg 同时诞生的内部监控系统。
Prometheus
Prometheus主要组件:
- Prometheus Server ,监控、告警平台核心,抓取目标端监控数据,生成聚合数据,存储时间序列数据
- exporter,由被监控的对象提供,提供API暴漏监控对象的指标,供prometheus 抓取
- node-exporter
- blackbox-exporter
- redis-exporter
- mysql-exporter
- custom-exporter
- ...
- pushgateway,提供一个网关地址,外部数据可以推送到该网关,prometheus也会从该网关拉取数据
- Alertmanager,接收Prometheus发送的告警并对于告警进行一系列的处理后发送给指定的目标
- Grafana:配置数据源,图标方式展示数据
应用监控
普通应用
对于普通应用只需要能够提供一个满足 prometheus 格式要求的 /metrics
接口就可以让 Prometheus 来接管监控,比如 Kubernetes 集群中非常重要的 CoreDNS 插件,一般默认情况下就开启了 /metrics
接口,一般接口端口为9153,通过访问http://Coredns pod ip:9153/metrics,可手动验证接口可用性。
应用开启满足 prometheus 格式要求的 /metrics
接口后,将接口配置到 prometheus.yml
中去,就可实现Prometheus对CoreDNS监控数据的拉起。
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
namespace: kube-mon
data:
prometheus.yml: |
global:
scrape_interval: 15s
scrape_timeout: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'coredns'
static_configs:
- targets: ['10.244.1.15:9153', '10.244.2.127:9153']
exporter监控
有一些应用可能没有自带 /metrics
接口供 Prometheus 使用,在这种情况下,就需要利用 exporter
服务来为 Prometheus 提供指标数据。Prometheus 官方为许多应用就提供了对应的 exporter
应用,也有许多第三方的实现,可以前往官方网站进行查看exporters。
通过一个 redis-exporter 的服务来监控 redis 服务,对于这类应用,我们一般会以 sidecar 的形式和主应用部署在同一个 Pod 中,比如我们这里来部署一个 redis 应用,并用 redis-exporter 的方式来采集监控数据供 Prometheus 使用。
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: kube-mon
spec:
selector:
matchLabels:
app: redis
template:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9121"
labels:
app: redis
spec:
containers:
- name: redis
image: redis:4
resources:
requests:
cpu: 100m
memory: 100Mi
ports:
- containerPort: 6379
- name: redis-exporter # redis-exporter sidecar
image: oliver006/redis_exporter:latest
resources:
requests:
cpu: 100m
memory: 100Mi
ports:
- containerPort: 9121 # /metrics端口为9121
---
kind: Service
apiVersion: v1
metadata:
name: redis
namespace: kube-mon
spec:
selector:
app: redis
ports:
- name: redis
port: 6379
targetPort: 6379
- name: prom
port: 9121
targetPort: 9121
同样的,现在我们只需要更新 Prometheus 的配置文件:
- job_name: 'redis'
static_configs:
- targets: ['redis:9121'] # 当Redis与Prometheus在同一个Namespace时,直接使用servicename调用服务
集群监控
应用监控监控的是Kubernetes集群中的应用,对于 Kubernetes 集群本身的监控也是非常重要的,对于集群的监控一般需要考虑以下几个方面:
- Kubernetes 节点的监控:比如节点的 cpu、load、disk、memory 等指标
- 内部系统组件的状态:比如 kube-scheduler、kube-controller-manager、kubedns/coredns 等组件的详细运行状态
- 编排级的 metrics:比如 Deployment 的状态、资源请求、调度和 API 延迟等数据指标
资源节点监控
资源节点监控数据通过 node_exporter 来获取,顾名思义,node_exporter 就是抓取用于采集服务器节点的各种运行指标,目前 node_exporter 支持几乎所有常见的监控点,比如 conntrack,cpu,diskstats,filesystem,loadavg,meminfo,netstat 等。
node_exporter通过 DaemonSet 控制器来部署该服务,这样每一个节点都会自动运行一个这样的 Pod,如果从集群中删除或者添加节点后,也会进行自动扩展。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter
namespace: kube-mon
labels:
app: node-exporter
spec:
selector:
matchLabels:
app: node-exporter
template:
metadata:
labels:
app: node-exporter
spec:
hostPID: true #使用主机PID namespace
hostIPC: true #使用主机IPC namespace
hostNetwork: true #使用主机网络
nodeSelector:
kubernetes.io/os: linux
containers:
- name: node-exporter
image: prom/node-exporter:v0.18.1
args:
- --web.listen-address=$(HOSTIP):9100
- --path.procfs=/host/proc
- --path.sysfs=/host/sys
- --path.rootfs=/host/root
- --collector.filesystem.ignored-mount-points=^/(dev|proc|sys|var/lib/docker/.+)($|/)
- --collector.filesystem.ignored-fs-types=^(autofs|binfmt_misc|cgroup|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|mqueue|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|sysfs|tracefs)$
ports:
- containerPort: 9100
env:
- name: HOSTIP
valueFrom:
fieldRef:
fieldPath: status.hostIP
resources:
requests:
cpu: 150m
memory: 180Mi
limits:
cpu: 150m
memory: 180Mi
securityContext:
runAsNonRoot: true
runAsUser: 65534
volumeMounts:
- name: proc
mountPath: /host/proc
- name: sys
mountPath: /host/sys
- name: root
mountPath: /host/root
mountPropagation: HostToContainer
readOnly: true
tolerations:
- operator: "Exists"
volumes: # 将主机的 /dev、/proc、/sys这些目录挂载到容器中,这些因为我们采集的很多节点数据都是通过这些文件夹下面的文件来获取到的,比如我们在使用 top 命令可以查看当前 cpu 使用情况,数据就来源于文件 /proc/stat,使用 free 命令可以查看当前内存使用情况,其数据来源是来自 /proc/meminfo 文件
- name: proc
hostPath:
path: /proc
- name: dev
hostPath:
path: /dev
- name: sys
hostPath:
path: /sys
- name: root
hostPath:
path: /
与应用监控不同,node-exporter 程序如果通过一个 Service 来将数据收集到一起,并用静态配置的方式配置到 Prometheus 去中,就只会显示一条数据,我们得自己在指标数据中去过滤每个节点的数据,当然也可以手动的把所有节点用静态的方式配置到 Prometheus 中去,但是以后要新增或者去掉节点的时候就还得手动去配置。
让 Prometheus 去自动发现我们节点的 node-exporter
程序,并且按节点进行分组呢?这就是 Prometheus 里面非常重要的服务发现功能了。
在 Kubernetes 下,Promethues 通过与 Kubernetes API 集成,主要支持5中服务发现模式,分别是:Node
、Service
、Pod
、Endpoints
、Ingress
。
Node服务发现模式:
在 prometheus.yml
文件中配置如下的 job 任务:
- job_name: 'kubernetes-nodes'
kubernetes_sd_configs:
- role: node
指定 kubernetes_sd_configs
的模式为node
,Prometheus 就会自动从 Kubernetes 中发现所有的 node 节点并作为当前 job 监控的目标实例,发现的节点 /metrics
接口是默认的 kubelet 的 HTTP 接口。
默认的Prometheus发现Node模式服务时,访问的是10250端口,是kubelet 自带的一些监控指标数据,可以通过正则表达式修改所有的端口;并通过 labelmap 这个属性来将 Kubernetes 的 Label 标签添加为 Prometheus 的指标数据的标签。
- job_name: 'kubernetes-nodes'
kubernetes_sd_configs:
- role: node
relabel_configs:
- source_labels: [__address__]
regex: '(.*):10250'
replacement: '${1}:9100'
target_label: __address__
action: replace
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
容器监控
cAdvisor是 Google 开源的容器资源监控和性能分析工具,它是专门为容器而生,是最经典的容器监控工具,已经内置在了 kubelet 组件之中,cAdvisor
的数据路径为 /api/v1/nodes/<node>/proxy/metrics
。
- job_name: 'kubernetes-cadvisor'
kubernetes_sd_configs:
- role: node
scheme: https # 使用https协议
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
# ca.cart 和 token 这两个文件可以在 Pod 中访问 apiserver
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels: [__meta_kubernetes_node_name]
regex: (.+)
target_label: __metrics_path__
replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
k8s组件监控
apiserver监控原理:apiserver作为endpoint,隶属于kubernetes服务,在Prometheus的endpoint类型下,筛选default命名空间、kubernetes服务,并配置https连接需要的ca证书。
- job_name: 'kubernetes-apiservers'
kubernetes_sd_configs:
- role: endpoints
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
relabel_configs:
- source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
action: keep
regex: default;kubernetes;https
其他k8s组件在kube-system命名空间下,需要手动创建service, kube-sheduler 的指标数据端口为 10251,kube-controller-manager 对应的端口为 10252。
应用链路追踪
应用链路追踪属于APM,APM (Application Performance Management)是对企业的应用系统进行实时监控,它是用于实现对应用程序性能管理和故障管理的系统化的解决方案。
在现代分布式系统中,追踪请求的路径和性能变得越来越重要。分布式追踪系统可以帮助开发者理解请求在多个服务之间的流转情况,识别性能瓶颈,并快速定位问题。
分布式追踪的核心思想是通过在请求的各个阶段插入追踪点,记录请求的路径、耗时、状态等信息。通常,追踪系统会生成一个唯一的 Trace ID,并在请求经过的每个服务中生成 Span,Span 包含了请求的详细信息,如开始时间、结束时间、操作名称等。即OpenTracing规范,通过提供平台无关、厂商无关的API
,使得开发人员能够方便添加和更换追踪系统的实现。
traceid:一个完整请求链路的追踪ID
spanid(核心):span 称为跨度,一个节点在收到请求以及完成请求的过程是一个 span,span 记录了在这个过程中产生的各种信息。每个节点处理每个请求时都会生成一个独一无二的的 span id,当 A -> C -> D 时,多个连续的 span 会产生父子关系,那么一个 span 除了保存自己的 span id,也需要关联父、子 span id。生成 span id 必须是高性能的,并且能够明确表示时间顺序。
parentid:父span id。
timestamp:时间戳,包括开始时间戳和结束时间戳。
cs和cr记录在A调用B的主调span,sr和ss记录在B返回A的被调span。
接下来分析三种主流的分布式追踪系统:SkyWalking(重点)、Zipkin 和 Jaeger。
Skywalking
skywalking支持dubbo、SpringCloud、SpringBoot集成,代码无侵入(字节码增强技术),通信方式采用GRPC,性能较好,实现方式是java探针,支持告警,支持JVM监控,支持全局调用统计等等,功能较完善。
- 上部分 Agent :负责从应用中,收集链路信息,发送给 SkyWalking OAP 服务器。目前支持 SkyWalking、Zikpin、Jaeger 等提供的 Tracing 数据信息。最常规采用的是,SkyWalking Agent 收集 SkyWalking Tracing 数据,传递给服务器。
- 下部分 SkyWalking OAP :负责接收 Agent 发送的 Tracing 数据信息,然后进行分析(Analysis Core) ,存储到外部存储器( Storage ),最终提供查询( Query )功能。
- 右部分 Storage :Tracing 数据存储。目前支持 ES、MySQL、Sharding Sphere、TiDB、H2 多种存储器。而我们目前采用的是 ES ,主要考虑是 SkyWalking 开发团队自己的生产环境采用 ES 为主。
- 左部分 SkyWalking UI :负责提供控台,查看链路等等。
搭建Skywalking OAP和UI后,如何启动Agent收集和传输链路数据?
需要将 Skywalking Agent目录拷贝到 Java 应用所在的服务器上。这样,Java 应用才可以配置使用该 SkyWalking Agent。
启动Java应用时,通过javaagent参数启动Skywalking Agent
java
-javaagent:/path/to/skywalking-agent.jar
-Dskywalking.agent.service_name=my-service
-Dskywalking.collector.backend_service=192.168.0.33:11800
-jar my-service.jar
-javaagent
:指定skywalking
中的agent
中的skywalking-agent.jar
的路径-Dskywalking.agent.service_name
:指定在skywalking
中的服务名称,一般是微服务的spring.application.name
-Dskywalking.collector.backend_service
:指定oap
服务绑定的地址,如果是本地,由于oap
服务默认的端口是11800
,因此只需要配置为127.0.0.1:11800
Sleuth+Zipkin
Sleuth是Spring Cloud提供的一个分布式追踪解决方案,它通过在请求中添加唯一标识和跟踪信息,来记录请求在系统中的传递过程。而Zipkin是一个开源的分布式追踪系统,它可以收集和展示分布式系统中的请求跟踪数据。
Zipkin 分为两端,一个是 Zipkin 服务端,一个是 Zipkin 客户端,客户端内置在微服务应用中,客户端会配置服务端的 URL 地址,一旦发生服务间的调用的时候,会被配置在微服务里面的 Sleuth 的监听器监听,并生成相应的 Trace 和 Span 信息发送给服务端。发送的方式有两种,一种是消息总线的方式如 RabbitMQ 发送,还有一种是 HTTP 报文的方式发送。
Zipkin和Sleuth对业务是有侵入的:
首先,需要在项目中引入Sleuth和Zipkin的依赖。在pom.xml
文件中添加以下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
在 Spring Boot 应用程序中,Spring Cloud Sleuth 会自动为每个请求生成一个唯一的跟踪 ID,并将跟踪 ID 添加到日志信息中。
在项目的配置文件中,添加以下配置:
spring:
zipkin:
base-url: http://localhost:9411/ # 服务端地址
sender:
type: web # 数据传输方式,web 表示以 HTTP 报文的形式向服务端发送数据
sleuth:
sampler:
probability: 1.0 # 收集数据百分比,默认 0.1(10%)
Jaeger
Jaeger 受Dapper和OpenZipkin的启发,是Uber Technologies以开源形式发布的分布式跟踪系统。
Jaeger有5个模块元素:
- Jaeger-client:Jaeger客户端代码库,便于不同语言的项目来介入到Jaeger中,当应用程序装载上之后,client会负责收集并发送数据到Agent。当前Jaeger的SDK支持有Go、Java、Python、C++、Node等;Jaeger-client实现了OpenTracing API接口,当应用建立Span并发出请求到下游的服务时,它会附带跟踪信息(Trace ID, Span ID, baggage)。其他信息比如请求的名字,请求的参数,日志不会发给下游服务,而会被取样并异步的通过Jaeger-client发送到Jaeger-agent。
- Jaeger-agent:Jaeger客户端代理,与应用运行在同一个机器里,负责接受从客户端通过UDP发来的Trace/Span信息并批量上传到Jaeger收集器。agent的被设计成一个基础组件,旨在作为基础架构组件部署到所有宿主机。
- Jaeger-collector:从agent收集traces信息,并通过处理管道处理(信息校验、检索),再写入后端存储(backends)
- DB:支持Cassandra,Elasticsearch和Kafka
- Query & UI:数据查询与前端界面展示。Query查询是一种从存储中检索trace,并提供UI以显示服务。
与Zipkin一样,SpringCloud或Springboot应用使用Jaeger也需要进行依赖配置和配置文件配置。
- 首先,安装Jaeger Collector、DB、Query等服务器端。
- 其次,安装Jaeger-agent,在K8S中,官方支持DaemonSet和Sidecar两种部署方式:
DaemonSet方式运行在节点,接受节点上所有应用pods发送的数据,可以节省计算和内存资源;但DaemonSet方式中pod和Agent不能通过localhost通信,需要考虑pod和Agent通信方式。
为解决通讯问题,Agent需要使用主机网络(hostNetwork), 应用中需要借用 Kubernetes Downward API 获取节点IP信息。
DaemonSet 模式部署 Agent:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: jaeger-agent
labels:
app: jaeger-agent
spec:
selector:
matchLabels:
app: jaeger-agent
template:
metadata:
labels:
app: jaeger-agent
spec:
containers:
- name: jaeger-agent
image: jaegertracing/jaeger-agent:1.12.0
env:
- name: REPORTER_GRPC_HOST_PORT
value: "jaeger-collector:14250" #配置Jaeger collector地址
resources: {}
hostNetwork: true # 使用主机网络,pod ip会与节点ip一致,pod端口占用节点端口
dnsPolicy: ClusterFirstWithHostNet
restartPolicy: Always
通过 Kubernetes Downward API 将节点的IP信息(status.hostIP) 以环境变量的形式注入到应用容器中:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: example/myapp:version
env:
- name: JAEGER_AGENT_HOST
valueFrom:
fieldRef:
fieldPath: status.hostIP
在 Sidecar 方式部署下,对于 Jaeger Agent 会作为 pod 中的一个容器和 tarcer 并存,由于运行在应用级别,不需要额外的权限。
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
labels:
app: myapp
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp # 应用容器
image: example/myapp:version
- name: jaeger-agent # Sidecar容器
image: jaegertracing/jaeger-agent:1.12.0
env:
- name: REPORTER_GRPC_HOST_PORT
value: "jaeger-collector:14250"
最后,应用兼容运行Jaeger client,以SpringCloud Java应用为例,将 Jaeger 依赖项添加到 pom 文件中
<dependency>
<groupId>io.opentracing.contrib</groupId>
<artifactId>opentracing-spring-jaeger-cloud-starter</artifactId>
<version>3.3.1</version>
</dependency>
在配置文件中增加opentracing.jaeger配置
opentracing:
jaeger:
enabled: true
log-spans: true
const-sampler:
decision: true
udp-sender:
host: localhost # Sidecar使用localhost,DaemonSet需使用传入的JAEGER_AGENT_HOST参数
port: 6831
APM工具对比
zipkin | jaeger | skywalking | |
---|---|---|---|
OpenTracing兼容 | 是 | 是 | 是 |
客户端支持语言 | java,c#,go,php等 | java,c#,go,php等 | Java, .NET Core, NodeJS and PHP |
存储 | ES,mysql,Cassandra,内存 | ES,kafka,Cassandra,内存 | ES,H2,mysql,TIDB,sharding sphere |
传输协议支持 | http,MQ | udp/http | gRPC |
ui丰富程度 | 低 | 中 | 中 |
实现方式-代码侵入性 | 拦截请求,侵入 | 拦截请求,侵入 | 字节码注入,无侵入 |
扩展性 | 高 | 高 | 中 |
trace查询 | 支持 | 支持 | 支持 |
告警支持 | 不支持 | 不支持 | 支持 |
jvm监控 | 不支持 | 不支持 | 支持 |
性能损失 | 中 | 中 | 低 |
日志收集
日志收集是帮助服务故障时快速定位问题的关键,在微服务架构中,每个微服务都会产生大量的日志数据,服务的调用错综复杂,分布式日志采集就成为关键。
ELK是成熟且使用最为广泛的分布式日志解决方案。
ELK不是一个框架,而是包含三款产品的组合:Elasticsearch、Logstash、Kibana :
Elasticsearch是个开源分布式搜索引擎,提供搜集、分析、存储数据三大功能。它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,restful风格接口,多数据源,自动搜索负载等。
Logstash 主要是用来日志的搜集、分析、过滤日志的工具,支持大量的数据获取方式。一般工作方式为c/s架构,client端安装在需要收集日志的主机上,server端负责将收到的各节点日志进行过滤、修改等操作在一并发往elasticsearch上去。
Kibana可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面,可以帮助汇总、分析和搜索重要数据日志。
简单理解:通过Logstash来收集数据,然后Logstash把数据存储到ElasticSearch中,在使用Kibana可视化工具和ElasticSearch交互,提供友好的界面来方便分析和统计数据。
微服务集成ELK
微服务日志收集到ELK服务器,实现原理就是通过logstash来收集logback日志框架打印的日志,然后发送到云服务器ELK中。
首先在微服务应用中添加logback依赖
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>5.2</version>
</dependency>
然后为微服务添加日志配置文件 logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<!--输出到控制台-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="stash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<!-- logstash服务器地址-->
<destination>192.168.119.129:5044</destination>
<encoder class="net.logstash.logback.encoder.LogstashEncoder" />
</appender>
<root level="info" additivity="false">
<appender-ref ref="console"/>
<appender-ref ref="stash"/>
</root>
</configuration>
K8S集成ELK
要在K8S内采集容器日志传输给ELK,如果不能像Springboot应用一样通过logback传输日志给logstash,就需要有组件主动采集标准输出或应用日志,如filebeat或logtail。
[!NOTE]
当Docker 作为 k8s 容器运行时,容器日志的落盘将由 docker 来完成,保存在/var/lib/docker/containers/$CONTAINERID 目录下。Kubelet 会在 /var/log/pods 和 /var/log/containers 下建立软链接,指向 /var/lib/docker/containers/CONTAINERID 该目录下的容器日志文件。
当Containerd 作为 k8s 容器运行时, 容器日志的落盘由 Kubelet 来完成,保存至 /var/log/pods/$CONTAINER_NAME 目录下,同时在 /var/log/containers 目录下创建软链接,指向日志文件。
Filebeat是用于转发和集中日志数据的轻量级传送工具。Filebeat监视指定的日志文件或位置,收集日志事件。Filebeat 只是一个二进制文件没有任何依赖。它占用资源极少。
对于 K8S 内的容器日志收集,一般有两种常用的方式:
- 使用 DaemonSet 在每台 Node 上部署一个日志收集容器,用于收集当前 Node 上所有容器挂载到宿主机目录下的日志。使用 DaemonSet 方式部署日志收集服务,管理起来简单,但是如果一个 Node 中运行了过多的 Pod,那么日志收集会存在性能瓶颈。
- 使用 SideCar 模式将日志收集容器与业务容器部署在同一个 Pod 中,只收集对应容器的日志。使用 SideCar 模式可以更有针对性的收集容器的日志,但是缺点是在运行了很多的业务时,SideCar 占用的资源也会增加。
Sidecar运行filebeat采集日志
首先,使用ConfigMap保存filebeat-config:
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-config
namespace: kube-system
labels:
k8s-app: filebeat
data:
filebeat.yml: |-
filebeat.inputs:
- type: container
paths:
- /var/log/containers/api-*.log
#多行合并
multiline.pattern: '^[0-9]{4}-[0-9]{2}-[0-9]{2}'
multiline.negate: true
multiline.match: after
multiline.timeout: 30
fields:
#自定义字段用于logstash识别k8s输入的日志
service: k8s-log
#禁止收集host.xxxx字段
#publisher_pipeline.disable_host: true
processors:
- add_kubernetes_metadata:
#添加k8s描述字段
default_indexers.enabled: true
default_matchers.enabled: true
host: ${NODE_NAME}
matchers:
- logs_path:
logs_path: "/var/log/containers/"
- drop_fields:
#删除的多余字段
fields: ["host", "tags", "ecs", "log", "prospector", "agent", "input", "beat", "offset"]
ignore_missing: true
output.redis:
hosts: ["192.168.3.44"]
#password: ""
key: "k8s-java-log_test"
db: 1
timeout: 5
#output.logstash:
# hosts: ["192.168.3.101:5044"]
创建应用Deployment时,filebeat作为sidecar运行。
containers:
- image: nginx:latest
name: nginx
ports:
- containerPort: 80
volumeMounts:
- name: access-log #日志同时挂载在nginx和filebeat中
mountPath: /var/log/nginx/
- image: docker.elastic.co/beats/filebeat:6.8.12
imagePullPolicy: Always
name: filebeat
volumeMounts:
- name: access-log #日志同时挂载在nginx和filebeat中
mountPath: /log
- name: filebeat-config
mountPath: /usr/share/filebeat/filebeat.yml
subPath: filebeat.yml
volumes:
- name: filebeat-config
configMap:
name: filebeat-config
items:
- key: filebeat.yml
path: filebeat.yml
DaemonSet运行filebeat采集日志
创建配置文件filebeat-config.yaml并运行
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-config
namespace: k8s-demo
data:
config: |
filebeat.prospectors:
- input_type: log
paths:
- /var/log/*
json:
keys_under_root: true # 将JSON格式的日志key拆分到file顶层,若不加该配置则会统一写入到message属性下
processors:
- drop_fields:
fields: ["input_type", "offset", "beat", "type"] # 写入ES时删除filebeat自带的一些属性
output.elasticsearch:
hosts: ['elasticsearch_host:9200']
username: username
password: pwd
indices:
- index: "filebeat-k8s-demo-log-%{+yyyyMMdd}"
启动DaemonSet形式的filebeat实例
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: elastic-filebeat
namespace: k8s-demo
labels:
server: filebeat-5.6.16
spec:
selector:
matchLabels:
name: elastic-filebeat
template:
metadata:
labels:
name: elastic-filebeat
spec:
containers:
- image: elastic/filebeat:5.6.16
name: honeypot-filebeat
securityContext:
runAsUser: 0
runAsGroup: 0
fsGroup: 0
volumeMounts:
- mountPath: /usr/share/filebeat/data
name: fb-data
- mountPath: /var/log
name: fb-log
- mountPath: /usr/share/filebeat/filebeat.yml
subPath: filebeat.yml # 只修改filebeat.yml,避免清空整个目录
name: cfg
readOnly: true
volumes:
- name: fb-data
hostPath:
path: /usr/share/filebeat/data # 该目录下有一个registry文件,里面记录了filebeat采集日志位置的相关内容,比如文件offset、source、timestamp等,如果Pod发生异常后K8S自动将Pod进行重启,不挂载的情况下registry会被重置,将导致日志文件又从offset=0开始采集,结果就是es中日志重复一份
- name: fb-log
hostPath:
path: /var/application-logs
- name: cfg
configMap:
name: filebeat-config
items:
- key: config
path: filebeat.yml