整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

「day-ui」 Alert 组件学习

「day-ui」 Alert 组件学习


样式功能来看,整体不是很复杂,alert 组件主要包括了主题色,title,关闭按钮,关闭事件,居中,加粗等

源码

  • template
<template>
  <!-- 显示隐藏有动画效果 -->
  <!-- 开发没用过,不是很理解为什么使用v-show判断显示 -->
  <transition name="d-alert-fade">
    <div
      class="d-alert"
      :class="[typeClass, center ? 'is-center' : '', 'is-' + effect]"
      v-show="visible"
      role="alert"
    >
      <!-- 左侧图标 -->
      <i
        class="d-alert__icon"
        :class="[iconClass, isBigIcon]"
        v-if="showIcon"
      ></i>
      <!-- title 和 描述 -->
      <div class="d-alert__content">
        <span
          class="d-alert__title"
          :class="[isBoldTitle]"
          v-if="title || $slots.title"
        >
          <slot name="title">{{ title }}</slot>
        </span>
        <p v-if="$slots.default && !description" class="d-alert__description">
          <slot></slot>
        </p>
        <p v-if="description && !$slots.default" class="d-alert__description">
          {{ description }}
        </p>
        <i
          class="d-alert__closebtn"
          :class="{
            'is-customed': closeText !=='',
            'd-icon-close': closeText===''
          }"
          v-show="closable"
          @click="close"
          >{{ closeText }}</i
        >
      </div>
    </div>
  </transition>
</template>

使用 role 属性告诉辅助设备(如屏幕阅读器)这个元素所扮演的角色。本质上是增强语义性,当现有的 HTML标签不能充分表达语义性的时候,就可以借助 role 来说明。

这里不是很理解为什么 titledescription 使用了属性和 slot 判断,有清楚的朋友可以帮忙解答

  • props 属性比较常规,这里就不介绍了哈
setup(props, { emit, slots }) {
    // 接受的属性转为响应式
    const { description, type }=toRefs(props)
    // 使用 v-show 显示隐藏
    const visible=ref(true)
    // 关闭事件
    const close=()=> {
      visible.value=false
      emit('close')
    }
    const typeClass=computed(()=> {
      return `d-alert--${type.value}`
    })

    const iconClass=computed(()=> {
      return TYPE_CLASSES_MAP[type.value] || 'd-icon-info'
    })

    const isBigIcon=computed(()=> {
      return description.value || slots.default ? 'is-big' : ''
    })
    const isBoldTitle=computed(()=> {
      return description.value || slots.default ? 'is-bold' : ''
    })
    return {
      close,
      visible,
      typeClass,
      iconClass,
      isBigIcon,
      isBoldTitle
    }
  }

组件介绍到这里就结束了,比较简单。为了凑字呢,这里在介绍下 transition 组件

transition

大部分朋友都了解这是设置组件动画的内置动画组件。通常有三种使用方式:

  1. CSS 过渡
  2. CSS 动画
  3. Javascript 钩子

CSS 过渡

我们通常使用的方法,css 配置 enterleave

<template>
  <div class="app">
    <button @click="show=!show">
      Toggle render
    </button>
    <transition name="fade">
      <p v-if="show">我是测试</p>
    </transition>
  </div>
</template>
<script>
  export default {
    data() {
      return {
        show: true
      }
    }
  }
</script>
<style>
  .fade-enter-active,
  .fade-leave-active {
    transition: opacity 0.5s ease;
  }
  .fade-enter-from,
  .fade-leave-to {
    opacity: 0;
  }
</style>

CSS 动画

<template>
  <div class="app">
    <button @click="show=!show">Toggle show</button>
    <transition name="bounce">
      <p v-if="show">我是测试</p>
    </transition>
  </div>
</template>
<script>
  export default {
    data() {
      return {
        show: true
      }
    }
  }
</script>
<style>
  .bounce-enter-active {
    animation: bounce-in 0.5s;
  }
  .bounce-leave-active {
    // reverse 很关键
    animation: bounce-in 0.5s reverse;
  }
  @keyframes bounce-in {
    0% {
      transform: scale(0);
    }
    50% {
      transform: scale(1.5);
    }
    100% {
      transform: scale(1);
    }
  }
</style>

js 钩子

监听 transition 组件的内置方法,js 控制动画

<template>
  <div class="app">
    <button @click="show=!show">
      Toggle render
    </button>
    <transition
      @before-enter="beforeEnter"
      @enter="enter"
      @before-leave="beforeLeave"
      @leave="leave"
      css="false"
    >
      <p v-if="show">hello</p>
    </transition>
  </div>
