没来的请举手

我们需要在一个 VPC 网络环境中采集一些服务的 metrics,TSDB 存储在中心机房中,存储节点、中心节点和采集端的网络是不通的,要访问的话必须开通防火墙配置。网络架构大体如下:

直接从 central 去访问应用的 metrics 暴露端口采集数据,肯定不显示,这样的话要开通的防火墙太多了,也就失去了防火墙的意义。

怎么才能尽可能少的开通防火墙,又保证数据采集呢?

最直观的方法是,直接用 vmagent 部署在 VPC 本地采集数据,采集之后将数据发送到 central 机房。

那么又有了一个问题:监控的监控。我们怎么去监控采集端 vmagent 的健康呢?简单的方法是,直接开一个双向的防火墙白名单,我们从 central 直接去监控 vmagent。但是我想到一个方法,可以免掉这个防火墙。

第一步,我们将 vmagent 的采集 target 加入到它自己的采集列表中,即,让它自己收集它自己的 metrics,然后发送到 central。

然后我们再配置 alert,难点就在这里。

Prometheus 触发 alert 的规则是:

  • 如果表达式的 evaluate 结果是 null,说明不满足条件,不 fire
  • 如果表达式的 evaluate 结果有值,说明满足设定的条件,fire

我们用如上的模式采集 vmagent 的 metrics,就会有这个问题:假如 vmagent 挂了,那么不会有人把 metrics 发回来,也就不会有 prometheus 的 up metric, 如果我们设置一个 alert rule: up < 1 , 也就不会 fire。就遇到了这个问题:没来的请举手。

解决方法是,既然已经确定这个 target 必然会存在,可以将默认值设置为 0,如果有值的话,会被 overwrite 成1.

PromQL 原生的表达式:

MetricsQL:

这样,只有确定采集端 vmagent 是 up 的时候,才不会触发 alert。

这个查询表达式对 Push 到 Prometheus 的模式也同样适用。

另外还有一个方法可以解决这个问题,就是在 VPC 中搭建两个 vmagent,互相采集对方的 metrics,发送回来。这样有一个缺点,就是如果中间的网络有问题,那么两个 vmagent 就会一起挂掉了,不会有 metrics,也就不会触发 alerts。

参考:

  1. https://nklya.medium.com/promql-how-to-return-0-instead-of-no-data-9e49f7ccb80d
 

婚礼回顾和一些经验分享

我在国庆完成了和老婆的婚礼,这件人生大事从计划到完成花费了至少1年的时间,牵扯了上百人的精力,值得记录一下自己的感受。

一场婚礼是非常耗费精力的事情,涉及到两大家族长时间的准备。一个家族内部之间的矛盾可能在这个过程被激发出来,鸡毛蒜皮的事情可能会导致出来一些纷争,牵扯出很久时间之前的恩怨;两个家族之间的价值观也会受到考验。事后来看,有些人之间的感情会更好,因为一起经历过了这件大事,有些人的感情可能会变差——由于有些地方事情办得不好。

家庭和亲戚们不是人能选择的,所以协调这样一个团队去完成一个大项目,本身就是一件很复杂的事情。

