Routing Mesh 的理解与应用
前言
在之前的学习中,我们学会了部署更新应用,那么swarm的应用怎么对外发布? 外部又如何访问呢?为什么swarm node 任意节点都可以访问service?
本章节就来详细说明
概述
Routing Mesh是docker swarm提供的确保service在多节点网络上可用的集群网络机制。其分为两类:
- Internal Routing Mesh
- Ingress Routing Mesh
Internal 容器与容器之间通过overlay网络进行访问。通过 service name 进行通信 (eg: 容器内 通过 ping service name ), 但是 service name 所对应的 ip 不是 真实 ip 而是 VIP (Virtual ip)
Ingress 如果service 有端口绑定,则可以通过swarm 任意节点的相应端口访问service
Internal
1、创建overlay网络
首先在Swarm的集群中创建一个overlay的network
$ docker network create -d overlay demo
查看
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
...
748vwsjqoqz7 demo overlay swarm
...
2、启动service
创建一个 whoami 的service ,使用demo网络
$ docker service create --name whoami -p 8000:8000 --network demo -d jwilder/whoami
当我们启动这个服务后,可以尝试去访问它:
$ curl http://127.0.0.1:8000
I'm 6fafaed61f64
我们再开启一个服务
$ docker service create --name test -d --network demo busybox:1.28.3 /bin/sh -c "while true;do sleep 3600;done"
busybox 1.29开始 nslookup 解析有改动,使用1.28版本
或者使用新版 busybox,命令改用 -type=a
此时我们2个服务是在不同节点上运行的
$ docker service ps whoami
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
yq3zung8aoum whoami.1 jwilder/whoami:latest node3 Running Running 8 minutes ago
$ docker service ps test
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
c3b8p5xqfu9k test.1 busybox:latest node1 Running Running 43 seconds ago
3、不同节点服务之间通信
- 进入test 容器访问 whoami
[root@node1] $ docker exec -it 67b6 /bin/sh
/ # ping whoami
PING whoami (10.0.4.2): 56 data bytes
64 bytes from 10.0.4.2: seq=0 ttl=64 time=0.206 ms
64 bytes from 10.0.4.2: seq=1 ttl=64 time=0.102 ms
可以看到,网络访问是通的,并且返回的地址是10.0.4.2,这是不是就说明了whoami容器的ip是这个呢?我们查看一下。
- 查看whoami 容器ip
[root@node3 ~]$ docker exec -it 6faf ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:0A:00:04:03
inet addr:10.0.4.3 Bcast:10.0.4.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
发现ip是10.0.4.3,为什么呢?
我们知道scale参数可以将容器水平扩展,不同的容器都有一个ip,为了保证这些服务的ip不变,这里使用了虚拟的ip,这个ip地址是不会改变的
创建多个whoami服务的容器
$ docker service scale whoami=3
再次测试
docker exec -it 67b6 ping whoami
/ # ping whoami
PING whoami (10.0.4.2): 56 data bytes
64 bytes from 10.0.4.2: seq=0 ttl=64 time=0.094 ms
64 bytes from 10.0.4.2: seq=1 ttl=64 time=0.096 ms
可以看到虽然whoami服务已经启动了3个容器,但是这个虚拟的ip还是没有变化,那么怎么查看这个虚拟ip对应的真实容器是哪个呢?可以通过以下命令查看:
$ docker exec -it 1a24 nslookup whoami
Server: 127.0.0.11
Address 1: 127.0.0.11
Name: whoami
Address 1: 10.0.4.2 bogon
$ docker exec -it 1a24 nslookup tasks.whoami
Server: 127.0.0.11
Address 1: 127.0.0.11
Name: tasks.whoami
Address 1: 10.0.4.3 whoami.1.yq3zung8aoumd72b4l06zwd54.demo
Address 2: 10.0.4.8 whoami.2.px32xnis2hnx8huukoegltrtj.demo
Address 3: 10.0.4.9 whoami.3.uuu0dvphuqzl7kav9yk6gm86b.demo
也可以这样去验证,只需要在本地上去访问这个服务,看它返回的主机信息:
[root@node1 ~]$ curl http://127.0.0.1:8000
I'm d32684a5c6b6
[root@node1 ~]$ curl http://127.0.0.1:8000
I'm 5080f4b56526
[root@node1 ~]$ curl http://127.0.0.1:8000
I'm 6fafaed61f64
Ingress
概述
当在 任何 一个 swarm 节点去访问 端口服务的时候 会通过 本节点 的 IPVS ( ip virtual service ) 到 真正的 swarm 节点上。
功能
- 外部访问的均衡负载
- 服务端口被暴露到各个swarm节点
- 内部通过 IPVS 进行均衡负载
原理分析
- 转发规则
用上一节使用whoami service 来查看
我们可以看看worker节点上没有存在的服务它是怎么访问的,如果访问它内部是怎么转发的。
$ iptables -L -n -t nat
...
Chain DOCKER-INGRESS (2 references)
target prot opt source destination
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8000 to:172.18.0.2:8000
RETURN all -- 0.0.0.0/0 0.0.0.0/0
...
可以看到iptables 会把请求转发到172.18.0.2:8000这个地址上去,那么我们看看本机的ip:
$ ip a
...
4: docker_gwbridge: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:64:49:83:64 brd ff:ff:ff:ff:ff:ff
inet 172.18.0.1/16 brd 172.18.255.255 scope global docker_gwbridge
valid_lft forever preferred_lft forever
inet6 fe80::42:64ff:fe49:8364/64 scope link
valid_lft forever preferred_lft forever
...
没有找到172.18.0.2,但是找到了docker_gwbridge,可以看到两个ip处于同一网段,那么172.18.0.2应该也连接上docker_gwbridge。查看下桥接配置
$ brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242abd9beea no
docker_gwbridge 8000.024264498364 no veth103b8e6
vetha3f1870
vethb8ad1cd
看到它有多个interface,但是哪一个才是 172.18.0.2使用的呢?查看下 docker_gwbridge
$ docker network inspect docker_gwbridge
...
"Containers": {
"890cd1e1521eaadd16ff81441c9682abcf8fa72750964084d2c82cb20d5059b4": {
"Name": "gateway_e5100e10eae9",
"EndpointID": "677710cde7562a42806011867919a2c413d0656158f5ca9abd8b48b0ef50bbd2",
"MacAddress": "02:42:ac:12:00:04",
"IPv4Address": "172.18.0.4/16",
"IPv6Address": ""
},
"d32684a5c6b6da417980f46be89e3bd97b99c399271bb56c3c28a61cbcb45efe": {
"Name": "gateway_efca9cc9bb58",
"EndpointID": "84cea76a6f86c7cbbbf23d7fc9ecca2d9fce10160a66bad6a7a58bc9ddaf647e",
"MacAddress": "02:42:ac:12:00:03",
"IPv4Address": "172.18.0.3/16",
"IPv6Address": ""
},
"ingress-sbox": {
"Name": "gateway_ingress-sbox",
"EndpointID": "8c140d8c03087243a6b899018c7577f850bd8c3ddd5d1fa96a07b96def786b07",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address":
...
可以发现gateway_ingress-sbox 的容器ip 就是我们需要找的。ingress-sbox就是 网络命名空间(network namespace)。
- 进入ingress-sbox
我们可以先查看docker中所有的Network Namespace:
$ ls /var/run/docker/netns
1-748vwsjqoq 1-dmmsdyma59 e5100e10eae9 efca9cc9bb58 ingress_sbox lb_748vwsjqo
然后通过下面的命令进入ingress_sbox:
nsenter --net=/var/run/docker/netns/ingress_sbox
查看ip:
$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
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
28: eth0@if29: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether 02:42:0a:00:00:07 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.0.0.7/24 brd 10.0.0.255 scope global eth0
valid_lft forever preferred_lft forever
inet 10.0.0.20/32 brd 10.0.0.20 scope global eth0
valid_lft forever preferred_lft forever
30: eth1@if31: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet 172.18.0.2/16 brd 172.18.255.255 scope global eth1
valid_lft forever preferred_lft forever
这个namespace中的ip就是我们要找的
当请求被转发到这个namespace后它又是怎么做的呢?我们先看看它的转发规则:
$ iptables -nL -t mangle
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
MARK tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8000 MARK set 0x117
可以看到,当转发8000端口时MARK set 0x117,接下来执行如下的命令:
$ ipvsadm -l
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
FWM 279 rr
-> 10.0.0.22:0 Masq 1 0 0
-> 10.0.0.23:0 Masq 1 0 0
-> 10.0.0.24:0 Masq 1 0 0
随便找个whoami容器,查看ip
$ docker exec -it d326 ifconfig
...
eth1 Link encap:Ethernet HWaddr 02:42:0A:00:00:16
inet addr:10.0.0.22 Bcast:10.0.0.255 Mask:255.255.255.0
...
可以看出 与 ipvsadm 对应上了
总结
原理如图
当我们访问任一节点的8000端口时,只要我们这个节点处于Swarm集群中,不管服务是否部署到这个节点都能访问,只要端口相同即可。我们本地的请求会被转发到Ingress_sbox这个Network Namespace中,在这个名称空间中再通过lvs转发到具体服务容器的ip和8000端口中去。
配置一个外部负载均衡
真正的生产环境一般是把代理服务器服务器暴露出去。这里使用haproxy来举例
这个是官方示例图。我们这里 还是用 whoami作为后端服务
可以在任一swarm 节点,或者 单独的机器安装haproxy,并配置
defaults
mode http
log global
option httplog
option dontlognull
option http-server-close
option forwardfor except 127.0.0.0/8
option redispatch
retries 3
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 1m
timeout server 1m
timeout http-keep-alive 10s
timeout check 10s
maxconn 3000
frontend http_front
bind *:80
stats uri /haproxy?stats
default_backend http_back
backend http_back
balance roundrobin
server node1 172.16.35.13:8000 check
server node2 172.16.35.10:8000 check
server node3 172.16.35.11:8000 check
当访问HAProxy负载均衡的80端口时,它会把你的请求转发到swarm节点。然后swarm节点的routing mesh把请求路由到一个可用的服务上