</template>
<script>
  export default {
    data() {
      return {
        show: true
      }
    },
    methods: {
      beforeEnter(el) {
        el.style.opacity=0
        el.style.transition='opacity 0.5s ease'
      },
      enter(el) {
        this.$el.offsetHeight
        el.style.opacity=1
      },
      beforeLeave(el) {
        el.style.opacity=1
      },
      leave(el) {
        el.style.transition='opacity 0.5s ease'
        el.style.opacity=0
      }
    }
  }
</script>

如果形参不指定 done ,则表明用户不手动控制动画的结束,而转由节点的 transition 或者 animationEnd 来标识动画结束,开始回调 afterEnter

钩子函数的形参的个数大于1,表示形参中有 done, 也就是说用户必须手动控制动画何时结束。所以一旦你配置了 done 形参,则转由你告诉框架,动画何时结束。需要在合适的时机调用 done,否则 afterEnter 接口就没法被调用了。

动画触发条件

  • 条件渲染(v-if)
  • 条件展示(v-show)
  • 动态组件
  • 组件根节点

执行原理

实例

<template>
  <div class="app">
    <button @click="show=!show">
      Toggle render
    </button>
    <transition name="fade">
      <p v-if="show">hello</p>
    </transition>
  </div>
</template>

编译生成的 render 函数(不是使用的模板组件)

import {
  createVNode as _createVNode,
  openBlock as _openBlock,
  createBlock as _createBlock,
  createCommentVNode as _createCommentVNode,
  Transition as _Transition,
  withCtx as _withCtx,
} from "vue";
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (
    // 收集动态节点 如 v-if v-for
    _openBlock(),
    _createBlock("template", null, [
      _createVNode("div", { class: "app" }, [
        _createVNode(
          "button",
          {
            onClick: ($event)=> (_ctx.show=!_ctx.show),
          },
          " Toggle render ",
          8 /* PROPS */,
          ["onClick"]
        ),
        _createVNode(
          _Transition,
          { name: "fade" },
          {
            // transition 只有一个子节点,默认插槽。多个子节点报错
            default: _withCtx(()=> [
              _ctx.show
                ? (_openBlock(), _createBlock("p", { key: 0 }, "hello"))
                : _createCommentVNode("v-if", true),
            ]),
            _: 1,
          }
        ),
      ]),
    ])
  );
}

那么如何在组建创建和销毁的时候执行事件呢?————创建钩子函数 transition 组件返回的是处理过的第一个子节点

  • 如果 Transition 组件内部嵌套的是 KeepAlive 组件,那么它会继续查找 KeepAlive 组件嵌套的第一个子元素节点,来作为渲染的元素节点。
  • 如果 Transition 组件内部没有嵌套任何子节点,那么它会渲染空的注释节点。

trantion 组件定义

const Transition=(props, { slots })=>
  //esolveTransitionProps 函数主要作用是,在我们给 Transition 传递的 Props 基础上做一层封装,然后返回一个新的 Props 对象,由于它包含了所有的 Props 处理
  h(BaseTransition, resolveTransitionProps(props), slots);