首先趁着记忆总结一下办一场尽量完美的婚礼的一些经验:

  1. 所有能提前做的事情都从确定结婚时间开始就准备,至少需要3个月的时间。包括:
    1. 拍婚纱照(需要预约档期)
    2. 定制西装(一般需要一个月)
    3. 租用婚纱(或者购买,也需要提前)
    4. 预定酒店(良辰吉日需要提前至少半年)
    5. 确定摄影跟拍、录像、司仪、跟妆的档期
    6. 找一个靠谱的婚庆
    7. 等等,只要现在具备了开始做的条件,就立即开始动手去做,不要说“这件事等提前一个月准备”就够了,能现在完成的事情就现在完成
  2. 确定整体的流程,要有一个在线的文档,跟所有的参与人员同步信息,就像项目管理一样。比如每一个时间该做什么事情,需要什么物件/物料,主要负责人是谁,联系电话等。
  3. 婚礼流程事无巨细落实,比如改口茶在哪里做,谁在场,杯子准备好了没,茶准备好了没,要说什么,要做什么,这个环节的负责人是谁,等等。
  4. 每一件事必须安排给一个负责人,不能是多个。比如签到处,可以安排3个人,包括引导员(入座),负责收红包记录礼金(很重要,千万不能收到无名红包),替班(不能只安排一个人让签到处没时间上厕所)等等。但是必须有一个签到处负责人,有事情打负责人电话,安排给负责人。告诉他他的职责。如果没有一个明确的负责人,很多任务无法落实。现场团队也必须有一个负责人,现场任何问题都直接找他,比如有客人临时参加没有位置,要现场负责人来解决。负责人一定要找靠谱、办事认真的人,否则一定会乱套。同步给所有准备婚礼的人,哪一个环节出了问题要找谁。
  5. 座位问题:邀请人参加婚礼,一旦确定客人要参加,一定问清楚是会来多少人,如果带小孩,小孩几岁。(酒店一般免费加宝宝椅但是不能加座位)。极力避免有客人来了没有找到自己的座位,或者到了之后发现自己的座位被占用的尴尬情况。(很尴尬我们就遇到了有客人没有位置吃饭的尴尬情况)
  6. 婚礼执行过程遇到任务不要抱怨,遇到问题只去想怎么能够解决。遇到一些有任务的人丢了东西,忘了流程,很正常,发火、抱怨没有用。我知道这些事之后从没有埋怨人,马上开始重新做补救措施。
  7. 预留充足的时间,因为整个流程完全按照计划发展几乎不可能(我们就取消了外景拍摄环节才让其他环节都顺畅)
  8. 摆正心态,这场婚礼最重要的是谁,是给谁办的,想明白这个问题,然后其他的问题都以这些人会不会高兴为准。
  9. 七大姑八大姨的“风俗”都去掉。这个博客的读者肯定不会认为“新娘的脚一旦落地”会让新人不幸福吧?按照我帮忙别人准备婚礼和我自己的婚礼的经验,每一次婚礼都会有 N 多人中间调出来说“哎呀你们没按规矩办啊!新娘不能xxx,新娘不能xxx” “发车时间得是几点几分”,“婚房外人不能进啊,摄影师拍摄也不能进!”。这些人就和恐怖分子一样来破坏婚礼,我在我们村随便拉一个大妈她都能我们全家说出来一些谁都没见过听过的习俗,这些习俗不知道也就罢了,知道了你不按照这些人说的办还心里膈应。所以,我的建议是,回到8,不是给这些亲戚或者外人办的,是给双方父母和我老婆(对我来说)办的,这些习俗对他们重要吗?重要,那就严格遵守;如果不重要,这些人爱咋说咋说。要和家人沟通好,任何中间跳出来的风俗习惯,就当听不见。
  10. 来帮忙的人不是越多越好,多给靠谱的人安排工作,不要不好意思,事后谢礼到位就行,记着人家的人情。不靠谱的人不如不来。
  11. 作为东道主,远道而来的客人需要好好招待。
  12. 婚庆的质量决定了整场婚礼的质量。一定要好好找,但是这个行业我觉得水很深,好在我的叔叔是做这一行的,我的婚礼是他操办的,非常成功。
  13. 敢于下判断,很多问题是能用钱解决的,不要过于纠结和花费过多精力。比如有临时客人来参加婚宴,现在看来位置不够直接加一桌要比重排座位要好。

其实无论是谁,第8个问题的答案一定是自己的爱人。这个很重要。

婚礼涉及的事情太多了,双方家庭一定会有在某些事情上不一致的地方,这对两个家庭的结合来说就是考验,对小夫妻也是考验,看能否协调好双方家庭。刚办完婚礼的我觉得如果婚礼能和谐地办好,那么以后幸福过日子不成问题。

婚礼看似费神费力,我的心态是,抱着享受过程的心态去准备每一件事,去体验。是一个和自己的兄弟们加深感情的机会(得请大家帮忙,团队合作),是一个双发家庭融合的机会,是一个考验小夫妻和双方父母三观的机会,也是你去认识对方家庭成员的机会,更是一个和自己的妻子加深感情的机会。

无论婚礼办成什么样子,只要第8个问题的答案这些人满意,那就是成功的。

我对自己的婚礼还是很满意的,虽然很累,但是办下来让我觉得很满足,这场婚礼也提醒了我有那么多关心我的人,让我知道这一切是多么来之不易。还认识了很多办事靠谱的人。比如我们的化妆师,从凌晨2点开始几乎没吃过东西,在最后婚礼上敬酒环节(最后一个环节,马上就结束了)依然一丝不苟的化妆,最后一次化妆间就和第一次化妆的细致程度一样,让我顿时觉得我也得成为这样对工作一丝不苟的人)。

