0%

k8s 1.2版本开始提供Ingress来实现对外暴露服务;目前k8s有三种暴露方式

  • LoadBlancer Service
  • NodePort Service
  • Ingress

K8s-LBS

LBS是k8s与云平台深度结合的一个组件,当使用LBS暴露服务时,实际上是通过底层云平台申请创建一个负载均衡器来向外暴露服务。目前LBS支持的云平台有GCE、DigitalOcean、Aliyun、私有云Openstack等等,由于LBS与云平台深度结合,所以只能在这些平台上使用。

NodePort

k8s_nodeport

k8s的端口分为:

  • Port
    service上暴露出来的端口,提供给集群(集群指整个容器集群)内部客户访问的端口。
  • NodePort
    node上暴露的端口,提供给集群外部客户访问的端口。
  • TargetPort
    endpoint上暴露的端口,也可以当作Pod上暴露的端口,无论从Port或NodePort上来的数据最终都会经过kube-proxy转发到Pod的TargetPort端口上。

k8s的IP分为:

  • ClusterIP
    service上虚拟ip地址,它由kube-proxy使用iptables规则重新定向到本地端口,再均衡到后端的Pod上。
  • NodeIP
    node节点的物理ip地址,它被kube-proxy使用iptables规则重定向到本地端口。
  • ContainerIP/PodIP
    K8s中以Pod为最小部署单位,一个Pod中共享一个网络资源(无论Pod中有几个容器)。每个Pod启动时,会自动创建一个镜像为gcr.io/google_containers/pause:0.8.0的容器,容器内部与外部的通信经由此容器代理,所以ContainerIP就是PodIP。

暴露方法及API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
name: svc-1
labels:
name: svc-1
spec:
type: NodePort
ports:
- port: 2222
targetPort: 22
nodePort: 30022
selector:
name: rc-test

API操作

实现原理

ip地址及端口的暴露都是通过修改iptables规则来实现的。

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
# iptables -t nat -L -n
...
Chain KUBE-NODEPORTS (1 references)
target prot opt source destination
KUBE-MARK-MASQ tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/svc-lonely: */ tcp dpt:30023
KUBE-SVC-E6FDK4HG4F4JSB77 tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/svc-lonely: */ tcp dpt:30023
KUBE-MARK-MASQ tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/svc-tmp: */ tcp dpt:30099
KUBE-SVC-OOWDNB3NCXKPBPZE tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/svc-tmp: */ tcp dpt:30099
KUBE-MARK-MASQ tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/svc-1: */ tcp dpt:30022
KUBE-SVC-D25WXD2YSOVKEUTU tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/svc-1: */ tcp dpt:30022
...
Chain KUBE-SEP-2LNK4QUGPB2C5PDO (2 references)
target prot opt source destination
KUBE-MARK-MASQ all -- 192.168.6.110 0.0.0.0/0 /* default/kubernetes:https */
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/kubernetes:https */ recent: SET name: KUBE-SEP-2LNK4QUGPB2C5PDO side: source mask: 255.255.255.255 tcp to:192.168.6.110:6443

Chain KUBE-SEP-5GTCHBFJM5RAI7LS (1 references)
target prot opt source destination
KUBE-MARK-MASQ all -- 10.254.86.9 0.0.0.0/0 /* default/svc-lonely: */
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/svc-lonely: */ tcp to:10.254.86.9:22

Chain KUBE-SEP-BCYRFQ26LZTOSSU7 (1 references)
target prot opt source destination
KUBE-MARK-MASQ all -- 10.254.86.4 0.0.0.0/0 /* default/svc-1: */
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/svc-1: */ tcp to:10.254.86.4:22

Chain KUBE-SEP-CLKUH4WMQ3CNBJ7K (1 references)
target prot opt source destination
KUBE-MARK-MASQ all -- 10.254.86.2 0.0.0.0/0 /* default/svc-1: */
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/svc-1: */ tcp to:10.254.86.2:22

Chain KUBE-SEP-D3FORTYMXA7BVSDA (1 references)
target prot opt source destination
KUBE-MARK-MASQ all -- 10.254.86.8 0.0.0.0/0 /* default/svc-1: */
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/svc-1: */ tcp to:10.254.86.8:22

Chain KUBE-SEP-F4EJGNTAH3JOOQC6 (1 references)
target prot opt source destination
KUBE-MARK-MASQ all -- 10.254.86.3 0.0.0.0/0 /* default/svc-1: */
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/svc-1: */ tcp to:10.254.86.3:22

Chain KUBE-SEP-HO23WAVQKIB2R4KD (1 references)
target prot opt source destination
KUBE-MARK-MASQ all -- 10.254.86.10 0.0.0.0/0 /* default/svc-tmp: */
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 /* default/svc-tmp: */ tcp to:10.254.86.10:99

Chain KUBE-SERVICES (2 references)
target prot opt source destination
KUBE-SVC-E6FDK4HG4F4JSB77 tcp -- 0.0.0.0/0 10.254.162.24 /* default/svc-lonely: cluster IP */ tcp dpt:2223
KUBE-SVC-OOWDNB3NCXKPBPZE tcp -- 0.0.0.0/0 10.254.119.86 /* default/svc-tmp: cluster IP */ tcp dpt:9999
KUBE-SVC-NPX46M4PTMTKRN6Y tcp -- 0.0.0.0/0 10.254.0.1 /* default/kubernetes:https cluster IP */ tcp dpt:443
KUBE-SVC-D25WXD2YSOVKEUTU tcp -- 0.0.0.0/0 10.254.159.12 /* default/svc-1: cluster IP */ tcp dpt:2222
KUBE-NODEPORTS all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL

Chain KUBE-SVC-D25WXD2YSOVKEUTU (2 references)
target prot opt source destination
KUBE-SEP-CLKUH4WMQ3CNBJ7K all -- 0.0.0.0/0 0.0.0.0/0 /* default/svc-1: */ statistic mode random probability 0.25000000000
KUBE-SEP-F4EJGNTAH3JOOQC6 all -- 0.0.0.0/0 0.0.0.0/0 /* default/svc-1: */ statistic mode random probability 0.33332999982
KUBE-SEP-BCYRFQ26LZTOSSU7 all -- 0.0.0.0/0 0.0.0.0/0 /* default/svc-1: */ statistic mode random probability 0.50000000000
KUBE-SEP-D3FORTYMXA7BVSDA all -- 0.0.0.0/0 0.0.0.0/0 /* default/svc-1: */

Chain KUBE-SVC-E6FDK4HG4F4JSB77 (2 references)
target prot opt source destination
KUBE-SEP-5GTCHBFJM5RAI7LS all -- 0.0.0.0/0 0.0.0.0/0 /* default/svc-lonely: */

Chain KUBE-SVC-NPX46M4PTMTKRN6Y (1 references)
target prot opt source destination
KUBE-SEP-2LNK4QUGPB2C5PDO all -- 0.0.0.0/0 0.0.0.0/0 /* default/kubernetes:https */ recent: CHECK seconds: 180 reap name: KUBE-SEP-2LNK4QUGPB2C5PDO side: source mask: 255.255.255.255
KUBE-SEP-2LNK4QUGPB2C5PDO all -- 0.0.0.0/0 0.0.0.0/0 /* default/kubernetes:https */

Chain KUBE-SVC-OOWDNB3NCXKPBPZE (2 references)
target prot opt source destination
KUBE-SEP-HO23WAVQKIB2R4KD all -- 0.0.0.0/0 0.0.0.0/0 /* default/svc-tmp: */

Ingress

Todo…

参考&鸣谢

Docker网络技术,用来保证Container之间正常通讯的技术,作为Docker自身提供的网络分为4种,Bridge、Host、None、Container。本文重点介绍** Bridge **

环境

  • Docker 版本
    Docker version 1.12.5, build 7392c3b
  • OS 版本
    Red Hat Enterprise Linux Server release 7.2 (Maipo)
  • kernel 版本
    Linux 3.10.0-327.el7.x86_64

Bridge介绍

longlong ago :-)

docker-network-bridge-br0

早期的二层网络中,bridge可以连接不同的LAN网,当host1 发出一个数据包时,LAN1的其他主机和网桥br0都会收到该数据包。网桥再将数据包从入口端广播到其他端口上(我的理解是,多端口网桥叫交换机)。因此,LAN2上的主机也会接收到host1发出的数据包,从而实现不同LAN网上所有主机的通信。

docker-network-bridge-linux

