十、Docker镜像

1、什么是镜像

镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需要的所有内容,包括代码,运行时(一个程序在运行或者在被执行的依赖)、库,环境变量和配置文件。

2、镜像加载原理

Docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统是UnionFS联合文件系统。

UnionFS联合文件系统

UnionFS (联合文件系统) : Union文件系统( UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。Union文件系统是Docker镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。

特性: 一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。

例如:有镜像1和镜像2,镜像1下载了centos内核,镜像2也需要centos内核,那此时镜像2就不用再下载centos内核,因为可以与镜像1的centos内核共用。

Docker镜像加载原理

docker的镜像实际上是由一层一层的文件系统组成,即UnionFS。

bootfs(boot file system)主要包含bootloader和kernel,bootloader主要是引导加载kernel,Linux刚启动时会加载bootfs文件系统,在Docker镜像的最底层是bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。

rootfs (root file system),在bootfs之上。包含的就是典型Linux系统中的/dev,/proc,/bin,/etc等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu,Centos等等。

平常的centos系统至少都几个g,为什么docker中的镜像才200多兆?

对于一个精简的OS,rootfs可以很小,只需要包含最基本的命令、工具和程序库就可以了。因为底层直接用宿主机的kernel,自己只需要提供rootfs就可以了。由此可见对于不同的linux发行版,bootfs基本是一致的,rootfs会有差别,因此不同的发行版可以共用bootfs。

3、分层理解

我们可以去下载一个镜像,查看日志输入,发现是分层下载的。

思考:为什么Docker镜像要采用这种结构呢?

最大的好处,我觉得莫过于是资源共享了!比如有多个镜像都从相同的Base镜像构建而来,那么宿主机只需在磁盘上保留一份base镜像,同时内存中也只需要加载一份base镜像,这样就可以为所有的容器服务了,而且镜像的每一层都可以被共享。

查看镜像分层的方式可以通过docker image inspect命令:

[root@VM-20-6-centos ~]# docker image inspect nginx:latest
[
    {
        "Id": "sha256:f2f70adc5d89aa922836e9cc6801980a12a7ff9012446cc6edf52ef8798a67bd",
        "RepoTags": [
            "nginx:latest"
        ],
        "RepoDigests": [
            "nginx@sha256:4ed64c2e0857ad21c38b98345ebb5edb01791a0a10b0e9e3d9ddde185cdbd31a"
        ],
        "Parent": "",
        "Comment": "",
        "Created": "2022-03-17T18:37:34.337367188Z",
        "Container": "9c0bfa83eb335b15e72e05cfa7222a68aee957bd04506b752307fdeeae7a6822",
        "ContainerConfig": {
            "Hostname": "9c0bfa83eb33",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "80/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "NGINX_VERSION=1.21.6",
                "NJS_VERSION=0.7.2",
                "PKG_RELEASE=1~bullseye"
            ],
            "Cmd": [
                "/bin/sh",
                "-c",
                "#(nop) ",
                "CMD [\"nginx\" \"-g\" \"daemon off;\"]"
            ],
            "Image": "sha256:9c088c153d0263afd5e854438887f0eb7ea145b0578e3b850013c6a3078c27e4",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": [
                "/docker-entrypoint.sh"
            ],
            "OnBuild": null,
            "Labels": {
                "maintainer": "NGINX Docker Maintainers <docker-maint@nginx.com>"
            },
            "StopSignal": "SIGQUIT"
        },
        "DockerVersion": "20.10.12",
        "Author": "",
        "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "80/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "NGINX_VERSION=1.21.6",
                "NJS_VERSION=0.7.2",
                "PKG_RELEASE=1~bullseye"
            ],
            "Cmd": [
                "nginx",
                "-g",
                "daemon off;"
            ],
            "Image": "sha256:9c088c153d0263afd5e854438887f0eb7ea145b0578e3b850013c6a3078c27e4",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": [
                "/docker-entrypoint.sh"
            ],
            "OnBuild": null,
            "Labels": {
                "maintainer": "NGINX Docker Maintainers <docker-maint@nginx.com>"
            },
            "StopSignal": "SIGQUIT"
        },
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 141523072,
        "VirtualSize": 141523072,
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/11042f23b32cacbae4c32f35a4dcb1e42902ed0e41707ad7fd1e32aca079de3d/diff:/var/lib/docker/overlay2/d494434e4bbd0bef1c2b8f6683760faa77d7668839125ee218e7ed8ff521062b/diff:/var/lib/docker/overlay2/9a2355a0ad67923ec7a8b543351bf278879a3b0902c2f4d94f1ae6429277b6b7/diff:/var/lib/docker/overlay2/a837b231718a84f25469112d850a494f0b09bac764135e5990ca0ef5b576088f/diff:/var/lib/docker/overlay2/40e739161ac41a0160c2a48010da59fc71b7f8ffd87f750beda5371b6e41d946/diff",
                "MergedDir": "/var/lib/docker/overlay2/e19485706300126dfff36beb3f0313564f0f4f1e478d27c8ead6f24f08840212/merged",
                "UpperDir": "/var/lib/docker/overlay2/e19485706300126dfff36beb3f0313564f0f4f1e478d27c8ead6f24f08840212/diff",
                "WorkDir": "/var/lib/docker/overlay2/e19485706300126dfff36beb3f0313564f0f4f1e478d27c8ead6f24f08840212/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:3a626bb08c24b5cc968d312bf5694aa87b6d9961c5f182c6bc138d8ca8ac13ee",
                "sha256:30c00b5281a1b19edcf44ab863eaf3ba4918b43c201fa778c0784c69882c8de5",
                "sha256:8b8ecda1d12d64d07bcf75282912d952666c4ca21b1d26874ba9cf1605b24f18",
                "sha256:2793e885dc34c929ce7875b84fee7940584689617c73347aef78d803b6cdd0b3",
                "sha256:d00147ef6763226b12c4de69c36000eb57718799411d6af91ab5761ddf77d761",
                "sha256:24037b645d66baa74b756af08594d33a72f5d5427e9032460d229bf120a8ff3e"
            ]
        },
        "Metadata": {
            "LastTagTime": "0001-01-01T00:00:00Z"
        }
    }
]

