Docker网络操作
Docker网络概述
Docker网络原理
Docker使用Linux桥接,在宿主机虚拟一个Docker容器网桥(docker0),Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址,称为Container-IP,同时Docker网桥是每个容器的默认网关。因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能够通过容器的Container-IP直接通信。
Docker网桥是宿主机虚拟出来的,并不是真实存在的网络设备,外部网络是无法寻址到的,这也意味着外部网络无法通过直接Container-IP访问到容器。如果容器希望外部访问能够访问到,可以通过映射容器端口到宿主主机(端口映射),即docker run创建容器时候通过 -p 或 -P 参数来启用,访问容器的时候就通过[宿主机IP]:[容器端口]访问容器。
四类网络模式
Docker网络模式 | 配置 | 说明 |
---|---|---|
host模式 | –net=host | 容器和宿主机共享Network namespace。 |
container模式 | –net=container:NAME_or_ID | 容器和另外一个容器共享Network namespace。 kubernetes中的pod就是多个容器共享一个Network namespace。 |
none模式 | –net=none | 容器有独立的Network namespace,但并没有对其进行任何网络设置,如分配veth pair 和网桥连接,配置IP等。 |
bridge模式 | –net=bridge | (默认为该模式) |
host模式
如果启动容器的时候使用host模式,那么这个容器将不会获得一个独立的Network Namespace,而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。但是,容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的。
使用host模式的容器可以直接使用宿主机的IP地址与外界通信,容器内部的服务端口也可以使用宿主机的端口,不需要进行NAT,host最大的优势就是网络性能比较好,但是docker host上已经使用的端口就不能再用了,网络的隔离性不好。
Host模式如下图所示
container模式
这个模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过 lo 网卡设备通信。
Container模式示意图
none模式
使用none模式,Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。
这种网络模式下容器只有lo回环网络,没有其他网卡。none模式可以在容器创建时通过–network=none来指定。这种类型的网络没有办法联网,封闭的网络能很好的保证容器的安全性。
None模式示意图
bridge模式
当Docker进程启动时,会在主机上创建一个名为docker0的虚拟网桥,此主机上启动的Docker容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。
从docker0子网中分配一个IP给容器使用,并设置docker0的IP地址为容器的默认网关。在主机上创建一对虚拟网卡veth pair设备,Docker将veth pair设备的一端放在新创建的容器中,并命名为eth0(容器的网卡),另一端放在主机中,以vethxxx这样类似的名字命名,并将这个网络设备加入到docker0网桥中。可以通过brctl show命令查看。
bridge模式是docker的默认网络模式,不写–net参数,就是bridge模式。使用docker run -p时,docker实际是在iptables做了DNAT规则,实现端口转发功能。可以使用iptables -t nat -vnL查看。
bridge模式如下图所示
Docker网络基本操作
查看网络
通过
docker network list
命令可以查看docker的网络列表
docker network list
我们发现默认只有三个网络
创建网络
通过
docker network create xxx
创建一个默认的bridge模式的网络
# 添加一个docker-network的网络
docker network create docker-network
# 查看网络列表
docker network list
我们发现已经创建了一个docker-network的网络
启动时指定网络
通过
--network
指定网络
# 运行容器并指定网络
docker run -d -P --network docker-network training/webapp python app.py
#查看运行的docker容器
docker ps
查看网络详细信息
可以通过
docker network inspect xxx
查看网络详细信息
docker inspect docker-network
通过该网络可以查看网络的详细信息,以及使用该网络的容器列表以及容器的分配的IP地址
创建容器指定IP
创建容器使用docker-network网络并指定静态IP,但是注意指定静态IP需要在该网络的子网范围内
# 创建容器并指定静态IP
docker run -d -P --network docker-network --ip 172.18.1.10 training/webapp python app.py
出了一个错误,大概意思是需要网络设置子网才可以创建静态IP
重新配置网络
先删除原有网络,在重新配置网络
# 查看网络类列表
docker network ls
#删除网络
docker network rm 99332ace0dbe
#创建带有子网以及网关的网络
docker network create --subnet 172.18.1.0/24 --gateway 172.18.1.1 docker-network
# 查看网络类列表
docker network ls
创建容器
这个时候就可以创建容器并指定静态IP了
# 创建容器并指定静态IP
docker run -d -P --network docker-network --ip 172.18.1.10 training/webapp python app.py
docker ps
网络端口映射
Docker服务内部端口映射到宿主linux机的端口上,从而实现外部可以通过端口直接访问呢docker容器。
不映射端口到宿主机
启动Web服务
启动一个Web服务进行访问测试
# 启动一个web服务不进行端口映射
docker run -d training/webapp python app.py
# 查看运行的容器
docker ps
# 查看容器IP
docker inspect --format='{{.NetworkSettings.IPAddress}}' d56fc69cf648
# 尝试访问docker 内部的 5000端口
curl 172.17.0.2:5000
# 尝试访问宿主机的5000端口
curl 127.0.0.1:5000
我们发现服务可以通过docker内部的ip访问,无法通过127.0.0.1进行访问
这是因为docker默认使用bridge模式,docker内部的ip通过docker0暴漏到了宿主机。
映射端口到宿主机
启动Web服务
端口映射到宿主机,从而宿主机外部可以直接通过宿主机linux的ip和端口号直接访问docker 容器服务
# 启动一个web服务不进行端口映射
docker run -d -p 5000:5000 training/webapp python app.py
# 查看运行的容器
docker ps
# 查看容器IP
docker inspect --format='{{.NetworkSettings.IPAddress}}' d56fc69cf648
# 尝试访问docker 内部的 5000端口
curl 172.17.0.2:5000
# 尝试访问宿主机的5000端口
curl 127.0.0.1:5000
加上
-p
参数暴漏端口后宿主机就可能进行访问了
原理探究
实际上这是因为Docker在
iptables
上面做了转发规则,将宿主机的端口转发到对应docker宿主机的IP
iptables -t nat -vnL
其他机器访问
这个时候其他的服务器就可以进行通过宿主机访问到docker内部的容器了
Docker 容器互联
link连接网络
容器的连接(
linking
)系统是除了端口映射外,另一种跟容器中应用交互的方式。该系统会在源和接收容器之间创建一个隧道,接收容器可以看到源容器指定的信息。注意!docker官方已不推荐使用docker run –link来链接2个容器互相通信,随后的版本中会删除–link
docker的每个容器相当于有个内网地址。比如 mymysql
容器172.17.0.2
,mynginx
容器为172.17.0.3
,那么他们的通信机制是连到了 docker0
这个bridge
,大概如下图:
link的作用
docker run –link可以用来链接2个容器,使得源容器(被链接的容器)和接收容器(主动去链接的容器)之间可以互相通信,并且接收容器可以获取源容器的一些数据,如源容器的环境变量。
link格式
name和id是源容器的name和id,alias是源容器在link下的别名
--link <name or id>:alias
创建数据库容器
我们先创建数据库容器
docker run -d --name db training/postgres
创建web容器
然后然后创建一个新的 web 容器,使用
--link
参数可以使容器相互连接,并将它连接到 db 容器
docker run -d -P --name web --link db:db training/webapp
进入web容器验证
进入
web
容器验证是否连接成功。
进入db容器验证
进入
db
容器验证是否连接成功
#进入db容器
docker exec -it db /bin/bash
#连接db容器测试
root@387f0e921460:/opt/webapp# ping db
从上面的操作可以看出web
容器可以访问db
容器,但是反过来db
容器无法访问web
容器,web
容器能够ping通db
容器其实就是修改了web
容器的 host
文件和设置了环境变量而已
link下容器间的通信
按照上例的方法就可以成功的将db和web容器链接起来,那这2个容器间是怎么通信传送数据的呢?
源容器和接收容器之间传递数据是通过以下2种方式:
- 设置环境变量
- 更新/etc/hosts文件
设置环境变量
当使用–link时,docker会自动在接收容器内创建基于–link参数的环境变量
docker会在接收容器中设置名为
我们进入node容器,看下此环境变量:
#进入web容器
docker exec -it web /bin/bash
root@387f0e921460:/opt/webapp# env | grep -i db
可见,确实有名为
DB_NAME=/web/db
的环境变量存在docker还会在接收容器中创建关于源容器暴露的端口号的环境变量,这些环境变量有一个统一的前缀名称
环境变量的注意事项
注意,接收容器环境变量中存储的源容器的IP,不会自动更新,即,若源容器重启,则接收容器环境变量中存储的源容器的IP很可能就失效了,所以,docker官方建议使用/etc/hosts来解决上述的IP失效问题。
更新/etc/hosts文件
docker会将源容器的host更新到目标容器的/etc/hosts中:
我们再进入node容器,查看node容器中的/etc/hosts文件的内容:
#进入web容器
docker exec -it web /bin/bash
root@387f0e921460:/opt/webapp# cat /etc/hosts
其中172.17.0.3是web容器的ip,并使用web容器的容器id作为host name。另外,源容器的ip和hostname也写进来了,172.17.0.2是db容器的ip,db是容器在link下的名称,后面是db容器的容器id。
如果重启了源容器,接收容器的/etc/hosts会自动更新源容器的新ip
小结
在–link标签下,接收容器就是通过设置环境变量和更新/etc/hosts文件来获取源容器的信息,并与之建立通信和传递数据的。
在docker的后续版本中,会取消docker run中的–link选项,但了解其如何在2个容器之间建立通信的原理是非常有用的,因为这有助于理解如何用官方推荐的所有容器在同一个network下来通信的方法,以及用docker-compose来链接2个容器来通信的方法。
连接容器
运行容器
运行一个容器并连接到新建的 test-net 网络
docker run -itd --name ubuntu1 --network test-net ubuntu /bin/bash
打开新的终端,再运行一个容器并加入到 test-net 网络
docker run -itd --name ubuntu2 --network test-net ubuntu /bin/bash
查看容器
docker ps
安装ping
如果 ubuntu1、ubuntu2容器内中无 ping 命令,则在容器内执行以下命令安装 ping(即学即用:可以在一个容器里安装好,提交容器到镜像,在以新的镜像重新运行以上俩个容器)。
apt-get update
apt install iputils-ping
验证互联
下面通过 ping 来证明 ubuntu1容器和 ubuntu2容器建立了互联关系。
ubuntu1验证
在 ubuntu1容器输入以下命令
ping ubuntu1
ping ubuntu2
ubuntu2验证
在 ubuntu1容器输入以下命令
ping ubuntu1
ping ubuntu2
这样,ubuntu1容器和 ubuntu2容器建立了互联关系。
如果你有多个容器之间需要互相连接,推荐使用 Docker Compose,后面会介绍。
配置 DNS
我们可以在宿主机的 /etc/docker/daemon.json 文件中增加以下内容来设置全部容器的 DNS:
"dns":["114.114.114.114","8.8.8.8"]
设置后,启动容器的 DNS 会自动配置为 114.114.114.114 和 8.8.8.8。
重启docker
配置完,需要重启 docker 才能生效。
service docker restart
验证效果
查看容器的 DNS 是否生效可以使用以下命令,它会输出容器的 DNS 信息:
docker run -it --rm ubuntu cat etc/resolv.conf
手动指定容器的配置
如果只想在指定的容器设置 DNS,则可以使用以下命令:
docker run -it -h host_ubuntu --dns=114.114.114.114 --dns-search=test.com ubuntu
参数说明
- -h HOSTNAME 或者 –hostname=HOSTNAME: 设定容器的主机名,它会被写到容器内的 /etc/hostname 和 /etc/hosts。
- –dns=IP_ADDRESS: 添加 DNS 服务器到容器的 /etc/resolv.conf 中,让容器用这个服务器来解析所有不在 /etc/hosts 中的主机名。
- –dns-search=DOMAIN: 设定容器的搜索域,当设定搜索域为 .example.com 时,在搜索一个名为 host 的主机时,DNS 不仅搜索 host,还会搜索 host.example.com。
验证
在容器执行命令验证结果
cat /etc/hosts
cat /etc/resolv.conf
如果在容器启动时没有指定 –dns 和 –dns-search,Docker 会默认用宿主主机上的 /etc/resolv.conf 来配置容器的 DNS。
常见网络问题
WARNING: IPv4 forwarding is disabled. Networking will not work.
centos 7 docker 启动了一个web服务 但是启动时上面错误
解决办法
修改配置文件
vi /etc/sysctl.conf
net.ipv4.ip_forward=1 #添加这段代码
重启network服务
systemctl restart network && systemctl restart docker
查看是否修改成功(备注:返回1,就是成功)
sysctl net.ipv4.ip_forward