NPD原理解析
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NPD原理解析相关的知识,希望对你有一定的参考价值。
参考技术A 节点问题检测器(Node Problem Detector) 是一个守护程序,用于监视和报告节点的健康状况(包括内核死锁、OOM、系统线程数压力、系统文件描述符压力等指标)。 你可以将节点问题探测器以 DaemonSet 或独立守护程序运行。 节点问题检测器从各种守护进程收集节点问题,并以 NodeCondition 和 Event 的形式报告给 API Server。
您可以通过检测相应的指标,提前预知节点的资源压力,可以在节点开始驱逐 Pod 之前手动释放或扩容节点资源压力,防止 Kubenetes 进行资源回收或节点不可用可能带来的损失。
Git 仓库地址: https://github.com/kubernetes/node-problem-detector
当kubernetes中节点发生上述问题,在整个集群中,k8s服务组件并不会感知以上问题,就会导致pod仍会调度至问题节点。
为了解决这个问题,我们引入了这个新的守护进程node-problem-detector,从各个守护进程收集节点问题,并使它们对上游层可见。一旦上游层面发现了这些问题,我们就可以讨论补救措施。
NPD使用Go modules管理依赖,因此构建它需要Go SDK 1.11+:
构建节点问题检测器的 docker 镜像时,会嵌入 默认配置 。
不过,你可以像下面这样使用 ConfigMap 将其覆盖:
1、更改 config/ 中的配置文件
2、创建 ConfigMap node-strick-detector-config:
3、更改 node-problem-detector.yaml 以使用 ConfigMap:
4、使用新的配置文件重新创建节点问题检测器:
说明: 此方法仅适用于通过 kubectl 启动的节点问题检测器。
如果节点问题检测器作为集群插件运行,则不支持覆盖配置。 插件管理器不支持 ConfigMap。
通常这些错误是比较难真实测试,只能通过发送消息到journal来模拟。
然后通过k8s控制台,你可以看到对应的信息:
然后通过以下命令来对应的event
Problem Daemon 是监控任务子守护进程,NPD 会为每一个 Problem Daemon 配置文件创建一个守护进程,这些配置文件通过 --config.custom-plugin-monitor、--config.system-log-monitor、--config.system-stats-monitor 参数指定。每个 Problem Daemon监控一个特定类型的节点故障,并报告给NPD。目前 Problem Daemon 以 Goroutine 的形式运行在NPD中,未来会支持在独立进程(容器)中运行并编排为一个Pod。在编译期间,可以通过相应的标记禁用每一类 Problem Daemon。
ProblemDaemonHandler 定义了 Problem Daemon 的初始化方法
在NPD启动时,init()方法中完成了 ProblemDaemonHandler 的注册:
Exporter 用于上报节点健康信息到某种控制面。在 NPD 启动时,会根据需求初始化并启动各种 Exporter。Exporter 分为三类:
ExporterHandler 和 ProblemDaemonHandler 功能类似,其定义了 Exporter 的初始化方法。也是在NPD启动时,init()方法中完成了 ExporterHandler 的注册
K8s Exporter 获取到的异常 Condition 信息会上报给 Condition Manager, Condition Manager 每秒检查 Condition 的变化,并同步到 API Server 的 Node 对象中。
Problem Client 负责与 API Server 交互,并将巡检过程中生成的 Events 和 Conditions 上报给 API Server。
Problem Detector 是 NPD 的核心对象,它负责启动所有的 Problem Daemon(也可以叫做 Monitor),并利用 channel 收集 Problem Daemon中发现的异常信息,然后将异常信息提交给 Exporter,Exporter 负责将这些异常信息上报到指定的控制面(如 API Server、Prometheus、 Stackdriver 等)。
Status 是 Problem Daemon 向 Exporter 上报的异常信息对象。
用于从外部控制协程的生命周期, 它的逻辑很简单,准备结束生命周期时:
NPD 启动过程完成的工作有:
采集节点的健康状态是为了能够在业务Pod不可用之前提前发现节点异常,从而运维或开发人员可以对Docker、Kubelet或节点进行修复。在NPDPlus中,为了减轻运维人员的负担,提供了根据采集到的节点状态从而进行不同自愈动作的能力。集群管理员可以根据节点不同的状态配置相应的自愈能力,如重启Docker、重启Kubelet或重启CVM节点等。同时为了防止集群中的节点雪崩,在执行自愈动作之前做了严格的限流,防止节点大规模重启。同时为了防止集群中的节点雪崩,在执行自愈动作之前做了严格的限流。具体策略为:
在同一时刻只允许集群中的一个节点进行自愈行为,并且两个自愈行为之间至少间隔1分钟
当有新节点添加到集群中时,会给节点2分钟的容忍时间,防止由于节点刚刚添加到集群的不稳定性导致错误自愈
此Problem Daemon为NPD提供了一种插件化机制,允许基于任何语言来编写监控脚本,只需要这些脚本遵循NPD关于退出码和标准输出的规范。通过调用用户配置的脚本来检测各种节点问题
脚本退出码:
脚本输出应该小于80字节,避免给Etcd的存储造成压力
使用标记禁用:disable_custom_plugin_monitor
plugin 是NPD或用户自定义的一些异常检查程序,可以用任意语言编写。custom-plugin-monitor 在执行过程中会执行这些异常检测程序,并根据返回结果来判断是否存在异常。NPD提供了三个 plugin,分别是:
health-checker 的执行流程可以分为三个步骤:
依赖的插件是 journald,其作用是统计指定的 journal 日志中近一段时间满足正则匹配的历史日志条数。
检查 conntrack table 的使用率是否超过 90%
system-log-monitor 用于监控系统和内核日志,根据预定义规则来报告问题、指标。它支持基于文件的日志、Journald、kmsg。要监控其它日志,需要实现LogWatcher接口
LogWatcher 的主要作用的监听文件更新,并将追加的文件内容写入 LogBuffer 中供 LogMonitor 处理。NPD 中提供了三种 LogWatcher 的实现:
LogWatcher 也需要在 init() 方法中完成注册。
filelog 通过监控指定的文件更新,并对日志内容进行正则匹配,以发现异常日志,从而判断组件是否正常。
journald 底层依赖 sdjournal 包,监控系统日志的更新,并且可以从指定的历史时间点开始读取。如果未指定 journal 日志路径,则从系统默认路径读取。读取到的日志会转换成 logtypes.Log 对象,并写入 logCh 通道中。journal 通过监控 journal 文件更新,并对日志内容进行正则匹配,以发现异常日志,从而判断组件是否正常。
kmsg 和 journald 的实现原理类似,它底层依赖 kmsgparser 包,实现内核日志的监控更新和回溯。默认的文件路径是 /dev/kmsg。kmsg 通过监控系统日志文件更新,并对日志内容进行正则匹配,以发现异常日志,从而判断组件是否正常。
LogBuffer 是一个可循环写入的日志队列,max 字段控制可记录日志的最大条数,当日志条数超过 max 时,就会从头覆盖写入。LogBuffer 也支持正则匹配 buffer 中的日志内容。
将各种健康相关的统计信息报告为Metrics
目前支持的组件仅仅有主机信息、磁盘:
使用标记禁用:disable_system_stats_monitor
health-checker-kubelet.json
kernel-monitor.json
node-problem-detector使用 Event 和 NodeCondition 将问题报告给apiserver。
通过配置 metricsReporting 可以选择是否开启 System Log Monitor 的指标上报功能。该字段默认为 true。
临时异常只会上报 counter 指标,如下:
永久异常会上报 gauge 指标和 counter 指标,如下:
Counter是一个累计类型的数据指标,它代表单调递增的计数器。
Gauge是可以任意上下波动数值的指标类型。
NPD对指标这一概念也进行了封装,它依赖OpenCensus而不是Prometheus这样具体的实现的API。
所有指标如下:
其中ProblemCounterID 和 ProblemGaugeID 是针对所有Problem的Counter/Gauge,其他都是SystemStatsMonitor暴露的指标。
在NPD的术语中,治愈系统(Remedy System)是一个或一组进程,负责分析NPD检测出的问题,并且采取补救措施,让K8S集群恢复健康状态。
目前官方提及的治愈系统有只有Draino。NPD项目并没有提供对Draino的集成,你需要手工部署和配置Draino。
Draino 是Planet开源的小项目,最初在Planet用于解决GCE上运行的K8S集群的持久卷相关进程(mkfs.ext4、mount等)永久卡死在不可中断睡眠状态的问题。Draino的工作方式简单粗暴,只是检测到NodeCondition并Cordon、Drain节点。
基于Label和NodeCondition自动的Drain掉故障K8S节点:
Draino可以联用Cluster Autoscaler,自动的终结掉Drained的节点。
在Descheduler项目成熟以后,可以代替Draino。
kubernetes addons之node-problem-detector
Kubernetes故障检测和自愈
Spring MVC工作原理及源码解析 ViewResolver实现原理及源码解析
0、ViewResolver原理介绍
- View resolveViewName(String viewName, Locale locale);
该接口的实现类有AbstractCachingViewResolver、BeanNameViewResolver、ContentNegotiatingViewResolver、StandaloneMockMvcBuilder和ViewResolverComposite。
1、AbstractCachingViewResolver:实现带缓存的ViewResolver
public View resolveViewName(String viewName, Locale locale) throws Exception { // 是否启用缓存,可通过setCache()方法或setCacheLimit()方法开启缓存,是一个ConcurrentHashMap,默认缓存大小1024 if (!this.isCache()) { return this.createView(viewName, locale); } else { // 得到 view 在缓存中的 key 值 Object cacheKey = this.getCacheKey(viewName, locale); View view = (View)this.viewAccessCache.get(cacheKey); // 如果没有找到 view 则创建,采用双重校验的方式进行安全创建 if (view == null) { synchronized(this.viewCreationCache) { view = (View)this.viewCreationCache.get(cacheKey); if (view == null) { // 具体的创建方式由子类实现 view = this.createView(viewName, locale); if (view == null && this.cacheUnresolved) { view = UNRESOLVED_VIEW; } if (view != null) { this.viewAccessCache.put(cacheKey, view); this.viewCreationCache.put(cacheKey, view); if (this.logger.isTraceEnabled()) { this.logger.trace("Cached view [" + cacheKey + "]"); } } } } } return view != UNRESOLVED_VIEW ? view : null; } }
1.1、ResourceBundleViewResolver
使用ResourceBundleViewResolver配置下bean就可以让视图解释器支持解析多种视图,而UrlBasedViewResolver,就只支持解释单一类型的视图。
<bean class="org.springframework.web.servlet.view.ResourceBundleViewResolver"> <!-- 设定属性文件名为views --> <property name="basename" value="views"></property> </bean>
1.2、XmlViewResolver
<bean class="org.springframework.web.servlet.view.XmlViewResolver"> <property name="location"> <value>/WEB-INF/spring-views.xml</value> </property> </bean>
1.3、UrlBasedViewResolver
支持解释单一类型的视图。
<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver"> <property name="prefix" value="/WEB-INF/" /> <property name="suffix" value=".jsp" /> <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView"/> </bean>
2、其他的 ViewResolver
2.1、BeanNameViewResolver
BeanNameViewResolver 是通过视图名称去容器中获取对应的 view 对象,所以在使用前需要将 view 对象注册到容器中。它没有使用缓存,实现方式如下:
@Override public View resolveViewName(String viewName, Locale locale) throws BeansException { ApplicationContext context = getApplicationContext(); // 根据viewName去容器中查找View对象 if (!context.containsBean(viewName)) { if (logger.isDebugEnabled()) { logger.debug("No matching bean found for view name \'" + viewName + "\'"); } // Allow for ViewResolver chaining... return null; } if (!context.isTypeMatch(viewName, View.class)) { if (logger.isDebugEnabled()) { logger.debug("Found matching bean for view name \'" + viewName + "\' - to be ignored since it does not implement View"); } // Since we\'re looking into the general ApplicationContext here, // let\'s accept this as a non-match and allow for chaining as well... return null; } return context.getBean(viewName, View.class); }
2.2、ContentNegotiatingViewResolver
ContentNegotiatingViewResolver本身不解析解析视图,而是用来整合所有的ViewResolver类,每次请求都会遍历所有的ViewResolver,然后找到最合适的处理View,并将其返回。源码如下:
@Override public View resolveViewName(String viewName, Locale locale) throws Exception { RequestAttributes attrs = RequestContextHolder.getRequestAttributes(); Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes"); // 获取Request的MediaType集合 List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest()); if (requestedMediaTypes != null) { // 通过遍历ViewResolver,获取所有符合条件的View List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes); // 遍历所有的SmartView,SmartView默认是RedirectView返回 // 否则,根据MediaType最合适的第一个View返回 View bestView = getBestView(candidateViews, requestedMediaTypes, attrs); if (bestView != null) { return bestView; } } if (this.useNotAcceptableStatusCode) { if (logger.isDebugEnabled()) { logger.debug("No acceptable view found; returning 406 (Not Acceptable) status code"); } return NOT_ACCEPTABLE_VIEW; } else { logger.debug("No acceptable view found; returning null"); return null; } }
2.3、StandaloneMockMvcBuilder
StandaloneMockMvcBuilder主要用于单元测试,代码如下所示:
/** * A {@link ViewResolver} that always returns same View.(始终返回同一个View,用于单元测试) */ private static class StaticViewResolver implements ViewResolver { private final View view; public StaticViewResolver(View view) { this.view = view; } @Override public View resolveViewName(String viewName, Locale locale) throws Exception { return this.view; } }
2.4、ViewResolverComposite
ViewResolverComposite是包含如上各个ViewResolver的组合类,其resolveViewName方法代码如下:
@Override public View resolveViewName(String viewName, Locale locale) throws Exception { for (ViewResolver viewResolver : this.viewResolvers) { // 生成View对象 View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } } return null; }
以上是关于NPD原理解析的主要内容,如果未能解决你的问题,请参考以下文章