Docker学习第三天——Docker网络

    科技2024-08-12  28

    文章目录

    摘要Linux 网络命名空间Docker bridge网络容器之间的Link容器的端口映射容器网络之host和none多容器复杂应用的部署多机器通信

    摘要

    Docker学习之旅第三天——Docker 网络。看完这篇文章将收获docker中网络相关的部分知识:

    单机上的Docker容器之间是如何通信的?Docker容器是如何访问外网的?多个容器之间如何建立关系?多台机器上的Docker如何通信

    环境准备

    两台安装了Docker的虚拟机,我使用的是Vagrant搭建的多台含有Docker的虚拟机。

    Vagrantfile文件内容:

    # -*- mode: ruby -*- # vi: set ft=ruby : Vagrant.configure("2") do |config| config.vm.define "vagrant1" do |vb| config.vm.provider "virtualbox" do |v| v.memory = 1024 v.cpus = 1 end vb.vm.host_name = "vagrant1" vb.vm.network :public_network, ip: "192.168.1.21" vb.vm.box = "centos/7" config.vm.provision "shell", inline: <<-SHELL sudo yum update sudo curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun SHELL end config.vm.define "vagrant2" do |vb| config.vm.provider "virtualbox" do |v| v.memory = 1024 v.cpus = 1 end vb.vm.host_name = "vagrant2" vb.vm.network :public_network, ip: "192.168.1.22" vb.vm.box = "centos/7" config.vm.provision "shell", inline: <<-SHELL sudo yum update sudo curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun SHELL end config.vm.define "vagrant3" do |vb| config.vm.provider "virtualbox" do |v| v.memory = 1024 v.cpus = 1 end vb.vm.host_name = "vagrant3" vb.vm.network :public_network, ip: "192.168.1.23" vb.vm.box = "centos/7" config.vm.provision "shell", inline: <<-SHELL sudo yum update sudo curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun SHELL end end

    Linux 网络命名空间

    创建一个一直运行的容器:

    docker run -d --name busybox busybox /bin/sh -c "while true; do sleep 3600; done"

    进入该容器查看该容器的网络命名空间:

    docker exec -it 14f669c35a2f /bin/sh # 14f669c35a2f为运行的容器id

    每个容器都有自己的网络命名空间,并且每个容器的是不同的。但是在同一台虚拟机上的不同容器间是可以相互ping通的。

    查看已有的网络命名空间

    sudo ip netns list

    发现该虚拟机中目前并没有存在的网络命名空间。

    增加网络命名空间

    sudo ip netns add test1

    发现有一个叫做net_space1的网络命名空间。

    删除网络命名空间

    sudo ip netns delete net_space1

    可以通过exec命令查看创建的网络命名空间具体内容:

    [vagrant@vagrant1 root]$ sudo ip netns exec test1 ip a 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

    发现创建的test1网络命名空间没有ip,并且只有一个本地接口,状态为DOWN未运行。

    查看虚拟机本地的网络link, 一共有6个网络连接,5个为UP运行状态,1个为UNKNOWN状态:

    刚才创建的test1的ip link是DOWN状态执行命令:sudo ip netns exec test1 ip link set dev lo up可以拉起本地网络接口, 拉起后发现状态为UNKNOWN状态而不是UP状态:

    这是因为链接为UP状态必须要两端都是连接起来的,就好像两台电脑要想成功链接就必须使用一根网线来连接起来,单独启动一台电脑而不使用网线链接两台电脑,就是UNKNOWN状态。

    Linux的网络命名空间也是这个道理,我们创建了两个网络命名空间,但是都是独立的,并没有相互连接起来,要想使状态变为UP状态就必须使用叫做Veth pair的linux技术连接起来:

    连接两个网络命名空间

    使用命令sudo ip link veth-test1 type veth peer name veth-test2创建veth网络接口:

    执行完后发现本地的网络命名空间多了两个接口, 这两个接口都有MAC地址但是没有ip, 状态都为DOWN。

    使用命令sudo ip link set veth-test1 netns test1将veth-test1接口添加到test1网络命名空间的link中:

    执行完查看test1的网络link发现多了一个网络接口(就是我们刚才设置的veth-test1):

    在查看下本地的ip link 发现veth-test1接口不见了:

    同理使用命令sudo ip link set veth-test2 netns test2给网络命名空间test2添加link。添加完后查看下本地link,发现veth-test2也没有了。

    分配ip地址

    这样两个网络命名空间就已经连接了,查看下test1和test2:

    发现这几个接口都没有ip地址而且都是DOWN的状态(test1是UNKNOWN是因为前边拉起过)。

    现在需要分别为veth-test1和veth-test2分配ip地址:

    [vagrant@vagrant1 root]$ sudo ip netns exec test1 ip addr add 192.168.1.1/24 dev veth-test1 [vagrant@vagrant1 root]$ sudo ip netns exec test2 ip addr add 192.168.1.2/24 dev veth-test2

    分配完后查看下test1和test2的ip详情,发现已经有了ip地址:

    拉起状态

    有了ip地址后需要做的就是拉起状态了:

    [vagrant@vagrant1 root]$ sudo ip netns exec test1 ip link set dev veth-test1 up [vagrant@vagrant1 root]$ sudo ip netns exec test2 ip link set dev veth-test2 up

    查看下test1的link内容,发现已经是UP状态并且有了ip地址,test2是一样的:

    到此为止,两个网络命名空间test1和test2就已经互通了:

    Docker bridge网络

    注:接下来使用到的命令brctl show 和 docker inspect network 查看桥接网络与建立联系的容器间的关系都是容器在运行中才会显示的。

    上边实现的两个连通的自定义网络命名空间只能相互间访问,无法访问外网,接下来研究docker中的容器是如何访问外网的。

    首先使用命令sudo docker network ls查看docker中的网络:

    [vagrant@vagrant1 ~]$ sudo docker network ls NETWORK ID NAME DRIVER SCOPE e284c339aa0b bridge bridge local ecd6a29a11fe host host local 9ac287424e80 none null local

    我们前边创建的busybox容器是可以访问外网的,这个容器其实是桥接到了docker的bridge网络才能访问外网的:

    通过命令sudo docker network inspect bridge查看下docker的桥接网络详情,发现两个docker容器都连接到了bridge网络:

    docker bridge网络原理

    为什么桥接到了docker的bridge网络就可以访问外网了呢?

    首先使用命令ip a查看下虚拟机的网络命名空间:

    每一个网络接口都有自己的作用,但是却多出了两个并不知道是干嘛的网络接口,想想如果容器想要访问外网就必须和docker0接口搭上关系,我想你大概已经猜到了:通过桥接的方式。

    通过一个命令brctl show可以查看linux桥接网络情况,可以通过sudo yum install bridge-utils下载:

    现在多出来的这两个接口和docker0接口是桥接的搞清楚了,那么busybox和registry容器是如何和这两个接口搭上关系的呢?这和上边连接自定义网络命名空间的原理是一样的:都是需要两个eth接口来连通容器和多出来的接口的。

    这样关系就明白了:

    docker0桥接到虚拟机多出来的接口,容器在构建一个额外的接口连接多出来的接口,这样容器就与虚拟主机建立了连接,并且容器间也通过docker0间接建立了连接:所以此时容器间可以相互通信。

    但是单独的容器是如何访问外网的呢?

    要想访问外网只有通过eth0网络接口,docker0通过网络地址转换(NAT)将内网地址转换为外网地址,就可以通过eth0访问外网了。

    容器之间的Link

    容器之间是可以相互建立连接的(不仅仅是相互通信),比如说一个后端程序需要用到SQL数据库和Redis数据库,而这几个数据库和后端程序是分离的,放在不同的容器之中。

    在使用数据库容器的时候可以通过传递命令(传递ip地址等信息)的方式去建立连接,但是有时候ip等信息可能会变动,而且这些东西又不好记。

    这个时候就需要容器之间相互关联,只需要起个见名知意的容器名就可以了,使用到的时候直接用容器名字就可以了。

    在运行一个容器是可以通过参数--link来连接两个容器:

    [vagrant@vagrant1 ~]$ sudo docker run -d --name busybox2 --link busybox busybox /bin/sh -c "while true; do sleep 3600; done"5fdd1706757b7b182ee1e257b9b015e512d1e900eb5503e27837c52c9f8b9772

    这里我们新创了一个busybox2容器来和上次创的busybox容器建立联系。

    我们进入这个buxybox2容器直接ping容器busybox的容器名是可以ping通的:

    但是需要注意的是link是单向连接,我们进入到busybox中去ping容器busybox2是ping不通的:

    但是通常不直接使用link来建立容器之间的连接,而是通过构建一个单独的桥接网络,在将两个容器都与这个桥接网络建立连接,这样两个容器间就建立了连接。

    构建自己的bridge网络

    [vagrant@vagrant1 ~]$ sudo docker network create -d bridge my-bridge 9e19ca6756cdab106e3b10d35e424cf852107e34969599d15c16f8a4eced59c4

    -d指定驱动为bridge, 构建完后查看下已有的docker网络,发现多了个my-bridge:

    [vagrant@vagrant1 ~]$ docker network ls NETWORK ID NAME DRIVER SCOPE e284c339aa0b bridge bridge local ecd6a29a11fe host host local 9e19ca6756cd my-bridge bridge local 9ac287424e80 none null local

    目前该桥接网络还没有与任何接口进行桥接,使用命令brctl show查看桥接情况:

    [vagrant@vagrant1 ~]$ brctl show bridge name bridge id STP enabled interfaces br-9e19ca6756cd 8000.0242921ba6dc no docker0 8000.0242f4f2d8d3 no veth7674b4b veth978d23c vethfe7385e

    建立bridge网络与容器之间联系

    新建立的容器与my-bridge网络建立联系:使用--network命令指定网络

    [vagrant@vagrant1 ~]$ sudo docker run -d --name busybox3 --network my-bridge busybox /bin/sh -c "while true; do sleep 3600; done" 967ebe56dadcf08cc274beaa10ee98de35ea84a1b36f6c8a2d31fd87a946c818

    使用brctl show命令查看下桥接网络情况:

    [vagrant@vagrant1 ~]$ brctl show bridge name bridge id STP enabled interfaces br-9e19ca6756cd 8000.024293486c85 no veth3b27801 docker0 8000.02425c4d17a5 no vethc6f1d6b

    可以发现新建立的my-bridge网络br-9e19ca6756cd已经与网络接口veth3b27801桥接。

    也可以使用命令sudo docker inspect network my-bridge来查看建立连接的容器:

    已有的容器与my-bridge网络建立联系:

    对于已经存在的容器可以使用docker network connect命令来建立容器与网络的联系:

    [vagrant@vagrant1 ~]$ sudo docker network connect my-bridge busybox2

    使用命令docker inspect network my-bridge查看与my-bridge桥接网络建立联系的容器,可以发现busybox2和busybox3容器都与该网络建立了桥接:

    需要注意的是一个容器可以与多个网络建立联系,busybox2在建立容器时就使用的是默认的bridge桥接网络,使用命令docker inspect network bridge查看下:

    测试容器之间是否已经连通

    使用命令docker exec -it busybox2 /bin/sh和docker exec -it busybox3 /bin/sh分别ping对方的名字都是可以ping通的。

    因此,如果想要多个容器之间可以互通,一般可以建立一个桥接网络,然后将这多个容器与该桥接网络建立联系即可。

    容器的端口映射

    运行容器时使用-p参数可以进行端口映射。

    例如运行一个nginx容器让浏览器访问时输入的是8888端口而不是默认的80端口:

    docker run --name nginx_8888 -d -p 8888:80 nginx

    其中80是nginx容器的端口,8888是容器建立的桥接网络的端口。

    查看浏览器:

    容器网络之host和none

    none网络

    首先运行一个网络驱动为none的容器:

    docker run -d --name busybox1 --network none busybox /bin/sh -c "while true; do sleep 3600;done"

    然后查看下none网络详情:

    docker inspect network none

    发现busybox1确实和none网路建立了联系,但是none网络没有mac地址也没有ip地址。

    进入容器内部查看ip也是只有本地接口没有别的接口:

    这意味着none网络只能通过本地(127.0.0.1)访问,无法通过其他方式访问。

    host网络

    首先运行一个网络驱动为host的容器:

    docker run -d --name busybox1 --network none busybox /bin/sh -c "while true; do sleep 3600;done"

    然后查看下host网络详情:

    docker inspect network host

    发现host网络也没有给容器分配ip地址和mac地址。

    再进入busybox2内部查看下ip详情,发现和本地(虚拟机)的网络命名空间是一样的:

    busybox2的网络命名空间:

    本地的网络命名空间:

    host网络模式就是使容器和本地共用一套网络命名空间,这样做既有优点也有缺点: 优点:这样我们在运行容器的时候不需要考虑端口映射的问题,因为使用的是同一套端口。缺点:但是容易造成端口冲突,比如说部署两个nginx容器默认使用的都是80端口就会造成端口冲突。

    多容器复杂应用的部署

    应用场景

    部署一个flask项目,该项目使用了redis数据库,redis和flask程序分别部署到不同的容器中。

    Dockerfile内容

    FROM python:2.7 LABEL maintaner="Peng Xiao xiaoquwl@gmail.com" COPY . /app WORKDIR /app RUN pip install flask redis EXPOSE 5000 CMD [ "python", "app.py" ]

    app.py内容

    from flask import Flask from redis import Redis import os import socket app = Flask(__name__) redis = Redis(host=os.environ.get('REDIS_HOST', '127.0.0.1'), port=6379) @app.route('/') def hello(): redis.incr('hits') return 'Hello Container World! I have been seen %s times and my hostname is %s.\n' % (redis.get('hits'),socket.gethostname()) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)

    build镜像

    docker build -t flask_5000_redis_6379/flask-redis .

    运行容器

    [vagrant@vagrant2 flask-redis]$ docker run -d --name flask-redis --link redis -e REDIS_HOST=redis -p 5000:5000 flask_5000_redis_6379/flask-redis

    -e参数是传递环境变量参数,进入容器查看下环境变量, 可以看到我们设置的环境变量:

    docker exec -it flask-redis /bin/sh

    验证程序

    多机器通信

    上边的docker实验都是在单机上进行的,接下来进行多机器间的docker实验。

    首先在两台虚拟机上搭建etcd存储集群,两台虚拟机的ip分别为192.168.43.21和192.168.43.22,hostname分别为docker-node1和docker-node2。

    搭建步骤

    在docker-node1上: vagrant@docker-node1:~$ wget https://github.com/coreos/etcd/releases/download/v3.0.12/etcd-v3.0.12-linux-amd64.tar.gz vagrant@docker-node1:~$ tar zxvf etcd-v3.0.12-linux-amd64.tar.gz vagrant@docker-node1:~$ cd etcd-v3.0.12-linux-amd64 vagrant@docker-node1:~$ nohup ./etcd --name docker-node1 --initial-advertise-peer-urls http://192.168.43.21:2380 \ --listen-peer-urls http://192.168.43.21:2380 \ --listen-client-urls http://192.168.43.21:2379,http://127.0.0.1:2379 \ --advertise-client-urls http://192.168.43.21:2379 \ --initial-cluster-token etcd-cluster \ --initial-cluster docker-node1=http://192.168.43.21:2380,docker-node2=http://192.168.43.22:2380 \ --initial-cluster-state new&

    wget太慢可以通过这个网站下载后通过sftp传上去。

    在docker-node2上 vagrant@docker-node2:~$ wget https://github.com/coreos/etcd/releases/download/v3.0.12/etcd-v3.0.12-linux-amd64.tar.gz vagrant@docker-node2:~$ tar zxvf etcd-v3.0.12-linux-amd64.tar.gz vagrant@docker-node2:~$ cd etcd-v3.0.12-linux-amd64/ vagrant@docker-node2:~$ nohup ./etcd --name docker-node2 --initial-advertise-peer-urls http://192.168.43.22:2380 \ --listen-peer-urls http://192.168.43.22:2380 \ --listen-client-urls http://192.168.43.22:2379,http://127.0.0.1:2379 \ --advertise-client-urls http://192.168.43.22:2379 \ --initial-cluster-token etcd-cluster \ --initial-cluster docker-node1=http://192.168.43.21:2380,docker-node2=http://192.168.43.22:2380 \ --initial-cluster-state new&

    搭建完毕后检查集群是否搭建成功:

    vagrant@docker-node2:~/etcd-v3.0.12-linux-amd64$ ./etcdctl cluster-health

    出现如下为成功:

    member 21eca106efe4caee is healthy: got healthy result from http://192.168.43.21:2379 member 8614974c83d1cc6d is healthy: got healthy result from http://192.168.43.22:2379 cluster is healthy

    重启docker服务

    在docker-node1上:

    $ sudo service docker stop $ sudo /usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-store=etcd://192.168.43.21:2379 --cluster-advertise=192.168.43.21:2375&

    在docker-node2上

    $ sudo service docker stop $ sudo /usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-store=etcd://192.168.43.22:2379 --cluster-advertise=192.168.43.22:2375&

    创建overlay network

    vagrant@docker-node1:~$ sudo docker network ls NETWORK ID NAME DRIVER SCOPE 0e7bef3f143a bridge bridge local a5c7daf62325 host host local 3198cae88ab4 none null local vagrant@docker-node1:~$ sudo docker network create -d overlay demo 3d430f3338a2c3496e9edeccc880f0a7affa06522b4249497ef6c4cd6571eaa9 vagrant@docker-node1:~$ sudo docker network ls NETWORK ID NAME DRIVER SCOPE 0e7bef3f143a bridge bridge local 3d430f3338a2 demo overlay global a5c7daf62325 host host local 3198cae88ab4 none null local

    我们会看到在node2上,这个demo的overlay network会被同步创建:

    vagrant@docker-node2:~$ sudo docker network ls NETWORK ID NAME DRIVER SCOPE c9947d4c3669 bridge bridge local 3d430f3338a2 demo overlay global fa5168034de1 host host local c2ca34abec2a none null local

    到此我们的简单集群算搭建成功了,现在在docker-node1虚拟机上使用overlay网络创建一个名称为test1的busybox容器:

    [vagrant@vagrant1 etcd-v3.0.12-linux-amd64]$ sudo docker run -d --name test1 --net demo busybox sh -c "while true ; do sleep 3600; done"

    再到另一台虚拟机上也创建一个名为test1的busybox容器,会报错test1名字已经被使用了,这就是ectd会在集群中查找到test1被使用了就不能再用了:

    换个名字就可以创建了:

    docker run -d --name test2 --net demo busybox sh -c "while true; do sleep 3600; done"

    查看下test1容器和test2容器的ip:

    在查看下overlay网络的详情,发现containers中包含了test1和test2的ip:

    这个时候两个不同机器上的不同容器间是可以相互通信的:

    test1容器ping test2容器: 同样的test2ping test1容器也是可以ping通的。

    Processed: 0.010, SQL: 8