【Docker】Docker 容器数据卷和 Docker 网络

5 容器数据卷

5.1 什么是容器数据卷

docker 的理念回顾

将应用和运行的环境打包形成容器运行,运行可以伴随着容器,但是我们对于数据的要求,是希望能够持久化的!就好比,你安装一个MySQL,结果你把容器删了,就相当于删库跑路了,这TM也太扯了吧!

所以我们希望容器之间有可能可以共享数据,Docker容器产生的数据,如果不通过docker commit生成新的镜像,使得数据作为镜像的一部分保存下来,那么当容器删除后,数据自然也就没有了!这样是行不通的!

为了能保存数据在Docker中我们就可以使用卷!让数据挂载到我们本地!这样数据就不会因为容器删除而丢失了!

数据?如果数据都在容器中,那么容器删除,数据就会丢失!需求:数据可以持久化

MySQL,容器删除了,数据则删除,删库跑路。需求:MySQL的数据存放到本地!

容器之间可以有一个数据共享的技术!Docker 容器中产生的数据,同步到本地!

这就是数据卷技术!目录的挂载,将我们容器内的目录,挂载到Linux系统指定的目录下。

作用:

卷就是目录或者文件,存在一个或者多个容器中,由docker挂载到容器,但不属于联合文件系统,因此能够绕过Union File System,提供一些用于持续存储或共享数据的特性:

卷的设计目的就是数据的持久化,完全独立于容器的生存周期,因此Docker不会在容器删除时删除其挂载的数据卷。

特点:

1、数据卷可在容器之间共享或重用数据

2、卷中的更改可以直接生效

3、数据卷中的更改不会包含在镜像的更新中

4、数据卷的生命周期一直持续到没有容器使用它为止

img

总结:容器的数据持久化和同步操作!容器间也是可以数据共享的!

5.2 使用数据卷

方式一:直接使用命令挂载 -v 参数

docker run -it -v 主机目录:容器内的目录

范例:

$ docker run -it -d \
  -v /data/ubuntu01:/home \
  --privileged=true \
  --name ubuntu01 ubuntu:20.04 /bin/bash

