Prometheus rate irate increase笔记

rate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
func extrapolatedRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper, isCounter bool, isRate bool) Vector {
ms := args[0].(*parser.MatrixSelector)
vs := ms.VectorSelector.(*parser.VectorSelector)
var (
// samples表示某个metric的某段时间的数据(区间向量)
samples = vals[0].(Matrix)[0]

// enh.Ts: 执行查询的时间.
// ms.Range: 区间向量表达式中括号内的时间转换成Duration.
// vs.Offset: 区间向量表达式中后面跟的offset 的时间转换成Duration.
rangeStart = enh.Ts - durationMilliseconds(ms.Range+vs.Offset)
rangeEnd = enh.Ts - durationMilliseconds(vs.Offset)
)

// No sense in trying to compute a rate without at least two points. Drop
// this Vector element.
if len(samples.Points) < 2 {
return enh.Out
}

resultValue := samples.Points[len(samples.Points)-1].V - samples.Points[0].V
// 如果exporter被重启,conter会从头开始计数,跟前面就不对应了,下面是斧正的逻辑
if isCounter {
var lastValue float64
for _, sample := range samples.Points {
if sample.V < lastValue {
resultValue += lastValue
}
lastValue = sample.V
}
}

// Duration between first/last samples and boundary of range.
// 区间向量第一个指标的时间戳 - rangeStart,除1000表示以Milliseconds为时间单位。
durationToStart := float64(samples.Points[0].T-rangeStart) / 1000
// 区间向量最后个指标(距当前时间最近一次的指标)的时间戳 - rangeStart,除1000表示以Milliseconds为时间单位。
durationToEnd := float64(rangeEnd-samples.Points[len(samples.Points)-1].T) / 1000

// 取区间向量第一个和最后一个指标时间的差值,除1000表示以Milliseconds为时间单位。
sampledInterval := float64(samples.Points[len(samples.Points)-1].T-samples.Points[0].T) / 1000
// 平均时间间隔
averageDurationBetweenSamples := sampledInterval / float64(len(samples.Points)-1)

if isCounter && resultValue > 0 && samples.Points[0].V >= 0 {
// Counters cannot be negative. If we have any slope at
// all (i.e. resultValue went up), we can extrapolate
// the zero point of the counter. If the duration to the
// zero point is shorter than the durationToStart, we
// take the zero point as the start of the series,
// thereby avoiding extrapolation to negative counter
// values.
durationToZero := sampledInterval * (samples.Points[0].V / resultValue)
if durationToZero < durationToStart {
durationToStart = durationToZero
}
}

// If the first/last samples are close to the boundaries of the range,
// extrapolate the result. This is as we expect that another sample
// will exist given the spacing between samples we've seen thus far,
// with an allowance for noise.
extrapolationThreshold := averageDurationBetweenSamples * 1.1
extrapolateToInterval := sampledInterval

if durationToStart < extrapolationThreshold {
extrapolateToInterval += durationToStart
} else {
extrapolateToInterval += averageDurationBetweenSamples / 2
}
if durationToEnd < extrapolationThreshold {
extrapolateToInterval += durationToEnd
} else {
extrapolateToInterval += averageDurationBetweenSamples / 2
}
resultValue = resultValue * (extrapolateToInterval / sampledInterval)
if isRate {
resultValue = resultValue / ms.Range.Seconds()
}

return append(enh.Out, Sample{
Point: Point{V: resultValue},
})
}


// === rate(node parser.ValueTypeMatrix) Vector ===
func funcRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return extrapolatedRate(vals, args, enh, true, true)
}

说明:
44行开始,推算就可以算出来具体的数值了,但是有些细节可以补充一下:

durationToStartdurationToEnd受查询时间和metricscrape time影响,如果超出了extrapolationThreshold时间,durationToStartdurationToEnd的值 = averageDurationBetweenSamples / 2

例如:假设当前的指标:A(conter类型),每秒以10的的速度增长,我们需要采集60s的指标,该指标的设置的采集间隔为5s,第一次采集的时间为00:01 00,最后一次的采集时间为00:02 00,当前查询时间为00:02 03,查询语句为A[1m],一般情况下,那么计算逻辑(伪代码)如下:

1
2
3
4
5
6
7
8
rangeStart = "00:02 03" (1m + 0) // 没有offset rangeStart == 00:01 03
rangeEnd = "00:02 03" - 0 // 没有offset rangeEnd == 00:02 03
durationToStart = (第一个metric的时间戳(00:02 00) - rangeStart) / 1000
durationToEnd = (最后一个metric的时间戳(00:01 00) - rangeStart) / 1000

sampledInterval = (第一个metric的时间戳(00:02 00) - (最后一个metric的时间戳(00:01 00) // sampledInterval = 60,0000

averageDurationBetweenSamples = sampledInterval / len(区间向量的数量,也就是12个) // averageDurationBetweenSamples = 60,0000 / 12

剩下的去套上面的程序(从第44行开始),至于resultValue可以随便编一个,但是要合理。

increase

1
2
3
4
// === rate(node parser.ValueTypeMatrix) Vector ===
func funcRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return extrapolatedRate(vals, args, enh, true, true)
}

increaserate共用一个函数extrapolatedRate,只是结果不需要执行extrapolatedRate函数第76行的内容。

irate

源代码(2021/4/14):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

// === irate(node parser.ValueTypeMatrix) Vector ===
func funcIrate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return instantValue(vals, enh.Out, true)
}
....

func instantValue(vals []parser.Value, out Vector, isRate bool) Vector {
samples := vals[0].(Matrix)[0]
// No sense in trying to compute a rate without at least two points. Drop
// this Vector element.
if len(samples.Points) < 2 {
return out
}

lastSample := samples.Points[len(samples.Points)-1]
previousSample := samples.Points[len(samples.Points)-2]

var resultValue float64
if isRate && lastSample.V < previousSample.V {
// Counter reset.
resultValue = lastSample.V
} else {
resultValue = lastSample.V - previousSample.V
}

sampledInterval := lastSample.T - previousSample.T
if sampledInterval == 0 {
// Avoid dividing by 0.
return out
}

if isRate {
// Convert to per-second.
resultValue /= float64(sampledInterval) / 1000
}

return append(out, Sample{
Point: Point{V: resultValue},
})
}

irate最终的计算规则:

1
(倒数第一个Metric Value - 减倒数第二个metris Value) / (倒数第一个Metric抓取时间(秒) - 减倒数第二个metris抓取时间(秒))

ssh tunnel 笔记

命令说明

约定:

  • SSH隧道发起者:本机,使用ssh主动建立连接的一方。
  • SSH隧道接受这:远端,被动接受ssh请求的一方。

ssh –help

  • -g 网关功能,会监听所有本地地址
  • -L 本地转发,格式为 ::,表示映射的本地端口和远程地址。注意remoteServerHost和RmotePort是指,只要是数据处理方可到达的地址。都有效并且可以使用。
  • -R 本地转发,格式为 ::,表示映射的本地端口和远程地址。注意remoteServerHost和RmotePort是指,只要是数据处理方可到达的地址。都有效并且可以使用。
  • -f 放到后台运行
  • -N -N不打开ssh回话,默认会开启ssh回话,一般配合-f使用。
  • -C 请求会话间的数据压缩传递。对于网络缓慢的主机,压缩对连接有所提升。但对网络流畅的主机来说,压缩只会更糟糕。
  • -q 静默模式。大多数警告信息将不输出。
  • -D 表示本地端口转发,当监听到此端口有连接时,此连接中的数据将通过安全隧道转发到server端,目前支持socks4和socks5协议。

本地转发

ssh隧道发起方监听端口。ssh隧道发起方接受请求,将数据包发送到远端服务器

1
$ ssh -g -N -f -C -L 8080:google:80 root@selinux.org

解释:在本机(SSH发起者/发送者)监听8080端口,数据包会送本机发送到selinux.org,selinux.org再转送给google:80

远程转发