后来linux kernel借鉴桥设备的原理实现了虚拟bridge,用到了veth pair技术,实现了不同子网通讯的二层基础。

Docker Bridge

(正题)Docker Bridge不同于linux bridge也不同于桥设备,但Docker Bridge的构建基于linux bridgeNetwork Namespaceiptables

  • Network Namespace
    实现了子网之间的隔离
  • iptables
    解决了NAT映射问题,使容器有(被)访问外网的能力。
  • linux bridge
    实现了Host内跨子网通讯

docker-network-bridge-main

在桥接模式下,Docker Daemon将veth0附加到docker0网桥上,保证宿主机的报文有能力发往veth0。再将veth1添加到Docker容器所属的网络命名空间,保证宿主机的网络报文若发往veth0可以立即被veth1收到。容器如果需要联网,则需要采用NAT方式。准确的说,是NATP(网络地址端口转换)方式。NATP包含两种转换方式:SNAT和DNAT。

下行访问流程

docker-network-bridge-downflow
由于容器的IP与端口对外都是不可见的,所以数据包的目的地址为宿主机的ip和端口,为192.168.1.10:24。
数据包经过路由器发给宿主机eth0,再经eth0转发给docker0网桥。
由于存在DNAT(Destination NAT,修改数据包的目的地址)规则,会将数据包的目的地址转换为容器的ip和端口,为172.17.0.n:24。宿主机上的docker0网桥识别到容器ip和端口,于是将数据包发送附加到docker0网桥上的veth0接口,veth0接口再将数据包发送给容器内部的veth1接口,容器接收数据包并作出响应。
docker-network-bridge-downflow-detail

上行访问流程

看了上面的下行访问流程用到了DNAT,那么上行访问一定会使用SNAT了吧。可实时却并非如此。
容器内的请求可以正常发送到host外,是因为host开启的ip_forward。如果host关闭转发功能echo 0 > /proc/sys/net/ipv4/ip_forward,容器能的请求只能发送到于自己相同网段的节点容器内,不同网段及跨主机的网段是不通的。
docker-network-bridge-upflow-detail

Docker bridge中关键技术

Docker bridge充分利用了linux bridge和iptabels、namespace等技术。将其中需要很多命令做成自动化脚本以方便执行维护。如:pipework是一个shell脚本,用于完成bridge网络管理。

veth pair

veth pair是一对虚拟网卡,从一张veth网卡发出的数据包可以直接到达它的peer veth,两者之间存在着虚拟链路。veth网卡和常规的以太网区别仅在于xmit接口:将数据发送到其peer,触发peer的Rx 过程。
docker-network-bridge-vethpair
veth pair是用于不同network namespace间进行通信的方式,veth pair将一个network namespace数据发往另一个network namespace的veth。如果多个network namespace需要进行通信,则需要借助bridge。

*** 属于iproute2工具包中的ip-link提供的功能 ***

创建veth pair,vp16与vp19是一对儿,vp26与vp29是一对儿。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ sudo ip link add vp16 type veth peer name vp19
$ sudo ip link add vp26 type veth peer name vp29
$ sudo ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp0s5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 00:1c:42:c6:de:63 brd ff:ff:ff:ff:ff:ff
3: vp19@vp16: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether b6:62:99:1e:0c:2a brd ff:ff:ff:ff:ff:ff
4: vp16@vp19: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 1e:ea:cf:86:51:ab brd ff:ff:ff:ff:ff:ff
5: vp29@vp26: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 4e:16:07:e6:77:2c brd ff:ff:ff:ff:ff:ff
6: vp26@vp29: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether be:98:08:90:da:7a brd ff:ff:ff:ff:ff:ff

创建namespace ns19和ns29,并设置vp19和vp29的netns

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ sudo ip netns add ns19
$ sudo ip netns add ns29
$ sudo ip netns list
ns19
ns29

$ sudo ip link set netns ns19 vp19
$ sudo ip link set netns ns29 vp29
$ sudo ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp0s5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 00:1c:42:c6:de:63 brd ff:ff:ff:ff:ff:ff
4: vp16@if3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 1e:ea:cf:86:51:ab brd ff:ff:ff:ff:ff:ff link-netnsid 0
6: vp26@if5: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether be:98:08:90:da:7a brd ff:ff:ff:ff:ff:ff link-netnsid 1

设置完netns后,在当前namespace中查看网卡信息就看不到vp19和vp29这两个网卡了,但在ns19和ns29 namespace中却能查看到对应的网卡。

1
2
3
4
5
6
7
8
9
10
$ sudo ip netns exec ns19 ip link show
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: vp19@if4: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether b6:62:99:1e:0c:2a brd ff:ff:ff:ff:ff:ff link-netnsid 0
$ sudo ip netns exec ns29 ip link show
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
5: vp29@if6: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 4e:16:07:e6:77:2c brd ff:ff:ff:ff:ff:ff link-netnsid 0

此时将这四个网卡激活,并配置ip,他们便可以相互通讯了。

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
$ sudo ip link set dev vp16 up
$ sudo ip link set dev vp26 up
$ sudo ip netns exec ns19 ip link set dev vp19 up
$ sudo ip netns exec ns29 ip link set dev vp29 up

$ sudo ip addr add 192.168.200.16/24 dev vp16
$ sudo ip addr add 192.168.200.26/24 dev vp26
$ sudo ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp0s5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:1c:42:c6:de:63 brd ff:ff:ff:ff:ff:ff
inet 192.168.3.5/24 brd 192.168.3.255 scope global enp0s5
valid_lft forever preferred_lft forever
inet6 fe80::21c:42ff:fec6:de63/64 scope link
valid_lft forever preferred_lft forever
4: vp16@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 1e:ea:cf:86:51:ab brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.200.16/24 scope global vp16
valid_lft forever preferred_lft forever
inet6 fe80::1cea:cfff:fe86:51ab/64 scope link
valid_lft forever preferred_lft forever
6: vp26@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether be:98:08:90:da:7a brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet 192.168.200.26/24 scope global vp26
valid_lft forever preferred_lft forever
inet6 fe80::bc98:8ff:fe90:da7a/64 scope link
valid_lft forever preferred_lft forever

$ sudo ip netns exec ns19 ip addr add 192.168.200.19/24 dev vp19
$ sudo ip netns exec ns19 ip addr show
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: vp19@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether b6:62:99:1e:0c:2a brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.200.19/24 scope global vp19
valid_lft forever preferred_lft forever
inet6 fe80::b462:99ff:fe1e:c2a/64 scope link
valid_lft forever preferred_lft forever

$ sudo ip netns exec ns29 ip addr add 192.168.200.29/24 dev vp29
$ sudo ip netns exec ns29 ip addr show
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
5: vp29@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 4e:16:07:e6:77:2c brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.200.29/24 scope global vp29
valid_lft forever preferred_lft forever
inet6 fe80::4c16:7ff:fee6:772c/64 scope link
valid_lft forever preferred_lft forever
  • ns19、ns29这两个namespace的网络不能ping通各自的192.168.200.0网段的ip地址
    将ns19、ns29中的lo设备激活(up),便能ping通各自的ip地址了。
  • ns19 namespace的网络能ping同192.168.200.16192.168.200.26,但不能ping通192.168.200.29
    ns29 namespace的网络却不能ping通任何一个ip地址。
  • 将ns29所对应的veth pair划分独立网段(192.168.29.0),veth pair对应的两个网卡便能正常ping通
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
$ sudo ip netns exec ns19 ping 192.168.200.16
ING 192.168.200.16 (192.168.200.16) 56(84) bytes of data.
64 bytes from 192.168.200.16: icmp_seq=1 ttl=64 time=0.060 ms
64 bytes from 192.168.200.16: icmp_seq=2 ttl=64 time=0.065 ms
...
$ sudo ip netns exec ns19 ping 192.168.200.26
PING 192.168.200.26 (192.168.200.26) 56(84) bytes of data.
64 bytes from 192.168.200.26: icmp_seq=1 ttl=64 time=0.041 ms
64 bytes from 192.168.200.26: icmp_seq=2 ttl=64 time=0.063 ms
64 bytes from 192.168.200.26: icmp_seq=3 ttl=64 time=0.057 ms
...
$ sudo ip netns exec ns29 ping 192.168.200.26 -w 5
PING 192.168.200.26 (192.168.200.26) 56(84) bytes of data.

