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模式如下图所示

img

container模式

这个模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过 lo 网卡设备通信。

Container模式示意图

Container模式

none模式

使用none模式,Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。

这种网络模式下容器只有lo回环网络,没有其他网卡。none模式可以在容器创建时通过–network=none来指定。这种类型的网络没有办法联网,封闭的网络能很好的保证容器的安全性。

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模式如下图所示

img

Docker网络基本操作

查看网络

通过docker network list命令可以查看docker的网络列表

docker network list

image-20210317093433440

我们发现默认只有三个网络

创建网络

通过 docker network create xxx创建一个默认的bridge模式的网络

# 添加一个docker-network的网络
docker network create docker-network
# 查看网络列表
docker network list

image-20210317093650896

我们发现已经创建了一个docker-network的网络

启动时指定网络

通过--network 指定网络

# 运行容器并指定网络
docker run -d -P --network docker-network training/webapp python app.py
#查看运行的docker容器
docker ps

image-20210317094826139

查看网络详细信息

可以通过docker network inspect xxx查看网络详细信息

docker inspect docker-network

通过该网络可以查看网络的详细信息,以及使用该网络的容器列表以及容器的分配的IP地址

image-20210317095029018

创建容器指定IP

创建容器使用docker-network网络并指定静态IP,但是注意指定静态IP需要在该网络的子网范围内

# 创建容器并指定静态IP
docker run -d -P --network docker-network --ip 172.18.1.10 training/webapp python app.py

出了一个错误,大概意思是需要网络设置子网才可以创建静态IP

image-20210317100016900

重新配置网络

先删除原有网络,在重新配置网络

# 查看网络类列表
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

image-20210317100417299

创建容器

这个时候就可以创建容器并指定静态IP了

# 创建容器并指定静态IP
docker run -d -P --network docker-network --ip 172.18.1.10 training/webapp python app.py
docker ps

image-20210317100646619

网络端口映射

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进行访问

image-20210317143200850

这是因为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参数暴漏端口后宿主机就可能进行访问了

image-20210317143828589

原理探究

实际上这是因为Docker在iptables上面做了转发规则,将宿主机的端口转发到对应docker宿主机的IP

iptables -t nat -vnL

image-20210317145038302

其他机器访问

这个时候其他的服务器就可以进行通过宿主机访问到docker内部的容器了

image-20210317145627765

Docker 容器互联

link连接网络

容器的连接(linking)系统是除了端口映射外,另一种跟容器中应用交互的方式。该系统会在源和接收容器之间创建一个隧道,接收容器可以看到源容器指定的信息。

注意!docker官方已不推荐使用docker run –link来链接2个容器互相通信,随后的版本中会删除–link

docker的每个容器相当于有个内网地址。比如 mymysql 容器172.17.0.2mynginx 容器为172.17.0.3 ,那么他们的通信机制是连到了 docker0 这个bridge,大概如下图:

img

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会在接收容器中设置名为_NAME的环境变量,该环境变量的值为:_NAME=/接收容器名/源容器alia

我们进入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

img

安装ping

如果 ubuntu1、ubuntu2容器内中无 ping 命令,则在容器内执行以下命令安装 ping(即学即用:可以在一个容器里安装好,提交容器到镜像,在以新的镜像重新运行以上俩个容器)。

apt-get update
apt install iputils-ping
验证互联

下面通过 ping 来证明 ubuntu1容器和 ubuntu2容器建立了互联关系。

ubuntu1验证

在 ubuntu1容器输入以下命令

ping ubuntu1
ping ubuntu2

img

ubuntu2验证

在 ubuntu1容器输入以下命令

ping ubuntu1
ping ubuntu2

img

这样,ubuntu1容器和 ubuntu2容器建立了互联关系。

如果你有多个容器之间需要互相连接,推荐使用 Docker Compose,后面会介绍。

配置 DNS

我们可以在宿主机的 /etc/docker/daemon.json 文件中增加以下内容来设置全部容器的 DNS:

"dns":["114.114.114.114","8.8.8.8"]

img

设置后,启动容器的 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

img

手动指定容器的配置

如果只想在指定的容器设置 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

img

如果在容器启动时没有指定 –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