ssh隧道接受方监听端口。ssh隧道发起方接受请求,将数据包发送到ssh隧道发起方。

1
$ ssh -g -N -f -C -R 6443:192.168.1.100:6443 root@selinux.org

解释:selinux监听一个6443端口,会把数据包发送到本地(ssh隧道发起者)然后发起者发送给192.168.1.100

socks4/socks5

1
$ ssh -D 1080 -N -f -C -q root@selinux.org

解释:本地监听端口,接受socks4/socks5协议,会将数据包发送到selinux.org,然后转发到数据包的要求的目的地址。

注意:上面两个都是讲的一对一的,这个是一对N,想跟谁沟通就可以跟谁沟通,不限地址和端口,而且支持TCP/UDP协议。

HTTPS笔记

摘自维基百科

超文本传输安全协议(英语:HyperText Transfer Protocol Secure,缩写:HTTPS;常称为 HTTP over TLS、HTTP over SSL 或 HTTP Secure)是一种通过计算机网络进行安全通信的传输协议。HTTPS 经由 HTTP 进行通信,但利用 SSL/TLS 来加密数据包。HTTPS 开发的主要目的,是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。这个协议由网景公司(Netscape)在 1994 年首次提出,随后扩展到互联网上。

为什么需要HTTPS

HTTP传输面临的风险有:

  1. 窃听风险:黑客可以获知通信内容。
  2. 篡改风险:黑客可以修改通信内容。
  3. 冒充风险:黑客可以冒充他人身份参与通信。

SSL/TSL

如上图所示 HTTPS 相比 HTTP 多了一层 SSL/TLS

SSL(Secure Socket Layer,安全套接字层):1994年为 Netscape 所研发,SSL 协议位于 TCP/IP 协议与各种应用层协议之间,为数据通讯提供安全支持。

TLS(Transport Layer Security,传输层安全):其前身是 SSL,它最初的几个版本(SSL 1.0、SSL 2.0、SSL 3.0)由网景公司开发,1999年从 3.1 开始被 IETF 标准化并改名,发展至今已经有 TLS 1.0、TLS 1.1、TLS 1.2 三个版本。SSL3.0和TLS1.0由于存在安全漏洞,已经很少被使用到。TLS 1.3 改动会比较大,目前还在草案阶段,目前使用最广泛的是TLS 1.1、TLS 1.2。

加密算法

对称加密

有流式、分组两种,加密和解密都是使用的同一个密钥。

例如:DES、AES-GCM、ChaCha20-Poly1305等

非对称加密

加密使用的密钥和解密使用的密钥是不相同的,分别称为:公钥、私钥,公钥和算法都是公开的,私钥是保密的。非对称加密算法性能较低,但是安全性超强,由于其加密特性,非对称加密算法能加密的数据长度也是有限的。

例如:RSA、DSA、ECDSA、 DH、ECDHE

协议设计演变过程

按照上面讲的三个HTTP的传输风险,先来解决第一个问题,然后再去想办法解决第二个问题

注:这是经过我个人理解后的想法,并不是真实的标准。

不完全解决窃听风险

如果我们单纯的使用对称加密算法,按照上图来简单的设计。来加密协议的数据,是不能做到防窃听的,因为第四步中间人是可以接收到服务器公钥,那么服务器发来的数据中间人就可以解密。

而且非对称加密性能较低,在大量数据的传输情况下,这种情况是不理想的,那么这两个问题如何解决?

答案是协议”握手”阶段使用非对称加密加密,之后使用对称加密来加密数据。

  1. 客户端给服务端发送请求
  2. 服务端返回客户端自己的公钥
  3. 客户端产生本次对话的对称密钥 SK,并用公钥 进行加密得到 SK后传给服务端
  4. 服务端收到 SK后用自己的私钥解密得到 SK;若成功,则返回客户端 OK,否则终止对话
  5. 接下来,客户端和服务端的对话均用 SK 加密后传输。

注:这只是一种广泛应用的加密方式,DH加密方式详细可以了解完全吃透 TLS/SSL