最后,结婚旅行也是一个不错的选择。祝各位读者幸福!

 

沉浸式工作

我发现屏幕上会动的东西都会吸引注意力,现在每天又收到很多消息,只要一个小时不看肯定会上百条。这样的工作状态很糟糕,一开始我强迫自己不要去关注未读消息数量,但是尝试了一段时间之后还是发现会不集中注意力。

最好的方法还是全部屏蔽掉实时的消息,在工作中用 IM 是一个很糟糕的主意,很多同事请 SRE 帮助的时候描述不清楚自己的问题,就导致发过去一条消息还要等他的回复。

也尝试过屏蔽一些消息,但是发现屏蔽不过来,总是有新的群拉起来,默认消息就是推送的,也会有新的陌生人过来问一些问题。

如果 IM 工具有“加急”的功能,那么总有一天,人们发送的所有消息都是“加急”的。

既然用户不喜欢使用提交 ticket,那么我们可以把 IM 用成 ticket。

我发现 Dozer网友推荐的一个非常好用的工具,它可以将 Mac 的 Toolbar 上面的活动图标给隐藏掉。

专心工作的时候,只需要留两个图标,一个是时间,另一个是每 30 分钟提醒站立的程序。

而且这个软件的 UI 设计的非常天才,只用两个点就可以完成全部操作。正好满足我的需求:隐藏掉 Toolbar 上面可以动的东西。

这样,只需要在工作的间隙定时去检查 IM 消息就好了。将全部 IM 回复一遍,如果无法一次性将问题描述清楚,就只要等待下一次沟通了。

另外一个烦人的地方是,别人 @我的时候,App 的图标会在 Dock 上跳一下。禁止所有的 App 跳动,可以用下面这两个命令:

  1. defaults write com.apple.dock no-bouncing -bool TRUE
  2. 然后让 Dock 重启一下:killall Dock

 

这样,专心工作的时候,屏幕上就没有会动的东西了。

 

用 PromQL 计算 SLI 和 SLO

用 PromQL 查询出来过去一个月中用掉的 Error budget,然后展示当前的 SLI。效果如下图所示:

这个查询的难点在于,PromQL 查询出来的内容都是时序的值,比如 memory > 0.6 这个查询,查到的所有满足条件的时序的时间和值的对应。让查询的结果是时间,就需要一些技巧。

实现的思路是:

  1. 首先定义分钟级的 up 标准,即 SLO 的定义:1min中如何算是 up 的,如何算是 down 的;
  2. 然后写一个查询,可以查到在一个时间区间内,有多少个分钟是满足条件,up 的,有多少分钟是 down 的;
  3. 最后就可以得到实时的 SLI 结果;

想到有两个思路能实现。

第一个是利用 recording rules. 首先要定义一个 rule 叫做 job:sla_minute_up,这个 rule evaluation 的结果是当前的这一分钟是否满足 up 的所有条件。所以可能是一个很多 and 连起来的复杂表达式。

然后我们只要将所有 up 的分钟数加起来即可,即 sum_over_time(job:sla_minute_up[30d])。最后除以一个月中所有的分钟数:sum_over_time(job:sla_minute_up[30d]) / 30 * 24 * 60,就是最后得到的 SLI。

但是这里面有一个特别重要的地方,就是 sum_over_time 计算的是这段时间内所有出现过的点。比如,假设采集间隔是 15s,那么 up 这个 metric 在每分钟内会有 4 个点,sum_over_time(up[1m]) 正常情况的结果应该是 4. 所以说,使用这种方法的话,recording rules 的 evaluation internal 必须设置为 60s,来做到每分钟只有一个点。

这种方式实际的结果和计算过程绑定了, 所以并不是很好,下面这种方法更巧妙一些。

我们可以换一个思路,直接计算这段时间内满足条件的百分比,然后百分比乘以时间段,就是最终的 up time。

得到这个百分比,首先依然是需要分钟级的 up 定义。不过我们关心的并不是具体的 value,而是是否满足条件,即非是即否。使用 >bool 这个运算,可以将结果转化成 bool,如果满足条件,结果就是1,不满足,结果就是0. 这样,我们只需要计算这段时间内的平均值,就可以得到 SLI 的百分比了。比如说,如果全部是 up,那么结果全部都是1,那么所有时间内的平均是100%。如果出现了不是 up 的点,那么按照占用所有的点的百分比,也是对的。

