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 类型的视图,如通过 ModelAndView 中的视图名称将其解析成 View,View 是用来渲染页面的,也就是将 Model 填入模板中,生成 html 或其他格式的文件。
可以设置多个解析策略,如可以根据 JSP 来解析,或者按照 Velocity 模版解析,如果设置了多个解析策略则可以通过 order 属性来设定其优先级,数值越小优先级越高,前面的视图解析器解析后就不会让后面的继续解析。默认的解析策略是 InternalResourceViewResolver,按照 JSP 页面来解析。ViewResolver 接口中的方法如下:
  • View resolveViewName(String viewName, Locale locale);

该接口的实现类有AbstractCachingViewResolver、BeanNameViewResolver、ContentNegotiatingViewResolver、StandaloneMockMvcBuilder和ViewResolverComposite。

1、AbstractCachingViewResolver:实现带缓存的ViewResolver

来看上图中的第一个实现类:AbstractCachingViewResolver,该类实现了ViewResolver的resolveViewName接口,与其他实现类不同,AbstractCachingViewResolver实现的是带有缓存的ViewResolver。
当前端控制器请求视图解析器解析 ModelAndView 时,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,就只支持解释单一类型的视图。

ResourceBundleViewResolver 根据 views.properties 文件来解析视图,这个文件位于 classpath 路径下,使用方式如下:
<bean class="org.springframework.web.servlet.view.ResourceBundleViewResolver">  
    <!-- 设定属性文件名为views -->  
    <property name="basename" value="views"></property>  
</bean>  

1.2、XmlViewResolver

XmlViewResolver 根据 xml 文件来解析视图,使用方式如下:
<bean class="org.springframework.web.servlet.view.XmlViewResolver">
    <property name="location">
        <value>/WEB-INF/spring-views.xml</value>
    </property>
</bean>

1.3、UrlBasedViewResolver

支持解释单一类型的视图。

UrlBasedViewResolver 提供了拼接 URL 的方式来解析视图,通过 prefix 属性拼接一个前缀,通过 suffix 属性拼接一个后缀,就得到了视图的 URL。还可以加入 redirect: 与 forword: 前缀,使用 redirect: 前缀会调用 HttpServletResponse对象的 sendRedirect() 方法进行重定向,使用 forword: 前缀会利用 RequestDispatcher的forword 方式跳转到指定的地址。另外,使用时还要指定 viewClass 属性,表示要解析成哪种 View,的使用方式如下:
<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原理解析的主要内容,如果未能解决你的问题,请参考以下文章

Bagging原理解析

Zookeeper原理解析

Kubernetes Service原理解析

WebRTC原理解析

SpringBoot Starter运行原理代码解析

【转载】AlphaGo原理解析