const BaseTransition={
  name: `BaseTransition`,
  props: {
    mode: String,
    appear: Boolean,
    persisted: Boolean,
    // enter
    onBeforeEnter: TransitionHookValidator,
    onEnter: TransitionHookValidator,
    onAfterEnter: TransitionHookValidator,
    onEnterCancelled: TransitionHookValidator,
    // leave
    onBeforeLeave: TransitionHookValidator,
    onLeave: TransitionHookValidator,
    onAfterLeave: TransitionHookValidator,
    onLeaveCancelled: TransitionHookValidator,
    // appear
    onBeforeAppear: TransitionHookValidator,
    onAppear: TransitionHookValidator,
    onAfterAppear: TransitionHookValidator,
    onAppearCancelled: TransitionHookValidator,
  },
  setup(props, { slots }) {
    const instance=getCurrentInstance();
    const state=useTransitionState();
    let prevTransitionKey;
    return ()=> {
      const children=slots.default && getTransitionRawChildren(slots.default(), true);
      if (!children || !children.length) {
        return;
      }
      // Transition 组件只允许一个子元素节点,多个报警告,提示使用 TransitionGroup 组件
      if (process.env.NODE_ENV !=="production" && children.length > 1) {
        warn(
          "<transition> can only be used on a single element or component. Use " +
            "<transition-group> for lists."
        );
      }
      // 不需要追踪响应式,所以改成原始值,提升性能
      const rawProps=toRaw(props);
      const { mode }=rawProps;
      // 检查 mode 是否合法
      if (
        process.env.NODE_ENV !=="production" &&
        mode &&
        !["in-out", "out-in", "default"].includes(mode)
      ) {
        warn(`invalid <transition> mode: ${mode}`);
      }
      // 获取第一个子元素节点
      const child=children[0];
      if (state.isLeaving) {
        return emptyPlaceholder(child);
      }
      // 处理 <transition><keep-alive/></transition> 的情况
      const innerChild=getKeepAliveChild(child);
      if (!innerChild) {
        return emptyPlaceholder(child);
      }
      const enterHooks=resolveTransitionHooks(
        innerChild,
        rawProps,
        state,
        instance
      );
      setTransitionHooks(innerChild, enterHooks);
      const oldChild=instance.subTree;
      const oldInnerChild=oldChild && getKeepAliveChild(oldChild);
      let transitionKeyChanged=false;
      const { getTransitionKey }=innerChild.type;
      if (getTransitionKey) {
        const key=getTransitionKey();
        if (prevTransitionKey===undefined) {
          prevTransitionKey=key;
        } else if (key !==prevTransitionKey) {
          prevTransitionKey=key;
          transitionKeyChanged=true;
        }
      }
      if (
        oldInnerChild &&
        oldInnerChild.type !==Comment &&
        (!isSameVNodeType(innerChild, oldInnerChild) || transitionKeyChanged)
      ) {
        const leavingHooks=resolveTransitionHooks(
          oldInnerChild,
          rawProps,
          state,
          instance
        );
        // 更新旧树的钩子函数
        setTransitionHooks(oldInnerChild, leavingHooks);
        // 在两个视图之间切换
        if (mode==="out-in") {
          state.isLeaving=true;
          // 返回空的占位符节点,当离开过渡结束后,重新渲染组件
          leavingHooks.afterLeave=()=> {
            state.isLeaving=false;
            instance.update();
          };
          return emptyPlaceholder(child);
        } else if (mode==="in-out") {
          leavingHooks.delayLeave=(el, earlyRemove, delayedLeave)=> {
            const leavingVNodesCache=getLeavingNodesForType(
              state,
              oldInnerChild
            );
            leavingVNodesCache[String(oldInnerChild.key)]=oldInnerChild;
            // early removal callback
            el._leaveCb=()=> {
              earlyRemove();
              el._leaveCb=undefined;
              delete enterHooks.delayedLeave;
            };
            enterHooks.delayedLeave=delayedLeave;
          };
        }
      }
      return child;
    };
  },
};

在渲染的过程中,Transition 组件还会通过 resolveTransitionHooks 去定义组件创建和删除阶段的钩子函数对象,然后再通过 setTransitionHooks 函数去把这个钩子函数对象设置到 vnode.transition 上。

hooks定义

const hooks={
    mode,
    persisted,
    beforeEnter(el) {
      let hook=onBeforeEnter;
      if (!state.isMounted) {
        if (appear) {
          hook=onBeforeAppear || onBeforeEnter;
        } else {
          return;
        }
      }
      if (el._leaveCb) {
        el._leaveCb(true /* cancelled */);
      }
      const leavingVNode=leavingVNodesCache[key];
      if (
        leavingVNode &&
        isSameVNodeType(vnode, leavingVNode) &&
        leavingVNode.el._leaveCb
      ) {
        leavingVNode.el._leaveCb();
      }
      callHook(hook, [el]);
    },
    enter(el) {
      let hook=onEnter;
      let afterHook=onAfterEnter;
      let cancelHook=onEnterCancelled;
      if (!state.isMounted) {
        if (appear) {
          hook=onAppear || onEnter;
          afterHook=onAfterAppear || onAfterEnter;
          cancelHook=onAppearCancelled || onEnterCancelled;
        } else {
          return;
        }
      }
      let called=false;
      const done=(el._enterCb=(cancelled)=> {
        if (called) return;
        called=true;
        if (cancelled) {
          callHook(cancelHook, [el]);
        } else {
          callHook(afterHook, [el]);
        }
        if (hooks.delayedLeave) {
          hooks.delayedLeave();
        }
        el._enterCb=undefined;
      });
      if (hook) {
        hook(el, done);
        if (hook.length <=1) {
          done();
        }
      } else {
        done();
      }
    },
    leave(el, remove) {
      const key=String(vnode.key);
      if (el._enterCb) {
        el._enterCb(true /* cancelled */);
      }
      if (state.isUnmounting) {
        return remove();
      }
      callHook(onBeforeLeave, [el]);
      let called=false;
      const done=(el._leaveCb=(cancelled)=> {
        if (called) return;
        called=true;
        remove();
        if (cancelled) {
          callHook(onLeaveCancelled, [el]);
        } else {
          callHook(onAfterLeave, [el]);
        }
        el._leaveCb=undefined;
        if (leavingVNodesCache[key]===vnode) {
          delete leavingVNodesCache[key];
        }
      });
      leavingVNodesCache[key]=vnode;
      if (onLeave) {
        onLeave(el, done);
        if (onLeave.length <=1) {
          done();
        }
      } else {
        done();
      }
    },
    clone(vnode) {
      return resolveTransitionHooks(vnode, props, state, instance);
    },
  };