这个方法就和 interval 无关了。interval 越小,数据点就多,精度会提高。interval 大,那么精度会损失。从客观来讲,表示的真实 SLI 在去掉精度后都是正确的。

如果要表示一天内的 SLI,假设 SLO 只有错误率的话,就可以用下面的查询(假设我们把错误率认为小于1%就是 up):

也可以和 Grafana 配合,使用 Grafana 选中的时间区间:

 

参考资料:

  1. https://stackoverflow.com/questions/54292803/calculate-the-duration-in-which-a-prometheus-metric-had-a-certain-value
  2. https://docs.bitnami.com/tutorials/implementing-slos-using-prometheus
 

用 Wireshark 分析 TCP 吞吐瓶颈

Debug 网络质量的时候,我们一般会关注两个因素:延迟和吞吐量(带宽)。延迟比较好验证,Ping 一下或者 mtr 一下就能看出来。这篇文章分享一个 debug 吞吐量的办法。

看重吞吐量的场景一般是所谓的长肥管道(Long Fat Networks, LFN, rfc7323). 比如下载大文件。吞吐量没有达到网络的上限,主要可能受 3 个方面的影响:

  1. 发送端出现了瓶颈
  2. 接收端出现了瓶颈
  3. 中间的网络层出现了瓶颈

发送端出现瓶颈一般的情况是 buffer 不够大,因为发送的过程是,应用调用 syscall,将要发送的数据放到 buffer 里面,然后由系统负责发送出去。如果 buffer 满了,那么应用会阻塞住(如果使用 block 的 API 的话),直到 buffer 可用了再继续 write,生产者和消费者模式。

图片来自 cisco

发送端出现瓶颈一般都比较好排查,甚至通过应用的日志看何时阻塞住了即可。大部分情况都是第 2,3 种情况,比较难以排查。这种情况发生在,发送端的应用已经将内容写入到了系统的 buffer 中,但是系统并没有很快的发送出去。

TCP 为了优化传输效率(注意这里的传输效率,并不是单纯某一个 TCP 连接的传输效率,而是整体网络的效率),会:

  1. 保护接收端,发送的数据不会超过接收端的 buffer 大小 (Flow control)。数据发送到接受端,也是和上面介绍的过程类似,kernel 先负责收好包放到 buffer 中,然后上层应用程序处理这个 buffer 中的内容,如果接收端的 buffer 过小,那么很容易出现瓶颈,即应用程序还没来得及处理就被填满了。那么如果数据继续发过来,buffer 存不下,接收端只能丢弃。
  2. 保护网络,发送的数据不会 overwhelming 网络 (Congestion Control, 拥塞控制), 如果中间的网络出现瓶颈,会导致长肥管道的吞吐不理想;

对于接收端的保护,在两边连接建立的时候,会协商好接收端的 buffer 大小 (receiver window size, rwnd), 并且在后续的发送中,接收端也会在每一个 ack 回包中报告自己剩余和接受的 window 大小。这样,发送端在发送的时候会保证不会发送超过接收端 buffer 大小的数据。(意思是,发送端需要负责,receiver 没有 ack 的总数,不会超过 receiver 的 buffer.)

图片来自 cisco

对于网络的保护,原理也是维护一个 Window,叫做 Congestion window,拥塞窗口,cwnd, 这个窗口就是当前网络的限制,发送端不会发送超过这个窗口的容量(没有 ack 的总数不会超过 cwnd)。

怎么找到这个 cwnd 的值呢?

这个就是关键了,默认的算法是 cubic, 也有其他算法可以使用,比如 Google 的 BBR.

主要的逻辑是,慢启动(Slow start), 发送数据来测试,如果能正确收到 receiver 那边的 ack,说明当前网络能容纳这个吞吐,将 cwnd x 2,然后继续测试。直到下面一种情况发生:

  1. 发送的包没有收到 ACK
  2. cwnd 已经等于 rwnd 了

第 2 点很好理解,说明网络吞吐并不是一个瓶颈,瓶颈是在接收端的 buffer 不够大。cwnd 不能超过 rwnd,不然会 overload 接收端。

对于第 1 点,本质上,发送端是用丢包来检测网络状况的,如果没有发生丢包,表示一切正常,如果发生丢包,说明网络处理不了这个发送速度,这时候发送端会直接将 cwnd 减半。

但实际造成第 1 点的情况并不一定是网络吞吐瓶颈,而可能是以下几种情况:

  1. 网络达到了瓶颈
  2. 网络质量问题丢包
  3. 中间网络设备延迟了包的送达,导致发送端没有在预期时间内收到 ACK