解决窃听、篡改、冒充风险

在 HTTPS 的握手阶段,一端向对端发送请求,对端返回自己的公钥;而一端未验证对方的身份和公钥,直接协商密钥。“中间人”看到了这个漏洞,夹在中间截获了对端的公钥,替换成了自己的公钥。正是这步“拿错了公钥”或者说“信错了对端”,使得 HTTPS 为加密(密钥协商的非对称加密和通信数据的对称加密)所做的努力全部泡汤。

这个时候就第三方权威认证机构来证明”服务器”是”可信”的。

那么如何证明服务器是可信的呢?

Mehr lesen

记一次OpenStack功能测试,出现xfs数据不一致的问题

前言

工作中所负责的OpenStack集群由于在前期规划当中,热迁移的网卡默认走的是管理网络,而管理网络在集群规划时,是基于千兆网络组网的,这就导致如果出现大量热迁移的时候,千兆网络会成为限制。

通过修改live_migrate_inbound_addr选项,是可以修改通过哪个网卡进行迁移的。

冠状病毒给我放了一个大寒假,来的第一天我就准备把热迁移网卡换成存储的万兆网口网络,本来想着这个操作很简单,就不在测试集群上试了,直接在实际环境中操作吧,当时的想法如下:

  • 寒假的第一天,蜜汁自信….
  • nova-compute服务自是管理虚拟机生命周期,短暂的重启nova-compute服务,并不会影响虚拟机,只要保证libvirtd服务的正常运行就行。
Mehr lesen

tcpdump笔记以及三次握手和四次关闭

参数笔记

  • -n 一个n表示不反向解析地址,两个n表示不反向解析端口。
  • -S 表示查看绝对序号,如客户端中的ack为1,这是相对的,要查看绝对序号需要加上该参数。
  • -v 表示查看详细的报文数据。
  • -r 该参数接收一个pcap文件的路径,不抓取数据包,而是去读取指定的pcap文件,并格式化输出。
  • -i 指定抓取的网卡,any表示监听全部。
  • -e 每行的打印输出中将包括数据包的数据链路层头部信息,我经常用它来看truck口的vlan id。
    详情请参考: http://linux.51yip.com/search/tcpdump

语法编写

所有经过目的或原地址是192.168.1.1的数据包

1
$ tcpdump host 192.168.1.1

源端口或者是目的端口为80的数据包

1
$ tcpdump port 80

所有目的地址为192.168.1.1的数据包

1
$ tcpdump dst host 192.168.1.1

相同的,所有目的端口为80的数据包为dst port

所有的源地址为192.168.1.1的数据包

1
$ tcpdump src host 192.168.1.1

相同的,所有源端口为80的数据包为src port

协议过滤

1
2
3
4
5
tcpdump arp
tcpdump ip
tcpdump tcp
tcpdump udp
tcpdump icmp
Mehr lesen

Docker执行pull做了什么

Docker manifest

mediaType

在下面后面的文章中需要注意到mediaType对应的值,其代表了文件的类型,在规范中定义了以下类型:

  • application/vnd.docker.distribution.manifest.v1+json: 代表了第一个版本的manifest格式,已经出现了第二个版本了(schemaVersion = 1)
  • application/vnd.docker.distribution.manifest.v2+json: 代表了新版本的manifest格式 (schemaVersion = 2)
  • application/vnd.docker.distribution.manifest.list.v2+json: Manifest list,也就是上方示例的manifest list文件
  • application/vnd.docker.container.image.v1+json: Container config JSON
  • application/vnd.docker.image.rootfs.diff.tar.gzip: “Layer”, as a gzipped tar,代表了镜像层的压缩类型
  • application/vnd.docker.image.rootfs.foreign.diff.tar.gzip: “Layer”, as a gzipped tar that should never be pushed,
  • application/vnd.docker.plugin.v1+json: Plugin config JSON
Mehr lesen

修改OpenStack VM中的resolv的search选项

前言

领导说要改一下OpenStack虚拟机中的resolv中的search的域,本以为是一个很简单的事情,三下两下就搞定了,果不其然的翻车了,不然也不会有这篇文章了…