钩子函数对象定义了 4 个钩子函数,分别是 beforeEnterenterleaveclone。在节点 patch 阶段的 mountElement 函数中,在插入节点前且存在过度会执行 vnode.transition 中的 beforeEnter 函数

//beforeEnter 钩子函数主要做的事情就是根据 appear 的值和 DOM 是否挂载,来执行 onBeforeEnter 函数或者是 onBeforeAppear 函数。appear 是否节点现实的时候执行动画
beforeEnter(el) {
  let hook=onBeforeEnter
  if (!state.isMounted) {
    if (appear) {
      hook=onBeforeAppear || onBeforeEnter
    }
    else {
      return
    }
  }
  if (el._leaveCb) {
    el._leaveCb(true /* cancelled */)
  }
  const leavingVNode=leavingVNodesCache[key]
  if (leavingVNode &&
    isSameVNodeType(vnode, leavingVNode) &&
    leavingVNode.el._leaveCb) {
    leavingVNode.el._leaveCb()
  }
  callHook(hook, [el])
}

resolveTransitionProps 函数

function resolveTransitionProps(rawProps) {
  let {
    name="v",
    type,
    css=true,
    duration,
    enterFromClass=`${name}-enter-from`,
    enterActiveClass=`${name}-enter-active`,
    enterToClass=`${name}-enter-to`,
    appearFromClass=enterFromClass,
    appearActiveClass=enterActiveClass,
    appearToClass=enterToClass,
    leaveFromClass=`${name}-leave-from`,
    leaveActiveClass=`${name}-leave-active`,
    leaveToClass=`${name}-leave-to`,
  }=rawProps;
  const baseProps={};
  for (const key in rawProps) {
    if (!(key in DOMTransitionPropsValidators)) {
      baseProps[key]=rawProps[key];
    }
  }
  if (!css) {
    return baseProps;
  }
  const durations=normalizeDuration(duration);
  const enterDuration=durations && durations[0];
  const leaveDuration=durations && durations[1];
  const {
    onBeforeEnter,
    onEnter,
    onEnterCancelled,
    onLeave,
    onLeaveCancelled,
    onBeforeAppear=onBeforeEnter,
    onAppear=onEnter,
    onAppearCancelled=onEnterCancelled,
  }=baseProps;
  const finishEnter=(el, isAppear, done)=> {
    removeTransitionClass(el, isAppear ? appearToClass : enterToClass);
    removeTransitionClass(el, isAppear ? appearActiveClass : enterActiveClass);
    done && done();
  };
  const finishLeave=(el, done)=> {
    removeTransitionClass(el, leaveToClass);
    removeTransitionClass(el, leaveActiveClass);
    done && done();
  };
  const makeEnterHook=(isAppear)=> {
    return (el, done)=> {
      const hook=isAppear ? onAppear : onEnter;
      const resolve=()=> finishEnter(el, isAppear, done);
      hook && hook(el, resolve);
      nextFrame(()=> {
        removeTransitionClass(el, isAppear ? appearFromClass : enterFromClass);
        addTransitionClass(el, isAppear ? appearToClass : enterToClass);
        if (!(hook && hook.length > 1)) {
          if (enterDuration) {
            setTimeout(resolve, enterDuration);
          } else {
            whenTransitionEnds(el, type, resolve);
          }
        }
      });
    };
  };
  return extend(baseProps, {
    onBeforeEnter(el) {
      onBeforeEnter && onBeforeEnter(el);
      addTransitionClass(el, enterActiveClass);
      addTransitionClass(el, enterFromClass);
    },
    onBeforeAppear(el) {
      onBeforeAppear && onBeforeAppear(el);
      addTransitionClass(el, appearActiveClass);
      addTransitionClass(el, appearFromClass);
    },
    onEnter: makeEnterHook(false),
    onAppear: makeEnterHook(true),
    onLeave(el, done) {
      const resolve=()=> finishLeave(el, done);
      addTransitionClass(el, leaveActiveClass);
      addTransitionClass(el, leaveFromClass);
      nextFrame(()=> {
        removeTransitionClass(el, leaveFromClass);
        addTransitionClass(el, leaveToClass);
        if (!(onLeave && onLeave.length > 1)) {
          if (leaveDuration) {
            setTimeout(resolve, leaveDuration);
          } else {
            whenTransitionEnds(el, type, resolve);
          }
        }
      });
      onLeave && onLeave(el, resolve);
    },
    onEnterCancelled(el) {
      finishEnter(el, false);
      onEnterCancelled && onEnterCancelled(el);
    },
    onAppearCancelled(el) {
      finishEnter(el, true);
      onAppearCancelled && onAppearCancelled(el);
    },
    onLeaveCancelled(el) {
      finishLeave(el);
      onLeaveCancelled && onLeaveCancelled(el);
    },
  });
}