2 和 3 原因都会造成 cwnd 下降,无法充分利用网络吞吐。

以上就是基本的原理,下面介绍如何定位这种问题。

rwnd 查看方式

这个 window size 直接就在 TCP header 里面,抓下来就能看这个字段。

但是真正的 window size 需要乘以 factor, factor 是在 TCP 握手节点通过 TCP Options 协商的。所以如果分析一条 TCP 连接的 window size,必须抓到握手阶段的包,不然就不可以知道协商的 factor 是多少。

cwnd 查看方式

Congestion control 是发送端通过算法得到的一个动态变量,会实时调整,并不会体现在协议的传输数据中。所以要看这个,必须在发送端的机器上看。

在 Linux 中可以使用 ss -i 选项将 TCP 连接的参数都打印出来。

这里展示的单位是 TCP MSS. 即实际大小是 1460bytes * 10.

Wireshark 分析

Wireshark 提供了非常实用的统计功能,可以让你一眼就能看出当前的瓶颈是发生在了哪里。但是第一次打开这个图我不会看,一脸懵逼,也没查到资料要怎么看。好在我同事会,他把我教会了,我在这里记录一下,把你也教会。

首先,打开的方式如下:

然后你会看到如下的图。

首先需要明确,tcptrace 的图表示的是单方向的数据发送,因为 tcp 是双工协议,两边都能发送数据。其中最上面写了你当前在看的图数据是从 10.0.0.1 发送到 192.168.0.1 的,然后按右下角的按钮可以切换看的方向。

X轴表示的是时间,很好理解。

然后理解一下 Y 轴表示的 Sequence Number, 就是 TCP 包中的 Sequence Number,这个很关键。图中所有的数据,都是以 Sequence Number 为准的。

所以,你如果看到如上图所示,那么说明你看反了,因为数据的 Sequence Number 并没有增加过,说明几乎没有发送过数据,需要点击 Switch Direction。

这就对了,可以看到我们传输的 Sequence Number 在随着时间增加而增加。

这里面有 3 条线,含义如下:

除此之外,另外还有两种线:

需要始终记住的是 Y 轴是 Sequence Number,红色的线表示 SACK 的线表示这一段 Sequence Number 我已经收到了,然后配合黄色线表示 ACK 过的 Sequence Number,那么发送端就会知道,在中间这段空挡,包丢了,红色线和黄色线纵向的空白,是没有被 ACK 的包。所以,需要重新传输。而蓝色的线就是表示又重新传输了一遍。

学会了看这些图,我们可以认识几种常见的 pattern:

丢包

很多红色 SACK,说明接收端那边重复在说:中间有一个包我没有收到,中间有一个包我没有收到。

吞吐受到接收 window size 限制

从这个图可以看出,黄色的线(接收端一 ACK)一上升,蓝色就跟着上升(发送端就开始发),直到填满绿色的线(window size)。说明网络并不是瓶颈,可以调大接收端的 buffer size.

吞吐受到网络质量限制

从这张图中可以看出,接收端的 window size 远远不是瓶颈,还有很多空闲。但是发送端不会一直发直到填满接收端的 buffer。

放大可以看出,中间有很多丢包和重传,并且每次只发送一点点数据,这说明很有可能是 cwnd 太小了,受到了拥塞控制算法的限制。

 

本文中用到的抓包文件可以从这里下载(credit: https://www.youtube.com/watch?v=yUmACeSmT7o):

  1. https://www.cloudshark.org/captures/f5eb7c033728
  2. https://www.cloudshark.org/captures/c967765aef38

其他的一些参考资料:

  1. https://www.stackpath.com/edge-academy/what-is-cwnd-and-rwnd/
  2. https://www.baeldung.com/cs/tcp-flow-control-vs-congestion-control
  3. https://www.cs.cornell.edu/courses/cs4450/2020sp/lecture21-congestion-control.pdf
  4. https://www.mi.fu-berlin.de/inf/groups/ag-tech/teaching/2011-12_WS/L_19531_Telematics/08_Transport_Layer.pdf
  5. https://wiki.aalto.fi/download/attachments/69901948/TCP-CongestionControlFinal.pdf
  6. https://paulgrevink.wordpress.com/2017/09/08/about-long-fat-networks-and-tcp-tuning/

帮这位老师的项目打一个广告:https://github.com/royzhr/spate 一个大流量网络性能测试工具。