Docker的初步认知

一.Docker的简介

1.什么是Docker

Docker的思想来自于集装箱,集装箱解决了什么问题?在一艘大船上,可以把货物规整的摆放起来。并且各种各样的货物被集装箱标准化了,集装箱和集装箱之间不会互相影响。那么我就不需要专门运送水果的船和专门运送化学品的船了。只要这些货物在集装箱里封装的好好的,那我就可以用一艘大船把他们都运走。

 

docker就是类似的理念,云计算就好比大货轮。docker就是集装箱。

2.举几个栗子

a.不同的应用程序可能会有不同的应用环境,比如.net开发的网站和php开发的网站依赖的软件就不一样,如果把他们依赖的软件都安装在一个服务器上就要调试很久,而且很麻烦,还会造成一些冲突。比如IIS和Apache访问端口冲突。这个时候你就要隔离.net开发的网站和php开发的网站。常规来讲,我们可以在服务器上创建不同的虚拟机在不同的虚拟机上放置不同的应用,但是虚拟机开销比较高。docker可以实现虚拟机隔离应用环境的功能,并且开销比虚拟机小,小就意味着省钱了。

b.你开发软件的时候用的是Ubuntu,但是运维管理的都是centos,运维在把你的软件从开发环境转移到生产环境的时候就会遇到一些Ubuntu转centos的问题,比如:有个特殊版本的数据库,只有Ubuntu支持,centos不支持,在转移的过程当中运维就得想办法解决这样的问题。这时候要是有docker你就可以把开发环境直接封装转移给运维,运维直接部署你给他的docker就可以了。而且部署速度快。

c.在服务器负载方面,如果你单独开一个虚拟机,那么虚拟机会占用空闲内存的,docker部署的话,这些内存就会利用起来。

3.Docker与虚拟机有什么区别?

很多在介绍Docker的时候会说Docker是一种轻量级的虚拟机,但是Docker容器不是虚拟机。

当虚拟机运行许多隔离应用的时候结构是这样的:

从下到上理解:

基础设施(Infrastructure):

它可以是你的个人电脑,数据中心的服务器,或者是云主机。

主操作系统(Host Operating System):

你的个人电脑之上,运行的可能是MacOS,Windows或者某个Linux发行版。

虚拟机管理系统(Hypervisor):

利用Hypervisor,可以在主操作系统之上运行多个不同的从操作系统。类型1的Hypervisor有支持MacOS的HyperKit,支持Windows的Hyper-V以及支持Linux的KVM。类型2的Hypervisor有VirtualBox和VMWare。

从操作系统(Guest Operating System):

假设你需要运行3个相互隔离的应用,则需要使用Hypervisor启动3个从操作系统,也就是3个虚拟机。这些虚拟机都非常大,也许有700MB,这就意味着它们将占用2.1GB的磁盘空间。更糟糕的是,它们还会消耗很多CPU和内存。

各种依赖:每一个从操作系统都需要安装许多依赖。如果你的的应用需要连接PostgreSQL的话,则需要安装libpq-dev;如果你使用Ruby的话,应该需要安装gems;如果使用其他编程语言,比如Python或者Node.js,都会需要安装对应的依赖库。

应用:安装依赖之后,就可以在各个从操作系统分别运行应用了,这样各个应用就是相互隔离的。

使用Docker容器运行多个相互隔离的应用时,如下图:

主操作系统(Host Operating System)

所有主流的Linux发行版都可以运行Docker。对于MacOS和Windows,也有一些办法"运行"Docker。

Docker守护进程(Docker Daemon)

Docker守护进程取代了Hypervisor,它是运行在操作系统之上的后台进程,负责管理Docker容器。

各种依赖

对于Docker,应用的所有依赖都打包在Docker镜像中,Docker容器是基于Docker镜像创建的

应用

应用的源代码与它的依赖都打包在Docker镜像中,不同的应用需要不同的Docker镜像。不同的应用运行在不同的Docker容器中,它们是相互隔离的。

对比虚拟机与Docker:

Docker守护进程可以直接与主操作系统进行通信,为各个Docker容器分配资源;它还可以将容器与主操作系统隔离,并将各个容器互相隔离。虚拟机启动需要数分钟,而Docker容器可以在数毫秒内启动。由于没有臃肿的从操作系统,Docker可以节省大量的磁盘空间以及其他系统资源。

 

虚拟机更擅长于彻底隔离整个运行环境。例如,云服务提供商通常采用虚拟机技术隔离不同的用户。

Docker通常用于隔离不同的应用,例如前端,后端以及数据库。

二.Docker中的镜像与容器

1.有关镜像

在Docker镜像分为基础镜像和父镜像,没有父镜像的镜像被称为基础镜像。用户是基于基础镜像来制作各种不同的应用镜像。这些应用镜像共享同一个基础镜像层,提高了存储效率。

当用户通过升级程序到新版本,改变了一个Docker镜像时,一个新的镜像层会被创建。因此,用户不用替换整个原镜像或者完全重新建立新镜像,只需要添加新层即可。在用户分发镜像的时,也只需要分发被改动的新层内容(增量部分)。这让Docker的镜像管理变得十分轻松级和快速。

 

通常我们下载一个centos镜像至少有3G,而在docker容器中使用docker pull centos下载的镜像只有200M:

Linux操作系统分别由两部分组成: 1.内核空间(kernel) 2.用户空间(rootfs)

内核空间(kernel)是kernel,Linux刚启动时会加载bootfs文件系统,之后bootf会被卸载掉,