我们来看 onBeforeEnter 函数,它的内部执行了基础 props 传入的 onBeforeEnter 钩子函数,并且给 DOM 元素 el 添加了 enterActiveClassenterFromClass 样式。

其中,props 传入的 onBeforeEnter 函数就是我们写 Transition 组件时添加的 beforeEnter 钩子函数。enterActiveClass 默认值是 v-enter-activeenterFromClass 默认值是 v-enter-from,如果给 Transition 组件传入了 nameprop,比如 fade,那么 enterActiveClass 的值就是 fade-enter-activeenterFromClass 的值就是 fade-enter-from。(onBeforeAppearonBeforeEnter 的逻辑类似,就不赘述了,它是在我们给 Transition 组件传入 appearProp,且首次挂载的时候执行的。执行完 beforeEnter 钩子函数,接着插入元素到页面,然后会执行 vnode.transition 中的 enter 钩子函数,上面的 hooks 中)

enter 函数内部,首先执行基础 props 传入的 onEnter 钩子函数,然后在下一帧给 DOM 元素 el 移除了 enterFromClass,同时添加了 enterToClass 样式(动画也就是所谓的样式交替改变)

Transition 组件允许我们传入 enterDuration 这个 prop,它会指定进入过渡的动画时长,当然如果你不指定,Vue.js 内部会监听动画结束事件,然后在动画结束后,执行 finishEnter 函数

来看它的实现

const finishEnter=(el, isAppear, done)=> {
  removeTransitionClass(el, isAppear ? appearToClass : enterToClass);
  removeTransitionClass(el, isAppear ? appearActiveClass : enterActiveClass);
  done && done();
};

其实就是给 DOM 元素移除 enterToClass 以及 enterActiveClass,同时执行 done 函数,进而执行 onAfterEnter 钩子函数

leave 钩子主要功能和 enter 相反。小伙伴们可自行查阅。

以上就是对 alert 组件的学习。如有不对欢迎指正。

么是JavaScript

JavaScript是一种基于对象和事件驱动的、并具有安全性能的脚本语言,已经被广泛用于Web应用开发,常用来为网页添加各式各样的动态功能,为用户提供更流畅美观的浏览效果。通常JavaScript脚本是通过嵌入在HTML中来实现自身的功能的。

JavaScript特点

是一种解释性脚本语言(代码不进行预编译)。

主要用来向HTML(标准通用标记语言下的一个应用)页面添加交互行为。

可以直接嵌入HTML页面,但写成单独的js文件有利于结构和行为的分离

跨平台特性,在绝大多数浏览器的支持下,可以在多种平台下运行(如Windows、Linux、Mac、Android、iOS等)。

JavaScript组成


JavaScript日常用途

1、嵌入动态文本于HTML页面。

2、对浏览器事件做出响应。

3、读写HTML元素

4、在数据被提交到服务器之前验证数据。

5、检测访客的浏览器信息。

6、控制cookies,包括创建和修改等。

7、基于Node.js技术进行服务器端编程。

JavaScript的基本结构

<script type="text/javascript">
 <!—
 JavaScript 语句;
 —>
</script >


示例:

……
<title>初学JavaScript</title>
</head>
<body>
<script type="text/javascript">
 document.write("初学JavaScript");
 document.write("<h1>Hello,JavaScript</h1>");
</script>
</body>
</html>


<script>…</script>可以包含在文档中的任何地方,只要保证这些代码在被使用前已读取并加载到内存即可

