web
中调用
api
,在
api
中调用
mysql
,接下来,我们就从网络开始,一探究竟。
还记得我们最开始通过docker network create -d overlay --attachable httpnet
创建的httpnet
网络吗?这一步究竟做了什么?如果不清楚,docker教给我们一个方法inspect
:
$ docker network inspect httpnet
[
...
{
"Name": "httpnet",
"IPAM": {
"Config": [
{
"Subnet": "10.0.1.0/24",
"Gateway": "10.0.1.1"
}
]
},
...
}
]
我们只截取了对网络httpnet
分析最重要的一部分,仔细看一下,我们会发现如下新的信息:
Subnet
子网,凡是注册到此网络上的服务或容器,docker都会从10.0.1.0/24
中自动分配一个或多个内部IP给它。Gateway
网关,httpnet
网络的网关是10.0.1.1
默认在同一个子网(内网),任何IP
都可以ping
通,任何PORT
都对外开放,这就是本质上容器间或服务间互相访问调用的方式。
我们已经知道,本质上容器间或服务间都是通过ip:port
的形式进行访问或调用,但是我们同时也清楚,容器的生命周期始于创建终于销毁,且容器的启停很频繁,那就意味着一个服务中容器的真实IP随时都可能改变。
因此,docker提供给我们的方式,通过命令的编写就能看出来,那就是名称,包括网络名称、stack名称、服务名称、容器名称等。那让我们看看,服务名称背后又是什么玄机?
$ docker service inspect http-v1
[
...
"Spec": {
"Name": "http-v1",
"Networks": [
{
"Target": "httpnet"
}
],
"Replicated": {
"Replicas": 2
}
}
"Endpoint": {
"Spec": {
"Mode": "vip",
"Ports": [
{
"Protocol": "tcp",
"TargetPort": 80,
"PublishedPort": 80,
"PublishMode": "ingress"
}
]
},
"VirtualIPs": [
{
"NetworkID": "m8hsd6mdesxey1m0sm6ipjxrt",
"Addr": "10.0.1.7/24"
}
]
...
}
]
我们只截取了对服务http-v1
分析最重要的一部分,仔细看一下,我们会发现如下新的信息:
Mode
机制,vip
代表对服务中所有容器的代理,不是直接访问,而是通过vip
地址。Addr
地址,vip
的地址为10.0.1.7
,它代表服务的地址,但不是真实的容器地址。我们可以任意进入一个容器里面看看:
$ docker exec -it http-v1.2.7xp9liu4tn21hzogtqhyswk5n /bin/sh
# traceroute http-v1
traceroute to http-v1 (10.0.1.7), 30 hops max, 46 byte packets
1 10.0.1.7 (10.0.1.7) 0.029 ms 0.672 ms 0.024 ms
# curl http-v1
{"version":"v1","hostname":"6e54dd2e8007","address":"10.0.1.9"}/app
# curl http-v1
{"version":"v1","hostname":"4d51a5226238","address":"10.0.1.8"}/app
# curl 10.0.1.7
{"version":"v1","hostname":"6e54dd2e8007","address":"10.0.1.9"}/app
# curl 10.0.1.7
{"version":"v1","hostname":"4d51a5226238","address":"10.0.1.8"}/app
不难看出,一个服务永远只对应一个VIP
,而VIP
通过轮询每次返回一个真实的容器IP
,且VIP
负责维护真实容器的所有IP
(创建、销毁)。
此外,如果一个服务对外暴露了端口,也可以通过外部ip:port
的方式进行调用。但是这种方式不仅舍近求远,还增加了IP地址转换的性能损耗,而且因为集群中每个节点的ip:port
都可以访问,那该选择一个固定的还是每次从所有的节点中随机?如果一个节点不可用又怎么办?
至此,我们终于弄明白了在【2.Swarm服务发现】文章中未深入介绍的服务发现和负载均衡原理,同时,如果在一个服务中想要调用另外一个服务,只需要在连接字符串中加入另外一个服务的名称和端口即可。