#查看容器的详细信息显示
#通过docker inpsect 容器ID 查看
#值得注意的是,主机目录会直接覆盖掉容器中要挂载的目录
$ docker inspect ubuntu01 | grep -A 9 "Mounts"
        "Mounts": [
            {
    
    
                "Type": "bind",
                "Source": "/data/ubuntu01",  #主机内的目录路径
                "Destination": "/home",      #Docker容器内的目录路径
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],

$ docker exec -it ubuntu01 /bin/bash
root@9afa7b8ee434:/# cd /home/
root@9afa7b8ee434:/home# mkdir host ; ls
host
root@9afa7b8ee434:/home# echo "Hello World!" > /home/docker.txt
root@9afa7b8ee434:/home# exit

$ ls /data/ubuntu01/
docker.txt  host

#再次测试
#删除原有的ubuntu01容器,启动ubuntu02容器进行挂载该目录
$ docker rm -f ubuntu01
$ docker run -it -d \
  --name ubuntu02 \
  --privileged=true \
  -v /data/ubuntu01:/home \
  ubuntu:20.04 /bin/bash
$ docker exec -it ubuntu02 /bin/bash
root@2ac37ab7725a:/# ls /home/
docker.txt  host
root@2ac37ab7725a:/# cat /home/docker.txt
Hello World!

数据卷好处:我们以后修改只需要修改本地修改即可,容器内会自动同步。类似共享文件是占用一份的存储。

5.3 实战:安装MySQL

思考:MySQL 的数据持久化的问题。

#获取并下载MySQL镜像
$ docker pull mysql:5.7.36
#官方测试 $ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql-root -d mysql:tag

#运行mysql容器,需要做数据挂载
#安装启动mysql,需要配置密码,注意事项
#-d 后台运行
#-p 端口映射
#-v 数据卷挂载
#-e 环境变量设置
#--name 容器名字
$ docker run -it -d \
  --name mysql01 \
  -p 3316:3306 \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -v /data/docker/mysql01/conf:/etc/mysql/conf.d \
  -v /data/docker/mysql01/data:/var/lib/mysql \
  mysql:5.7.36
#如果在挂载时,外部是新建目录或空目录会将容器中映射目录也变成空目录
$ cat > /data/docker/mysql01/conf/my.cnf <<EOF
[client]
default-character-set=utf8mb4

[mysql]
default-character-set=utf8mb4

[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
EOF
$ ls /data/docker/mysql01/conf/
my.cnf
$ ls /data/docker/mysql01/data/
auto.cnf    client-cert.pem  ib_buffer_pool  ib_logfile1  performance_schema  server-cert.pem
ca-key.pem  client-key.pem   ibdata1         ibtmp1       private_key.pem     server-key.pem
ca.pem      ib_logfile0     mysql        public_key.pem      sys
#默认 -v 是先主机挂载覆盖了,然后 MySQL 程序启动才自动创建的数据,会直接生成/var/lib/mysql/目录下的数据库数据

$ docker restart mysql01
mysql01
#启动成功之后,在本地使用Navicat等数据库连接工具进行连接即可。
#Navicat 连接到服务器 3316 --> 3316 端口是和容器的内部的 3306 映射,这个时候就可以连接上了

#在本地创建一个测试数据库dbtest,可以在挂载目录下查看到相应的目录
$ ls -l /data/docker/mysql01/data/dbtest/
total 4
-rw-r----- 1 polkitd input 67 Aug 25 17:10 db.opt

img

假设我们将容器删除了。

发现,我们挂载到本地的数据卷依旧没有丢失,这就实现了容器的数据持久化的功能!

5.4 具名挂载和匿名挂载

#匿名挂载
-v 容器内路径
$ docker run -it -d -P --name nginx01 -v /etc/nginx nginx

#查看所有 volume 的情况
$ docker volume ls
DRIVER    VOLUME NAME
local     b5c1c38d5935b1e1adc829c3eb28af15188368169745afe75f230858969b6df9
#这里发现,这种就是匿名挂载,我们在 -v 只写了容器内的路径,没有写容器外的路径

$ docker inspect nginx01 | grep -A 9 "Mounts"
        "Mounts": [
            {
    
    
                "Type": "volume",
                "Name": "b5c1c38d5935b1e1adc829c3eb28af15188368169745afe75f230858969b6df9",
                "Source": "/var/lib/docker/volumes/b5c1c38d5935b1e1adc829c3eb28af15188368169745afe75f230858969b6df9/_data",
                "Destination": "/etc/nginx",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""

$ cd /var/lib/docker/volumes/b5c1c38d5935b1e1adc829c3eb28af15188368169745afe75f230858969b6df9/_data
$ ls
conf.d  fastcgi_params  mime.types  modules  nginx.conf  scgi_params  uwsgi_params

#具名挂载
$ docker run -it -d -P --name nginx02 -v nginx-volume:/etc/nginx nginx
62c42ad0e4ca2374ee30af5b18b48a40e197d41126225446716c241639c3dbf3
#通过 -v 卷名:容器内路径

#查看所有 volume 的情况
$ docker volume ls
DRIVER    VOLUME NAME
local     nginx-volume
#查看一下这个具名卷
$ docker volume inspect nginx-volume
[
    {
    
    
        "CreatedAt": "2022-08-25T20:41:56+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/nginx-volume/_data",
        "Name": "nginx-volume",
        "Options": null,
        "Scope": "local"
    }
]

img

所有的 Docker 容器内的卷,没有指定目录的情况下都是在 “/var/lib/docker/volumes/xxx/_data”

我们通过具名挂载可以方便的让我们找到对应的数据卷名,大多数情况是使用"具名挂载"

思考:如何请是具名挂载还是匿名挂载,还是指定路径挂载!

  • -v 容器内路径 #匿名挂载
  • -v 卷名:容器内路径 #具名挂载
  • -v /宿主机路径:容器内路径 #指定路径挂载

拓展:

#通过 -v 容器内路径:ro rw 改变读写权限
ro readonly  #只读
rw readwrite #可读可写

#一旦这个设置了容器,容器对我们挂载出来的内容就有限定了!
$ docker run -it -d -P --name nginx03 -v nginx-volume:/etc/nginx:ro nginx
$ docker run -it -d -P --name nginx03 -v nginx-volume:/etc/nginx:rw nginx

#默认是rw,如果是 ro 就说明只能通过宿主机来操作,容器内部是无法操作的

5.5 初识 Dockerfile

Dockerfile就是用来构建docker镜像的构建文件!命令脚本!先体验一下!

通过这个脚本可以生成镜像,镜像是一层一层的,对应的就是脚本一个个的。每一个命令都是一层!

$ mkdir -pv /root/docker/docker-volume ; cd /root/docker/docker-volume
#创建一个Dockerfile文件,名字可以随机 建议使用Dockerfile
#文件中的内容指令(大写) 参数
$ vim Dockerfile
FROM ubuntu:20.04

#匿名挂载
VOLUME ["/volume01", "/volume02"]

RUN echo "-----> Success ......"
CMD /bin/bash
#这里的每一个命令,就是镜像的一层

$ docker build -t kube-ubuntu:1.0 -f Dockerfile .
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM ubuntu:20.04
---> ba6acccedd29
Step 2/4 : VOLUME ["/volume01", "/volume02"]
---> Running in f77e4a3d3e7f
Removing intermediate container f77e4a3d3e7f
---> 91315a9d10d0
Step 3/4 : RUN echo "-----> Success ......"
---> Running in fa440b3de704
-----> Success ......
Removing intermediate container fa440b3de704
---> ffe4ed184001
Step 4/4 : CMD /bin/bash
---> Running in f27e036a6862
Removing intermediate container f27e036a6862
---> 1f2a4e5e429d
Successfully built 1f2a4e5e429d
Successfully tagged kube-ubuntu:1.0

$ docker images kube-ubuntu:1.0
REPOSITORY    TAG       IMAGE ID       CREATED              SIZE
kube-ubuntu   1.0       1f2a4e5e429d   About a minute ago   72.8MB

$ docker run -itd --name ku1 kube-ubuntu:1.0 /bin/bash
$ docker exec -it ku1 /bin/bash
#这个目录就是我们生成镜像的时候自动挂载的,数据卷目录
root@6a6138c411dd:/# echo "Hello volume01" > /volume01/container01.txt
root@6a6138c411dd:/# echo "Hello volume02" > /volume02/container02.txt
root@6a6138c411dd:/# ls -l /volume0*
/volume01:
total 4
-rw-r--r-- 1 root root 15 Aug 25 13:08 container01.txt

/volume02:
total 4
-rw-r--r-- 1 root root 15 Aug 25 13:08 container02.txt
root@6a6138c411dd:/# exit
exit
#这个数据卷和外部一定有一个同步的目录
$ docker inspect ku1 | grep -A 21 "Mounts"
        "Mounts": [
            {
    
    
                "Type": "volume",
                "Name": "604c9c1d68d41958287107effbd1dcfbd0bbd875c9cb7dd8b35982f446e35f68",
                "Source": "/var/lib/docker/volumes/604c9c1d68d41958287107effbd1dcfbd0bbd875c9cb7dd8b35982f446e35f68/_data",
                "Destination": "/volume01",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            },
            {
    
    
                "Type": "volume",
                "Name": "f6262a1ec8581c5b6a6b78e3beacd4881b435a5d331f5c33d88e5c9e0b8bfb89",
                "Source": "/var/lib/docker/volumes/f6262a1ec8581c5b6a6b78e3beacd4881b435a5d331f5c33d88e5c9e0b8bfb89/_data",
                "Destination": "/volume02",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],
#测试一下,刚才的文件是否同步了!
#这种方式我们未来使用的十分多,因为我们通常会构建自己的镜像!

$ cat /var/lib/docker/volumes/604c9c1d68d41958287107effbd1dcfbd0bbd875c9cb7dd8b35982f446e35f68/_data/container01.txt
Hello volume01
$ cat /var/lib/docker/volumes/f6262a1ec8581c5b6a6b78e3beacd4881b435a5d331f5c33d88e5c9e0b8bfb89/_data/container02.txt
Hello volume02

img

假设构建镜像时候没有挂载卷,要手动镜像挂载 -v 卷名:容器内路径!

5.6 数据卷容器

img

#启动3个容器,通过我们刚才自己写的镜像启动
$ docker images kube-ubuntu:1.0
REPOSITORY    TAG       IMAGE ID       CREATED          SIZE
kube-ubuntu   1.0       317c07f88b30   30 minutes ago   72.8MB

#写/bin/bash是规定镜像启动时执行这条指令,centos镜像启动时默认会执行/bin/bash,因此可以不写
$ docker run -itd --name docker01 kube-ubuntu:1.0 /bin/bash
$ docker run -itd --name docker02 --volumes-from docker01 kube-ubuntu:1.0 /bin/bash
#--volumes-from 实际上多个容器挂载的是同一个数据卷
#--volumes-from 就可以实现容器间的数据共享了

#在docker01容器中创建测试文件,检验docker02是否也有其测试文件
#docker01创建测试文件
$ docker exec -it docker01 /bin/bash
root@51ae030c5f2a:/# echo "Hello docker02 volume01" > /volume01/test01.txt
root@51ae030c5f2a:/# echo "Hello docker02 volume02" > /volume02/test02.txt
#docker02查看测试文件
$ docker exec -it docker02 /bin/bash
root@9807aeded26e:/# cat /volume01/test01.txt
Hello docker02 volume01
root@9807aeded26e:/# cat /volume02/test02.txt
Hello docker02 volume02

#docker03继承docker02的数据卷信息,并创建测试文件
$ docker run -it --name docker03 --volumes-from docker02 kube-ubuntu:1.0 /bin/bash
root@bdc47c45e893:/# echo "Hello I am docker03" > /volume01/docker03.txt
root@bdc47c45e893:/# echo "Hello I am docker03 Here" > /volume02/docker03.txt
#docker01可以查看到docker03的测试文件
$ docker exec -it docker01 /bin/bash
root@51ae030c5f2a:/# cat /volume01/docker03.txt
Hello I am docker03
root@51ae030c5f2a:/# cat /volume02/docker03.txt
Hello I am docker03 Here

img

#测试:可以删除 docker01,查看一下docker02和docker03是否还可以访问这个文件
#测试依旧可以访问
$ docker rm -f docker01
#三个容器都是挂载到宿主机的同一个目录下的,如果删掉宿主机下的这个目录的文件,三个容器的数据都会消失
#可以发现三个容器的匿名数据卷挂载的路径是一样的。

img

img

已验证,多个 MySQL 实现数据共享失败。原因后续继承该MySQL 配置和数据的方式依旧是父MySQL,会导致后续的MySQL 启动失败。数据卷容器 只能使用在 无状态服务。注意两个mysql虽然共用一个数据,但是只能同时在线一个MySQL容器,否则另外一个连接不上。

结论:

容器之间配置信息的传递,数据卷容器的生命周期一直持续到没有容器使用为止。

但是一旦数据持久化到本地,这个时候,本地的数据是不会删除的!

数据卷是被设计用来持久化数据的,它的生命周期独立于容器,Docker 不会在容器被删除后自动删除数据卷。如果需要在删除容器的同时移除数据卷,可以在删除容器的时候使用 docker rm -v 这个参数。

6 Docker 网络

6.1 理解 docker0

清空所有的 Docker 环境。

$ ip addr show
#本地回环网卡
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
#本机的内网IP地址
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:16:73:35 brd ff:ff:ff:ff:ff:ff
    inet 10.0.0.101/24 brd 10.0.0.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe16:7335/64 scope link
       valid_lft forever preferred_lft forever
#docker服务生成的docker0网卡
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:fc:db:df:7e brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:fcff:fedb:df7e/64 scope link
       valid_lft forever preferred_lft forever

三个网络:

问题:docker 是如何处理容器网络访问的?

$ docker run -d -P --name tomcat01 tomcat:8.5.81

#查看容器的内部网络地址 ip addr show
#发现容器启动的时候会得到一个 eth0@xxx ip地址,docker DHCP分配
$ docker exec -it tomcat01 /bin/bash
root@f408a9817eee:/usr/local/tomcat# apt update
root@f408a9817eee:/usr/local/tomcat# apt install -y iproute2 iputils-ping &> /dev/null
root@767f7355e9eb:/usr/local/tomcat# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
77: eth0@if78: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

#思考:Linux能不能ping通容器内部
$ ping -c1 -W1 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.034 ms

#Linux 可以ping通 docker容器内部

原理:

  1. 每启动一个 docker 容器,docker 就会给 docker 容器分配一个 IP 地址,安装了docker,就会又一个网卡 docker0

桥接模式,使用的技术是 veth-pair 技术

再次测试宿主机 ip addr

img

  1. 在启动一个 tomcat 容器测试,发现又多了一对网卡!
$ docker run -d -P --name tomcat02 tomcat
$ docker exec -it tomcat02 /bin/bash
root@15eb260c7094:/usr/local/tomcat# apt update
root@15eb260c7094:/usr/local/tomcat# apt install -y iproute2 iputils-ping &> /dev/null
root@15eb260c7094:/usr/local/tomcat# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
79: eth0@if80: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

img

我们发现这个容器带来的网卡,都是一对一对的。

#veth-pair 就是一对虚拟设备接口,他们都是成对出现的,一段连着协议,一段彼此相连

#正因为有这个特性,veth-pair 充当了一个桥接,链接各种虚拟网络设备

#OpenStack Docker容器之间连接 OVS 连接 都是使用 veth-pair 技术。

  1. 我们来测试一下 tomcat01 和 tomcat02 是否可以ping 通
$ docker exec -it tomcat01 /bin/bash
root@767f7355e9eb:/usr/local/tomcat# ping -c 1 -W 1 172.17.0.3
PING 172.17.0.3 (172.17.0.3) 56(84) bytes of data.
64 bytes from 172.17.0.3: icmp_seq=1 ttl=64 time=0.069 ms

--- 172.17.0.3 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.069/0.069/0.069/0.000 ms

#结论:容器和容器之间是可以相互ping通的!

img

路由的定义是指路由寻址功能,跨网段的时候会生成一个路由表,转发到对应的接口属于三层;

二层交换只能在局域网(同网段)连接。

结论:tomcat01 和 tomcat02 是拥有一个公用的路由器 docker0

所有的容器不指定网络的情况下,都是 docker0 路由的,Docker 会给我们的容器分配一个默认的可用的IP地址

Docker 默认的网段是 172.17.0.0/16 B类地址默认可以存放 65535 个地址

小结:

Docker中 docker0 使用的是 Linux 的桥接技术,docker0 和 容器之间是使用 veth-pair 虚拟接口技术。

即宿主机中是一个 Docker 的网桥:docker0。Docker 中的所有的网络接口都是虚拟的。虚拟的转发效率高(内网传递数据)。

只要容器删除,对应的网桥就没了(即虚拟接口 veth-pair 也会消失)。

img

docker network inspect bridge

img

6.2 --link

思考一个场景:我们编写了一个微服务,database url=ip,项目不重启,数据库IP地址掉了,我们希望可以处理这个问题,可以通过名字来访问容器?

$ docker exec -it tomcat01 ping tomcat02
ping: tomcat02: Name or service not known
#默认是ping 不通名字

#如何进行解决呢?
#使用--link就可以解决网络连通问题
$ docker run -it -d --name tomcat03 --link tomcat02 tomcat
#tomcat03 ping tomcat02
$ docker exec -it tomcat03 ping -c 1 -W 1 tomcat02
PING tomcat02 (172.17.0.3) 56(84) bytes of data.
64 bytes from tomcat02 (172.17.0.3): icmp_seq=1 ttl=64 time=0.121 ms

--- tomcat02 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.121/0.121/0.121/0.000 ms
#tomcat02 ping tomcat03
$ docker exec -it tomcat02 ping tomcat03
ping: tomcat03: Name or service not known

#查看bridge网络模式下的详细信息
$ docker network inspect bridge

探究 docker network inspect

img

$ docker inspect tomcat03 | grep -A 3 "Links"
            "Links": [
                "/tomcat02:/tomcat03/tomcat02"
            ],
#说明了tomcat03 和 tomcat02 的额绑定关系,=/接收容器名/源容器名或者别名

其实这个 tomcat03 就是在本地配置了 tomcat02 的信息配置

#登录tomcat03 的容器
$ docker exec -it tomcat03 /bin/bash
root@868350cec9a0:/usr/local/tomcat# cat /etc/resolv.conf
# Generated by NetworkManager
nameserver 114.114.114.114
nameserver 8.8.8.8
root@868350cec9a0:/usr/local/tomcat# cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.3      tomcat02 15eb260c7094
172.17.0.4      868350cec9a0

root@868350cec9a0:/usr/local/tomcat# ping -c 1 -W 1 tomcat02
PING tomcat02 (172.17.0.3) 56(84) bytes of data.
64 bytes from tomcat02 (172.17.0.3): icmp_seq=1 ttl=64 time=0.096 ms

--- tomcat02 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.096/0.096/0.096/0.000 ms

#--link 就是在容器的 /etc/hosts 配置中增加了一个 172.17.0.3      tomcat02 15eb260c7094 的条目信息
#探究:--link 本质就是容器中 /etc/hosts 新增相应的条目。 

现在 Docker 已经不建议使用 --link 了;目前推荐使用自定义网络。

默认 docker0 问题,不支持容器名连接访问

6.3 自定义网络

容器互联技术:–link 和 自定义网络

#查看所有的Docker网络信息
$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
4490c3a848b4   bridge    bridge    local
2373b4b3186d   host      host      local
5536bc79b4b6   none      null      local

Docker 网络模式

网络模型 简介
bridge 为每一个容器分配、设置IP等,并将容器连接到一个docker0虚拟网桥,默认为该模式
host 容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口
none 容器有独立的 Network Namespace(网络名称空间),但是并没有对其进行任何网络设置,如分配 veth pair 和网桥连接,IP等。(基本上不会使用)
container 新创建的容器不会创建自己的网卡和配置自己的IP,而是和一个指定的容器共享IP,端口范围等

docker network 主流的是 bridge、host、none,常用的是 bridge、host。

bridge:桥接 docker (默认,自定义网络也是 bridge 模式)

none:不配置网络

host:和宿主机共享网络

container:容器网络联通(局限性很大)

测试:

#直接启动命令默认会添加--net bridge,而这个就是docker0
$ docker run -it -d -P --name tomcat01 tomcat
#等价于
$ docker run -it -d -P --name tomcat01 --net bridge tomcat

#docker0特点:默认,容器间的域名是不通,--link可以打通连通

#可以创建自定义网络
#--driver bridge         :管理网络的驱动程序(默认为“桥接”)
#--subnet 192.168.0.0/16 :CIDR格式的子网,表示一个网段
#--gateway 192.168.0.1   :主子网的IPv4或IPv6网关 
$ docker network create \
      --driver bridge \
      --subnet 192.168.0.0/16 \
      --gateway 192.168.0.1 mynet

$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
e79095f379fb   bridge    bridge    local
993e748549d7   host      host      local
3e91d8f383af   mynet     bridge    local
4bf3eb064757   none      null      local

自定义网络就已经创建完毕了

img

$ docker run -it -d --name alpine01 --network mynet alpine
$ docker run -it -d --name alpine02 --network mynet alpine

$ docker inspect alpine02 | grep -A 18 "Networks"
            "Networks": {
    
    
                "mynet": {
    
    
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": [
                        "348e6354072b"
                    ],
                    "NetworkID": "5aa4b631f9ef3b6189496fd2bdccf673f18dcee69232b09cb4095870eef20387",
                    "EndpointID": "64fdcb4b0dd537b2aeef757b8f2114ed84187aec7c99ec3ea611d375b0646c2a",
                    "Gateway": "192.168.0.1",
                    "IPAddress": "192.168.0.3",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:c0:a8:00:03",
                    "DriverOpts": null
                }
            }

#再次测试 ping 连接
#首先使用alpine01 ping alpine02的IP地址
$ docker exec -it alpine01 ping -c 1 -W 1 192.168.0.3
PING 192.168.0.3 (192.168.0.3): 56 data bytes
64 bytes from 192.168.0.3: seq=0 ttl=64 time=0.094 ms

--- 192.168.0.3 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.094/0.094/0.094 ms

#再次直接使用 alpine01 ping alpine02
#现在不使用--link也可以 ping 名字!!!
$ docker exec -it alpine01 ping -c 1 -W 1 alpine02
PING alpine02 (192.168.0.3): 56 data bytes
64 bytes from 192.168.0.3: seq=0 ttl=64 time=0.127 ms

--- alpine02 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.127/0.127/0.127 ms

自定义的网络 Docker 都已经帮运维/开发 维护好了对应的关系,推荐使用自定义网络。

原理就是这两个容器都是连的自定义网络,连接在同一个自定义网络的容器之间端口会自动相互暴露,而且不会向以外的显示任何端口,这样就更好的进行了容器见相互通信和隔离

好处:不同的集群使用不同的自定义网络,保证集群是安全和健康的。

6.4 网络连通

img

$ docker run -it --name alpine01-docker0 --network bridge -d alpine
$ docker run -it --name alpine02-docker0 --network bridge -d alpine

$ docker run -it -d --name alpine01 --network mynet alpine
$ docker run -it -d --name alpine02 --network mynet alpine

#默认不同的网络命名空间是不能够相互通信的
$ docker exec -it alpine01-docker0 ping -c 1 -W 1 192.168.0.2
PING 192.168.0.2 (192.168.0.2): 56 data bytes

--- 192.168.0.2 ping statistics ---
1 packets transmitted, 0 packets received, 100% packet loss
$ docker exec -it alpine01-docker0 ping alpine01
ping: bad address 'alpine01'

img

#测试打通alpine01-docker0 - alpine01
$ docker network connect mynet alpine01-docker0
#联通之后,就是将 alpine01-docker0 放到了 mynet 网络下
$ docker inspect alpine01-docker0
#一个容器两个IP地址

img

#再次测试
$ docker exec -it alpine01-docker0 ping -c 1 -W 1 192.168.0.2
PING 192.168.0.2 (192.168.0.2): 56 data bytes
64 bytes from 192.168.0.2: seq=0 ttl=64 time=0.088 ms

--- 192.168.0.2 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.088/0.088/0.088 ms

$ docker exec -it alpine01-docker0 ping -c 1 -W 1 alpine01
PING alpine01 (192.168.0.2): 56 data bytes
64 bytes from 192.168.0.2: seq=0 ttl=64 time=0.117 ms

--- alpine01 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.117/0.117/0.117 ms
#alpine02-docker0 还是依旧无法联通alpine01 alpine02

结论:假设要跨网络操作其他容器,就需要使用 docker network connect 联通了!

6.5 实战:部署 Redis 集群

img

#创建网卡
$ docker network create redis --subnet 172.38.0.0/16

#批量创建六个 Redis的Docker容器
for port in {
    
    1..6} ;do
    mkdir -pv /data/redis/node-${port}/conf
    touch /data/redis/node-${port}/conf/redis.conf
    cat << EOF > /data/redis/node-${port}/conf/redis.conf
port 6379
bind 0.0.0.0
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.38.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
appendonly yes
EOF
done

for port in {
    
    1..6} ; do
docker run -p 637${port}:6379 -p 1637${port}:16379 --name redis-${port} \
    -v /data/redis/node-${port}/data:/data \
    -v /data/redis/node-${port}/conf/redis.conf:/etc/redis/redis.conf \
    -d --net redis --ip 172.38.0.1${port} redis:5.0.9-alpine3.11 \
    /usr/local/bin/redis-server /etc/redis/redis.conf
done

#范例:
docker run -p 6371:6379 -p 16371:16379 --name redis-1 \
    -v /data/redis/node-1/data:/data \
    -v /data/redis/node-1/conf/redis.conf:/etc/redis/redis.conf
    -d --net redis --ip 172.38.0.11 redis:5.0.9-alpine3.11 \
    /usr/local/bin/redis-server /etc/redis/redis.conf

范例:

#创建Redis集群
redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 \
172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1

$ docker exec -it redis-1 /bin/sh
/data # redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 \
> 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 172.38.0.15:6379 to 172.38.0.11:6379
Adding replica 172.38.0.16:6379 to 172.38.0.12:6379
Adding replica 172.38.0.14:6379 to 172.38.0.13:6379
M: 24a8d8f3302ce28f1b9b06ce3bec871adb8585df 172.38.0.11:6379
   slots:[0-5460] (5461 slots) master
M: 2eff279dc91be6f8840a59f990b58ac740ebfa01 172.38.0.12:6379
   slots:[5461-10922] (5462 slots) master
M: 5090c8478f101c5f1520b9264cab5d602befd6a8 172.38.0.13:6379
   slots:[10923-16383] (5461 slots) master
S: 2d2733a06379f90a2c51c0e131b0391160d8f6b8 172.38.0.14:6379
   replicates 5090c8478f101c5f1520b9264cab5d602befd6a8
S: 55d41b51e6053d6dcf9b730a79ce6cca1314e363 172.38.0.15:6379
   replicates 24a8d8f3302ce28f1b9b06ce3bec871adb8585df
S: f8d17750650743957b846ccaab8799fca7c301a9 172.38.0.16:6379
   replicates 2eff279dc91be6f8840a59f990b58ac740ebfa01
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
...
>>> Performing Cluster Check (using node 172.38.0.11:6379)
M: 24a8d8f3302ce28f1b9b06ce3bec871adb8585df 172.38.0.11:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: 2d2733a06379f90a2c51c0e131b0391160d8f6b8 172.38.0.14:6379
   slots: (0 slots) slave
   replicates 5090c8478f101c5f1520b9264cab5d602befd6a8
S: 55d41b51e6053d6dcf9b730a79ce6cca1314e363 172.38.0.15:6379
   slots: (0 slots) slave
   replicates 24a8d8f3302ce28f1b9b06ce3bec871adb8585df
M: 5090c8478f101c5f1520b9264cab5d602befd6a8 172.38.0.13:6379
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
M: 2eff279dc91be6f8840a59f990b58ac740ebfa01 172.38.0.12:6379
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: f8d17750650743957b846ccaab8799fca7c301a9 172.38.0.16:6379
   slots: (0 slots) slave
   replicates 2eff279dc91be6f8840a59f990b58ac740ebfa01
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

/data # redis-cli -c
#查看Redis集群的信息
127.0.0.1:6379> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:249
cluster_stats_messages_pong_sent:250
cluster_stats_messages_sent:499
cluster_stats_messages_ping_received:245
cluster_stats_messages_pong_received:249
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:499
#查看Redis集群节点健康
127.0.0.1:6379> cluster nodes
24a8d8f3302ce28f1b9b06ce3bec871adb8585df 172.38.0.11:6379@16379 myself,master - 0 1661526536000 1 connected 0-5460
2d2733a06379f90a2c51c0e131b0391160d8f6b8 172.38.0.14:6379@16379 slave 5090c8478f101c5f1520b9264cab5d602befd6a8 0 1661526537442 4 connected
55d41b51e6053d6dcf9b730a79ce6cca1314e363 172.38.0.15:6379@16379 slave 24a8d8f3302ce28f1b9b06ce3bec871adb8585df 0 1661526537000 5 connected
5090c8478f101c5f1520b9264cab5d602befd6a8 172.38.0.13:6379@16379 master - 0 1661526536000 3 connected 10923-16383
2eff279dc91be6f8840a59f990b58ac740ebfa01 172.38.0.12:6379@16379 master - 0 1661526536433 2 connected 5461-10922
f8d17750650743957b846ccaab8799fca7c301a9 172.38.0.16:6379@16379 slave 2eff279dc91be6f8840a59f990b58ac740ebfa01 0 1661526535526 6 connected
127.0.0.1:6379> set k1 v1
-> Redirected to slot [12706] located at 172.38.0.13:6379 #redis-3 的容器操作
OK
#redis-3暂停后再次查看
/data # redis-cli -c
127.0.0.1:6379> cluster nodes
24a8d8f3302ce28f1b9b06ce3bec871adb8585df 172.38.0.11:6379@16379 myself,master - 0 1661526692000 1 connected 0-5460
2d2733a06379f90a2c51c0e131b0391160d8f6b8 172.38.0.14:6379@16379 master - 0 1661526693000 7 connected 10923-16383
55d41b51e6053d6dcf9b730a79ce6cca1314e363 172.38.0.15:6379@16379 slave 24a8d8f3302ce28f1b9b06ce3bec871adb8585df 0 1661526693143 5 connected
5090c8478f101c5f1520b9264cab5d602befd6a8 172.38.0.13:6379@16379 master,fail - 1661526636272 1661526634000 3 connected
2eff279dc91be6f8840a59f990b58ac740ebfa01 172.38.0.12:6379@16379 master - 0 1661526692740 2 connected 5461-10922
f8d17750650743957b846ccaab8799fca7c301a9 172.38.0.16:6379@16379 slave 2eff279dc91be6f8840a59f990b58ac740ebfa01 0 1661526691733 6 connected
127.0.0.1:6379> get k1
-> Redirected to slot [12706] located at 172.38.0.14:6379 #redis-4 从容器变成Master
"v1"

#将redis-3 容器暂停
$ docker stop redis-3

猜你喜欢

转载自blog.csdn.net/weixin_40274679/article/details/131736882