修改过程

去官网的文档上看了一下,发现这个字段是受neutron.conf中的dns_domain控制的,改了之后运行虚拟机中的dhclient获取,发现,不对,怎么多了一个novalocal??

1
2
3
4
5
6
# dhclient
# cat /etc/resolv.conf
; generated by /usr/sbin/dhclient-script
search xx.com xxx1.com novalocal
nameserver 10.1.2.3
nameserver 10.1.2.4

会不会是cloud-init的操作?应该不会啊,还是看一下metadata

1
2
3
4
5
6
7
8
9
10
11
12
13
14
curl http://169.254.169.254/openstack/latest/meta_data.json | python -m json.tool
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 912 100 912 0 0 1304 0 --:--:-- --:--:-- --:--:-- 1306
{
"availability_zone": "nova",
"devices": [],
"hostname": "xxx.novalocal",
"launch_index": 0,
"name": "xxx",
"project_id": "ae39f633e6f14a578ab383b673f31f95",
"random_seed": "bWUqK7dMennhQp5OGtgOT9aI2hcBiM7hdg0SN5dfQB1kuvrnO5rqZAcEXtr7vgwHmeEuxSSzSDcf9YcGyoAg2+NgTplfp8FShzFjeTNJAMc9R5swuIuIXBqR65LDdIJVrIaG9L7ndJ71yfcMxWBprckxLAzgMHlIg4MQwLDLDCR9AIeX46gyQddzrzbuaRolZ0GWJjLCvEnq6AcPmYh9e9quhEuYkm1AwHdufrts52ivHLFBsW04zqMQ18ZBaRwX3ylBZbzOz3x1p2mqs+YYpGsWW4CcoTsIKvlfh3MAVLnkVFwxwjCzFYvX/TpnCETbsjfiPoJ74e9icKSeE1vSxqmLpRSyTV2ybmbt95g2SHjQSovndQiPnxZZY9+gR/CrNbDVylTIcxPgSJwXDkOZIxJBTm2CdSclll1p2dsZW6kEJlaho0bAV0/n9UKWoeQ8UHID2cdy0oSmqHrhdpDUG1Mb0wqKyzjvxOQN7ZpLKGfwLjHLHwPZr1LsY6IvTNhvhNpJB/GvKV6IlZxhM0LGggzgNxTBFf2XqYgQtAzX4W+ivTrikg9ILwBI2Ys/pWQMwE0G6hi5PJJMlKNsjZMiXfX/y1/uyu+vQ9DyDzLJgpJZaYXo9r4dF+AbnSDtY5YZMjRmyfYBwxPv5aGWIBE/nJUcyu2y3k0zWHbT4H2fBXk=",
"uuid": "0291b7c7-60e5-4945-9c64-2d5b0365dd5d"
}

之后在nova的代码当中找到了,dhcp_domain默认的值为novalocal(这个值是主要是在hostname后面一个域名,例如你的主机名为:aa,那创建后的主机名为:aa.novalocal)

