之前的文章中,我们一直在使用 docker 命令,第一步就是通过 docker pull ,那么问题来了,到底什么是镜像呢?

镜像是什么

镜像是一种 轻量级、可执行 的独立软件包,用来打包软件。运行环境开发的软件,他包含运行某个软件所需要的的所有内容,包括代码、数据库、环境变量以及配置文件等

docker 特点:

轻量级 docker 的镜像相比之下都比较小】

可执行 【拿到 docker 的镜像只需要简单运行命令即可】

弱安全 :【 docker 能够对多种 OS 资源进行隔离,但是它本质上依托于内核,因此所有的内核漏洞都是 Docker 的致命伤】

镜像加载原理

UnionFs 【联合文件系统】

UnionFs (联合文件系统): Union 文件系统( UnionFs )是一种 分层、轻量级并且高性能 的文件系统,他支持对文件系统的修改作为一次提交来一层层的叠加,因此相同的文件层不需要再次拉取,同时可以将不同目录挂载到同一个虚拟文件系统下( unite several directories into a single virtual filesystem)。 Union文件系统是 Docker镜像的基础 。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像
特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录

  • 是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改,作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下( unite directories into a single virtual filesystem )。
  • UnionFS Docker 镜像的基础, 镜像可以通过分层来继承,基于基础镜像(没有父镜像的镜像) ,可以制作各种具体的应用镜像
  • 特性 一次同时加载多个文件系统,但从外面看起来只能看到一个文件系统 ,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录
  • 例子:比如说 mysql tomcat 都需要 centos 环境,先安装了 mysql ,就有了 centos 的环境,那再安装 tomcat ,就可以共用这一层 centos ,不需要再下载 centos

    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 等等。

  • 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 中我们使用镜像拉取一下 ContenOs 仅仅有200多MB

    这就符合 docker 的轻小的特点,对于精简的 OS,rootfs 可以很小,只需要包含最基本的命令,工具和程序库就可以了,因为底层直接使用 Host kernel ,自己只需要提供 rootfs 就可以了。由此可见对于不用的 Linux 发行版, boots 会有差别,因此不同的发型可以公用 bootfs 【VM虚拟机是分钟级别,容器就是秒级的】

    当我们使用 docker pull 容器名 命令拉取一个新的镜像时,我们会发现输出的日志,是一层一层在下载的,每一层都会有自己的唯一ID

    我们再次拉取一个版本的nginx,进行对比

    对比发现,文件的第一层是已经存在的并没有再次拉取下载。

    那么问题来了? docker 为什么要采用这用分层的结构呢?(换句话说,这种分层思想做法有什么优点?)

  • 资源共享,当我们使用 docekr 拉取 nginx 的镜像时,由于 nginx 版本不同,由于版本不同,看能只有部分层中的文件有变化,那么我们就可以只拉起有变化的层,公用部分层级文件,达到资源共享
  • 充分利用系统存储资源
  • docker image inspect 查看镜像详情
  • docker imgae inspect 镜像名:tag
    

    docker 联合文件系统UnionFS

    上面我们简单知道了什么是分层文件系统,docker镜像是由分层系统构成的。下面有必要深刻认识一下docker的联合文件系统UnionFS

    docker支持的联合文件系统有很多种,不过原理都一样,包括:AUFS、overlay、overlay2、DeviceMapper、VSF等

    Linux 中各发行版实现的 UnionFS 各不相同,所以docker在不同 linux 发行版中使用的也不同。通过docker info 命令可以查看当前系统所使用哪种 UnionFS,常见的几种发行版使用如下:

    CentOS, Storage Driver: overlay2、overlay
    debain, Storage Driver: aufs
    RedHat, Storage Driver: devicemapper
    
    docker info
    

    分层文件系统剖析

    我们常用的就是CentOS发行版的overlay2,我们就依overlay2分析:

    根据上图我们可以大致分析出,联合文件系统分为三层:lowerdirupperdirmerged

    docker 环境中,我们通过 docker inspect 容器ID 就可以看到每一层所在的实际文件目录

    docker inspect 容器ID
    

    lowerdir层

    docker镜像中这一层称为init,构成容器文件系统的文件路径集合,通过 冒号 : 分割,越是后面的表示越底层。 由于组成LowerDir的镜像层是不可修改的,因此他们可以被一个宿主机上所有容器共用的一层。

    lowerdir是只读的镜像层(image layer),其中就包含bootfs/rootfs层,bootfs(boot file system)主要包含bootloaderkernelbootloader主要是引导加载kernel,当boot成功 kernel 被加载到内存中,bootfs就被umount了,rootfs(root file system)包含的就是典型Linux系统中的/dev、/proc、/bin、/etc等标准目录。

    lowerdir是可以分很多层的,除了bootfs/rootfs层以外,还可以通过Dockerfile建立很多image层,构建过程如下:

    Dockerfile中每一个指令都会生成一个新的image层,如上图所示。

    FROM时就已经生成了bootfs/rootfs层,也就是kernelbase层。

    upperdir层

    upperdir层是lowerdir的上一层,只有这一层可读可写的,其实就是Container层,在启动一个容器的时候会在最后的image层的上一层自动创建,所有对容器数据的更改都会发生在这一层。

    docker将一个镜像作为lowerdir层,并且建立一个upperdir层,upperdir层也称为容器读写层(联想我们在docker容器里编辑或者新增文件)。最后将这两层挂载到一个特定的路径下,最终形成docker容器的rootfs

    由于联合文件系统采用了copy-on-wirte的方式来读写文件:当对一个文件进行编辑的时候,会先从上至下逐层查找,如果找到了就copyupper层,然后进行在upperdir层对文件进行修改。

    merged层

    merged是容器层和镜像层的联合视图

    merged层就是联合挂载层,也就是给用户暴露的统一视觉,将image层和container层结合,就如最上边的图中描述一致,同一文件,在此层会展示离它最近的层级里的文件内容,或者可以理解为,只要container层中有此文件,便展示container层中的文件内容,若container层中没有,则展示image层中的。

    merge路径下 编辑/写入一个文件,其首先会从upper层中寻找看是否存在目标文件,如果存在,则直接修改,如果不存在,则跳入到lower层查找,如果找到了,会把lower层的目标文件拷本一份到upper层,然后对拷贝到upper层的目标文件进行编辑,如果在lower层也查不到,则会将编辑的文件保存一份到upper层。这样,最后用户在merge层看到的视图就是 编辑/写入 的文件。

    联合挂载系统的工作原理

    如果文件在upperdir(容器)层,直接读取文件;

    如果文件不在upperdir(容器)层,则从镜像层(lowerdir)读取;

    首次写入:如果upperdir中不存在,overlayoverlay2执行copy_up操作,把文件从lowdir拷贝到upperdir中,由于overlayfs是文件级别的(即使只有很少的一点修改,也会产生copy_up的动作),后续对同一文件的再次写入操作将对已经复制到容器层的文件副本进行修改,这也就是尝尝说的写时复制(copy-on-write)。

    删除文件或目录:当文件被删除时,在容器层(upperdir)创建whiteout文件,镜像层(lowerdir)的文件是不会被删除的,因为它们是只读的,但without文件会阻止它们显示,当目录被删除时,在容器层(upperdir)一个不透明的目录,这个和上边的whiteout的原理一样,组织用户继续访问,image层不会发生改变

  • copy_up操作只发生在文件首次写入,以后都是只修改副本
  • overlayfs只适用两层目录,,相比于比AUFS,查找搜索都更快
  • 容器层的文件删除只是一个“障眼法”,是靠whiteout文件将其遮挡,image层并没有删除,这也就是为什么使用docker commit 提交保存的镜像会越来越大,无论在容器层怎么删除数据,image层都不会改变
  • 容器整体构成图

    docker容器数据卷

    Docker中的数据可以存储在类似于虚拟机磁盘的介质中,在Docker中称为数据卷(Data Volume

    镜像产生问题

    我们将应用和环境打包成一个镜像,那么数据呢,如何进行数据的持久化

    问题:容器的数据持久化和数据同步共享问题

    数据卷是什么

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

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

    如何使用数据卷

    创建数据卷,只要在docker run命令后面跟上-v参数即可创建一个数据卷,当然也可以跟多个-v参数来创建多个数据卷,当创建好带有数据卷的容器后,就可以在其他容器中通过--volumes-froms参数来挂载该数据卷了,而不管该容器是否运行。也可以在Dockerfile中通过VOLUME指令来增加一个或者多个数据卷

    数据卷的备份:不能使用docker export、save、cp等命令来备份数据卷的内容,因为数据卷是存在于镜像之外的。备份方法: 创建一个新容器,挂载数据卷容器,同时挂载一个本地目录,然后把远程数据卷容器的数据卷通过备份命令备份到映射的本地目录里面。如下:

    docker run --rm --volumes-from DATA -v $(pwd):/backup busybox tar cvf /backup/backup.tar /data
    

    Docker Volume数据卷可以实现

    绕过“拷贝写”系统,以达到本地磁盘IO的性能,(比如运行一个容器,在容器中对数据卷修改内容,会直接改变宿主机上的数据卷中的内容,所以是本地磁盘IO的性能,而不是先在容器中写一份,最后还要将容器中的修改的内容拷贝出来进行同步。)

    绕过“拷贝写”系统,有些文件不需要在docker commit打包进镜像文件。

    数据卷可以在容器间共享和重用数据、在宿主和容器间共享数据

    数据卷数据改变是直接修改的

    数据卷是持续性的,直到没有容器使用它们。即便是初始的数据卷容器或中间层的数据卷容器删除了,只要还有其他的容器使用数据卷,那么里面的数据都不会丢失。

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

    通过docker容器的数据卷,解决mysql数据持久化问题

  • 拉取mysql 5.7镜像
  • docker pull mysql:5.7
    
  • 创建myslq需要挂载的配置文件和目录
  • mkdir -p /data/mysql/conf && mkdir -p /data/mysql/data && mkdir -p /data/mysql/log
    #进入conf目录创建my.cnf文件 
    [mysqld]
    user=mysql
    character-set-server=utf8
    default_authentication_plugin=mysql_native_password
    secure_file_priv=/var/lib/mysql
    expire_logs_days=7
    sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
    max_connections=1000
    [client]
    default-character-set=utf8
    [mysql]
    default-character-set=utf8
    
  • 运行mysql ,这里需要注意启动myslq是需要配置密码的
  • docker run --name mysql01 -p 3306:3306 -v /data/mysql/conf/my.cnf:/etc/mysql/my.cnf -v /data/mysql/data:/var/lib/mysql -v /data/mysql/log:/var/log/mysql -e MYSQL_ROOT_PASSWORD=123456  -d mysql:5.7
    #启动我们得
    -d 后台运行
    -p 端口映射
    -v 卷挂载【宿主机文件/目录:容器里对应的文件/目录】
    # 这里需要注意 宿主机上的文件/目录是要提前存在的
    -e 环境配置
    -- name 容器名字
    #数据卷权限:
    #挂载的数据默认为可读写权限。
    #但也可以根据自己的需求,将容器里挂载共享的数据设置为只读,这样数据修改就只能在宿主机上操作
    

    启动成功远程测试连接

    测试连接正常,查看宿主机上挂载的Mysql数据

    测试创建的test数据库以及被持久化

    停止并且删除mysql容器

    再次查看宿主机中mysql持久化的数据

    数据依然存在

    具名和匿名挂载

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

  • 运行并匿名挂载nginx容器
  • docker run -d -P --name nginx01 -v /etc/nginx nginx
    
  • 查看所有的数据卷volume的情况, VOLUME NAME这里的值是真实存在的目录
  • 具名挂载,就是指定文件夹名称,区别于指定路径挂载,这里的指定文件夹名称是在Docker指定的默认数据卷路径下的。通过docker volume ls命令可以查看当前数据卷的目录情况。

    docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx nginx
    4ceaff19e5275dcd3014a8e7a8af618f7f7ce0da18d605c7c41a8653e78bf912
    # 挂载的时候,指定一个名称
    root@iZwz99sm8v95sckz8bd2c4Z ~]# docker volume ls
    DRIVER    VOLUME NAME
    local     0cd45ab893fc13971219ac5127f9c0b02491635d76d94183b0261953bdb52d26
    local     668a94251e562612880a2fdb03944d67d1acdbbdae6ef7c94bee8685644f2956
    local     e605f3dc4bf11ab693972592b55fb6911e5bf2083425fd58869c5f574998a09a
    local     juming-nginx
    

    查看指定的数据卷信息的命令:docker volume inspect数据卷名称

    docker volume inspect 
    [root@iZwz99sm8v95sckz8bd2c4Z ~]# docker volume inspect juming-nginx
            "CreatedAt": "2020-12-29T22:40:25+08:00",
            "Driver": "local",
            "Labels": null,
            "Mountpoint": "/var/lib/docker/volumes/juming-nginx/_data",
            "Name": "juming-nginx",
            "Options": null,
            "Scope": "local"
    

    可以看到主机数据卷挂载在/var/lib/docker/volumes/juming-nginx/_data上

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

    [root@iZwz99sm8v95sckz8bd2c4Z volumes]# ls
    0cd45ab893fc13971219ac5127f9c0b02491635d76d94183b0261953bdb52d26  backingFsBlockDev                                                 juming-nginx
    668a94251e562612880a2fdb03944d67d1acdbbdae6ef7c94bee8685644f2956  e605f3dc4bf11ab693972592b55fb6911e5bf2083425fd58869c5f574998a09a  metadata.db
    

    匿名挂载,具名挂载,指定路径挂载的命令区别如下:
    -v 容器内路径 #匿名挂载

    -v 卷名:容器内路径 #具名挂载

    -v /宿主机路径:容器内路径 #指定路径挂载

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

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

    rw ----- readwrite 可读可写

    [root@iZwz99sm8v95sckz8bd2c4Z ~]# docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:ro nginx
    [root@iZwz99sm8v95sckz8bd2c4Z ~]# docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:rw nginx
    

    容器数据卷

    容器数据卷是指建立数据卷,来同步容器之间的数据,实现容器间的数据同步【简单来说就是,容器挂载同一个目录,到达数据持久化共享据】

    volumes-from容器间传递共享

    容器数据卷

    docker run -it --name dc02 --volunes-from dc01 leyton/centos
    

    相关Docker文章推荐

    Docker文件系统 & 数据卷

    Docker 安装及基本命令

    Docker 部署nginx/tomcat/ES+kibana

    Docker网络

    Docker C/S架构

    Dockerfile自定义镜像

    Image的本地存储结构【https://segmentfault.com/a/1190000017579626】
    深入理解 Docker 容器镜像分层的原理【https://iswbm.com/371.html】
    docker文件系统分层存储原理【https://www.cnblogs.com/v-fan/p/14453223.html】
    挂载Linux系统外的文件【http://c.biancheng.net/view/885.html】