<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>slack | 伪架构师</title>
    <link>/tags/slack/</link>
      <atom:link href="/tags/slack/index.xml" rel="self" type="application/rss+xml" />
    <description>slack</description>
    <generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>zh</language><lastBuildDate>Wed, 07 Feb 2018 05:30:24 +0800</lastBuildDate>
    <image>
      <url>/img/logo-wide.png</url>
      <title>slack</title>
      <link>/tags/slack/</link>
    </image>
    
    <item>
      <title>ChatBot：在 Slack 中使用监控和告警</title>
      <link>/post/errbot-on-slack/</link>
      <pubDate>Wed, 07 Feb 2018 05:30:24 +0800</pubDate>
      <guid>/post/errbot-on-slack/</guid>
      <description>

&lt;p&gt;前面文章中我们使用 Errbot 通过 Kubernetes API 在 Slack 中进行 Kubernetes 查询。这种方式很局限。毕竟拉更多组件下水，写更多代码才是大势所趋 LOL。本文以 Istio 中的响应时间监控为例，看看 Errbot 和 Prometheus 的互动。&lt;/p&gt;

&lt;h2 id=&#34;前提&#34;&gt;前提&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Istio&lt;/li&gt;
&lt;li&gt;Prometheus&lt;/li&gt;
&lt;li&gt;公网部署&lt;/li&gt;
&lt;li&gt;接入 Slack 的 Errbot&lt;/li&gt;
&lt;li&gt;Errbot 开放 3141 端口，能够被 Alertmanager 使用。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;注意：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;这里只是介绍一下这种思路，例子要求比较高，源文件也不会很完整。要投入实际使用，需要更多配合。&lt;/li&gt;
&lt;li&gt;选择 Istio 做监控目标，只是因为目前环境方便，并无特殊含义。&lt;/li&gt;
&lt;li&gt;Errbot 的 Slack 接入在之前的文章有一些介绍可以参考。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;在-bot-中获取监控指标&#34;&gt;在 Bot 中获取监控指标&lt;/h2&gt;

&lt;p&gt;Prometheus 提供了很方便的 &lt;a href=&#34;https://prometheus.io/docs/prometheus/latest/querying/api/&#34; target=&#34;_blank&#34;&gt;HTTP API（注 1）&lt;/a&gt;，简单的使用 HTTP 客户端即可获取。下面的表达式用于从 Istio 中获取响应时间的 p99 指标：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    histogram_quantile(0.99,
    sum(irate(istio_request_duration_bucket[1m]))
    by (source_service, source_version,
    destination_service, destination_version, le))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里为行文方便，直接硬编码，实际上可以通过查询模板、Bot 参数等方式，获得更灵活的查询。或者更加简单粗暴的，在聊天室向 Bot 提供查询公式直接查询。&lt;/p&gt;

&lt;p&gt;下面的代码，会将 Prometheus 中的指标，在 Slack 中以卡片的方式输出到指定聊天室中。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;指定一个 &lt;code&gt;query&lt;/code&gt; 参数，取值为刚才提到的 Prometheus 查询语句，&lt;code&gt;{&amp;quot;query&amp;quot;: prom}&lt;/code&gt;的形式进行 URL Encode。&lt;/li&gt;
&lt;li&gt;API 指令发出后，Prometheus 会响应一段 JSON 报文，报文的&lt;code&gt;result&lt;/code&gt;字段包含一个列表，包含了各个服务的各个版本之间的通信响应时间。&lt;/li&gt;
&lt;li&gt;这里使用 Errbot 的 &lt;a href=&#34;http://errbot.io/en/latest/errbot.botplugin.html#errbot.botplugin.BotPlugin.send_card&#34; target=&#34;_blank&#34;&gt;send_card（注 2）&lt;/a&gt;功能，通过 Field 方式逐个输出服务记录。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;room = self.build_identifier(&amp;quot;#bot&amp;quot;)&lt;/code&gt;，使用 &lt;a href=&#34;http://errbot.io/en/latest/errbot.botplugin.html#errbot.botplugin.BotPlugin.build_identifier&#34; target=&#34;_blank&#34;&gt;self.build_identifier（注 3）&lt;/a&gt;设置发送目标。&lt;/li&gt;
&lt;li&gt;代码放入 Errbot 插件之后，私聊窗口输入&lt;code&gt;!restart&lt;/code&gt;，重启。会看到 Errbot 发出重启信息之后断线。&lt;/li&gt;
&lt;li&gt;等 Errbot 再次在线，在聊天室输入指令&lt;code&gt;!service quality&lt;/code&gt;，会看到类似如下的信息回应：&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&#34;images/errbot-prom1.png&#34; alt=&#34;metrics&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;使用-bot-接收-prometheus-告警&#34;&gt;使用 Bot 接收 Prometheus 告警&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;实际上 Prometheus 的 Alert Manager 提供了到 Slack 的集成，这里纯属演示，绕了个远。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&#34;配置-bot&#34;&gt;配置 Bot&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;在聊天室私信中发布命令 &lt;code&gt;!plugin config Webserver {&#39;HOST&#39;: &#39;0.0.0.0&#39;,&#39;PORT&#39;: 3141}&lt;/code&gt;，要求 Errbot 开放端口启动 Webhook 服务。&lt;/li&gt;