1
2
3
4
5
# cat nova/conf/api.py
218 cfg.StrOpt("dhcp_domain",
219 deprecated_group="DEFAULT",
220 default="novalocal",
221 help="""

把这个配置指定为空之后,重新获取发现还是不对…

1
2
3
4
5
6
# dhclient
# cat /etc/resolv.conf
; generated by /usr/sbin/dhclient-script
search xx.com xxx1.com novalocal
nameserver 10.1.2.3
nameserver 10.1.2.4

之后我找到neutron-dhcp-agent对应的代码,看了一下这个字段到底是怎么拼接的,受哪些控制

1
2
3
4
5
6
7
8
9
@six.add_metaclass(abc.ABCMeta)
class DhcpBase(object):

def __init__(self, conf, network, process_monitor,
version=None, plugin=None):
self.conf = conf
self.network = network
self.dns_domain = (self.network.get('dns_domain') or
self.conf.dns_domain)

在上面的代码可以看到,self.dns_domain是受network.dns_domain这个值控制的,如果network.dns_domain的值为空,再去读配置文件中的dns_domain配置的值,我的环境中的网络中的dns_domain的值是为None的,所以肯定是会读配置文件中的,这里基本上就可以断定不是OpenStack的配置问题了,我就开始考虑是不是虚拟机哪里出了问题。

看了好久和也想了好久,metadata中是没有这些信息的,跟cloud-init没关系,毫无头绪,最后我看了一下虚拟机上面的hostname的全称,发现是xxx.novalocal,这个时候我就知道了,可能是dhcclient把这个加进去了,最后找到/usr/sbin/dhcclient-script中发现,确实是这样的,随后把hostnamenovalocal,再重新获取成功了

Mehr lesen

OpenStack 使用TAP/TUN设备

OpenStack Neutron

TAP/TUN

虚拟设备和物理设备的区别

在Linux内核中有一个网络设备管理层,处于网络设备驱动和协议栈之间,负责衔接它们之间的数据交互。驱动不需要了解协议栈的细节,协议栈也不需要了解设备驱动的细节。

对于一个网络设备来说,就像一个管道(pipe)一样,有两端,从其中任意一端收到的数据将从另一端发送出去。

比如一个物理网卡eth0,它的两端分别是内核协议栈(通过内核网络设备管理模块间接的通信)和外面的物理网络,从物理网络收到的数据,会转发给内核协议栈,而应用程序从协议栈发过来的数据将会通过物理网络发送出去。

对于一个虚拟的网络设备,首先它也归内核的网络设备管理子系统管理,对于Linux内核网络设备管理模块来说,虚拟设备和物理设备没有区别,都是网络设备,都能配置IP,从网络设备来的数据,都会转发给协议栈,协议栈过来的数据,也会交由网络设备发送出去,至于是怎么发送出去的,发到哪里去,那是设备驱动的事情,跟Linux内核就没关系了,所以说虚拟网络设备的一端也是协议栈,而另一端是什么取决于虚拟网络设备的驱动实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
+-----------------------------------------------+
| |
| +-------------+ |
| |Application A| | User Stack
| +-------------+ |
| |↑ |
| || |
|········||·····································|
| || +-----------> +--------+ |
| || |+----------- |tun/tap0| |
| || || +--------+ |
| || || ↑| |
| ↓| ↓| |↓ |
| +------------+ +----------------------+ | Kernel Stack
| |/dev/net/tun| |Network Protocol Stack| |
| +------------+ +----------------------+ |
| |
+-----------------------------------------------+

实现tun/tap设备的内核模块为tun,其模块介绍为Universal TUN/TAP device driver,该模块提供了一个设备接口/dev/net/tun供用户层程序读写,用户层程序通过读写/dev/net/tun来向主机内核协议栈注入数据或接收来自主机内核协议栈的数据,可以把tun/tap看成数据管道,它一端连接主机协议栈,另一端连接用户程序
为了使用tun/tap设备,用户层程序需要通过系统调用打开/dev/net/tun获得一个读写该设备的文件描述符(FD),并且调用ioctl()向内核注册一个TUN或TAP类型的虚拟网卡(实例化一个tun/tap设备),其名称可能是tap7b7ee9a9-c1/vnetXX/tunXX/tap0等。此后,用户程序可以通过该虚拟网卡与主机内核协议栈交互。当用户层程序关闭后,其注册的TUN或TAP虚拟网卡以及路由表相关条目(使用tun可能会产生路由表条目,比如openvpn)都会被内核释放。可以把用户层程序看做是网络上另一台主机,他们通过tap虚拟网卡相连。

TAP TUN的区别

TUN和TAP设备区别在于他们工作的协议栈层次不同,TAP等同于一个以太网设备,用户层程序向tap设备读写的是二层数据包如以太网数据帧,tap设备最常用的就是作为虚拟机网卡。TUN则模拟了网络层设备,操作第三层数据包比如IP数据包,openvpn使用TUN设备在C/S间建立VPN隧道

Mehr lesen