--- 192.168.200.26 ping statistics ---
6 packets transmitted, 0 received, 100% packet loss, time 4999ms
$ sudo ip netns exec ns29 ping 192.168.200.16 -w 5
PING 192.168.200.16 (192.168.200.16) 56(84) bytes of data.

--- 192.168.200.16 ping statistics ---
6 packets transmitted, 0 received, 100% packet loss, time 4999ms
$ sudo ip netns exec ns29 ip addr delete 192.168.200.29/24 dev vp29
$ sudo ip netns exec ns29 ip addr add 192.168.29.29/24 dev vp29
$ sudo ip addr delete 192.168.200.26/24 dev vp26
$ sudo ip addr add 192.168.29.26/24 dev vp26
$ sudo ip netns exec ns29 ping 192.168.29.26
PING 192.168.29.26 (192.168.29.26) 56(84) bytes of data.
64 bytes from 192.168.29.26: icmp_seq=1 ttl=64 time=0.059 ms
64 bytes from 192.168.29.26: icmp_seq=2 ttl=64 time=0.051 ms
...

brctl

brctl是bridge-utils包中的程序,用于管理linux bridge的CLI工具。

创建bridge设备

1
2
3
4
$ sudo brctl addbr vb
$ sudo brctl show
bridge name bridge id STP enabled interfaces
vb 8000.000000000000 no

将网卡vp16和vp26添加到bridge设备中,可实现vp19与vp29的通讯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ sudo brctl addif vb vp16
$ sudo brctl addif vb vp26
$ sudo brctl show
bridge name bridge id STP enabled interfaces
vb 8000.1eeacf8651ab no vp16
vp26
$ sudo ip netns exec ns29 ping 192.168.200.19
PING 192.168.200.19 (192.168.200.19) 56(84) bytes of data.
64 bytes from 192.168.200.19: icmp_seq=1 ttl=64 time=0.063 ms
64 bytes from 192.168.200.19: icmp_seq=2 ttl=64 time=0.072 ms
...
$ sudo ip netns exec ns19 ping 192.168.200.29
PING 192.168.200.29 (192.168.200.29) 56(84) bytes of data.
64 bytes from 192.168.200.29: icmp_seq=1 ttl=64 time=0.066 ms
64 bytes from 192.168.200.29: icmp_seq=2 ttl=64 time=0.058 ms
...

若希望namespace网络能ping通宿主机内所有网络,需要添加默认网关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ sudo ip netns exec ns19 ping 192.168.3.5
connect: Network is unreachable
$ sudo ip netns exec ns29 ping 192.168.3.5
connect: Network is unreachable
$ sudo ip netns exec ns19 route add default gw 192.168.200.1
$ sudo ip netns exec ns19 route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.200.1 0.0.0.0 UG 0 0 0 vp19
192.168.200.0 0.0.0.0 255.255.255.0 U 0 0 0 vp19
$ sudo ip netns exec ns19 ping 192.168.3.5
PING 192.168.3.5 (192.168.3.5) 56(84) bytes of data.

--- 192.168.3.5 ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 2999ms

配置完默认网关后,网络不可达变成了访问超时,此时需要将网卡vp16vp26的IP地址删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ sudo ip addr delete 192.168.200.16/24 dev vp16
$ sudo ip addr delete 192.168.200.26/24 dev vp26
$ sudo ip addr show
...
4: vp16@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master vb state UP group default qlen 1000
link/ether 1e:ea:cf:86:51:ab brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::1cea:cfff:fe86:51ab/64 scope link
valid_lft forever preferred_lft forever
6: vp26@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master vb state UP group default qlen 1000
link/ether be:98:08:90:da:7a brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::bc98:8ff:fe90:da7a/64 scope link
valid_lft forever preferred_lft forever
7: vb: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 1e:ea:cf:86:51:ab brd ff:ff:ff:ff:ff:ff
inet 192.168.200.1/24 scope global vb
valid_lft forever preferred_lft forever
inet6 fe80::1cea:cfff:fe86:51ab/64 scope link
valid_lft forever preferred_lft forever
$ sudo ip netns exec ns19 ping 192.168.3.5
PING 192.168.3.5 (192.168.3.5) 56(84) bytes of data.
64 bytes from 192.168.3.5: icmp_seq=1 ttl=64 time=0.064 ms
64 bytes from 192.168.3.5: icmp_seq=2 ttl=64 time=0.062 ms
...

iptables/netfilter

netfilter是Linux操作系统核心层内部的一个数据包处理模块,它具有网络地址转换(Network Address Translate)、数据包过滤、数据包处理、地址伪装、透明代理,以及基于用户及媒体访问控制(Media Access Control,MAC)地址的过滤和基于状态的过滤、包速率限制等。** netfilter工作在三层 **

iptables是与netfilter交互的CLI工具。

按照上述的配置方式,只能做到访问宿主机内的所有网络,若希望ping通宿主机所在网络的其它主机,需要开启ip_forward(echo 1 >> /proc/sys/net/ipv4/ip_forward,需要以root身份操作,仅仅使用root权限不够),并关闭防火墙(iptables)或配置防火墙POSTROUTING

1
2
3
4
5
6
7
8
9
10
11
12
13
$ sudo iptables -t nat -A POSTROUTING -s 192.168.200.0/24 -p all -j MASQUERADE
hain PREROUTING (policy ACCEPT)
target prot opt source destination