JavaScript的执行原理


网页中引用JavaScript的方式

1、使用<script>标签

2、外部JS文件

<script src="export.js" type="text/javascript"></script>


3.直接在HTML标签中

<input name="btn" type="button" value="弹出消息框" 
 onclick="javascript:alert('欢迎你');"/>


JavaScript核心语法:


1. 变量

①先声明变量再赋值

var width;
width=5;
var - 用于声明变量的关键字
width - 变量名


②同时声明和赋值变量

var catName="皮皮";
var x, y, z=10;


③不声明直接赋值【一般不使用】

width=5;


变量可以不经声明而直接使用,但这种方法很容易出错,也很难查找排错,不推荐使用。

2. 数据类型

①undefined:示例:var width;

变量width没有初始值,将被赋予值undefined

②null:表示一个空值,与undefined值相等

③number:

var iNum=23; //整数

var iNum=23.0; //浮点数

④Boolean:true和false 但是JS会把他们解析成1;0

⑤String:一组被引号(单引号或双引号)括起来的文本 var string1="This is a string";

3. typeof运算符

typeof检测变量的返回值;typeof运算符返回值如下:

①undefined:变量被声明后,但未被赋值.

②string:用单引号或双引号来声明的字符串。

③boolean:true或false。

④number:整数或浮点数。

⑤object:javascript中的对象、数组和null。

rometheus发出告警时分为两部分。首先,Prometheus按告警规则(rule_files配置块)向Alertmanager发送告警(即告警规则是在Prometheus上定义的)。然后,由Alertmanager来管理这些告警,包括去重(Deduplicating)、分组(Grouping)、静音(silencing)、抑制(inhibition)、聚合(aggregation ),最终将面要发出的告警通过电子邮件、webhook等方式将告警通知路由(route)给对应的联系人。

分组:就是将具有相同性质的告警先分类,然后当作单个通知发送出来。比如A和B两台主机的磁盘(CPU/内存)使用率都在告警,则磁盘的告警就可以合并在一个通知中发送出来。可以想像某个服务部署了100个节点,在一次升版后因为bug,日志中均报同样一类错误,如果不合并这类通知,那就是告警风暴。

抑制:就是某些告警触发后,则抑制(禁止)另一些告警。比如收到一条告警提示集群故障无法访问,那么在该集群上的所有其他警告应该被抑制。

静音(默):将某些在预期内的告警设置为静默(即不发送告警)。静默是基于配置匹配规则。Alertmanager会检查从Prometheus推送过来的告警事件,看这些告警事件是否与配置的静默规则能匹配上,如果匹配则不发送任何通知。配置静默方法是在Alertmanager的Web界面中,也可以使用amtool工具。

总之:Alertmanager制定这一系列规则目的只有一个,就是提高告警质量。


配置Alertmanager来做告警通知主要分三个步骤:

一、安装并配置Alertmanager

# tar -xvf alertmanager-0.20.0.linux-amd64.tar.gz

mv alertmanager-0.20.0.linux-amd64 /usr/local/alertmanager


# cat alertmanager.yml

global:

resolve_timeout: 5m

smtp_smarthost: 'smtp.mxhichina.com:465'

smtp_from: 'noreply@demo.com'

smtp_auth_username: 'noreply@demo.com'

smtp_auth_password: '1235698'

smtp_require_tls: false

templates:

- '/usr/local/alertmanager/template/default.tmpl'

route:

group_by: ['alertname']

group_wait: 30s

group_interval: 2m

repeat_interval: 10m

receiver: 'email'

# continue default is false

continue: true

receivers:

- name: 'email'

email_configs:

- to: 'cookingit222@163.com,itcooking222@163.com'

send_resolved: true

html: '{{ template "default.html" . }}'

headers: { Subject: "{{ .GroupLabels.SortedPairs.Values }} [{{ .Status | toUpper }}:{{ .Alerts.Firing | len }}]" }

inhibit_rules:

- source_match:

severity: 'critical'

target_match:

severity: 'warning'

equal: ['alertname', 'dev', 'instance']


alertmanager配置简要说明:

global:全局配置,主要配置告警方式,如邮件、webhook等。

route:Prometheus的告警先是到达alertmanager的根路由(route),alertmanager的根路由不能包含任何匹配项,因为根路由是所有告警的入口点。另外,根路由需要配置一个接收器(receiver),用来处理那些没有匹配到任何子路由的告警(如果没有配置子路由,则全部由根路由发送告警),即缺省接收器。告警进入到根route后开始遍历子route节点,如果匹配到,则将告警发送到该子route定义的receiver中,然后就停止匹配了。因为在route中continue默认为false,如果continue为true,则告警会继续进行后续子route匹配。如果当前告警仍匹配不到任何的子route,则该告警将从其上一级(匹配)route或者根route发出(按最后匹配到的规则发出邮件)。

