什么是docker镜像?
docker镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等);镜像不包含任何动态数据,其内容在构建之后也不会被改变。
- 如何获得镜像?
- 1:远程下载
- 2:朋友拷贝
- 3:自己制作
镜像的分层系统
因为镜像包含操作系统完整的 root 文件系统,其体积往往是庞大的,因此在 Docker 设计时,就充分利用 的技术,将其设计为分层存储的架构。所以严格来说,镜像并非是像一个 ISO 那样的打包文件,镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系统联合组成。
镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。因此,在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。
分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。
镜像加载的原理
Docker的镜像实际由一层一层的文件系统组成:
- bootfs(boot file system)主要包含bootloader和kernel。bootloader主要是引导加载kernel,完成后整个内核就都在内存中了。此时内存的使用权已由bootfs转交给内核,系统卸载bootfs。可以被不同的Linux发行版公用。
- rootfs(root file system),包含典型Linux系统中的/dev,/proc,/bin,/etc等标准目录和文件。rootfs就是各种不同操作系统发行版(Ubuntu,Centos等)。因为底层直接用Host的kernel,rootfs只包含最基本的命令,工具和程序就可以了。
- 分层理解
所有的Docker镜像都起始于一个基础镜像层,当进行修改或增加新的内容时,就会在当前镜像层之上,创建新的容器层。
容器在启动时会在镜像最外层上建立一层可读写的容器层(R/W),而镜像层是只读的(R/O)。
自定义镜像
我们下载的tomcat的镜像是webapps里面是没有内容的,我们要下面尝试把文件加进去然后做成一个新的镜像。
docker commit 提交容器成为一个新的版本
# 命令和git 原理类似
docker commit -m="提交的描述信息" -a="作者" 容器id 目标镜像名:[TAG]
docker commit -a="xiaofan" -m="add webapps app" d798a5946c1f tomcat007:1.0
执行
[root@iZwz9hv1phm24s3jicy8x1Z ~]# docker commit -a="冬木" -m "add webapps project" 9f216ab4d820 tomcat02:1.0
sha256:558f9dd09adfbc929760de46a49dc7ca7c77c045f4fee7107edcf85821e94ef4
[root@iZwz9hv1phm24s3jicy8x1Z ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
tomcat02 1.0 558f9dd09adf 38 seconds ago 684MB
nginx latest 605c77e624dd 3 months ago 141MB
tomcat 9.0 b8e65a4d736d 3 months ago 680MB
tomcat latest fb5657adc892 3 months ago 680MB
redis latest 7614ae9453d1 3 months ago 113MB
centos latest 5d0da3dc9764 7 months ago 231MB
portainer/portainer latest 580c0e4e98b0 13 months ago 79.1MB
elasticsearch 7.6.2 f29a1ee41030 2 years ago 791MB
容器数据卷
容器存在的问题:如果我们把镜像删除了,那么原来镜像中的数据都会消失,如果MySQL部署在docker中,就会导致数据库没了,这是很可怕的事情。那么这个数据卷就是为了解决这个问题。
数据卷可以把容器中的数据同步到本地。
卷挂载:将我们容器中的目录挂挂载到linux虚拟机上。
实际上就是容器的持久化和同步操作,容器间也是可以数据共享的。
使用数据卷
docker run -it -v 主机目录:容器内目录地址
首先看一下原本的/home/dongmu目录下面的内容
[root@iZwz9hv1phm24s3jicy8x1Z ~]# cd /home
[root@iZwz9hv1phm24s3jicy8x1Z home]# ls
dongmu pb_cms redis www
[root@iZwz9hv1phm24s3jicy8x1Z home]# cd dongmu
[root@iZwz9hv1phm24s3jicy8x1Z dongmu]# ls
dongmu.java jdk1.8.0_121 jdk-8u121-linux-x64.tar.gz
运行centos容器,把对应的目录挂在完成
[root@iZwz9hv1phm24s3jicy8x1Z dongmu]# docker run -it -v /home/dongmu:/home centos /bin/bash
[root@ab9a51ea4ebd /]# ls
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
查看我们容器内的目录
[root@ab9a51ea4ebd /]# cd home
[root@ab9a51ea4ebd home]# ls
dongmu.java jdk-8u121-linux-x64.tar.gz jdk1.8.0_121
[root@ab9a51ea4ebd home]#
这个时候测试了一下,我在容器内部创建的文件会和外部的文件联通。好像是vue里面双向绑定,无论我在容器内部还是主机上的对应的文件夹里面创建新的文件都会自动同步到另一个地方。删除也是一样会同步,这就解决了前面提到的不进入容器中就可以修改容器内部的东西。
在主机上使用docker inspect centos的id
命令查看运行的centos容器的详细信息,在mounts里面可以看到我们挂载的详细信息。
[root@iZwz9hv1phm24s3jicy8x1Z dongmu]# docker inspect ab9a51ea4ebd
[//篇幅限制这里只展示部分内容
"Mounts": [
{
"Type": "bind",
"Source": "/home/dongmu",
"Destination": "/home",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
]
数据卷实战练习
- 安装mysql镜像
docker pull mysql:5.7
- 官方启动命令
$ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag
。 - 使用启动命令
docker run -d -p 3344: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@iZwz9hv1phm24s3jicy8x1Z ~]# docker run -d -p 3344: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
ca03bea4bb42a073d91b860572f87f19a3318b7b23d7e1b1798829608b709621
使用docker inspect
命令查看挂载的配置
"Mounts": [
{
"Type": "bind",
"Source": "/home/mysql/conf",
"Destination": "/etc/mysql/conf.d",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
},
{
"Type": "bind",
"Source": "/home/mysql/data",
"Destination": "/var/lib/mysql",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
使用本地的navicat连接容器中的mysql,因为我的阿里云安全组3344端口是打开的,那么我这里就直接使用这个端口了。连接成功。
这个时候即使我把docker中的mysql容器删除了原来主机上的数据也不会丢失。
具名挂载和匿名挂载
- 匿名挂载就是-v的时候只指定容器内的目录,不指定容器外的目录。
-v 容器内路径
- 具名挂载:就是不加/,那么-v后面冒号前面就是后面这个挂载的名字
-v 卷名:容器内路径
容器内路径:ro
:readOnly,只能从容器外更改,容器内只读。容器内路径:rw
:可读可写。
查看映射的路径docker volume inspect 卷名
上面介绍的都是目录挂载,我们也可以直接创建数据卷,然后将容器内的目录挂载到这个数据卷上面
- docker volume create:创建数据卷
- docker volume ls:查看所有数据卷
- docker volume inspect:查看数据卷详细信息,包括关联的宿主机目录位置
- docker volume rm:删除指定数据卷
- docker volume prune:删除所有未使用的数据卷
使用样例:-v html:/root/htm
:把html数据卷挂载到容器内的/root/html这个目录中
docker run \
--name mn \
-v html:/root/html \
-p 8080:80
nginx \
查看html这个数据卷对应的磁盘目录
# 查看html数据卷的位置
docker volume inspect html
# 进入该目录
cd /var/lib/docker/volumes/html/_data
# 修改文件
vi index.html
自定义挂载目录的方式实现起来比较灵活,但是麻烦。
使用数据卷的话比较方便,但是挂载的目录是固定的不够灵活。
DockerFile(参考视频)
常见的镜像在DockerHub就能找到,但是我们自己写的项目就必须自己构建镜像了。
而要自定义镜像,就必须先了解镜像的结构才行。
镜像是将应用程序及其需要的系统函数库、环境、配置、依赖打包而成。dockerFile是用来构建doker镜像的构建文件,告诉Docker,我们的镜像的组成,需要哪些BaseImage、需要拷贝什么文件、需要安装什么依赖、启动脚本是什么,将来Docker会帮助我们构建镜像。
简单来说,镜像就是在系统函数库、运行环境基础上,添加应用程序文件、配置文件、依赖文件等组合,然后编写好启动脚本打包在一起形成的文件。
我们要构建镜像,其实就是实现上述打包的过程。
Dockerfile就是一个文本文件,其中包含一个个的指令(Instruction),用指令来说明要执行什么操作来构建镜像。每一个指令都会形成一层Layer。
首先在主机中构建下面的文件
- 实际上就是一个shell脚本
- 内容是指令加参数,指令都是大写的。
- docker是分层的,这里的每个命令都是一层。
[root@iZwz9hv1phm24s3jicy8x1Z docker-test-volume]# vim dockerfile1
[root@iZwz9hv1phm24s3jicy8x1Z docker-test-volume]# cat dockerfile1
FROM centos
VOLUME ["volume01","volume02"]
CMD echo "-------------end--------------"
CMD /bin/bash
然后利用下面的命令docker build -f dockerfile1 -t dongmu/centos .
build:构建镜像
-f:文件的地址
-t:target,生成的目标
[root@iZwz9hv1phm24s3jicy8x1Z docker-test-volume]# docker build -f dockerfile1 -t dongmu/centos .
Sending build context to Docker daemon 14.85kB
Step 1/4 : FROM centos
---> 5d0da3dc9764
Step 2/4 : VOLUME ["volume01","volume02"]
---> Running in c62bf4f11adb
Removing intermediate container c62bf4f11adb
---> f04dde4d3b44
Step 3/4 : CMD echo "-------------end--------------"
---> Running in d8b20649bbb2
Removing intermediate container d8b20649bbb2
---> c623130d05ab
Step 4/4 : CMD /bin/bash
---> Running in f9e7190262f1
Removing intermediate container f9e7190262f1
---> b9481f89302d
Successfully built b9481f89302d
Successfully tagged dongmu/centos:latest
[root@iZwz9hv1phm24s3jicy8x1Z docker-test-volume]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
dongmu/centos latest b9481f89302d About a minute ago 231MB
tomcat02 1.0 558f9dd09adf 6 hours ago 684MB
nginx latest 605c77e624dd 3 months ago 141MB
启动容器[root@iZwz9hv1phm24s3jicy8x1Z docker-test-volume]# docker run -it b9481f89302d /bin/bash
下面可以看到我们刚才挂载的数据卷目录。(volume01,volume02)
[root@931a8935a97a /]# ls -l
total 56
lrwxrwxrwx 1 root root 7 Nov 3 2020 bin -> usr/bin
drwxr-xr-x 5 root root 360 Apr 14 07:38 dev
drwxr-xr-x 1 root root 4096 Apr 14 07:38 etc
drwxr-xr-x 2 root root 4096 Nov 3 2020 home
lrwxrwxrwx 1 root root 7 Nov 3 2020 lib -> usr/lib
lrwxrwxrwx 1 root root 9 Nov 3 2020 lib64 -> usr/lib64
drwx------ 2 root root 4096 Sep 15 2021 lost+found
drwxr-xr-x 2 root root 4096 Nov 3 2020 media
drwxr-xr-x 2 root root 4096 Nov 3 2020 mnt
drwxr-xr-x 2 root root 4096 Nov 3 2020 opt
dr-xr-xr-x 199 root root 0 Apr 14 07:38 proc
dr-xr-x--- 2 root root 4096 Sep 15 2021 root
drwxr-xr-x 11 root root 4096 Sep 15 2021 run
lrwxrwxrwx 1 root root 8 Nov 3 2020 sbin -> usr/sbin
drwxr-xr-x 2 root root 4096 Nov 3 2020 srv
dr-xr-xr-x 13 root root 0 Apr 14 07:38 sys
drwxrwxrwt 7 root root 4096 Sep 15 2021 tmp
drwxr-xr-x 12 root root 4096 Sep 15 2021 usr
drwxr-xr-x 20 root root 4096 Sep 15 2021 var
drwxr-xr-x 2 root root 4096 Apr 14 07:38 volume01
drwxr-xr-x 2 root root 4096 Apr 14 07:38 volume02
在volume01中添加文件
[root@931a8935a97a /]# cd volume01
[root@931a8935a97a volume01]# touch container.txt
[root@931a8935a97a volume01]# ls
container.txt
在主机中查看
[root@iZwz9hv1phm24s3jicy8x1Z /]# docker inspect 931a8935a97a
...
"Mounts": [
{
"Type": "volume",
"Name": "34dfbe4cf6d2055f42bdc917e567d64d9de7e074d924edeb73adc59533de92ab",
"Source": "/var/lib/docker/volumes/34dfbe4cf6d2055f42bdc917e567d64d9de7e074d924edeb73adc59533de92ab/_data",
"Destination": "volume01",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
},
...
查询到主机的映射地址之后进入看看到底有没有
[root@iZwz9hv1phm24s3jicy8x1Z /]# ls
bin boot dev etc home lib lib64 lost+found media mnt opt patch proc root run sbin srv sys tmp usr var www
[root@iZwz9hv1phm24s3jicy8x1Z /]# cd /var/lib/docker/volumes/34dfbe4cf6d2055f42bdc917e567d64d9de7e074d924edeb73adc59533de92ab/_data
[root@iZwz9hv1phm24s3jicy8x1Z _data]# ls
container.txt
可以看到已经挂载成功了。
总结步骤:
- 编写一个dockerfile文件
- docker build构建成为一个镜像
- docker run运行这个镜像
- docker push发布一个镜像(dockerHub或者阿里云镜像仓库)
我们可以通过dockerHub查询一个centos然后点进去就会发现跳转到github的地址,然后就发现其实就是个镜像文件。
就是以后我们以后我们做项目就会把我们的项目打包成自己做的镜像然后就可以直接部署了。然后镜像运行起来就是容器,运行的就是我们开发的服务。
数据卷容器
两个MySQL做数据同步,如何做呢?
首先了解一下容器的目录的同步。
- 首先启动一个我们自创的镜像
docker run -it --name docker01 b9481f89302d
[root@iZwz9hv1phm24s3jicy8x1Z ~]# clear
[root@iZwz9hv1phm24s3jicy8x1Z ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a2aeebf94b57 b9481f89302d "/bin/sh -c /bin/bash" 23 seconds ago Up 22 seconds docker01
- 再创建第二个centos镜像和第一个同步
docker run -it --name docker02 --volumes-from docker01 b9481f89302d
- 这个时候这两个容器中的volume01和volume02中的文件是共享的,会自动地相互同步,但是其他文件夹的文件不共享。
看一下docker01的挂载信息
"Mounts": [
{
"Type": "volume",
"Name": "4f734fc2827efd168b706b4ca192138f8efd241b96be1c11a4c39992110add7e",
"Source": "/var/lib/docker/volumes/4f734fc2827efd168b706b4ca192138f8efd241b96be1c11a4c39992110add7e/_data",
"Destination": "volume01",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
},
{
"Type": "volume",
"Name": "6e1b948b746ca97a2fc6ecc0743969935ba59bfe270c9c13c6697449e525d1e7",
"Source": "/var/lib/docker/volumes/6e1b948b746ca97a2fc6ecc0743969935ba59bfe270c9c13c6697449e525d1e7/_data",
"Destination": "volume02",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
docker02的挂载信息
"Mounts": [
{
"Type": "volume",
"Name": "4f734fc2827efd168b706b4ca192138f8efd241b96be1c11a4c39992110add7e",
"Source": "/var/lib/docker/volumes/4f734fc2827efd168b706b4ca192138f8efd241b96be1c11a4c39992110add7e/_data",
"Destination": "volume01",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
},
{
"Type": "volume",
"Name": "6e1b948b746ca97a2fc6ecc0743969935ba59bfe270c9c13c6697449e525d1e7",
"Source": "/var/lib/docker/volumes/6e1b948b746ca97a2fc6ecc0743969935ba59bfe270c9c13c6697449e525d1e7/_data",
"Destination": "volume02",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
},
可以看到他们文件挂载的主机的对应的文件路径是相同的。
即使把docker01或者02其中的一个删除其他的一样存在,就是复制。
两个mysql数据库做同步
docker run -d -p 3344: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
docker run -d -p 3344:3306 -v /home/mysql/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=123456 --name mysql02 --volumes-from mysql01 mysql:5.7