Chain INPUT (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination

Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 192.168.200.0/24 0.0.0.0/0

TUN/TAP驱动

Todo.

参考&鸣谢

什么是服务发现

容器可以对其它容器提供服务,如mysql(数据库服务)、nginx、mangodb等等。这些服务都可以通过IP+Port方式访问。由于容器会经常重建迁移,所以IP会发生变化,如果调用服务的程序使用IP访问就会很不方便,为此可以使用DNS、zookeeper、etcd等技术实现服务访问与IP的解耦。这种解耦方式叫做服务发现。

版本

  • rancher-dns v0.13.3

Rancher服务发现构成及更新流程

rancher-dns-and-metadata

  • Cattle
    容器管理器(rancher核心)。
  • Event Subscriber
    轻量级的消息队列,用于通知rancher-metadata更新数据
    ** 猜测:采用websocket协议通讯,Cattle为消息生产者,rancher-metadata、scheduler、rancher-agent为消费者或消息订阅者 **
  • rancher-dns
    向容器提供DNS服务
  • rancher-metadata
    rancher中的元数据管理器

rancher-dns结构

rancher-dns-struct

  • TCP、UDP Listen
    rancher-dns监听53端口,用于接收域名解析请求。
  • miekg DNS
    DNS协议封装解析库
  • ROUTE
    处理域名解析业务逻辑
  • metadata client
    负责与rancher-metadata同步answer数据,每5秒同步一次。
  • answers
    负责存储解析metadata client同步来的answer数据(/etc/rancher/answers.json

rancher-dns ROUTE工作流程

rancher-dns-route-workflow

*** 关于域名协议流程参见:《DNS协议》中相关内容 ***

mac-sqlite-ad-bugfix

问题

mac上有很多流氓软件,尤其是你通过非app store安装的时候。今天我遇到了这样一个问题,mac的通知中心通知我Get it NOW!,这是SQLite Editor的一个广告推送。频率为每天一次,记住当它提示你get的时候,千万别点,点了便每天会自动打开App Store + Web来提示你购买。我操,真是烦死了。

版本

OS X 10.11.6

解决方法

  • 清理base-sqlite的推送程序
    在”/Users/zhoub/Library/Application Support”目录下,有一个”com.asoffertest.base-sqlite”目录,这里面的asoffer.py就是完成广告推送的罪魁祸首,不要由于果断将”com.asoffertest.base-sqlite”目录删除。
  • 清理NotificationCenter数据库中的记录
    虽然清理了推送程序,但是通知中心中依然有推送程序的选项卡——一个叫python的选项卡。所以需要按照《Mac中NotificationCenter残留应用删除》的方法清除“python选项卡”。
  • 重启系统
    ……系统启动后,通知中心的选项卡中没有“python”了。

原因

上诉的推送程序本人也不知道是装哪个软件装上的。但本人在MPlayerX官网上下载过osx安装包,并安装过。然后提示我已经安装了mackeeper,mackeeper可是个…以前就被这个mk(mackeeper)坑过,这次估计又是这厮搞得。

*** 以上纯属猜测,没有直接根据。***

在mac系统的“设置”-“通知”中有很多应用标签,这些标签中有些是我们所需要的,有些是某某某流氓软件,强行装上的(流氓软件卸载后,该应用标签一直存在)。那么这个应用标签该如何清理呢?

上网找了好多方法,其中以删除~/Library/Application Support/NotificationCenter/<id>然后重启,这种方法最火。不知道这种方法实在osx(or macos)的哪个版本上的,本人mbp是osx 10.11.6,在我的mbp上没有NotificationCenter这个目录。对于没有这个目录的可以查看一下getconf DARWIN_USER_DIR这个目录。该目录下有个com.apple.notificationcenter目录,这个目录才是你要找到目录。

mac上NotificationCenter中的应用标签是存储在SQLite3数据库中的。可使用sqlite3 \getconf DARWIN_USER_DIR`com.apple.notificationcenter/db/db`打开sqlite数据库

1
2
3
4
$ sqlite3 `getconf DARWIN_USER_DIR`com.apple.notificationcenter/db/db                                                                                     1 ↵
SQLite version 3.8.10.2 2015-05-20 18:17:19
Enter ".help" for usage hints.
sqlite>

然后可用.tables查看库中的表

1
2
3
4
5
6
7
sqlite> .tables
app_info notifications
app_loc presented_alerts
app_push presented_notifications
app_source scheduled_notifications
dbinfo today_summary_notifications
notification_source tomorrow_summary_notifications

由于sqlite mode默认是list模式,需要将其改为line模式

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
sqlite> .show
echo: off
eqp: off
explain: off
headers: off
mode: list
nullvalue: ""
output: stdout
colseparator: "|"
rowseparator: "\n"
stats: off
width:
sqlite> .mode line
sqlite> .show
echo: off
eqp: off
explain: off
headers: off
mode: line
nullvalue: ""
output: stdout
colseparator: "|"
rowseparator: "\n"
stats: off
width:

可用select查询app_info表,并删除想要删除的记录,然后重启系统,通知中的应用标签消失。

参考&鸣谢

介绍

IP地址对于人来说很难记忆区分,但域名却很方便记忆,所以要将域名与IP地址对应起来,就催生了DNS。DNS不仅提供域名到IP的映射服务,还能提供主机别名、邮件服务器识别、负载均衡服务。

协议

DNS属于应用层协议,通常由HTTP、SMTP、FTP等协议使用,占用53端口。

交互流程

dns_protocol

  • 客户端发送一个包含域名的请求给DNS服务器(DNS查询报文)
  • DNS服务器查询到域名对应的IP地址后,给客户端一个应答回复(DNS应答报文),回复中包含客户端所请求域名对应的IP地址
  • 客户端收到回复后,取出IP地址,与该地址服务器建立链接

协议格式

DNS协议分为查询协议和应答协议,这两种协议的格式是一样的。

dns-protocol-format

DNS协议包括两部分,协议头和协议体

协议头

DNS协议头由固定的12个字节组成

  • ID
    由程序分配的16位标识符,该标识在查询时产生,应答报文中该ID与对应的查询请求ID相同。
  • QR
    表示该报文类型,“0”表示查询,“1”表示应答
  • OPcode
    表示查询种类,只在查询协议中作用。“0”为标准查询(QUERY),“1”为反相查询(IQUERY),“2”为服务器状态请求(STATUS),“3”~“15”为保留
  • AA
    授权应答的标志位。该位在应答报文中有效,“1”表示名字服务器是权限服务器
  • TC
    截断标志位。1表示响应已超过512字节并已被截断。(截断和UDP协议有关)
  • RD
    期望递归标志,作用在查询报文中,该位为“1”表示客户端希望得到递归应答
  • RA
    递归可用标志,作用在应答报文中,该位为“1”表示可以得到递归应答
  • zero
    用“0”占位,保留字段
  • Rcode
    返回码,在应答报文中出现,“0”表示无差错,“1”表示格式差错,“2”表示问题在域名服务器上,“3”表示域参照问题,“4”表示查询类型不支持,“5”表示在管理上被禁止,“6”~“15”预留
  • QD Count
    查询信息的数量
  • AN Count
    应答信息的数量
  • NS Count
    授权信息的数量
  • AR Count
    附加信息的数量

协议体

查询段

描述查询信息

  • QNAME
    表示需要查询的域名,该字段为变长字段,用标签序列表示域名(如:www.baidu.com 显示为 03 77 77 77 05 62 61 69 64 75 03 63 6f 6d 00)
  • QTYPE
    表示查询资源的类型,详细请见下文资源类型列表
  • QCLASS
    表示查询网络类别,“1”表示Internet互联网系统(助记“IN”),“CH”表示Chaos

应答段、授权段、附加段

对应答信息、授权信息、附加信息的描述

  • NAME
    资源记录对应的域名,该字段为变长字段,格式同QNAME
  • TYPE
    同QTYPE
  • CLASS
    同QCLASS
  • TTL
    表示资源记录的生命周期(以秒为单位),一般用于当地址杰西程序读取资源记录后决定保存及使用缓存数据的时间
  • RDLENGTH
    表示资源数据的长度
  • RDATA
    资源数据,按查询段要求返回的相关资源记录数据。
    若其TYPE为A,则返回4字节的IP地址;
    若其TYPE为NS,则返回授权域名服务器的域名;
    若其TYPE为CNAME,则返回规范名或与别名对应的真实名称。

资源类型列表

助记符 说明
A 指定主机名(或域名)对应的IPv4地址记录
AAAA 指定主机名(或域名)对应的IPv6地址记录
CNAME 别名 如:dig www.baidu.com, www.baidu.com.的cname就是www.a.shifen.com.
PTR 指针记录,用于将一个IP地址映射到对应的主机名,也可以看成是A记录的反向,通过IP访问域名
MX 邮件路由记录,用户可以将该域名下的邮件服务器指向到自己的mail server上,然后即可自行操控所有的邮箱设置
TXT 一般指为某个主机名或域名设置的说明,没啥用,可忽略
SRV 记录了哪台计算机提供了哪个服务
NS 域名解析服务器记录,如果要将子域名指定某个域名服务器来解析,需要设置NS记录
  • SRV
    格式:优先级 权重 端口 服务的名字.协议的类型.域名
    eg:
    1
    2
    3
    4
    5
    6
    _http._tcp.example.com. SRV 10 5 80. www.example.com
    _http - 服务名
    _tcp - 协议
    10 - 优先级
    5 - 权重
    80 - 端口

内部流程

dns-work-flow

参考&鸣谢

背景

analyse-paxos-byzantine

拜占庭位于如今的土耳其的伊斯坦布尔,是东罗马帝国的首都。由于当时拜占庭罗马帝国国土辽阔,为了防御目的,因此每个军队都分隔很远,将军与将军之间只能靠信差传消息。 在战争的时候,拜占庭军队内所有将军和副官必需达成一致的共识,决定是否有赢的机会才去攻打敌人的阵营。但是,在军队内有可能存有叛徒和敌军的间谍,左右将军们的决定又扰乱整体军队的秩序。在进行共识时,结果并不代表大多数人的意见。这时候,在已知有成员谋反的情况下,其余忠诚的将军在不受叛徒的影响下如何达成一致的协议,拜占庭问题就此形成。

拜占庭将军问题是一个协议问题,拜占庭帝国军队的将军们必须全体一致的决定是否攻击某一支敌军。问题是这些将军在地理上是分隔开来的,并且将军中存在叛徒。叛徒可以任意行动以达到以下目标:欺骗某些将军采取进攻行动;促成一个不是所有将军都同意的决定,如当将军们不希望进攻时促成进攻行动;或者迷惑某些将军,使他们无法做出决定。如果叛徒达到了这些目的之一,则任何攻击行动的结果都是注定要失败的,只有完全达成一致的努力才能获得胜利。

拜占庭假设是对现实世界的模型化,由于硬件错误、网络拥塞或断开以及遭到恶意攻击,计算机和网络可能出现不可预料的行为。拜占庭容错协议必须处理这些失效,并且这些协议还要满足所要解决的问题要求的规范。这些算法通常以其弹性t作为特征,t表示算法可以应付的错误进程数。

很多经典算法问题只有在t<n/3时才有解,如拜占庭将军问题,其中n是系统中进程的总数。

为了解决拜占庭将军问题,图灵奖大神Leslie Lamport提出了Paxos算法,该算法可以帮助解决分布式系统中的一致性问题。

原理

在分布式系统中,为了保证数据的高可用,我们会将数据保留多个副本,这些副本会放置在不同的物理机上。为了对用户提供正确的读写,我们需要保证这些放置在不同物理机上的副本是一致的。

其中Proposer与Acceptor之间的交互主要有两个阶段、4类消息构成。

  • Phase1
    本阶段由2类消息构成prepare和promise,Proposer向网络内超过半数的Acceptor发送prepare消息
  • Phase2
    prepare、promise、accept、accepted。

角色

Paxos中有三类角色Proposer、Acceptor、Learner

analyse-paxos-members

选举流程

整个Paxos算法流程分为3个阶段

  • 准备阶段
  • 决议阶段
  • 学习阶段

analyse-paxos-flow

准备阶段

  • Proposer向大多数Acceptor发起自己要发起Proposal(epochNo, value)的Prepare请求
  • Acceptor收到Prepare请求,如果epochNo比已经接受的小的,直接拒绝; 如果epochNo比已经接受的大,保证不再接受比该epochNo小的请求,且将已经接受的epochNo最大的Proposal返回给Proposer

决议阶段

  • Proposer收到大多数Acceptor的Prepare应答后,如果已经有被接受的Proposal,就从中选出epochNo最大的Proposal, 发起对该Proposal的Accept请求。如果没有已经接受的Proposal, 就自己提出一个Proposal, 发起Accept请求。
  • Acceptor收到Accept请求后,如果该Proposal的epochNo比它最后一次应答的Prepare请求的epochNo要小,那么要拒绝该请求;否则接受该请求。

学习阶段

  • 当各个Acceptor达到一致之后,需要将达到一致的结果通知给所有的Learner

analyse-paxos-flow-detail

Proposer角色

(Phase1.a) 向所有的acceptors发送Prepare(i, b)请求;

(Phase2.a) 如果收到Reject(i,b)消息,那么重新发送Prepare(i,b+n),n为一个整型值,不同的proposer具有不同的n值,使得proposer之间保持一个偏序关系,保证不同的proposer不会使用相同的b值,即提案编号;

(Phase2.a) 如果收到acceptors集合的任意一个majority的Promise(i, b, V, VB)回复,那么如果所有的V均为空,proposer可以自由选取一个v(value),一般为用户提出的请求,回发Accept(i, b, v);否则回发Accept(i,b,V);

(Phase2.b) 如果收到Nack(b),回到(Phase1.a)发送Prepare(i,b+n);

(Phase2.b) 如果收到任意一个majority所有成员的Accepted(i,b,v)消息(表明投票已经完成)。这个过程learner也能收到Accepted消息,learner查看i是否为当前需要确认的iid,如果是则立即执行这个被批准的决议v;否则将该Accepted保存下来。

Phase2.b阶段完成后,各个角色上对应该实例的状态都将变为closed状态,即该实例已经选出决议,proposer不能再提出新的提案。这样保证一个实例只能选出一个决议。在实际应用过程中,为了简化实现,常常在proposers中选举出一个leader,来充当协调者。当leader选举出来后,系统中只能由leader向acceptors发出Prepare请求,也就是说这能由leader发起提案,而其它的proposers则只干一件事,即定时检测系统中的leader是否还在工作,如果在一定时间内收不到leader的心跳消息,则剩下的proposers发起新一轮leader竞选,选取新的leader。

Acceptor

acceptor会维护一个状态记录表,表的每一行维护这样四个数据<iid, B, V, VB>, iid表示实例id。B是一个整数,用来表示同意或接受过的该提案的最高编号。V表示该提案对应的决议,里面保存着客户端发送过来的数据。VB表示已经接受过的提案的编号。

(Phase 1.b) 接收Prepare(i,b)消息,i为实例id号,b为提案编号。对于同一个i,如果b>B,那么回复Promise(i, b, V, VB),并设B=b;否则,回复Reject(i,b),其中b=B。

(Phase 2.b) 接收Accept(i, b, v),如果b<B,那么回复Nack(b)信息,其中b=B(暗示该proposer提出提案后至少有一个其它的proposer广播了具有更高编号的提案);否则设置V=v,VB=b,并且回复Accepted(i,b,v)消息。

其中:Promise(i, b, V, VB)表示向proposer保证对于该实例不再接受编号不大于b的相同iid的提案;Accepted表示向learner和proposer发送该提案被通过的消息。

Learner

learner的主要任务就是监听来自acceptors的消息,用以最终确认并学习决议(value),即被批准的提案。当learner收到来自大多数(majority)acceptors的接受消息后,就可以确定该实例(instance)的value已经被最终无歧义的确认。这个时候便可以执行决议里的操作。决议序列在所有learner上顺序都是一致的,每一个提案的发起将会触发一次Paxos过程,每个这样的过程是一个Paxos的实例。而在实际应用中常使用单增的整数来标识每一个实例,即iid(instance id)。iid从1开始,而所有从1开始到当前iid的实例都必须是已经被确认过的,即这些决议都已经被执行过。比如:learner A已经确认了前10个实例,这时iid为11的决议还没有被通过,而iid为12和13的提案已经得到大多数acceptors的接受。此时就会产生一个决议序列缺口(gap),在这种情况下,A不能跳过11直接确认12和13,而是去询问acceptors是否已经通过11的决议。只有当iid为11的决议被确认后,iid为12和13的决议才能被确认学习。

活锁问题

Todo…

应用

Paxos在Ceph Monitor应用。Monitor要做的事情很明确了,就是管理、维护和发布集群的状态信息,但是为了避免单点故障或者性能热点问题,一般使用多个Monitor来做这一件事情,也就是管理层有多个成员。集群的正常运行,首先需要管理层达成一致,达成一致就需要有一个能拍板的monitor(leader),大家都听它的就行了。所以要达成一致核心问题就是在众多monitor中选出那个能拍板的monitor。Ceph解决这个问题的方法很简单,有点类似于领导人的选举,即有资格的monitor先形成一个quorum(委员会),然后委员会的成员在quorum这个范围内选出一个leader,集群状态信息的更新以及quorum成员的维护就有这个leader负责。Leader的选取规则也比较简单,每个monitor在初始化的时候都会根据它的IP地址被赋予一个rank值,当选举leader时,rank值最小的monitor胜出当选leader。当quorum成员发生变化时(增加或者减少),都会触发重新选举流程,再选出一个leader。

monitor的代码目录结构:
analyse-paxos-ceph-monitor-src.png

架构设计

paxos-ceph-monitor-frame.png

  • DBStore层
    数据的最终存储组件,以leveldb为例
  • Paxos层
    在集群上对上层提供一致的数据访问逻辑,在这一层看来所有的数据都是kv;上层的多中PaxosService将不同的组件的map数据序列化为单条value,公用同一个paxos实例
  • PaxosService层
    每个PaxosService代表集群的一种状态信息。对应的,Ceph Moinitor中包含分别负责OSD Map,Monitor Map, PG Map, CRUSH Map的几种PaxosService。PaxosService负责将自己对应的数据序列化为kv写入Paxos层。Ceph集群所有与Monitor的交互最终都是在调用对应的PaxosSevice功能

关键流程及结构

初始化流程

analyse-paxos-ceph-monitor-init-flow.png

  • 自下而上依次初始化上述的三大组成部分:DBStroe,Paxos,PaxoService
  • 初始化Messager,并向其中注册命令执行回调函数。Messager是Ceph中的网络线程模块,Messager会在收到网络请求后,回调Moniotor在初始化阶段注册命令处理函数
  • Bootstrap过程在整个Monitor的生命周期中被反复调用

Boostrap

  1. 执行Boostrap的Monitor节点会首先进入PROBING状态,并开始向所有monmap中其他节点发送Probing消息
  2. 收到Probing消息的节点执行Boostrap并回复Probing_ack,并给出自己的last_commit以及first_commit,其中first_commit指示当前机器的commit记录中最早的一条,其存在使得单个节点上可以仅保存最近的几条记录
  3. 收到Probing_ack的节点发现commit数据的差距早于对方first_commit,则主动发起全同步,并在之后重新Boostrap
  4. 收到超过半数的ack并不需要全同步时,则进入选主过程

analyse-paxos-ceph-monitor-boostrap-1.png

经过boostrap过程,保证可以与半数以上的节点通讯,并且节点间commit数据历史差距不大了。

select & victory

analyse-paxos-ceph-monitor-victory.png

select
  1. 将election_epoch加1,向Monmap中的所有其他节点发送Propose消息
  2. 收到Propose消息的节点进入election状态并仅对有更新的election_epoch且rank值大于自己的消息答复Ack。这里的rank简单的由ip大小决定
  3. 发送Propose的节点统计收到的Ack数,超时时间内收到Monmap中大多数的ack后可进入victory过程,这些发送ack的节点形成quorum
victory
  1. election_epoch加1,可以看出election_epoch的奇偶可以表示是否在选举轮次
  2. 向quorum中的所有节点发送VICTORY消息,并告知自己的epoch及quorum
  3. 当前节点完成Election,进入Leader状态
  4. 收到VICTORY消息的节点完成Election,进入Peon状态

recovery

经过了Boostrap、select、victory,能确定leader和peon角色,以及quorum成员。在recovery阶段将leader和quorum节点间的数据更新到一致。整个集群进入可用状态。

analyse-paxos-ceph-monitor-recovery.png

一致性读写流程

经过了上面的初始化流程,整个集群进入到一个正常状态,可以用Paxos进行一致性读写了。其中读流程比较简单,lease内的所有quorum均可以提供读服务。而所有写都会转发给leader。

一致性写流程

  1. leader在本地记录要提交的value,并向quroum中的所有节点发送begin消息,其中携带了要提交的value, accept_pn及last_commit
  2. peon收到begin消息,如果accept过更高的pn则忽略,否则将value写入db并返回accept消息。同时peon会将当前的lease过期掉,在下一次收到lease前不再提供服务
  3. leader收到 全部 quorum的accept后进行commit。本地commit后向所有quorum节点发送commit消息
  4. peon收到commit消息,本地commit数据
  5. leader通过lease消息将整个集群带入到active状态

analyse-paxos-ceph-monitor-rw.png

状态转换

初始化阶段状态转换

analyse-paxos-ceph-monitor-status-1.png

  • STATE_PROBING
    boostrap过程中节点间相互探测,发现数据差距
  • STATE_SYNCHRONIZING
    当数据差距较大无法通过后续机制补齐时,进行全同步
  • STATE_ELECTING
    Monitor在进行选主
  • STATE_LEADER
    当前Monitor成为leader
  • STATE_PEON
    非leader节点

一致性读写阶段状态转换

analyse-paxos-ceph-monitor-status-2.png

  • STATE_RECOVERING
    对应上述RECOVERING过程
  • STATE_ACTIVE
    leader可以读写或peon拥有lease
  • STATE_UPDATING
    向quroum发送begin,等待accept
  • STATE_WRITING
    收到accept
  • STATE_REFERSH
    本地提交并向quorum发送commit

参考&鸣谢

Docker历史

版本

Docker 1.2

Docker架构

Docker对使用者来说是一个C/S模式的架构,S端采用松耦合架构,各模块有机组合并支撑Docker运行。

docker-frame-main

用户是使用Docker Client与Docker Daemon建立通信,并发送请求给后者。而Docker Daemon作为Docker架构中的主体部分,首先提供Server的功能使其可以接受Docker Client的请求;而后Engine执行Docker内部的一系列工作,每一项工作都是以一个Job的形式的存在。
Job的运行过程中,当需要容器镜像时,则从Docker Registry中下载镜像,并通过镜像管理驱动graphdriver将下载镜像以Graph的形式存储;当需要为Docker创建网络环境时,通过网络管理驱动networkdriver创建并配置Docker容器网络环境;当需要限制Docker容器运行资源或执行用户指令等操作时,则通过execdriver来完成。
libcontainer是一项独立的容器管理包,networkdriver以及execdriver都是通过libcontainer来实现具体对容器进行的操作。
当执行完运行容器的命令后,一个实际的Docker容器就处于运行状态,该容器拥有独立的文件系统,独立并且安全的运行环境等。

功能模块

Docker Client

Docker Client可以通过以下三种方式和Docker Daemon建立通信:tcp://host:port,unix://path_to_socket和fd://socketfd。Docker Client可以通过设置命令行flag参数的形式设置安全传输层协议(TLS)的有关参数,保证传输的安全性。
Docker Client发送容器管理请求后,由Docker Daemon接受并处理请求,当Docker Client接收到返回的请求相应并简单处理后,Docker Client一次完整的生命周期就结束了。当需要继续发送容器管理请求时,用户必须再次通过docker可执行文件创建Docker Client。

Docker Daemon

接受并处理Docker Client发送的请求。该守护进程在后台启动了一个Server,Server负责接受Docker Client发送的请求;接受请求后,Server通过路由与分发调度,找到相应的Handler来执行请求。

docker-frame-daemon-main
Docker Daemon的大致可以分为三部分:Docker Server、Engine和Job。

  • Docker Server
    专门服务于Docker Client的server。接受并调度分发Docker Client发送的请求。
    docker-frame-daemon-server
    通过包gorilla/mux,创建了一个mux.Router,提供请求的路由功能。在Golang中,gorilla/mux是一个强大的URL路由器以及调度分发器。该mux.Router中添加了众多的路由项,每一个路由项由HTTP请求方法(PUT、POST、GET或DELETE)、URL、Handler三部分组成。

  • Docker Engine
    Engine是Docker架构中的运行引擎,同时也Docker运行的核心模块。

  • Docker Job
    一个Job可以认为是Docker架构中Engine内部最基本的工作执行单元。Docker可以做的每一项工作,都可以抽象为一个job。

Docker Registry

是一个存储容器镜像的仓库。而容器镜像是在容器被创建时,被加载用来初始化容器的文件架构与目录。
在Docker的运行过程中,Docker Daemon会与Docker Registry通信,并实现搜索镜像、下载镜像、上传镜像三个功能,这三个功能对应的job名称分别为”search”,”pull” 与 “push”。

Graph

已下载容器镜像的保管者,以及已下载容器镜像之间关系的记录者。一方面,Graph存储着本地具有版本信息的文件系统镜像,另一方面也通过GraphDB记录着所有文件系统镜像彼此之间的关系。

docker-frame-graph-main

  • GraphDB
    一个构建在SQLite之上的小型图数据库,实现了节点的命名以及节点之间关联关系的记录。它仅仅实现了大多数图数据库所拥有的一个小的子集,但是提供了简单的接口表示节点之间的关系。
  • Repository
    关于每一个的容器镜像,具体存储的信息有:该容器镜像的元数据,容器镜像的大小信息,以及该容器镜像所代表的具体rootfs。

Driver

通过Driver驱动,Docker可以实现对Docker容器执行环境的定制。由于Docker运行的生命周期中,并非用户所有的操作都是针对Docker容器的管理,另外还有关于Docker运行信息的获取,Graph的存储与记录等。因此,为了将Docker容器的管理从Docker Daemon内部业务逻辑中区分开来,设计了Driver层驱动来接管所有这部分请求。
在Docker Driver的实现中,可以分为以下三类驱动:graphdriver、networkdriver和execdriver。

graphdriver

graphdriver主要用于完成容器镜像的管理,包括存储与获取。即当用户需要下载指定的容器镜像时,graphdriver将容器镜像存储在本地的指定目录;同时当用户需要使用指定的容器镜像来创建容器的rootfs时,graphdriver从本地镜像存储目录中获取指定的容器镜像。

docker-frame-graphdriver-main

在graphdriver的初始化过程之前,有4种文件系统或类文件系统在其内部注册,它们分别是aufs、btrfs、vfs和devmapper。而Docker在初始化之时,通过获取系统环境变量”DOCKER_DRIVER”来提取所使用driver的指定类型。而之后所有的graph操作,都使用该driver来执行。

networkdriver

networkdriver的用途是完成Docker容器网络环境的配置,其中包括Docker启动时为Docker环境创建网桥;Docker容器创建时为其创建专属虚拟网卡设备;以及为Docker容器分配IP、端口并与宿主机做端口映射,设置容器防火墙策略等。

docker-frame-networkdriver-main

execdriver

execdriver作为Docker容器的执行驱动,负责创建容器运行命名空间,负责容器资源使用的统计与限制,负责容器内部进程的真正运行等。在execdriver的实现过程中,原先可以使用LXC驱动调用LXC的接口,来操纵容器的配置以及生命周期,而现在execdriver默认使用native驱动,不依赖于LXC。具体体现在Daemon启动过程中加载的ExecDriverflag参数,该参数在配置文件已经被设为”native”。

docker-frame-execdriver-main

libcontainer

bcontainer是Docker架构中一个使用Go语言设计实现的库,设计初衷是希望该库可以不依靠任何依赖,直接访问内核中与容器相关的API。正是由于libcontainer的存在,Docker可以直接调用libcontainer,而最终操纵容器的namespace、cgroups、apparmor、网络设备以及防火墙规则等。这一系列操作的完成都不需要依赖LXC或者其他包。

docker-frame-libcontainer-main

Docker container

Todo…

流程

docker pull

docker-flow-pull

docker run

docker-flow-run

参考&鸣谢

介绍

Kubernetes是为生产环境而设计的容器调度管理系统,对于负载均衡、服务发现、高可用、滚动升级、自动伸缩等容器云平台的功能要求有原生支持。由于Kubernetes在K和s间有8个字母,因此常简称K8s。事实上,随着对K8s系统架构与设计理念的了解深入,我们会发现K8s系统正是处处为运行云原生应用而设计考虑;同时,随着对K8s系统使用的加深和加广,也会有越来越多有关云原生应用的设计模式产生出来,使得基于K8s系统设计和开发生产级的复杂云原生应用变得像启动一个单机版容器服务那样简单易用。

系统架构

一个K8s集群是由etcd(分布式存储)、Minion/node(服务节点)、Master(控制节点)构成的。集群状态都保存在etcd中,Master节点上则运行集群的管理控制模块,多个Master节点以Active/Standby方式运行。Minion节点是真正运行应用容器的主机节点,在每个Minion节点上都会运行一个Kubelet代理,控制该节点上的容器、镜像和存储卷等。

k8s_frame

Master中模块

API Server

Kubernetes系统的入口,其封装了核心对象的增删改查操作,以RESTful API接口方式提供给外部客户和内部组件调用。维护的REST对象持久化到Etcd中存储。

Scheduler

为新建立的Pod进行节点选择(即分配机器),负责集群的资源调度。组件抽离,可以方便替换成其他调度器。

Controller

  • Replication Controller(RC)
    保证定义的副本数量与实际运行Pod数量一致。
  • Node Controller
    管理维护Minion/node,定期检查Node的健康状态,标识出(失效|未失效)的节点。
  • Namespace Controller
    管理维护Namespace,定期清理无效的Namespace。
  • Service Controller
    Todo…
  • EndPoints Controller
    Todo…
  • Service Account Controller
    Todo…
  • Persistent Volume Controller
    Todo…
  • Daemon Set Controller
    管理维护Daemon Set,负责创建Daemon Pod,保证指定的Node上正常的运行Daemon Pod。
  • Deployment Controller
    Todo…
  • Job Controller
    管理维护Job,为Jod创建一次性任务Pod,保证完成Job指定完成的任务数目
  • Pod Autoscaler Controller
    实现Pod的自动伸缩,定时获取监控数据,进行策略匹配,当满足条件时执行Pod的伸缩动作。

Minion中模块

Kubelet

负责管控容器,Kubelet会从Kubernetes API Server接收Pod的创建请求,启动和停止容器,监控容器运行状态并汇报给Kubernetes API Server。

Kube proxy

Kube-proxy是K8s集群内部的负载均衡器。它是一个分布式代理服务器,在K8s的每个节点上都有一个;这一设计体现了它的伸缩性优势,需要访问服务的节点越多,提供负载均衡能力的Kube-proxy就越多,高可用节点也随之增多。

Pod

Pod是在K8s集群中运行部署应用或服务的最小单元,它是可以支持多容器的。Pod的设计理念是支持多个容器在一个Pod中共享网络地址和文件系统,可以通过进程间通信和文件共享这种简单高效的方式组合完成服务。Pod对多容器的支持是K8最基础的设计理念。Pod是K8s集群中所有业务类型的基础,目前K8s中的业务主要可以分为长期伺服型(long-running)、批处理型(batch)、节点后台支撑型(node-daemon)和有状态应用型(stateful application),分别对应控制器为Deployment Controller、Job Controller、Daemon Set Controller、Pet Set Controller

参考&鸣谢

介绍

docker不是一项新技术,docker是老旧技术的组合,为了更方便使用容器技术,docker提供了简单方便的UI。docker隔离资源主要用到了两种技术namespace、cgroup。

Namespace

namespace是linux内核提供的隔离技术,它包括六种资源隔离,UTS(主机名与域名)、IPC(信号量、消息队列和共享内存)、PID(进程编号)、NET(网络设备、网络栈、端口…)、Mount(文件系统、挂载点)、User(用户和用户组)。

Type Sys Params ker ver
UTS CLONE_NEWUTS 2.6.19
IPC CLONE_NEWIPC 2.6.19
PID CLONE_NEWPID 2.6.24
NET CLONE_NEWNET 2.6.29
Mount CLONE_NEWNS 2.4.19
User CLONE_NEWUSER 3.8

操作系统调用接口:

  • clone()
    创建一个独立的进程独立的namespace
  • setns()
    使用已有的一个namespace
  • unshare()
    不启动新进程,在原进程上进行namespace隔离

docker run中提供了使用namespace的接口:

1
2
3
4
5
# docker run --help | grep -i namespace
--ipc string IPC namespace to use
--pid string PID namespace to use
--userns string User namespace to use
--uts string UTS namespace to use

UTS

提供了主机名和域名的隔离,这样每个容器就可以拥有了独立的主机名和域名,在网络上可以被视作一个独立的节点而非宿主机上的一个进程。
docker在run或create时,使用-h--hostname指定hostname

IPC

IPC是Unix/linux下进程间通讯的一种方式,包括信号量、消息队列、共享内存。容器内部进程间通信对宿主机来说,实际上是具有相同PID namespace中的进程间通信,因此需要一个唯一的标识符来进行区别。申请IPC资源就申请了这样一个全局唯一的32位ID,所以IPC namespace中实际上包含了系统IPC标识符以及实现POSIX消息队列的文件系统。在同一个IPC namespace下的进程彼此可见,而与其他的IPC namespace下的进程则互相不可见。

在宿主机上创建IPC(以消息队列为例):

1
2
# ipcmk -Q
消息队列 id:32768

在宿主机上查询IPC:

1
2
3
4
5
# ipcs

--------- 消息队列 -----------
键 msqid 拥有者 权限 已用字节数 消息
0xabba8164 32768 root 644 0 0

在容器中查询IPC:

1
2
3
4
5
6
7
8
9
10
11
# docker exec -it net_5 ipcs

------ Message Queues --------
key msqid owner perms used-bytes messages

------ Shared Memory Segments --------
key shmid owner perms bytes nattch status

------ Semaphore Arrays --------
key semid owner perms nsems

PID

它对进程PID重新标号,即两个不同namespace下的进程可以有同一个PID。每个PID namespace都有自己的计数程序。内核为所有的PID namespace维护了一个树状结构,最顶层的是系统初始时创建的,我们称之为root namespace。他创建的新PID namespace就称之为child namespace(树的子节点),而原先的PID namespace就是新创建的PID namespace的parent namespace(树的父节点)。通过这种方式,不同的PID namespaces会形成一个等级体系。所属的父节点可以看到子节点中的进程,并可以通过信号等方式对子节点中的进程产生影响。反过来,子节点不能看到父节点PID namespace中的任何内容。
* 每个PID namespace中的第一个进程“PID 1“,都会像传统Linux中的init进程一样拥有特权,起特殊作用。
* 一个namespace中的进程,不可能通过kill或ptrace影响父节点或者兄弟节点中的进程,因为其他节点的PID在这个namespace中没有任何意义。
* 如果你在新的PID namespace中重新挂载/proc文件系统,会发现其下只显示同属一个PID namespace中的其他进程。(挂载/proc 文件系统尤为重要)
* 在root namespace中可以看到所有的进程,并且递归包含所有子节点中的进程。

docker-resource-separation-pid

以容器的ceph-mon节点为例:

1
2
3
4
5
6
7
8
9
10
11
# 宿主机上查看 ceph-mon 进程
# ps -ef| grep 9184
root 9184 3122 0 1月20 ? 00:00:04 docker-containerd-shim be0583fade06df3f6510dd629bde4a636e68c755aa8b0733db6702493b1d0c38 /var/run/docker/libcontainerd/be0583fade06df3f6510dd629bde4a636e68c755aa8b0733db6702493b1d0c38 docker-runc
64045 9200 9184 0 1月20 ? 00:01:03 /usr/bin/ceph-mon --cluster ceph -d -i rhel82 --public-addr 192.168.1.82:6789 --setuser ceph --setgroup ceph
root 21893 9855 0 16:15 pts/5 00:00:00 grep --color=auto 9184

# 容器中查看 ceph-mon 进程
# docker exec -it ceph-mon ps -ef
UID PID PPID C STIME TTY TIME CMD
ceph 1 0 0 Jan20 ? 00:01:03 /usr/bin/ceph-mon --cluster ceph
root 26 0 0 08:15 ? 00:00:00 ps -ef

NET

Pv4和IPv6协议栈、IP路由表、防火墙、/proc/net目录、/sys/class/net目录、端口(socket)等等。一个物理的网络设备最多存在在一个network namespace中,你可以通过创建veth pair(虚拟网络设备对:有两端,类似管道,如果数据从一端传入另一端也能接收到,反之亦然)在不同的network namespace间创建通道,以此达到通信的目的。

一般情况下,物理网络设备都分配在最初的root namespace(表示系统默认的namespace,在PID namespace中已经提及)中。但是如果你有多块物理网卡,也可以把其中一块或多块分配给新创建的network namespace。需要注意的是,当新创建的network namespace被释放时(所有内部的进程都终止并且namespace文件没有被挂载或打开),在这个namespace中的物理网卡会返回到root namespace而非创建该进程的父进程所在的network namespace。

当我们说到network namespace时,其实我们指的未必是真正的网络隔离,而是把网络独立出来,给外部用户一种透明的感觉,仿佛跟另外一个网络实体在进行通信。为了达到这个目的,容器的经典做法就是创建一个veth pair,一端放置在新的namespace中,通常命名为eth0,一端放在原先的namespace中连接物理网络设备,再通过网桥把别的设备连接进来或者进行路由转发,以此网络实现通信的目的。

docker-resource-separation-network

可通过ip netnsbrctl管理Network Namespace,docker创建的netns路径为/proc/{进程ID}/ns/netip netns访问的默认路径为/var/run/netns/
若需要访问其Network Namespace内部,先创建软连接链至ip netns访问路径,然后使用ip netns exec访问该网络内部。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 在ip netns访问路径下创建network namespace的软链接
# ln -s /proc/7013/ns/net /var/run/netns/net_5

# 使用 ip netns exec 访问指定network namespace的网络
# ip netns exec net_5 ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
15: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.3/16 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::42:acff:fe11:3/64 scope link
valid_lft forever preferred_lft forever

Mount

隔离后,不同mount namespace中的文件结构发生变化也互不影响。你可以通过/proc/[pid]/mounts查看到所有挂载在当前namespace中的文件系统,还可以通过/proc/[pid]/mountstats看到mount namespace中文件设备的统计信息,包括挂载文件的名字、文件系统类型、挂载位置等等。(此处用到了mount propagation 技术)

docker-resource-separation-mount.png
上图mount挂载方式有待确认。

USER

主要隔离了安全相关的标识符(identifiers)和属性(attributes),包括用户ID、用户组ID、root目录、key(指密钥)以及特殊权限。说得通俗一点,一个普通用户的进程通过clone()创建的新进程在新user namespace中可以拥有不同的用户和用户组。这意味着一个进程在容器外属于一个没有特权的普通用户,但是他创建的容器进程却属于拥有所有权限的超级用户,这个技术为容器提供了极大的自由。

docker通过/proc/{进程ID}/uid_map/proc/{进程ID}/gid_map把容器中的uid、gid和真实系统的uid、gid给映射在一起,格式为:ID-inside-ns ID-outside-ns length

  • ID-inside-ns 表示在容器内显示的uid或gid
  • ID-outside-ns 表示在容器外映射的真实的uid或gid
  • length 表示映射范围,一般为1,表示一一对应(把ID-outside-ns ~(ID-outside-ns+length) 映射到 ID-inside-ns ~(ID-inside-ns+length)上)

cgroups

Control Groups(cgroups),是Linux 内核提供的一种可以限制、记录、隔离进程组所使用的物理资源(如 cpu、memory、磁盘IO等等) 的机制,被LXC、docker等很多项目用于实现进程资源控制。

cgroups子系统:

  • cpu
    使用调度程序提供对 CPU 的 cgroup 任务访问
  • cpuset
    为cgroup中的任务分配独立CPU(在多核系统)和内存节点
  • devices
    可允许或者拒绝 cgroup 中的任务访问设备
  • blkio
    为块设备设定输入/输出限制,比如物理设备(磁盘,固态硬盘,USB 等等)
  • freezer
    挂起或者恢复 cgroup 中的任务
  • memory
    设定 cgroup 中任务使用的内存限制,并自动生成由那些任务使用的内存资源报告
  • net_cls
    使用等级识别符(classid)标记网络数据包,可允许 Linux 流量控制程序(tc)识别从具体 cgroup 中生成的数据包

CPU

cpu-shares

docker create/run容器时可以通过--cpu-shares参数来指定容器所使用的CPU加权值。默认情况下,每个docker容器的cpu-shares值都是1024。单独一个容器的cpu-shares是没有意义的,只在多个容器分配的资源紧缺时,也就是说在需要对容器使用的资源进行限制时,才会生效。配之后,可通过./cgroup/cpu/docker/<容器ID>/cpu.shares查看。

cpu-period & cpu-quota

  • cpu-period 用来指定容器对CPU的使用要在多长时间内做一次重新分配
  • cpu-quota 用来指定在这个周期内,最多可以有多少时间用来跑这个容器
    docker create/run时使用,参数为--cpu-period--cpu-quota单位为微秒,cpu-period的默认值为0.1秒(100000 微秒),cpu-quota的默认值为-1(表示不控制)。配置后,可通过./cgroup/cpu/docker/<容器ID>/cpu.cfs_period_us./cgroup/cpu/docker/<容器ID>/cpu.cfs_quota_us查看。

cpuset

docker可使用--cpuset-cpus--cpuset-mems参数控制容器运行限定使用哪些cpu和内存节点。配之后,可通过./cgroup/cpuset/docker/<容器ID>/cpuset.cpus./cgroup/cpuset/docker/<容器ID>/cpuset.mems查看。

*** 注:对于具有NUMA的服务器很重要 ***

Memory

docker create/run时,可以对内存资源加以限制。

  • kernel-memory
    使用参数--kernel-memory,限制内核内存,该内存不会被交换到swap上。
  • memory
    使用参数--memory,设置容器使用的最大内存上限。默认单位为byte,可以使用K、G、M等带单位的字符串。
  • memory-reservation
    使用参数--memory-reservation,启用弹性的内存共享,当宿主机资源充足时,允许容器尽量多地使用内存,当检测到内存竞争或者低内存时,强制将容器的内存降低到memory-reservation所指定的内存大小。不设置此选项时,有可能出现某些容器长时间占用大量内存,导致性能上的损失。
  • memory-swap
    使用参数--memory-swap,设置总内存大小,相当于内存和swap大小的总和,设置-1时,表示swap分区大小是无限的。默认单位为byte,可以使用K、G、M等带单位的字符串。
  • memory-swappiness
    使用参数--memory-swappiness,设置控制进程将物理内存交换到swap分区的倾向,系数越小,就越倾向于使用物理内存。值范围为0-100。当值为100时,表示尽量使用swap分区;当值为0时,表示禁用容器 swap 功能(这点不同于宿主机,宿主机 swappiness 设置为 0 也不保证 swap 不会被使用)

Block Device

I/O

  • device-read-bps
    限制此设备上的读速度(bytes per second),单位可以是kb、mb或者gb
  • device-read-iops
    通过每秒读IO次数来限制指定设备的读速度
  • device-write-bps
    限制此设备上的写速度(bytes per second),单位可以是kb、mb或者gb
  • device-write-iops
    通过每秒写IO次数来限制指定设备的写速度
  • blkio-weight
    容器默认磁盘IO的加权值,有效值范围为10-100。要使-–blkio-weight生效,需要保证IO的调度算法为CFQ
    echo "cfq" > /sys/block/<设备名>/queue/scheduler
  • blkio-weight-device
    针对特定设备的IO加权控制。其格式为DEVICE_NAME:WEIGHT

Volume

使用参数--storage-opt,传入dm.basesize=<容量大小>可以设置rootfs大小。如果不设置dm.basesize,默认值为10G,若要使dm.basesize生效,storage driver 必须是 device mapper。
设置rootfs大小后,需要重启docker服务,并且--storage-opts参数需要在启动docker服务时使用。
以RHEL7.2为例,需要修改/etc/systemd/system/multi-user.target.wants/docker.service/usr/bin/dockerd的参数。

参考&鸣谢