用户空间(rootfs)的文件系统是rootfs,包含常见的目录,如/dev、/proc、/bin、/etc等等 (linux文件目录结构

不同linux发行版本的区别主要就是rootfs。比如 ubuntu 14.04使用upstart 管理服务,apt管理软件包,而centos7 使用systemd和yum。这些都是用户空间上的区别,linux kernel差别不大。

所以docker可以同时支持多种linux镜像,模拟出多种操作系统环境。即上层提供各自的rootfs,底层共用docker host的kernel。

说明:容器运行只能使用host的kernel,并且不能修改。所有容器共用host的kernel,在容器中没办法对kernel升级。如果

容器对kernel版本有要求,建议使用虚拟机。

2.镜像的分层结构

采用分层结构最大的一个好处:共享资源,比如:有多个镜像都从相同的base镜像构建而来,那么docker host只需在磁盘上保存一份base镜像,同时内存中也只需加载一份base镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享。

镜像(Image)就是一堆只读层(read-only layer)的统一视角,从左边我们看到了多个只读层,它们重叠在一起。除了最下面一层,其它层都会有一个指针指向下一层。这些层是Docker内部的实现细节,并且能够 在主机(宿主机:运行Docker的机器)的文件系统上访问到。统一文件系统(union file system)技术能够将不同的层整合成一个文件系统,为这些层提供了一个统一的视角,这样就隐藏了多层的存在,在用户的角度看来,只存在一个文件系统。 我们可以在图片的右边看到这个视角的形式。

3.容器和镜像的关系

容器(container)的定义和镜像(image)几乎一模一样,也是一堆层的统一视角,唯一区别在于容器的最上面那一层是可读可写的。

容器 = 镜像 + 可读层。

镜像是静态的,镜像的每一层都只是可读的,而容器是动态的里面运行着我们指定的应用,容器里面的应用可能会新建一个文件,修改一个目录,这些操作所带来的改变并不会作用到镜像里面,因为镜像只是可读的。所以通过镜像创建容器就是在镜像上加一个可读写的层。一个镜像可创建多个容器,每个容器都有各自的一个可读写层,这些层相互独立共享下面的镜像

4.运行的容器

一个运行态容器(running container)被定义为一个可读写的统一文件系统加上隔离的进程空间和包含其中的进程,一个容器中的进程可能会对文件进行修改、删除、创建,这些改变都将作用于可读写层(read-write layer)

三.构建镜像和容器的过程

1.获取镜像

我们通过 docker pull java 从Docker Hub下载镜像

这种方式可以很方便的获取共有镜像,利用共有镜像去部署我们需要的应用。但是共有镜像很难满足我们实际过程中的自定义需求,要想实现一次部署,处处执行的docker理念,我们需要去构建我们自己的镜像。

2.构建镜像

构建镜像的方式有两种:

第一种:利用Docker的commit命令创建镜像

当我们需要定制容器的时候,我们会先从Dockerhub上拉取共有镜像,例如centos。假设我们这个时候需要一个可以运行Web服务的容器,我们需要在centos中安装Apache,python等等服务。在利用共有镜像穿件容器之后,安装我们需要的服务,随后退出容器去执行docker commit 命令

docker commit 容器ID 目标镜像仓库和镜像名

docker commit 只是提交创建容器的镜像与容器的当前状态有差异的部分,是一种轻量级的操作。

第二种:利用dockerfile创建镜像

在实际中,我们并不推荐使用commit的方式去创建镜像,而是通过构建Dockerfile去创建镜像。

原因是镜像的分层系统在dockerfile中的构建方式是一致的,当我们在利用dockerfile构建镜像时,会根据每一行的命令构建每一层镜像,构建出来的镜像层可以进行复用,并且在实际开发

以下贴出来的是运行在公司阿里云ECI弹性容器上的dockerfile:

# Version: 0.0.1

FROM ubuntu:14.04

MAINTAINER Yu "512688011@qq.com"

ENV DEBIAN_FRONTEND noninteractive

COPY platform_operation  /code/platform_operation/

RUN  sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list \

     && apt-get clean \

     && apt-get update \

     && apt-get install -y vim && apt-get install -y python-minimal python-dev \

                   && apt-get install -y libmysqlclient-dev && apt-get install -y python-pip

COPY pip.conf  /root/.pip/

WORKDIR /code/platform_operation

RUN  pip install -r /code/platform_operation/requirement.txt

CMD ["python", "synchronous_db.py", "test"]

这个dockerfile的主要作用是运行公司同步注册账号的python脚本和定时任务。

创建好dockerfile之后,我们可以通过docker build 目录 的方式去构建镜像

以下用一个简单的dockerfile(不是上面这个)去说明何为镜像层复用

dockerfile:

FROM ubuntu:16.04

MAINTAINER yu '512688011@qq.com'

RUN apt-get update

RUN echo 'aa'>aaa.log

第一次构建过程:

[root@iZuf6awl0s0uer2sg1hc6dZ image_make]# docker build .

Sending build context to Docker daemon   35.8MB

Step 1/4 : FROM ubuntu:16.04

---> 13c9f1285025

Step 2/4 : MAINTAINER yu '512688011@qq.com'

---> Running in 0576e8f42a8c

Removing intermediate container 0576e8f42a8c

---> e0b8adad867a

Step 3/4 : RUN apt-get update

---> Running in 969d718b2ab1

Get:1 http://archive.ubuntu.com/ubuntu xenial InRelease [247 kB]

Get:2 http://security.ubuntu.com/ubuntu xenial-security InRelease [109 kB]

Get:3 http://security.ubuntu.com/ubuntu xenial-security/main amd64 Packages [892 kB]

Get:4 http://archive.ubuntu.com/ubuntu xenial-updates InRelease [109 kB]

Get:5 http://archive.ubuntu.com/ubuntu xenial-backports InRelease [107 kB]

Get:6 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages [1558 kB]

Get:7 http://security.ubuntu.com/ubuntu xenial-security/restricted amd64 Packages [12.7 kB]

Get:8 http://security.ubuntu.com/ubuntu xenial-security/universe amd64 Packages [567 kB]

Get:9 http://security.ubuntu.com/ubuntu xenial-security/multiverse amd64 Packages [6121 B]

Get:10 http://archive.ubuntu.com/ubuntu xenial/restricted amd64 Packages [14.1 kB]

Get:11 http://archive.ubuntu.com/ubuntu xenial/universe amd64 Packages [9827 kB]

Get:12 http://archive.ubuntu.com/ubuntu xenial/multiverse amd64 Packages [176 kB]

Get:13 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages [1275 kB]

Get:14 http://archive.ubuntu.com/ubuntu xenial-updates/restricted amd64 Packages [13.1 kB]

Get:15 http://archive.ubuntu.com/ubuntu xenial-updates/universe amd64 Packages [974 kB]

Get:16 http://archive.ubuntu.com/ubuntu xenial-updates/multiverse amd64 Packages [19.1 kB]

Get:17 http://archive.ubuntu.com/ubuntu xenial-backports/main amd64 Packages [7942 B]

Get:18 http://archive.ubuntu.com/ubuntu xenial-backports/universe amd64 Packages [8532 B]

Fetched 15.9 MB in 6s (2590 kB/s)

Reading package lists...

Removing intermediate container 969d718b2ab1

---> 78de1a4c80d7

Step 4/4 : RUN echo 'aa'>aaa.log

---> Running in c07271ec42a9

Removing intermediate container c07271ec42a9

---> 1c6e7034f1de

Successfully built 1c6e7034f1de

第二次构建过程

[root@iZuf6awl0s0uer2sg1hc6dZ image_make]# docker build .

Sending build context to Docker daemon   35.8MB

Step 1/4 : FROM ubuntu:16.04

---> 13c9f1285025

Step 2/4 : MAINTAINER yu '512688011@qq.com'

---> Using cache

---> e0b8adad867a

Step 3/4 : RUN apt-get update

---> Using cache

---> 78de1a4c80d7

Step 4/4 : RUN echo 'aa'>aaa.log

---> Using cache

---> 1c6e7034f1de

Successfully built 1c6e7034f1de

我们可以看出,当我们第一次构建镜像的时候,指令是按照每一行来执行的,每执行完一行我们都会得到一个新的镜像层,我们可以理解为每一行执行之后都commit了一个新的镜像,而下一层的指令会在新的镜像层上进行。

当我们进行第二次构建的时候,会发现同样的镜像层直接使用cache进行构建。这样的构建更加迅速,粒度也更好。

3.关于上下文

首先需要理解build命令的工作原理:

 

Docker是cs架构,也就是docker在运行时分为服务端(docker引擎)和客户端。用户在客户端所输入的一系列以docker开头的命令都是在通过一组RestAPI在远程与docker引擎交互。因此,虽然看起来我们是在本地直接操作docker,但是其实质是在远程调用并执行。基于这样的设计,我们很轻松地操作远端服务器上的Docker引擎。、

dockerfile中经常会有ADD或是COPY这样拷贝文件的指令。而build的过程中,服务端的docker引擎需要知道你本地文件的路径,因此引入了上下文概念。

当在build命令中指定了编译路径时,那么docker引擎会将该路径下的所有文件打包,然后上传至docker引擎,此时,docker服务端就收到了这个上下文的路径内容,对其展开便能够看到所有该路径下的文件。

 

但是上下文路径并不一定是Dockerfile所在的路径

默认情况下建议将Dockerfile和所需的各种文件以及包放在同一个目录下,理所当然此时所构建的上下文路径就是指Dockerfile所在的路径。但是并不是一定要这样做,-f 参数可以指定Dockerfile的所在路径以及名字,但是大多时候都是使用默认的名字即可。

使用时注意,不要将Dockerfile置于根目录下进行编译

建议是建一个空目录,创建Dockerfile,并将你要用的文件和所有包置于该目录下。

 

4.联合文件系统 AUFS (Another Union File System 或 Advanced Multilayered Unification File System)

4.1 docker思想的实现方式

Docker联合文件系统Union File System,它是实现Docker镜像的技术基础,是一种轻量级的高性能分层文件系统,支持将文件系统中的修改进行提交和层层叠加,这个特性使得镜像可以通过分层实现和继承。同时支持将不同目录挂载到同一个虚拟文件系统下。

 

4.2 docker 加载过程

在上文中2.1中已经叙述过

Linux操作系统分别由两部分组成: 1.内核空间(kernel) 2.用户空间(rootfs)

内核空间(kernel)是kernel,Linux刚启动时会加载bootfs文件系统,之后bootf会被卸载掉,

用户空间(rootfs)的文件系统是rootfs,包含常见的目录,如/dev、/proc、/bin、/etc等等 (linux文件目录结构

 

Linux在启动后,首先将RootFS 置为 Readonly,进行一系列检查后将其切换为Readwrite供用户使用。在Docker中,也是利用该技术,然后利用Union Mount在Readonly的RootFS文件系统之上挂载Readwrite文件系统。并且向上叠加, 使得一组Readonly和一个Readwrite的结构构成一个Container的运行目录、每一个被称作一个文件系统Layer。

 

AUFS的特性, 使得每一个对Readonly层文件/目录的修改都只会存在于上层的Writeable层中。这样由于不存在竞争、而且多个Container可以共享Readonly文件系统层。

 

在Docker中,将Readonly的层称作“image” 镜像。对于Container整体而言,整个RootFS变得是read-write的,但事实上所有的修改都写入最上层的writeable层中,image不保存用户状态,可以用于模板、重建和复制。

因此,想要从一个Image启动一个Container,Docker会先逐次加载其父Image直到Base Image,用户的进程运行在Writeable的文件系统层中。所有父Image中的数据信息以及ID、网络具体container的配置等,构成一个Docker概念上的Container。

 

这就是docker容器和镜像的实现方式,docker也因为AUFS才有用了轻量级的优势:

1.节省存储空间 - 多个container可以共享base image存储

2.快速部署 - 如果要部署多个container,base image可以避免多次拷贝

3.内存更省 - 因为多个container共享base image, 以及OS的disk缓存机制, 多个container中的进程命中缓存内容的几率大大增加

4.升级更方便 - 相比于 copy-on-write 类型的FS,base-image也是可以挂载为可writeable的,可以通过更新base image而一次性更新其之上的container

5.允许在不更改base-image的同时修改其目录中的文件 - 所有写操作都发生在最上层的writeable层中,这样可以大大增加base image能共享的文件内容。

以上5条 1-3 条可以通过 copy-on-write 的FS实现, 4可以利用其他的union mount方式实现, 5只有AUFS实现的很好。

这也是为什么Docker一开始就建立在AUFS之上。

 

再举个栗子:

Dockerfile中命令与镜像层一一相应。那么意味着docker build完成之后,镜像的总大小=每一层镜像的大小总和。

 

尽管终于镜像的大小是每层镜像的累加,可是须要额外注意的是:Docker镜像的大小并不等于容器中文件系统内容的大小(不包含挂载文件,/proc、/sys等虚拟文件)。个中缘由,就和联合文件系统有非常大的关系了。

首先来看一下这个简单的Dockerfile样例(假如在Dockerfile当前文件夹下有一个100MB的压缩文件compressed.tar):

FROM ubuntu:14.04

ADD compressed.tar /

RUN rm /compressed.tar

ADD compressed.tar /

1.FROM ubuntu:镜像ubuntu:14.04的大小为200MB;

2.ADD compressed.tar /: compressed.tar文件为100MB,因此当前镜像层的大小为100MB,镜像总大小为300MB。

3.RUN rm /compressed.tar:删除文件compressed.tar,此时的删除并不会删除下一层的compressed.tar文件。仅仅会在当前层产生一个compressed.tar的删除标记,确保通过该层将看不到compressed.tar,因此当前镜像层的大小也为0。镜像总大小为300MB。

4.ADD compressed.tar /:compressed.tar文件为100MB,因此当前镜像层的大小为300MB+100MB。镜像总大小为400MB。

分析完成之后,我们发现镜像的总大小为400MB。可是假设执行该镜像的话,我们非常快能够发如今容器根文件夹下执行du -sh之后。显示的数值并不是400MB,而是300MB左右。

基本的原因还是:联合文件系统的性质保证了两个拥有compressed.tar文件的镜像层,仅仅会容器看到一个。同一时候这也说明了一个现状,当用户基于一个非常大,甚至好几个GB的镜像执行容器时。在容器内部查看根文件夹大小,发现居然仅仅有500MB不到

结论:镜像大小和容器大小有着本质的差别。


至此docker的基础定义差不多介绍结束

 

参考博客:https://baijiahao.baidu.com/s?id=1594187941922400728&wfr=spider&for=pc  

                 https://blog.51cto.com/liuleis/2298633

                 https://www.cnblogs.com/claireyuancy/p/7029126.html

                 https://www.jianshu.com/p/50f48eb25215

                 https://www.cnblogs.com/bethal/p/5942369.html

                 https://blog.csdn.net/qq_37788081/article/details/79044119

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值