group_by:用于分组聚合,对告警通知按标签(label)进行分组,将具有相同标签或相同告警名称(alertname)的告警通知聚合在一个组,然后作为一个通知发送。如果想完全禁用聚合,可以设置为group_by: [...]

group_wait: 当一个新的告警组被创建时,需要等待'group_wait'后才发送初始通知。这样可以确保在发送等待前能聚合更多具有相同标签的告警,最后合并为一个通知发送。

group_interval: 当第一次告警通知发出后,在新的评估周期内又收到了该分组最新的告警,则需等待'group_interval'时间后,开始发送为该组触发的新告警,可以简单理解为,group就相当于一个通道(channel)。

repeat_interval: 告警通知成功发送后,若问题一直未恢复,需再次重复发送的间隔。


查看你的告警路由树,将alertmanager.yml配置文件复制到对话框,然后点击"Draw Routing Tree"

https://www.prometheus.io/webtools/alerting/routing-tree-editor/


修改好配置文件后,可以使用amtool工具检查配置

# ./amtool check-config alertmanager.yml

Checking 'alertmanager.yml' SUCCESS


# cat >/usr/lib/systemd/system/alertmanager.service <<EOF

[Unit]

Description=alertmanager

[Service]

ExecStart=/usr/local/alertmanager/alertmanager --config.file=/usr/local/alertmanager/alertmanager.yml --storage.path=/usr/local/alertmanager/data --web.listen-address=:9093 --data.retention=120h

Restart=on-failure

[Install]

WantedBy=multi-user.target


EOF


# systemctl enable alertmanager

# systemctl restart alertmanager


alertmanager默认运行端口是:9093

alertmanager也可以同prometheus一样热加载配置

1)向alertmanager进程发送SIGHUP信号,如:kill -SIGHUP alertmanager_pid

2)curl -X POST http://prometheus_ip:9093/-/reload


二、修改prometheus的配置,关联Alertmanager服务,同时添加对alertmanager的监控

# Alertmanager configuration

alerting:

alertmanagers:

- static_configs:

- targets:

- monitor01:9093

rule_files:

- "rules/*_rules.yml"

- "rules/*_alerts.yml"

......

- job_name: 'alertmanager'

static_configs:

- targets: ['localhost:9093']

labels:

app: alertmanager


三、修改prometheus的配置,添加记录规则和告警规则。

# cat node_rules.yml

groups:

- name: node_rules

#interval: 15s

rules:

- record: instance:node_cpu_usage

expr: 100 - avg(irate(node_cpu_seconds_total{mode="idle"}[1m])) by (nodename) * 100

labels:

metric_type: CPU_monitor

- record: instance:node_1m_load

expr: node_load1

labels:

metric_type: load1m_monitor

- record: instance:node_mem_usage

expr: 100 - (node_memory_MemAvailable_bytes)/(node_memory_MemTotal_bytes) * 100

labels:

metric_type: Memory_monitor

- record: instance:node_root_partition_predit

expr: round(predict_linear(node_filesystem_free_bytes{device="rootfs",mountpoint="/"}[2h],12*3600)/(1024*1024*1024), 1)

labels:

metric_type: root_partition_monitor


# cat node_alerts.yml

groups:

- name: node_alerts

rules:

- alert: cpu_usage_over_threshold

expr: instance:node_cpu_usage > 80

for: 1m

labels:

severity: warning

annotations:

summary: 主机 {{ $labels.nodename }} 的 CPU使用率持续1分钟超出阈值,当前为 {{humanize $value}} %

- alert: system_1m_load_over_threshold

expr: instance:node_1m_load > 20

for: 1m

labels:

severity: warning

annotations:

summary: 主机 {{ $labels.nodename }} 的 1分负载超出阈值,当前为 {{humanize $value}}

- alert: mem_usage_over_threshold

expr: instance:node_mem_usage > 80

for: 1m

annotations:

summary: 主机 {{ $labels.nodename }} 的 内存 使用率持续1分钟超出阈值,当前为 {{humanize $value}} %

- alert: root_partition_usage_over_threshold

expr: instance:node_root_partition_predit < 60

for: 1m

annotations:

summary: 主机 {{ $labels.nodename }} 的 根分区 预计在12小时使用将达到 {{humanize $value}}GB,超出当前可用空间,请及时扩容!


