有一个问题,同一宿主机上的容器之间如何进行通信。我觉得最原始的方法可以在run容器的时候指定一个端口映射,其他容器访问本机这个被映射的端口即可实现。但这个方法似乎有些不太友好,当宿主机上存在大量的容器我们要配置大量的端口映射,且这样会导致所有的容器之间都可以互相通信,无法做到网络隔离。如何做到不同集群之间网络隔离且同集群容器之间可以互相通信呢?答案是自定义网络即docker network
docker的桥接网络模式
docker具有五种网络模式,具体可以参考:初探Docker的网络模式
当我们run一个镜像的时候可以指定-- net host
指定网络,如果不指定使用的是默认的bridge网络。该网络即接在是ip a
下docker0虚拟网卡上的
# ip a
1. ...
2. ...
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
link/ether 02:42:91:b9:20:9f brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 scope global docker0
valid_lft forever preferred_lft forever
# docker network inspect bridge
[
{
"Name": "bridge",
"Id": "937b66d796bdb5d16b4f7fb80cae0ff55e40a021db763d64b4cb4bfa3de671ef",
"Created": "2021-04-18T13:10:46.978091586+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"5aecaa04db4f7effe826b60e4a6a9c4f7e695c8de32f5fe73566f57209b32968": {
"Name": "app1",
"EndpointID": "f0633f5548b4290f7b057d6b08aea3d81e70084fbe4a06b151a2797fd27f33cb",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
这里可以看出我的bridge的网段是172.17.0.0/16,网关是172.17.0.1。且这个网络下挂了一个app1的容器,该容器ip是172.17.0.2。
网段是16的话该段下可以分配个ip, 如果网段是24的话可以分配个ip
创建网络
# docker network create --help # 参数
Usage: docker network create [OPTIONS] NETWORK
Create a network
Options:
--attachable Enable manual container attachment
--aux-address map Auxiliary IPv4 or IPv6 addresses used by Network driver (default map[])
--config-from string The network from which to copy the configuration
--config-only Create a configuration only network
-d, --driver string Driver to manage the Network (default "bridge")
--gateway strings IPv4 or IPv6 Gateway for the master subnet
--ingress Create swarm routing-mesh network
--internal Restrict external access to the network
--ip-range strings Allocate container ip from a sub-range
--ipam-driver string IP Address Management Driver (default "default")
--ipam-opt map Set IPAM driver specific options (default map[])
--ipv6 Enable IPv6 networking
--label list Set metadata on a network
-o, --opt map Set driver specific options (default map[])
--scope string Control the network's scope
--subnet strings Subnet in CIDR format that represents a network segment
# # 创建一个叫mynet1的网络,默认是bridge模式,该参数可省略
# docker network create --subnet 172.18.0.0./16 --gateway 172.18.0.1 --driver bridge mynet1
创建完后宿主机上ip a
会发现多了一个网卡
# ip a
......
4: br-a32d7755e777: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
link/ether 02:42:e3:50:de:6f brd ff:ff:ff:ff:ff:ff
inet 172.18.0.1/16 scope global br-a32d7755e777
valid_lft forever preferred_lft forever
容器互联
这里摘自docker中文社区的一段文字
在同一个网络中的容器,可以互联,并且,Docker 内置了 DNS,容器内的应用可以使用服务名、容器名、别名来进行服务发现,名称会经由内置的 DNS 进行解析,其结果是动态的;而不在同一网络中的容器,不可以互联。现在早就不用 --link 了,而且非常不建议使用。
这里说明了为什么不用link,原文链接
这里我使用了docker-compose官方文档(链接)上的例子(我做了修改)。两个镜像,创建了三个容器,其中app1在bridge网络即172.17下,app2和redis在mynet网络即172.18下,app1和app2都需要连接redis。
创建镜像与启动容器
app.py
import time
import redis
from flask import Flask, jsonify
app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)
def get_hit_count():
retries = 5
while True:
try:
return cache.incr('hits')
except redis.exceptions.ConnectionError as exc:
if retries == 0:
raise exc
retries -= 1
time.sleep(0.5)
@app.route('/')
def hello():
count = get_hit_count()
data = {'code': 200, 'message': 'Hello World! I have been seen {} times'.format(count)}
return jsonify(data)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Dockerfile
FROM python:3.7
WORKDIR /root
COPY app.py /root/
COPY requirements.txt /root/requirements.txt
RUN pip install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
EXPOSE 5000
CMD ["python", "app.py"]
run
# docker build -t app . # 构建镜像
# docker run -d -P --name app1 app # app1在bridge网络下
# docker run -d -P --net mynet1 --name app2 app # app2在mynet1网络下
# docker pull redis
# docker run -d -P --net mynet1 --name redis redis # redis,在mynet1网络下
#
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5aecaa04db4f app "python app.py" 2 hours ago Up 2 hours 0.0.0.0:49159->5000/tcp, :::49159->5000/tcp app1
62d8e19385c1 app "python app.py" 2 hours ago Up 2 hours 0.0.0.0:49157->5000/tcp, :::49157->5000/tcp app2
0e22a3a9d2ab de974760ddb2 "docker-entrypoint.s…" 2 hours ago Up 2 hours 6379/tcp redis
网络打通
app, ip, 端口对于关系如下
容器 | 宿主机端口 | 容器内端口 | ip |
---|---|---|---|
app1 | 49159 | 5000 | 172.17.0.2 |
app2 | 49157 | 5000 | 172.18.0.4 |
redis | 6379 | 172.18.0.3 |
注意,这里我没有开启redis的端口映射
此时app2和redis在同一网络下,访问app2正常。app1与redis不在同一网段,因此访问app1将会报错。
# curl 127.0.0.1:49157
{"code":200,"message":"Hello World! I have been seen 13 times"}
# # 宿主机和子网是通的
# curl 172.18.0.4:5000
{"code":200,"message":"Hello World! I have been seen 14 times"}
# # app1与redis不通, 查看app的日志可以看到连接不上redis
# docker logs app1
redis.exceptions.ConnectionError: Error -2 connecting to redis:6379. Name or service not known.
怎么使app1与redis通呢?我们可以让app1连接上mynet1网段,即现在app1有两个ip,一个在bridge,一个mynet1。
# docker network connet --help
Usage: docker network connect [OPTIONS] NETWORK CONTAINER
Connect a container to a network
Options:
--alias strings Add network-scoped alias for the container
--driver-opt strings driver options for the network
--ip string IPv4 address (e.g., 172.30.100.104)
--ip6 string IPv6 address (e.g., 2001:db8::33)
--link list Add link to another container
--link-local-ip strings Add a link-local address for the container
# docker network connect mynet1 app1
这个时候查看docker inspect app1
的Networks发现下面多了一个mynet1网络,并为其分配了一个172.18.0.2的ip
# curl 172.18.0.2:5000
{"code":200,"message":"Hello World! I have been seen 15 times"}
# curl 172.17.0.2:5000
{"code":200,"message":"Hello World! I have been seen 16 times"}
# curl 127.0.0.1:49159
{"code":200,"message":"Hello World! I have been seen 17 times"}
这个时候回发现app1与app2直接也是互相通的
# docker exec -it app1 ping app2
PING app2 (172.18.0.4) 56(84) bytes of data.
64 bytes from app2.mynet1 (172.18.0.4): icmp_seq=1 ttl=64 time=0.069 ms
64 bytes from app2.mynet1 (172.18.0.4): icmp_seq=2 ttl=64 time=0.077 ms
# docker exec -it app2 ping app1
PING app1 (172.18.0.2) 56(84) bytes of data.
64 bytes from app1.mynet1 (172.18.0.2): icmp_seq=1 ttl=64 time=0.050 ms
64 bytes from app1.mynet1 (172.18.0.2): icmp_seq=2 ttl=64 time=0.072 ms