这里指示了分层信息:

"RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:3a626bb08c24b5cc968d312bf5694aa87b6d9961c5f182c6bc138d8ca8ac13ee",
                "sha256:30c00b5281a1b19edcf44ab863eaf3ba4918b43c201fa778c0784c69882c8de5",
                "sha256:8b8ecda1d12d64d07bcf75282912d952666c4ca21b1d26874ba9cf1605b24f18",
                "sha256:2793e885dc34c929ce7875b84fee7940584689617c73347aef78d803b6cdd0b3",
                "sha256:d00147ef6763226b12c4de69c36000eb57718799411d6af91ab5761ddf77d761",
                "sha256:24037b645d66baa74b756af08594d33a72f5d5427e9032460d229bf120a8ff3e"
            ]
        },

理解:

所有的Docker镜像都起始于一个基础镜像层,当进行修改或增加新的内容时,就会在当前镜像层之上创建新的镜像层。

举一个简单的例子,假如基于Ubuntu Linux 16.04创建一个新的镜像,这就是新镜像的第一层;如果在该镜像中添加Python包,就会在基础镜像层之上创建第二个镜像层;如果继续添加一个安全补丁,就会创建第三个镜像层。该镜像当前已经包含3个镜像层,如下图所示(这只是一个于演示的很简单的例子 )。

docker分层理解1

在添加额外的镜像层的同时,镜像始终保持是当前所有镜像的组合,理解这一点非常重要。下图中举了一个简单的例子:第1层包含3个文件,第2层也包含3个文件,且他们之间没有冲突,组合之后镜像包含了来自两个镜像层的6个文件。

docker分层理解2

上图中的镜像层跟之前图中的略有区别,主要目的是便于展示文件。
下图中展示了一个稍微复杂的三层镜像,在外部看来整个镜像只有6个文件,这是因为最上层中的文件7是文件5的一个更新版本。

docker分层理解3

这种情况下,上层镜像层中的文件覆盖了底层镜像层中的文件。这样就使得文件的更新版本作为一个新镜像层添加到镜像当中。

Docker通过存储引擎(新版本采用快照机制)的方式来实现镜像层堆栈,并保证多镜像层对外展示为统一的文件系统。

Linux上可用的存储引擎有AUFS、Overlay2、Device Mapper、Btrfs 以及ZFS。顾名思义,每种存储引擎都基于Linux中对应的文件系统或者块设备技术,并且每种存储引擎都有其独有的性能特点。

Docker在Windows上仅支持windowsfilter一种存储引擎,该引擎基于NTFS文件系统之上实现了分层和CoW[1]。

下图展示了与系统显示相同的三层镜像。所有镜像层堆叠并合并,对外提供统一的视图。

特点

Docker镜像都是只读的,当容器启动时,一个新的可写层被加载到镜像的顶部

这一层就是我们通常说的容器层,容器之下的都叫镜像层,而我们每对容器进行的修改都会新加一层

4、commit镜像

#使用docker commit 命令提交容器成为一个新的版本

docker commit -m=“提交的描述信息”  -a="作者" 容器id 目标镜像名:[TAG]

由于默认的Tomcat镜像的webapps文件夹中没有任何内容,需要从webapps.dist中拷贝文件到webapps文件夹。下面自行制作镜像:就是从webapps.dist中拷贝文件到webapps文件夹下,并提交该镜像作为一个新的镜像。使得该镜像默认的webapps文件夹下就有文件。具体命令如下:

#启动Tomcat
docker run -it tomcat /bin/bash
#复制
cp -r webapps.dist/* webapps
#新建终端,查看正在运行的容器
docker ps
#提交容器成为镜像
docker commit -m="add webapps" -a="srx" 2a3bf3eaa2e4 mytomcat:1.0
#查看镜像
docker images
#运行mytomcat:1.0
docker run -it mytomcat:1.0 /bin/bash #进入可以看到webapps文件夹中有我们之前复制的文件

十一、容器数据卷

1、数据卷简介

Docker将运用与运行的环境打包形成容器运行, Docker容器产生的数据,如果不通过docker commit生成新的镜像,使得数据做为镜像的一部分保存下来, 那么当容器删除后,数据自然也就没有了。 为了能保存数据在Docker中我们使用卷。

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

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

数据卷的特点:

  • 数据卷可在容器之间共享或重用数据
  • 卷中的更改可以直接生效
  • 数据卷中的更改不会包含在镜像的更新中
  • 数据卷的生命周期一直持续到没有容器使用它为止

2、数据卷的简单使用

方式一:直接通过命令 -v

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

测试:

#终端一启动centos
docker run -it -v /home/test:/home centos /bin/bash
#终端二可看到创建的test文件夹
cd /home
ls
#终端一创建文件
touch test.sh
#终端二中可查看到文件test.sh
cd /home/test
ls
#终端二中创建文件
touch demo.sh
#终端一中可查看到文件
cd /home
ls

查看容器对应元数据docker inspect 容器id,可以在Mounts节点查看建立的数据卷信息:

"Mounts": [
            {
                "Type": "bind",
                "Source": "/home/test", #主机内的地址
                "Destination": "/home", #容器内的地址
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],

即使容器停止运行或者容器删除,仍然可以实现数据同步,本地的数据卷不会丢失,因为他们是共享同一个地址的,所以不存在数据不同步和丢失的情况

3、MySQL建立数据卷

docker run -d -p 6603: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

如果使用配置的密码连接mysql服务失败,原因很大可能是本机挂载的配置文件中已有文件,将容器中的配置给覆盖了,我们将相应的本机文件中的文件配置删除即可。

4、常用命令

  • 创建数据卷

    docker volume create 数据卷名称
    
  • 查看所有数据卷

    docker volume ls
    
  • 查看指定数据卷的信息

    docker volume inspect 数据卷名称
    
  • 删除数据卷

    docker volume rm 数据卷名称
    
  • 删除容器之时删除相关的卷

    docker rm -v ...
    
  • 删除无主的数据卷

    docker volume prune
    

    数据卷是被设计用来持久化数据的,它的生命周期独立于容器,Docker 不会在容器被删除后自动删除数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷 。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用 docker rm -v 这个命令。

5、具名挂载和匿名挂载

1.匿名挂载

匿名挂载就是在指定数据卷的时候,不指定容器路径对应的主机路径,这样对应映射的主机路径就是默认的路径/var/lib/docker/volumes/中自动生成一个随机命名的文件夹。

如下运行并匿名挂载Nginx容器:

docker run -d -P --name nginx01 -v /etc/nginx nginx

2.具名挂载

具名挂载,就是指定文件夹名称,区别于指定路径挂载,这里的指定文件夹名称是在Docker指定的默认数据卷路径下的。通过docker volume ls命令可以查看当前数据卷的目录情况。

如下运行并具名挂载Nginx容器:

docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx nginx

查看指定的数据卷信息的命令:

docker volume inspect 数据卷名称

Docker所有的数据卷默认在/var/lib/docker/volumes/ 目录下

匿名挂载,具名挂载,指定路径挂载的命令区别如下:

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

指定数据卷映射的相关参数:

ro —— readonly 只读。设置了只读则只能操作宿主机的路径,不能操作容器中的对应路径。

rw ----- readwrite 可读可写

docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:ro nginx
docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:rw nginx

6、Dockerfile中设置数据卷

我们可以在Dockerfile中使用VOLUME指令来给镜像添加一个或多个数据卷。

下面使用Dockerfile构建一个新的镜像,dockerfile01文件的内容,匿名挂载了volume01和volume02两个目录:

FROM centos

VOLUME ["volume01","volume02"]

CMD echo "----end----"
CMD /bin/bash

执行并构建镜像:

[root@iZwz99sm8v95sckz8bd2c4Z docker-test-volume]# docker build -f /www/dockerfile -t mycentos:1.0 .
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM centos
 ---> 300e315adb2f
Step 2/4 : VOLUME ["volume01","volume02"]
 ---> Running in 215ef28fd5a6
Removing intermediate container 215ef28fd5a6
 ---> f506ddf133d2
Step 3/4 : CMD echo "----end----"
 ---> Running in 62a1c4b9dc7b
Removing intermediate container 62a1c4b9dc7b
 ---> bbea81a6e94e
Step 4/4 : CMD /bin/bash
 ---> Running in 245d239f3776
Removing intermediate container 245d239f3776
 ---> 1df90e6fd790
Successfully built 1df90e6fd790
Successfully tagged ethan/centos:1.0
[root@iZwz99sm8v95sckz8bd2c4Z docker-test-volume]# docker images
REPOSITORY            TAG       IMAGE ID       CREATED          SIZE
mycentos              1.0       1df90e6fd790   13 minutes ago   209MB
mytomcat              1.0       f189aac861de   25 hours ago     653MB
mysql                 5.7       f07dfa83b528   7 days ago       448MB
tomcat                latest    feba8d001e3f   11 days ago      649MB
nginx                 latest    ae2feff98a0c   13 days ago      133MB
centos                latest    300e315adb2f   3 weeks ago      209MB
portainer/portainer   latest    62771b0b9b09   5 months ago     79.1MB
elasticsearch         7.6.2     f29a1ee41030   9 months ago     791MB

完成镜像的生成后,启动自己生成的容器:

[root@73422f706266 /]# docker run -it 89aed38ec414 /bin/bash
[root@73422f706266 /]# ls
bin  etc   lib    lost+found  mnt  proc  run   srv  tmp  var       volume02
dev  home  lib64  media       opt  root  sbin  sys  usr  volume01 #可看到挂载的volume01、volume02

可以看到自动挂载的数据卷目录。下面查看对应宿主机的数据卷目录:

docker inspect 73422f706266

 "Mounts": [
            {
                "Type": "volume",
                "Name": "8280ed081572b9b3a0cc53871c9da128722aa47bdf02d6751306f15d3030928a",
                "Source": "/var/lib/docker/volumes/8280ed081572b9b3a0cc53871c9da128722aa47bdf02d6751306f15d3030928a/_data",
                "Destination": "volume01",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            },
            {
                "Type": "volume",
                "Name": "80530e4b8ea2479e19febb6b17a548b8d8672a6e26c1052e772368b048d4ad43",
                "Source": "/var/lib/docker/volumes/80530e4b8ea2479e19febb6b17a548b8d8672a6e26c1052e772368b048d4ad43/_data",
                "Destination": "volume02",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],

可以看到Mounts下有宿主机的挂载目录。因为dockerfile中没有指定宿主机目录,所以属于匿名挂载,在/var/lib/docker/volumes/目录下生成了随机命名的路径。

7、容器数据卷

容器数据卷是指建立数据卷,来同步多个容器间的数据,实现容器间的数据同步

首先启动容器1,volume01、volume02为挂载目录。

[root@VM-20-6-centos www]# docker run -it --name docker01 mycentos:1.0
[root@cb2f66d0d356 /]# ls
bin  etc   lib    lost+found  mnt  proc  run   srv  tmp  var       volume02
dev  home  lib64  media       opt  root  sbin  sys  usr  volume01

然后启动容器2,通过参数--volumes-from,设置容器2和容器1建立数据卷挂载关系。

[root@VM-20-6-centos www]# docker run -it --name docker02 --volumes-from docker01 mycentos:1.0
[root@0a071281109e /]# ls
bin  etc   lib    lost+found  mnt  proc  run   srv  tmp  var       volume02
dev  home  lib64  media       opt  root  sbin  sys  usr  volume01

首先在容器2中的volume01中添加文件:

[root@0a071281109e /]# cd /volume01
[root@0a071281109e volume01]# ls
[root@0a071281109e volume01]# touch docker
[root@0a071281109e volume01]# ls
docker

然后就可以看到容器1的文件也会添加上了

下面同步两个MySQL的数据库和配置文件,与上面的操作相同,首先建立数据卷,然后给另一个MySQL容器建立容器数据卷挂载,示例:

[root@iZwz99sm8v95sckz8bd2c4Z home]# docker run -d -p 6603: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
[root@iZwz99sm8v95sckz8bd2c4Z home]# docker run -d -p 6604:3306 -e MYSQL_ROOT_PASSWORD=123456 --name mysql02 --volumes-from mysql01 mysql:5.7

十二、Dockerfile

1、Dockerfile介绍

Dockerfile是用来构建Docker镜像的文本文件,也可以说是命令参数脚本。docker build命令用于从Dockerfile构建镜像。可以在docker build命令中使用-f标志指向文件系统中任何位置的Dockerfile。

Docker镜像发布的步骤:

  1. 编写一个dockerfile文件

  2. docker build 构建成为一个镜像

  3. docker run 镜像

  4. docker push 镜像(发布镜像到DockerHub、阿里云镜像仓库)

2、Dockerfile指令说明

指令 说明
FROM 指定基础镜像
MAINTAINER 镜像是谁写的,姓名+邮箱
RUN 镜像构建的时候需要运行的命令
ADD 将本地文件添加到容器中,tar类型文件会自动解压(网络压缩资源不会被解压),可以访问网络资源,类似wget
WORKDIR 镜像的工作目录
VOLUME 挂载的目录
EXPOSE 保留端口配置
CMD 指定这个容器启动的时候要运行的命令(会被追加的命令替换,只有最后一个会生效)
EMTRYPOINT 指定这个容器启动的时候要运行的命令,可以追加命令
ONBUILD ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN, COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行
COPY 功能类似ADD,但是不会自动解压文件,也不能访问网络资源
ENV 构建的时候设置环境变量

Dockerfile 一般分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令,’#’ 为 Dockerfile 中的注释。

关于DockerFile文件的脚本注意点有:

  1. 每个保留关键字(指令)约定为大写字母

  2. 文件中的指令从上到下顺序执行,第一个指令必须是FROM

  3. # 号表示注释

  4. 每一个指令都会创建一个新的镜像层,并提交

3、制作CentOS镜像

下面通过编写Dockerfile文件来制作Centos镜像,并在官方镜像的基础上添加vim和net-tools工具。首先在/home/dockerfile目录下新建文件mydockerfile-centos。然后使用上述指令编写该文件。

FROM centos:7

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

逐行解释该Dockerfile文件的指令:

  • FROM centos:7:该image文件继承官方的centos7,后面加冒号如centos:7,用于指定镜像的版本
  • ENV MYPATH /usr/local:设置环境变量MYPATH ,后面有用到,为一个键值对
  • WORKDIR $MYPATH:直接使用上面设置的环境变量,指定/usr/local为工作目录
  • RUN yum -y install vimRUN yum -y install net-tools:在/usr/local目录下,运行yum -y install vim和yum -y install net-tools命令安装工具,注意安装后的所有依赖和工具都会打包到image文件中
  • EXPOSE 80:将容器80端口暴露出来,允许外部连接这个端口
  • CMD:指定容器启动的时候运行命令

通过这个dockerfile构建镜像,构建镜像命令:

docker build -f /home/dockerfile/dockerfile -t mycentos:1.0 . #注意后面有个.

上面命令中,-t参数用来指定 image 文件的名字,后面还可以用冒号指定标签。如果不指定,默认的标签就是latest。最后的那个点表示这是在指定context路径,有人翻译为上下文路径(context)

build命令可以省略-f,但是规定dockerfile文件必须命名为Dockerfile,且执行该命令的路径必须在Dockerfile文件所在目录

下面执行build命令生成image文件,如果执行成功,可以通过docker images来查看新生成的镜像文件。

[root@VM-20-6-centos dockerfile]# docker build -f /home/dockerfile/dockerfile -t mycentos:1.0 .
Sending build context to Docker daemon  2.048kB
Step 1/9 : FROM centos:7
7: Pulling from library/centos
2d473b07cdd5: Pull complete 
Digest: sha256:c73f515d06b0fa07bb18d8202035e739a494ce760aa73129f60f4bf2bd22b407
Status: Downloaded newer image for centos:7
 ---> eeb6ee3f44bd
Step 2/9 : ENV MYPATH /usr/local
 ---> Running in cfa4ef3e8e2c
Removing intermediate container cfa4ef3e8e2c
 ---> c44817f29f28
Step 3/9 : WORKDIR $MYPATH
 ---> Running in 4c9e6d2d560d
Removing intermediate container 4c9e6d2d560d
 ---> fb5cff96b3d0
Step 4/9 : RUN yum -y install vim
 ---> Running in d72f9228f9ad
Removing intermediate container d72f9228f9ad
 ---> 700553ee3fb7
Step 5/9 : RUN yum -y install net-tools
 ---> Running in 221f2ef33ae4
Removing intermediate container 221f2ef33ae4
 ---> a51d78c1f517
Step 6/9 : EXPOSE 80
 ---> Running in b269f3d1dec8
Removing intermediate container b269f3d1dec8
 ---> a97cf4da3f78
Step 7/9 : CMD echo $MYPATH
 ---> Running in 9e30be0e229d
Removing intermediate container 9e30be0e229d
 ---> 8c07e3a50490
Step 8/9 : CMD echo "---end---"
 ---> Running in aa88c5a1db09
Removing intermediate container aa88c5a1db09
 ---> 29aac32b5519
Step 9/9 : CMD /bin/bash
 ---> Running in 0d30d7c67e1a
Removing intermediate container 0d30d7c67e1a
 ---> 232ef116fcaf
Successfully built 232ef116fcaf
Successfully tagged mycentos:1.0

下面生成容器,测试相关命令,查看默认工作目录是否设置成功,vim和net-tools工具是否下载成功。

[root@VM-20-6-centos dockerfile]# docker run -it mycentos:1.0
[root@ca8e324b7b58 local]# pwd
/usr/local #该目录为Dockerfile文件中的环境目录
[root@ca8e324b7b58 local]# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500 #ifconfig可以使用了
        inet 172.17.0.2  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:ac:11:00:02  txqueuelen 0  (Ethernet)
        RX packets 8  bytes 656 (656.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

[root@ca8e324b7b58 local]# vim test #vim命令也可以使用了

另外,我们通过docker history 容器id命令来查看镜像的构建步骤

4、CMD、RUN和EMTRYPOINT

RUN命令与CMD命令的区别在哪里?

简单说,RUN命令在 image 文件的构建阶段执行,执行结果都会打包进入 image 文件;CMD命令则是在容器启动后执行。另外,一个 Dockerfile 可以包含多个RUN命令,但是只能有一个CMD命令

注意,指定了CMD命令以后,docker container run命令就不能附加命令了(比如前面的/bin/bash),否则它会覆盖CMD命令。

CMD和ENTRYPOINT的区别在哪里?

  • CMD :指定容器启动的时候要运行的命令,只有最后一个会生效

  • ENTRYPOINT :指定容器启动的时候要运行的命令,命令可以追加

以下是CMD和ENTRYPOINT的区别

首先是使用CMD指令:

[root@iZwz99sm8v95sckz8bd2c4Z dockerfile]# cat dockerfile-cmd-test
FROM centos
CMD ["ls","-a"]
[root@iZwz99sm8v95sckz8bd2c4Z dockerfile]# docker build -f dockerfile-cmd-test -t cmdtest:1.0 .
Sending build context to Docker daemon  3.072kB
Step 1/2 : FROM centos
 ---> 300e315adb2f
Step 2/2 : CMD ["ls","-a"]
 ---> Running in 6d4d0112322f
Removing intermediate container 6d4d0112322f
 ---> b6ec5224d2ac
Successfully built b6ec5224d2ac
Successfully tagged cmdtest:1.0
#启动镜像,可正常执行CMD的命令
[root@iZwz99sm8v95sckz8bd2c4Z dockerfile]# docker run cmdtest:1.0
.
..
.dockerenv
bin
dev
etc
home
lib
lib64
lost+found
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
#由于使用的是 CMD指令,命令无追加,-l取代了原本的ls -a,而-l命令不存在所以报错。
[root@iZwz99sm8v95sckz8bd2c4Z dockerfile]# docker run cmdtest:1.0 -l
docker: Error response from daemon: OCI runtime create failed: container_linux.go:370: starting container process caused: exec: "-l": executable file not found in $PATH: unknown.

下面使用ENTRYPOINT来构建一个镜像:

#1.修改dockerfile文件
[root@iZwz99sm8v95sckz8bd2c4Z dockerfile]# cat dockerfile-cmd-test
FROM centos
ENTRYPOINT ["ls","-a"]
#2.构建镜像
[root@iZwz99sm8v95sckz8bd2c4Z dockerfile]# docker build -f dockerfile-cmd-test -t cmdtest:2.0 .
Sending build context to Docker daemon  3.072kB
Step 1/2 : FROM centos
 ---> 300e315adb2f
Step 2/2 : ENTRYPOINT ["ls","-a"]
 ---> Running in 61389c0c1967
Removing intermediate container 61389c0c1967
 ---> ac7b7e83ff88
Successfully built ac7b7e83ff88
Successfully tagged cmdtest:2.0
#3.运行镜像
[root@iZwz99sm8v95sckz8bd2c4Z dockerfile]# docker run cmdtest:2.0
.
..
.dockerenv
bin
dev
etc
home
lib
lib64
lost+found
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
#4.追加镜像再次运行,此时是可以运行的,不像CMD那样会报错
[root@iZwz99sm8v95sckz8bd2c4Z dockerfile]# docker run cmdtest:2.0 -l
total 56
drwxr-xr-x   1 root root 4096 Jan  1 03:55 .
drwxr-xr-x   1 root root 4096 Jan  1 03:55 ..
-rwxr-xr-x   1 root root    0 Jan  1 03:55 .dockerenv
lrwxrwxrwx   1 root root    7 Nov  3 15:22 bin -> usr/bin
drwxr-xr-x   5 root root  340 Jan  1 03:55 dev
drwxr-xr-x   1 root root 4096 Jan  1 03:55 etc
drwxr-xr-x   2 root root 4096 Nov  3 15:22 home
lrwxrwxrwx   1 root root    7 Nov  3 15:22 lib -> usr/lib
lrwxrwxrwx   1 root root    9 Nov  3 15:22 lib64 -> usr/lib64
drwx------   2 root root 4096 Dec  4 17:37 lost+found
drwxr-xr-x   2 root root 4096 Nov  3 15:22 media
drwxr-xr-x   2 root root 4096 Nov  3 15:22 mnt
drwxr-xr-x   2 root root 4096 Nov  3 15:22 opt
dr-xr-xr-x 106 root root    0 Jan  1 03:55 proc
dr-xr-x---   2 root root 4096 Dec  4 17:37 root
drwxr-xr-x  11 root root 4096 Dec  4 17:37 run
lrwxrwxrwx   1 root root    8 Nov  3 15:22 sbin -> usr/sbin
drwxr-xr-x   2 root root 4096 Nov  3 15:22 srv
dr-xr-xr-x  13 root root    0 Dec 29 15:41 sys
drwxrwxrwt   7 root root 4096 Dec  4 17:37 tmp
drwxr-xr-x  12 root root 4096 Dec  4 17:37 usr
drwxr-xr-x  20 root root 4096 Dec  4 17:37 var

5、制作Tomcat镜像

1.准备Tomcat

[root@VM-20-6-centos tomcat]# vim readme.md
[root@VM-20-6-centos tomcat]# ls
apache-tomcat-9.0.60.tar.gz  readme.md

2.编写Dockerfile

vim Dockerfile
FROM centos:7

COPY readme.md /usr/local/readme.md

#复制并解压
ADD apache-tomcat-9.0.60.tar.gz /usr/local/

#安装jdk
RUN yum install -y java-1.8.0-openjdk-devel.x86_64

#设置工作目录
ENV MYPATH /usr/local
WORKDIR $MYPATH

#配置Tomcat
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.60
ENV CATALINA_BASH /usr/local/apache-tomcat-9.0.60
ENV PATH $PATH:$CATALINA_HOME/lib:$CATALINA_HOME/bin

EXPOSE 8080

#创建日志输出
CMD /usr/local/apache-tomcat-9.0.60/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.60/logs/catalina.out

3.构建镜像

#需要在Dockerfile所在目录下
docker build -t diytomcat:1.0 .

4.启动镜像

这里设置了数据卷,宿主机的/home/tomcat/test对应该容器的/usr/local/apache-tomcat-9.0.60/webapps/test。这样关于test项目的修复只需要在宿主机上修改就可以了,不需要进入到容器中修改。

docker run -d -p 9080:8080 --name diytomcat -v /home/tomcat/test:/usr/local/apache-tomcat-9.0.60/webapps/test diytomcat:1.0

5.测试挂载

此时访问http://ip:9080可以访问到Tomcat的默认页面

在宿主机/home/tomcat/test目录下,新建文件index.html,测试是否可以正常挂载

<!DOCTYPE html>
<html>
    <head>
         <meta charset="UTF-8"/>
        <title>这是个标题</title>
    </head>
    <body>
        <h1>这是一个一个简单的HTML</h1>
        <p>Hello World!</p>
    </body>
</html>

访问http://ip:9080/test,可访问到index.html

十三、Docker0网络

1、Docker的网络连通原理

输入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
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 52:54:00:75:17:13 brd ff:ff:ff:ff:ff:ff
    inet 10.0.20.6/22 brd 10.0.23.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::5054:ff:fe75:1713/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:30:83:ed:0d 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:30ff:fe83:ed0d/64 scope link 
       valid_lft forever preferred_lft forever

其中lo为本机环回地址,eth0为内网地址,docker0为docker的地址

主机可以ping通容器,那容器间是否可以相互ping通呢?

先运行一个容器:docker run -d -P --name tomcat01 tomcat

查看容器内部地址: docker exec -it tomcat01 ip addr

1: 1o: <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 1o
valid_ .1ft forever preferred. _1ft forever
60: eth0@61: <BROADCAST, MULTICAST ,UP ,LOWER_ _UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02 :42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff link -netnsid 0
inet 172.18.0.2/16 brd 172.18. 255.255 scope global eth0
valid_ 1ft forever preferred_ 1ft forever

看到有个261: eth0@if262:

本机中再: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
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 52:54:00:75:17:13 brd ff:ff:ff:ff:ff:ff
    inet 10.0.20.6/22 brd 10.0.23.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::5054:ff:fe75:1713/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:30:83:ed:0d 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:30ff:fe83:ed0d/64 scope link 
       valid_lft forever preferred_lft forever
61: veth635863b@if60: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether 1e:dc:84:c3:c8:e1 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::1cdc:84ff:fec3:c8e1/64 scope link 
       valid_lft forever preferred_lft forever

可以看到有个61: veth635863b@if60

这个就是docker为容器创建的一个地址,通过这个地址,主机可以ping通容器,而容器间也可以相互ping通

2、Docker默认的网络模式

使用以下命令查看所有的Docker网络:

[root@VM-20-6-centos bbs]# docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
09effc5a67d7   bridge    bridge    local
74feed8e6018   host      host      local
5a520470e49e   none      null      local

Docker默认提供了四个网络模式:

  • bridge:容器默认的网络是桥接模式(自己搭建的网络默认也是使用桥接模式,启动容器默认也是使用桥接模式)。此模式会为每一个容器分配、设置IP等,并将容器连接到一个docker0虚拟网桥,通过docker0网桥以及Iptables nat表配置与宿主机通信。

  • none:不配置网络,容器有独立的Network namespace,但并没有对其进行任何网络设置,如分配veth pair 和网桥连接,配置IP等。

  • host:容器和宿主机共享Network namespace。容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。

  • container:创建的容器不会创建自己的网卡,配置自己的IP容器网络连通。容器和另外一个容器共享Network namespace(共享IP、端口范围)。

容器默认使用bridge网络模式,我们使用该docker run --network=选项指定容器使用的网络:

  • host模式:使用 --net=host 指定。
  • none模式:使用 --net=none 指定。
  • bridge模式:使用 --net=bridge 指定,默认设置。
  • container模式:使用 --net=container:NAME_or_ID 指定。

1.host模式

Namespace的简要说明:

Docker使用了Linux的Namespaces技术来进行资源隔离,如PID Namespace隔离进程,Mount Namespace隔离文件系统,Network Namespace隔离网络等。

一个Network Namespace提供了一份独立的网络环境,包括网卡、路由、Iptable规则等都与其他的NetworkNamespace隔离。一个Docker容器一般会分配一个独立的Network Namespace。

如果启动容器的时候使用host模式,那么这个容器将不会获得一个独立的Network Namespace,而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。但是,容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的。

使用host模式的容器可以直接使用宿主机的IP地址与外界通信,容器内部的服务端口也可以使用宿主机的端口,不需要进行NAT,host最大的优势就是网络性能比较好,但是docker host上已经使用的端口就不能再用了,网络的隔离性不好。

2.container模式

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

3.none模式

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

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

4.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查看。

3、自定义网络

因为docker0,默认情况下不能通过容器名进行访问。需要通过–link进行设置连接。这样的操作比较麻烦,更推荐的方式是自定义网络,容器都使用该自定义网络,就可以实现通过容器名来互相访问了。

查看network的相关命令:docker network --help

查看默认的网络bridge的详细信息:

[root@VM-20-6-centos bbs]# docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "09effc5a67d7fdde17c50f3c3ab427cc59946fc389f48f4d8f44721a702fad0c",
        "Created": "2022-03-25T14:12:28.755675457+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": { #这里描述了所有容器的网络信息
            "c818daefe0b17c2a8f76cc7a63855c95bf0730800a365fe32764d339077f7f35": {
                "Name": "tomcat01",
                "EndpointID": "f722ccde62177d0769ab15990ec25dbe88d554c7dce240d27668de0d68288cc1",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        },
        "Labels": {}
    }
]

查看 network create命令的相关参数:docker network create --help

下面自定义一个网络

docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet

参数说明:

--driver bridge   #指定bridge驱动程序来管理网络
--subnet 192.168.0.0/16 #指定网段的CIDR格式的子网
--gateway 192.168.0.1 	#指定主子网的IPv4或IPv6网关

网络mynet创建成功后,查看网络信息:docker network inspect mynet

[root@VM-20-6-centos bbs]# docker network inspect mynet
[
    {
        "Name": "mynet",
        "Id": "3c53732f649da6544b227671838c93aba47cb58dae560c52e5bcd3243a50312a",
        "Created": "2022-03-30T16:46:54.10443745+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "192.168.0.0/16",
                    "Gateway": "192.168.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

下面启动两个容器,指定使用该自定义网络mynet,测试处于自定义网络下的容器,是否可以直接通过容器名进行网络访问。

docker run -d -P --name tomcat-net-01 --net mynet tomcat 
docker run -d -P --name tomcat-net-02 --net mynet tomcat 
docker network inspect mynet
[root@VM-20-6-centos bbs]# docker network inspect mynet
[
    {
        "Name": "mynet",
        "Id": "3c53732f649da6544b227671838c93aba47cb58dae560c52e5bcd3243a50312a",
        "Created": "2022-03-30T16:46:54.10443745+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "192.168.0.0/16",
                    "Gateway": "192.168.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": { #可查看到自定义中容器的相关网络信息
            "224fe08cec38572c124138f7c4762a2ab3668c572d97aad5848b1c1203694c8a": {
                "Name": "tomcat-net-02",
                "EndpointID": "e20dccdbdce90e800209d77aacabd2624785ceb51f3256a181dffa44d296a58a",
                "MacAddress": "02:42:c0:a8:00:03",
                "IPv4Address": "192.168.0.3/16",
                "IPv6Address": ""
            },
            "ccea31548995c404553fa0467fa73b02c529578bd5d8414ce8a329f0a8efcad1": {
                "Name": "tomcat-net-01",
                "EndpointID": "b941dda9f3a7cb958fa0e2ba4dbde6444a71fff25fff762e9b93f1d70d00c0ac",
                "MacAddress": "02:42:c0:a8:00:02",
                "IPv4Address": "192.168.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

下面通过容器名来测试容器tomcat-net-01 和容器tomcat-net-02之间是否能正常网络通信

docker exec -it tomcat-net-01 ping tomcat-net-02
docker exec -it tomcat-net-02 ping tomcat-net-01
docker exec -it tomcat-net-01 ping 192.168.0.1

可以发现,在我们的自定义网络下,容器之间既可以通过容器名也可以通过ip地址进行网络通信。 我们自定义的网络默认已经帮我们维护了容器间的网络通信问题,这是实现网络互联的推荐方式。

4、Docker容器网络之间的互联

没有设置的情况下,不同网络间的容器是无法进行网络连接的。如图,两个不同的网络docker0和自定义网络mynet的网络模型图:

Docker网络连通

在默认网络bridge下启动容器tomcat-01,尝试连接mynet网络下的tomcat-net-01容器。可以看到是无法网络连接的。不同Docker网络之间的容器需要连接的话需要把作为调用方的容器注册一个ip到被调用方所在的网络上。需要使用docker connect命令。

下面设置容器tomcat-01连接到mynet网络上。并查看mynet的网络详情,可以看到给容器tomcat-01分配了一个ip地址。

#docker network connect 自定义网络名 容器名
docker network connect mynet tomcat01
[root@VM-20-6-centos bbs]# docker network inspect mynet
[
    {
        "Name": "mynet",
        "Id": "3c53732f649da6544b227671838c93aba47cb58dae560c52e5bcd3243a50312a",
        "Created": "2022-03-30T16:46:54.10443745+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "192.168.0.0/16",
                    "Gateway": "192.168.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": { #可以看到Tomcat01容器的网络信息出现了
            "224fe08cec38572c124138f7c4762a2ab3668c572d97aad5848b1c1203694c8a": {
                "Name": "tomcat-net-02",
                "EndpointID": "e20dccdbdce90e800209d77aacabd2624785ceb51f3256a181dffa44d296a58a",
                "MacAddress": "02:42:c0:a8:00:03",
                "IPv4Address": "192.168.0.3/16",
                "IPv6Address": ""
            },
            "c818daefe0b17c2a8f76cc7a63855c95bf0730800a365fe32764d339077f7f35": {
                "Name": "tomcat01",
                "EndpointID": "06e33f9f68aafdaf3fbfd17fc7ef6c77b62668e83af53d2b7abf8067fff089d0",
                "MacAddress": "02:42:c0:a8:00:04",
                "IPv4Address": "192.168.0.4/16",
                "IPv6Address": ""
            },
            "ccea31548995c404553fa0467fa73b02c529578bd5d8414ce8a329f0a8efcad1": {
                "Name": "tomcat-net-01",
                "EndpointID": "b941dda9f3a7cb958fa0e2ba4dbde6444a71fff25fff762e9b93f1d70d00c0ac",
                "MacAddress": "02:42:c0:a8:00:02",
                "IPv4Address": "192.168.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

设置完成后我们就可以实现不同网络之间的容器互联了。

5、创建redis集群

部署三主三从的Redis集群

1.创建自定义网络

docker network create redis --subnet 172.38.0.0/16

2.创建redis配置信息

#直接输入命令即可
for port in $(seq 1 6); \
do \
mkdir -p /mydata/redis/node-${port}/conf
touch /mydata/redis/node-${port}/conf/redis.conf
cat << EOF >/mydata/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

3.启动redis容器,并挂载

for port in $(seq 1 6); \
do
docker run -p 637${port}:6379 -p 1637${port}:16379 --name redis-${port} \
-v /mydata/redis/node-${port}/data:/data \
-v /mydata/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 redis-server /etc/redis/redis.conf; \
done

4.创建集群

#进入redis-1
docker exec -it redis-1 /bin/sh
#创建集群
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
#输入:yes

5.测试

#查看集群信息
redis-cli -c
cluster info
#查看节点信息
cluster nodes
#测试主从复制是否生效
#先设置一个key
127.0.0.1:6379> set k v
-> Redirected to slot [7629] located at 172.38.0.12:6379
OK
#可以看到是redis-2处理了
#新建一个会话,停止Redis-2容器服务
docker stop afe82eccc706
#此时重新连接Redis-cli客户端,再次获取k
127.0.0.1:6379> get k
-> Redirected to slot [7629] located at 172.38.0.16:6379
"v"
#可以看到是redis-6处理

十四、springboot打包成Docker镜像

1.编写springboot项目

2.将springboot项目打包成jar

3.编写Dockerfile

FROM openjdk:8-jdk-alpine
VOLUME /tmp
#修改时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ADD demo-0.0.1-SNAPSHOT.jar app.jar #demo-0.0.1-SNAPSHOT.jar为打包后的jar包名
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]

4.构建镜像: docker build -t demo .

5.运行镜像:docker run -d -p 8080:8080 demo