镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,他包含运行某个软件所需的所有内容,包括代码、运行时库、环境变量和配置文件。
所有应用,直接打包docker镜像,就可以直接跑起来!
如何得到镜像:
UnionFS (联合文件系统)
**UnionFs(联合文件系统):**Union文件系统(UnionFs)是一种分层、轻量级并且高性能的文件系统,他支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下( unite several directories into a single virtual filesystem)。Union文件系统是 Docker镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
**特性:**一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。
Docker镜像加载原理
Docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统UnionFS。
**boots(boot file system)*主要包含 bootloader和 Kernel, bootloader主要是引导加 kernel, Linux刚启动时会加bootfs文件系统,在 Docker镜像的最底层是 boots。这一层与我们典型的Linux/Unix系统是一样的,包含boot加載器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由 bootfs转交给内核,此时系统也会卸载bootfs。
rootfs(root file system),在 bootfs之上。包含的就是典型 Linux系统中的/dev,/proc,/bin,/etc等标准目录和文件。 rootfs就是各种不同的操作系统发行版,比如 Ubuntu, Centos等等。
平时我们安装进虚拟机的CentOS都是好几个G,为什么Docker这里才200M?
对于个精简的OS,rootfs可以很小,只需要包合最基本的命令,工具和程序库就可以了,因为底层直接用Host的kernel,自己只需要提供rootfs就可以了。由此可见对于不同的Linux发行版, boots基本是一致的, rootfs会有差別,因此不同的发行版可以公用bootfs.
虚拟机是分钟级别,容器是秒级!
分层的镜像
我们可以去下载一个镜像,注意观察下载的日志输出,可以看到是一层层的在下载
思考:为什么Docker采用这种分层的结构呢?
最大的好处,我觉得莫过于资源共享了!比如有多个镜像都从相同的Base镜像构建而来,那么宿主机只需在磁盘上保留一份base镜像,同时内存中也只需要加载一份base镜像,这样就可以为所有的容器服务了,而且镜像的每一层都可以被共享。
查看镜像分层的方式可以通过docker image inspect
命令
[root@zjdzka ~]# docker image inspect redis
[
{
"Id": "sha256:621ceef7494adfcbe0e523593639f6625795cc0dc91a750629367a8c7b3ccebb",
"RepoTags": [
"redis:latest"
],
"RepoDigests": [
"redis@sha256:0f97c1c9daf5b69b93390ccbe8d3e2971617ec4801fd0882c72bf7cad3a13494"
],
"Parent": "",
"Comment": "",
"Created": "2021-01-13T09:45:41.527587343Z",
"Container": "16535cfaf84a4049b6c02840219e8473787d5610e29409049df3a41bbf77a333",
"ContainerConfig": {
"Hostname": "16535cfaf84a",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"6379/tcp": {
}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"GOSU_VERSION=1.12",
"REDIS_VERSION=6.0.10",
"REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-6.0.10.tar.gz",
"REDIS_DOWNLOAD_SHA=79bbb894f9dceb33ca699ee3ca4a4e1228be7fb5547aeb2f99d921e86c1285bd"
],
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"redis-server\"]"
],
"Image": "sha256:222c0cecc006d8c73a04a58b5fa15ebae171a6e82a8ee8650ae616f6f1798ef4",
"Volumes": {
"/data": {
}
},
"WorkingDir": "/data",
"Entrypoint": [
"docker-entrypoint.sh"
],
"OnBuild": null,
"Labels": {
}
},
"DockerVersion": "19.03.12",
"Author": "",
"Config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"6379/tcp": {
}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"GOSU_VERSION=1.12",
"REDIS_VERSION=6.0.10",
"REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-6.0.10.tar.gz",
"REDIS_DOWNLOAD_SHA=79bbb894f9dceb33ca699ee3ca4a4e1228be7fb5547aeb2f99d921e86c1285bd"
],
"Cmd": [
"redis-server"
],
"Image": "sha256:222c0cecc006d8c73a04a58b5fa15ebae171a6e82a8ee8650ae616f6f1798ef4",
"Volumes": {
"/data": {
}
},
"WorkingDir": "/data",
"Entrypoint": [
"docker-entrypoint.sh"
],
"OnBuild": null,
"Labels": null
},
"Architecture": "amd64",
"Os": "linux",
"Size": 104285909,
"VirtualSize": 104285909,
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/4ea64043cf121b3633eca9ba2cb2a85682c62a1c6f074c501569766c1e0ce945/diff:/var/lib/docker/overlay2/2c49976e43d1f9bb6ce97a8edf287e418e46a99e454ea95a6a443ea2973f18ad/diff:/var/lib/docker/overlay2/782c232af88b85bb60cf2f6e36b958e699367045fe7bea2ac770818270571b1d/diff:/var/lib/docker/overlay2/9606bb1f1b3ff06a56813d192546dd20ed5f9f6fd2384c4b28ed0cdc1becab15/diff:/var/lib/docker/overlay2/42ff0bd2b7e0e2c6cd1a5d856eeca813dd1414fcba4864fb87f62a87344c10a0/diff",
"MergedDir": "/var/lib/docker/overlay2/a5609f3560fc767cca20491b9814fd52cedbe4a7ce2cd330d04bd320d6f4c5b7/merged",
"UpperDir": "/var/lib/docker/overlay2/a5609f3560fc767cca20491b9814fd52cedbe4a7ce2cd330d04bd320d6f4c5b7/diff",
"WorkDir": "/var/lib/docker/overlay2/a5609f3560fc767cca20491b9814fd52cedbe4a7ce2cd330d04bd320d6f4c5b7/work"
},
"Name": "overlay2"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:cb42413394c4059335228c137fe884ff3ab8946a014014309676c25e3ac86864",
"sha256:8e14cb7841faede6e42ab797f915c329c22f3b39026f8338c4c75de26e5d4e82",
"sha256:1450b8f0019c829e638ab5c1f3c2674d117517669e41dd2d0409a668e0807e96",
"sha256:f927192cc30cb53065dc266f78ff12dc06651d6eb84088e82be2d98ac47d42a0",
"sha256:a24a292d018421783c491bc72f6601908cb844b17427bac92f0a22f5fd809665",
"sha256:3480f9cdd491225670e9899786128ffe47054b0a5d54c48f6b10623d2f340632"
]
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]
理解:
所有的 Docker镜像都起始于一个基础镜像层,当进行修改或培加新的内容时,就会在当前镜像层之上,创建新的镜像层。
举一个简单的例子,假如基于 Ubuntu Linux16.04创建一个新的镜像,这就是新镜像的第一层;如果在该镜像中添加 Python包,
就会在基础镜像层之上创建第二个镜像层;如果继续添加一个安全补丁,就会创健第三个镜像层该像当前已经包含3个镜像层,如下图所示(这只是一个用于演示的很简单的例子)。
在添加额外的镜像层的同时,镜像始终保持是当前所有镜像的组合,理解这一点.
在添加额外的镜像层的同时,镜像始终保持是当前所有镜像的组合,理解这一点非常重要。下图中举了一个简单的例子,每个镜像层包含3个文件,而镜像包含了来自两个镜像层的6个文件。
上图中的镜像层跟之前图中的略有区別,主要目的是便于展示文件
下图中展示了一个稍微复杂的三层镜像,在外部看来整个镜像只有6个文件,这是因为最上层中的文件7是文件5的一个更新版。
文种情況下,上层镜像层中的文件覆盖了底层镜像层中的文件。这样就使得文件的更新版本作为一个新镜像层添加到镜像当中
Docker通过存储引擎(新版本采用快照机制)的方式来实现镜像层堆栈,并保证多镜像层对外展示为统一的文件系统
Linux上可用的存储引撃有AUFS、 Overlay2、 Device Mapper、Btrfs以及ZFS。顾名思义,每种存储引擎都基于 Linux中对应的
件系统或者块设备技术,井且每种存储引擎都有其独有的性能特点。
Docker在 Windows上仅支持 windowsfilter 一种存储引擎,该引擎基于NTFS文件系统之上实现了分层和CoW [1]。
下图展示了与系统显示相同的三层镜像。所有镜像层堆并合井,对外提供统一的视图。
特点
Docker 镜像都是只读的,当容器启动时,一个新的可写层加载到镜像的顶部!
这一层就是我们通常说的容器层,容器之下的都叫镜像层!
docker commit 提交容器成为一个新的副本
# 命令和git原理类似
docker commit -m="描述信息" -a="作者" 容器id 目标镜像名:[版本TAG]
实战测试
# 1、启动一个默认的tomcat
[root@iz2zeak7sgj6i7hrb2g862z ~]# docker run -d -p 8080:8080 tomcat
de57d0ace5716d27d0e3a7341503d07ed4695ffc266aef78e0a855b270c4064e
# 2、发现这个默认的tomcat 是没有webapps应用,官方的镜像默认webapps下面是没有文件的!
#docker exec -it 容器id /bin/bash
[root@iz2zeak7sgj6i7hrb2g862z ~]# docker exec -it de57d0ace571 /bin/bash
root@de57d0ace571:/usr/local/tomcat#
# 3、从webapps.dist拷贝文件进去webapp
root@de57d0ace571:/usr/local/tomcat# cp -r webapps.dist/* webapps
root@de57d0ace571:/usr/local/tomcat# cd webapps
root@de57d0ace571:/usr/local/tomcat/webapps# ls
ROOT docs examples host-manager manager
# 4、将操作过的容器通过commit调教为一个镜像!我们以后就使用我们修改过的镜像即可,而不需要每次都重新拷贝webapps.dist下的文件到webapps了,这就是我们自己的一个修改的镜像。
docker commit -m="描述信息" -a="作者" 容器id 目标镜像名:[TAG]
docker commit -a="zjdzka" -m="add webapps app" 容器id tomcat02:1.0
[root@iz2zeak7sgj6i7hrb2g862z ~]# docker commit -a="csp提交的" -m="add webapps app" de57d0ace571 tomcat02:1.0
sha256:d5f28a0bb0d0b6522fdcb56f100d11298377b2b7c51b9a9e621379b01cf1487e
[root@iz2zeak7sgj6i7hrb2g862z ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
tomcat02.1.0 latest d5f28a0bb0d0 14 seconds ago 652MB
tomcat latest 1b6b1fe7261e 5 days ago 647MB
nginx latest 9beeba249f3e 5 days ago 127MB
mysql 5.7 b84d68d0a7db 5 days ago 448MB
elasticsearch 7.6.2 f29a1ee41030 8 weeks ago 791MB
portainer/portainer latest 2869fc110bf7 2 months ago 78.6MB
centos latest 470671670cac 4 months ago 237MB
hello-world latest bf756fb1ae65 4 months ago 13.3kB
如果你想要保存当前容器的状态,就可以通过commit来提交,获得一个镜像,就好比我们我们使用虚拟机的快照。
Docker的理念:将应用和环境打包成一个镜像!
数据?如果都在容器中,那么我们删除容器,数据就会丢失!
数据可以持久化
例如:Mysql,容器删了=删库跑路!===>Mysql的数据可以存储在本地!
**容器之间可以有一个数据共享的技术!**Docker容器中产生的数据,同步到本地!
这就是卷技术!目录的挂载,将我们容器内的目录,挂载到Linux上!
**总结:**容器的持久化和同步操作!容器间也是可以数据共享的!
方式一:直接使用命令挂载 -v
docker run -it -v 主机目录:容器内目录
#测试绑定数据卷
docker run -it -v /home/ceshi:/home centos /bin/bash
#用另一个会话测试:查看信息
docker inspect a0d694176556
思考:Mysql的数据持久化问题
#1.获取镜像
docker pull mysql:5.7
#2.运行容器,需要做数据挂载
# -d 后台运行
# -p 端口映射
# -v 卷挂载
# -e 环境配置
#--name 名字
docker run -d -p 3310:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7
#3.启动成功后,测试连接mysql
#sqlyog-连接到服务器的3310---3310和容器内的3306映射,这个时候,我们就可以连接上!
如果我们将容器删除,我们挂载到本地的数据依旧存在,这就是数据卷持久化!
# 匿名挂载
-v 容器内路径!
docker run -d -p --name nginx01 -v /etc/nginx nginx
#查看所有卷的情况
docker volume ls
#具名挂载
-v 卷名:容器内目录
-v juming-nginx:/etc/nginx
所有的docker容器内的卷,没有指定目录的情况下都是在/var/lib/docker/volumes/xxx/_data
#如何确定是哪种形式的挂载?
-v 容器内路径 #匿名
-v 卷名:容器内路径 #具名
-v /宿主机路径/..:容器内路径 #指定路径
扩展:
docker run -d -p -m --name nginx01 -v juming-nginx:/etc/nginx:ro nginx
#ro 或者 rw
ro:read only
rw:read write
#一旦设置了容器权限,容器对我们挂载出来的内容就有限定了!
Dockerfile就是用来构建docker镜像的构建文件!-----命令脚本
方式二:通过脚本生成镜像
#1.创建一个docker文件
#2.编写Dockerfile
FROM centos
VOLUME ["volume01","volume02"]
CMD echo "----end----"
CMD /bin/bash
#3.构建镜像
#4.测试一下自己写的镜像
#启动三个容器,通过我们自己写的镜像测试
结论:
容器之间配置信息的传递,数据卷容器的生命周期一直持续到没有容器使用为止。
但是一旦你持久化到了本地,本地的数据是不会删除的!
Dockerfile是用来构建docker镜像文件!
构建步骤:
基础知识:
1.每个保留关键字(指令)都必须是大写字母
2.执行顺序从上到下
3.#表示注释
4.每一个指令都会创建提交一个新的镜像层,并提交!
dockerfile是面向开发的,我们以后要发布项目,做镜像,就需要编写dockerfile文件,这个文件十分简单!
Docker镜像逐渐成为企业交付的标准!
DockerFile:构建文件,定义了一切的步骤,源代码
DockerImages:通过DockerFile构建生成的镜像,最终发布和运行的产品
Docker容器:容器就是镜像运行起来提供服务的
FROM #基础镜像,一切从这里开始
MAINTAINER #镜像的作者,名字+邮箱
RUN #镜像构建的时候运行的命令
ADD #步骤:tomcat镜像,这个tomcat压缩包!添加内容
WORKDIR #镜像的工作目录
VOLUME #挂载的目录
EXPORT #暴露端口配置= -p
CMD #指定容器启动的时候,运行的命令==>只要最后一个会生效,可替代
ENTRYPOINT #指定容器启动的时候,运行的命令==>可以追加命令
ONBUILD #当构建一个被继承DockerFile 这个时候就会运行这个指令,触发指令
COPY #类似ADD,拷贝
ENV #构建的时候,设置环境变量
第一步:编写dockerfile
[root@zjdzka dockerfile]# vim mydockerfile-centos
[root@zjdzka dockerfile]# cat mydockerfile-centos
FROM centos
MAINTAINER zjdzka<[email protected]>
ENV MYPATH /usr/local
WORKDIR $MYPATH
RUN yum -y install vim
RUN yum -y install net-tools
EXPOSE 80
CMD echo $MYPATH
CMD echo "----end----"
CMD /bin/bash
第二步:通过文件构建镜像
[root@zjdzka dockerfile]# docker build -f mydockerfile-centos -t zjdzkacentos:0.1 .
第三步:测试运行
CMD #指定容器启动的时候,运行的命令==>只要最后一个会生效,可替代
ENTRYPOINT #指定容器启动的时候,运行的命令==>可以追加命令
Dockrfile中很多命令十分相似,我们要对比学习,测试效果!
# 1. 编写dockerfile文件
[root@iZ2zeg4ytp0whqtmxbsqiiZ dockerfile]# vim dockerfile-cmd-test
FROM centos
CMD ["ls", "-a"]
# 2. 构建镜像
[root@iZ2zeg4ytp0whqtmxbsqiiZ dockerfile]# docker build -f dockerfile-cmd-test -t cmdtest .
# 3. run运行, 发现我们的ls -a 命令生效
[root@iZ2zeg4ytp0whqtmxbsqiiZ dockerfile]# docker run ebe6a52bb125
.
..
.dockerenv
bin
dev
etc
home
lib
lib64
# 想追加一个命令 -l 变成 ls -al
[root@iZ2zeg4ytp0whqtmxbsqiiZ dockerfile]# docker run ebe6a52bb125 -l
docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"-l\": executable file not found in $PATH": unknown.
[root@iZ2zeg4ytp0whqtmxbsqiiZ dockerfile]# docker run ebe6a52bb125 ls -l
# cmd的情况下 -l替换了CMD["ls", "-a"]命令, -l不是命令,所以报错了
# 1. 编写dockerfile文件
[root@iZ2zeg4ytp0whqtmxbsqiiZ dockerfile]# vim dockerfile-entrypoint-test
FROM centos
ENTRYPOINT ["ls", "-a"]
# 2. 构建文件
[root@iZ2zeg4ytp0whqtmxbsqiiZ dockerfile]# docker build -f dockerfile-entrypoint-test -t entrypoint-test .
# 3. run运行 发现我们的ls -a 命令同样生效
[root@iZ2zeg4ytp0whqtmxbsqiiZ dockerfile]# docker run entrypoint-test
.
..
.dockerenv
bin
dev
etc
home
lib
# 4. 我们的追加命令, 是直接拼接到ENTRYPOINT命令的后面的!
[root@iZ2zeg4ytp0whqtmxbsqiiZ dockerfile]# docker run entrypoint-test -l
total 56
drwxr-xr-x 1 root root 4096 Aug 13 07:52 .
drwxr-xr-x 1 root root 4096 Aug 13 07:52 ..
-rwxr-xr-x 1 root root 0 Aug 13 07:52 .dockerenv
lrwxrwxrwx 1 root root 7 May 11 2019 bin -> usr/bin
drwxr-xr-x 5 root root 340 Aug 13 07:52 dev
drwxr-xr-x 1 root root 4096 Aug 13 07:52 etc
drwxr-xr-x 2 root root 4096 May 11 2019 home
lrwxrwxrwx 1 root root 7 May 11 2019 lib -> usr/lib
lrwxrwxrwx 1 root root 9 May 11 2019 lib64 -> usr/lib64
drwx------ 2 root root 4096 Aug 9 21:40 lost+found
# docker 是如何处理容器的网络访问的?
#1.启动tomcat
docker run -d -P --name tomcat01 tomcat
#2.查看容器的内网地址 ip addr
#docker exec -it tomcat01 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
26: eth0@if27: <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通docker容器?
# ping 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.076 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.042 ms
64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.052 ms
#linux可以ping通容器内部
分析原理
1.我们每启动一个docker容器,docker就会给docker容器分配一个ip,我们只要安装了docker,就会有一个网卡docker0桥接技术,使用的技术是evth—pair技术!
#我们发现这个容器带来的网卡,都是一对对的
#evth-pair 就是一对的虚拟设备接口,他们都是成对出现的,一段连着协议,一段彼此相连
#正因为有这个特性,evth-pair 充当一个桥梁,连接各种虚拟网络设备的
2.我们测试tomcat01 和tomcat02 是否可以ping通
#测试成功,ping通!
docker exec -it tomcat02 ping 172.17.0.2
tomcat01和tomcat02共用的一个路由器:docker0
所有的容器不知道网络的情况下,都是docker0路由的,docker会给我们容器分配一个默认的可用ip
只要容器删除,对应的网桥也随之没了
小结
Docker 使用的是Linux的桥接网卡,宿主机中是一个Docker容器的网桥 docker0。
Docker中的所有的网络接口都是虚拟的,虚拟的转发效率高!
思考一个场景,我们编写了一个微服务,database url=ip:,项目不重启,数据库ip换掉了,我们希望可以处理这个问题:通过名字来访问容器
我们发现直接ping,并不能解决问题
测试:
[root@zjdzka ~]# docker run -d -P --name tomcat03 --link tomcat02 tomcat
e1cfed7c63e87d6b74eb0a17fc23270881d9f7258d9bd5497ec5158157176a78
[root@zjdzka ~]# docker exec -it tomcat03 ping 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.103 ms
64 bytes from tomcat02 (172.17.0.3): icmp_seq=2 ttl=64 time=0.070 ms
64 bytes from tomcat02 (172.17.0.3): icmp_seq=3 ttl=64 time=0.085 ms
#发现通过--link就可解决问题,但是反向可以嘛? tomcat02 ping tomcat03
[root@zjdzka ~]# docker exec -it tomcat02 ping tomcat 03
ping: tomcat: Name or service not known
tomcat03和tomcat02通过–link建立连接,其中发生了什么?
原理:其实就是在我们的hosts配置中添加了一个映射
docker0问题:不支持容器名连接访问!
bridge:桥接 docker(默认)
none:不配置网络
host: 和宿主机共享网络
container:容器网络连通(用到少!)
#w我们直接启动的命令 --net bridge,而这个就是我们的docker0
docker run -d -P --name tomcat01 tomcat
docker run -d -P --name tomcat01 --net bridge tomcat
#自定义网络
# --driver bridge
# --subnet 192.168.0.0/16
# --gateway 192.168.0.1
docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet
docker network inspect mynet
[root@zjdzka ~]# docker run -d -P --name tomcat-net-01 --net mynet tomcat
70cef0b1886e7488e9f1bbcdebcddad27e0c8b95e3008499051977e45a5baaea
[root@zjdzka ~]# docker run -d -P --name tomcat-net-02 --net mynet tomcat
ee6882df470433c46df318634b83f912aabb0c08562e50503f52c7ca5a490a09
[root@zjdzka ~]# docker exec -it tomcat-net-01 ping tomcat-net-02
好处:
redis-不同的集群使用不同的网络,保证集群是安全和健康的
mysql-不同的集群使用不同的网络,保证集群是安全和健康的
#打通tomcat01---tomcat-net-01
docker network connect mynet tomcat01
#连通之后,将我们的tomcat01放到了mynet网络下
#一个容器两个ip:公网ip,私网ip
docker exec -it tomcat01 ping tomcat-net-01