&lt;li&gt;&lt;p&gt;同样的使用私信发送命令 &lt;code&gt;!status&lt;/code&gt;，查询插件运行状态，可以看到 &lt;code&gt;Webserver&lt;/code&gt; 已经启动。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/bot-status.png&#34; alt=&#34;Bot Status&#34; /&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;为 Bot 插件加入 Webhook 代码，并重新启动 Bot，这里定义了路径&lt;code&gt;alertmanager&lt;/code&gt;用于接受告警，同样使用卡片方式，发送告警信息到&lt;code&gt;bot&lt;/code&gt;频道，在卡片中显示告警的相关服务及其版本：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;def alertmanager(self, data):
  &amp;quot;&amp;quot;&amp;quot;Webhook for alerts from Prometheus&amp;quot;&amp;quot;&amp;quot;
  target = self.build_identifier(&amp;quot;#bot&amp;quot;)
  self.send(target, &amp;quot;Alert!!&amp;quot;)
  for alert in data[&#39;alerts&#39;]:
      self.send_card(
          to=target,
          summary=&#39;[{}] {}&#39;.format(
              data[&#39;status&#39;].upper(),
              data[&#39;commonLabels&#39;][&#39;alertname&#39;]
          ),
          title=&amp;quot;Prometheus Alert&amp;quot;,
          fields=[(&#39;From&#39;, alert[&#39;annotations&#39;][&#39;from&#39;]),
                  (&#39;To&#39;, alert[&#39;annotations&#39;][&#39;to&#39;])],
          body=alert[&#39;annotations&#39;][&#39;summary&#39;]
      )
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&#34;prometheus-配置&#34;&gt;Prometheus 配置&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;配置 Prometheus 告警规则：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;使用前面提到的同一个公式&lt;/li&gt;
&lt;li&gt;使用注解来传递应用标签&lt;/li&gt;

&lt;li&gt;&lt;p&gt;这里我们配置大于 3 秒钟的耗时发生告警&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;groups:
- name: fake
rules:
- alert: rules-alert
expr: |
    histogram_quantile(0.99,
    sum(irate(istio_request_duration_bucket[1m]))
    by (source_service, source_version,
    destination_service, destination_version, le)) &amp;gt; 3
for: 1m
labels:
    alertname: &amp;quot;request-duration-3&amp;quot;
annotations:
    summary: &amp;quot;Request duration gt 3&amp;quot;
    from: &amp;quot;{{ $labels.source_service }}:{{ $labels.source_version }}&amp;quot;
    to: &amp;quot;{{ $labels.destination_service }}:{{ $labels.destination_version }}&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;配置和重启 Alert Manager，定义使用 Web hook 通知 errbot 发送告警到 Slack：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;route:
    receiver: &#39;webhook&#39;
    group_wait: 30s
    group_interval: 5m
    repeat_interval: 4h
    group_by: [&#39;from&#39;, &#39;to&#39;]
    routes:

receivers:
- name: &#39;webhook&#39;
    webhook_configs:
    - url: &amp;quot;http://errbot.default:3141/alertmanager&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Prometheus 连接到 AlertManager 上，用于发送告警，修改后重启 Prometheus：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;alerting:
alertmanagers:
- scheme: http
  static_configs:
  - targets:
    - &amp;quot;alertmanager:9093&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;运行&#34;&gt;运行&lt;/h2&gt;

&lt;p&gt;在负载上调用耗时操作，例如持续访问&lt;code&gt;httpbin&lt;/code&gt;的&lt;code&gt;/delay/5&lt;/code&gt;指令，满足标准后，可以看到，告警的传递线路：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/prom-alert.png&#34; alt=&#34;prom alert&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/alert-alert.png&#34; alt=&#34;alert alert&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/slack-alert-card.png&#34; alt=&#34;slack alert&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;注&#34;&gt;注&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;https://prometheus.io/docs/prometheus/latest/querying/api/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http://errbot.io/en/latest/errbot.botplugin.html#errbot.botplugin.BotPlugin.send_card&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http://errbot.io/en/latest/errbot.botplugin.html#errbot.botplugin.BotPlugin.build_identifier&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;附录&#34;&gt;附录&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;查询&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;@botcmd
def service_quality(self, message, args):
url = &amp;quot;http://prometheus-server.kube-system/api/v1/query?&amp;quot;
prom = &amp;quot;&amp;quot;&amp;quot;
histogram_quantile(0.99,
sum(irate(istio_request_duration_bucket[1m]))
by (source_service, source_version,
destination_service, destination_version, le))
&amp;quot;&amp;quot;&amp;quot;
query = {&amp;quot;query&amp;quot;: prom}
query = urlencode(query)
f = urllib.request.urlopen(url + query)
resp = f.read()
record_list = json.loads(resp)
room = self.build_identifier(&amp;quot;#bot&amp;quot;)
field_list = []
for record in record_list[&amp;quot;data&amp;quot;][&amp;quot;result&amp;quot;]:
    metric = record[&amp;quot;metric&amp;quot;]
    value = record[&amp;quot;value&amp;quot;]
    domain = &amp;quot;.svc.cluster.local&amp;quot;
    field = (&amp;quot;From {}:{} to {}:{}&amp;quot;.format(
        metric[&amp;quot;source_service&amp;quot;].replace(domain, &amp;quot;&amp;quot;),
        metric[&amp;quot;source_version&amp;quot;],
        metric[&amp;quot;destination_service&amp;quot;].replace(domain, &amp;quot;&amp;quot;),
        metric[&amp;quot;destination_version&amp;quot;],
    ), value[-1])
    field_list.append(field)
self.send_card(to=room, body=prom,
               title=&amp;quot;Query Statement&amp;quot;, summary=&amp;quot;From prometheus&amp;quot;,
               color=&amp;quot;green&amp;quot;, fields=field_list)
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
  </channel>
</rss>