for 表示告警持续的时长,若持续时长小于该时间就不发给alertmanager了,大于该时间再发。for的值不要小于prometheus中的scrape_interval,例如scrape_interval为30s,for为15s,如果触发告警规则,则再经过for时长后也一定会告警,这是因为最新的度量指标还没有拉取,在15s时仍会用原来值进行计算。另外,要注意的是只有在第一次触发告警时才会等待(for)时长。

例如:10:43:00 触发了集群A中的h1告警;10:43:10又触发了集群A中的h2告警。则在10:44:10后发生一条告警是只包含h1的邮件,在10:44:20时会收到h1和h2聚合后的告警邮件,若h1和h2持续未恢复,则在repeat_interval后仍以聚合方式发送告警。

注:h1和h2告警名相同,只是在不同主机上。


完成上述配置后,主机CPU、负载、内存、磁盘超出阈值时就发触发邮件告警。

上述配置仅是对主机资源做了监控,并且告警只发到了缺省联系人组。设想一下,在实际生产环境中,可能会按产品线或业务功能进行分组来研发,不同的服务出现告警时只发送通知到对应的联系人组,其他不相关的组不需要接告警收通知,这就需要我们修改告警路由规则,而Alertmanager的核心和最复杂的地方就在告警路由规则的设置上。


Prometheus的告警规则在推送给Alertmanager前有的三种状态:

1、没有触发告警阈值时,状态为:inactive

2、触发了告警阈值但未满足告警持续时间(for)时,状态为:pending,如果不配置for或者指定for的值为0,则将跳过pending状态。

3、已触发告警阈值并达到告警持续时间,开始将告警事件推送到Alertmanager,此时状态为:firing。


配置告警静默(silence),用于在预期内的维护升级等操作。

当我们在对系统进行维护升级时,通常不希望触发告警通知;另外,当上游服务出现异常,想让下游的服务不触发告警,Prometheus将用于这种配置称为silence(静默)。silence用于设定一个(维护)时间段,如1小时,也可提前手动触发silence过期,表示维护结束,恢复对应服务的告警通知功能。

设置silence的方式有以下2种:

1、登录到alertmanager的控制台操作

2、通过amtool命令进行操作


配置告警模板

Alertmanager的通知模板是基于Go模板系统,详细可参考官网。

https://golang.org/pkg/text/template/

https://prometheus.io/docs/alerting/latest/notifications/#alert

https://prometheus.io/docs/prometheus/latest/configuration/template_reference/

模板配置步骤:

1、修改alertmanager.yml,配置模板地址,然后在每个receiver引用模板

templates:

- '/usr/local/alertmanager/template/default.tmpl'

...

...

html: '{{ template "default.html" . }}'

headers: { Subject: "{{ .GroupLabels.SortedPairs.Values }} [{{ .Status | toUpper }}:{{ .Alerts.Firing | len }}]" }


2、配置模板

default.tmpl

{{ define "default.html" }}

{{- if gt (len .Alerts.Firing) 0 -}}

[{{ .Status | toUpper }}:{{ .Alerts.Firing | len }}]

{{ range $i, $alert :=.Alerts }}

<pre>

告警节点:{{ index $alert.Labels "nodename" }}

告警服务:{{ index $alert.Labels "alertname" }}

报警详情:{{ index $alert.Annotations "summary" }}

开始时间:{{ $alert.StartsAt }}

</pre>

{{ end }}

{{ end }}

{{- if gt (len .Alerts.Resolved) 0 -}}

[{{ .Status | toUpper }}:{{ .Alerts.Resolved | len }}]

{{ range $i, $alert :=.Alerts }}

<pre>

恢复节点:{{ index $alert.Labels "nodename" }}

恢复服务:{{ index $alert.Labels "alertname" }}

状 态:{{ index $alert.Status }}

开始时间:{{ $alert.StartsAt }}

恢复时间:{{ $alert.EndsAt }}

</pre>

{{ end }}

{{ end }}

{{- end }}


注:告警模板如果配置有问题,会导致邮件发送失败,注意观察日志。


以下是静默设置步骤:

1、登录到alertmanager控制台(http://IP:9093/#/alerts),点击上方菜单栏中Alerts,可看到当前有2个告警。

2、点击右上角"New Silence",创建Silence任务,具体设置如下,匹配规则支持正则。

注:可按instance或job_name等方式来进行正则匹配。


3、Silence创建成功后,可以看到处于Active状态的Silence

总之,多实践。