Docker实践之02-使用镜像及定制

时间:2023-01-20 22:34:54

目录

一.获取镜像

Docker运行容器前需要本地存在对应的镜像,如果本地不存在该镜像,Docker会从镜像仓库下载该镜像。
从Docker镜像仓库获取镜像的命令是docker pull,其命令格式为:

docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]

如:

$ docker pull ubuntu:16.04
16.04: Pulling from library/ubuntu
297061f60c36: Pull complete 
e9ccef17b516: Pull complete 
dbc33716854d: Pull complete 
8fe36b178d25: Pull complete 
686596545a94: Pull complete 
Digest: sha256:1dfb94f13f5c181756b2ed7f174825029aca902c78d0490590b1aaa203abc052
Status: Downloaded newer image for ubuntu:16.04

上面的命令中没有给出Docker镜像仓库地址,因此将会从Docker Hub获取镜像(如果配置镜像加速器,则从镜像加速器上拉取镜像)。而镜像名称是ubuntu:16.04,因此将会获取官方镜像library/ubuntu仓库中标签为16.04的镜像。
从下载过程中可以看到我们之前提及的分层存储的概念,镜像是由多层存储所构成。下载也是一层层的去下载,并非单一文件。下载过程中给出了每一层的ID前12位,并且下载结束后,给出该镜像完整的sha256的摘要,以确保下载一致性。

二.使用镜像启动容器实例

使用命令:docker run从镜像启动容器实例。

$ docker run -it --rm ubuntu:16.04 bash

docker run就是运行容器的命令,简要的说明一下上面用到的参数。
-it:这是2个参数,一个是-i:交互式操作,一个-t:终端。
--rm:这个参数说明容器退出之后随之将其删除。默认情况,不使用--rm参数启动的容器在退出之后不会立即删除,除非使用命令明确删除:docker rm
ubuntu:16.04:指定使用ubuntu:16.04这个镜像为基础启动容器
bash:放在镜像名之后的是命令,即:启动容器之后在容器中执行的命令

使用exit命令退出容器。

三.列出镜像

使用docker image ls命令列出当前已经下载到本地的镜像。

$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              16.04               0b1edfbffd27        4 days ago          113MB
hello-world         latest              e38bc07ac18e        2 weeks ago         1.85kB

输出列表中包含了仓库名,标签,镜像ID,创建时间和所占用的空间。
镜像ID则是镜像的唯一标识,一个镜像可以对应多个标签(不同标签下的同一个镜像的ID值相同)。

四.删除本地镜像

$ docker image rm <镜像id>

五.定制镜像

镜像的定制实际上就是定制每一层所添加的配置和文件,定制镜像有2种方式:commit,Dockerfile。

通过commit命令定制镜像

使用镜像启动容器实例之后,在容器内部做的所有修改,都可以使用commit命令将容器存储层保存为镜像。如:

$ docker commit --author "zhangsan@xxx.com" --message "modify default index page content" webserver nginx:v2

--author指定修改的作者,而--message则是记录本次修改的内容,webserver为容器名称,nginx:v2为需要保存为的镜像仓库名和标签。

慎用docker commit命令保存镜像!制作镜像应该使用Dockerfile实现。使用docker commit命令保存镜像存在一些缺陷:
首先,在对容器进行配置时,会涉及多个文件的修改或添加,但其实有些文件是不需要保存为镜像的,如果不进行小心清理,会导致最终保存的镜像文件过于臃肿。
其次,使用命令方式意味着所有对镜像的操作都是黑箱的,不利于重现镜像制作的步骤,以及后期的维护。

通过Dockerfile定制镜像

通过Dockerfile定制镜像就是把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。
Dockerfile是一个文本文件,其内包含了一条条的指令,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。

使用了docker build命令进行镜像构建,其格式为:

docker build [选项] <上下文路径/URL/->

例如: $ docker build -t nginx:v3 .

docker build的工作原理

首先我们要理解docker build的工作原理。Docker在运行时分为Docker引擎(也就是服务端守护进程)和客户端工具。Docker的引擎提供了一组REST API,被称为Docker Remote API,而如docker命令这样的客户端工具,则是通过这组API与Docker引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种Docker功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成的。也因为这种C/S设计,让我们操作远程服务器的Docker引擎变得轻而易举。
当我们进行镜像构建的时候,并非所有定制都会通过RUN指令完成,经常会需要将一些本地文件复制进镜像,比如通过COPY指令,ADD指令等。而docker build命令构建镜像,其实并非在本地构建,而是在服务端,也就是Docker引擎中构建的。那么在这种C/S架构中,如何才能让服务端获得本地文件呢?这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径,docker build命令得知这个路径后,会将路径下的所有内容打包,然后上传给Docker引擎。这样Docker引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。如果在Dockerfile中这么写:“COPY ./package.json /app/”,这并不是要复制执行docker build命令所在的目录下的package.json ,也不是复制Dockerfile所在目录下的 package.json ,而是复制上下文(context)目录下的package.json。因此,COPY这类指令中的源文件的路径都是相对路径。这也是初学者经常会问的为什么“COPY ../package.json /app”或者“COPY /opt/xxxx /app”无法工作的原因,因为这些路径已经超出了上下文的范围,Docker引擎无法获得这些位置的文件。如果真的需要那些文件,应该将它们复制到上下文目录中去。
现在就可以理解刚才的命令docker build -t nginx:v3 .中的这个.实际上是在指定上下文的目录,docker build命令会将该目录下的内容打包交给Docker引擎以帮助构建镜像。
如果观察docker build输出,我们其实已经看到了这个发送上下文的过程:

$ docker build -t nginx:v3 .
Sending build context to Docker daemon 2.048 kB
...

理解构建上下文对于镜像构建是很重要的,避免犯一些不应该的错误。比如有些初学者在发现COPY /opt/xxxx /app不工作后,于是干脆将Dockerfile放到了硬盘根目录去构建,结果发现docker build执行后,在发送一个几十GB的东西,极为缓慢而且很容易构建失败。那是因为这种做法是在让docker build打包整个硬盘,这显然是使用错误。
一般来说,应该将Dockerfile置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给Docker引擎,那么可以用.gitignore一样的语法写一个.dockerignore ,该文件是用于剔除不需要作为上下文传递给Docker引擎的。那么为什么会有人误以为“.”是指定Dockerfile所在目录呢?这是因为在默认情况下,如果不额外指定Dockerfile的话,会将上下文目录下的名为“Dockerfile”的文件作为构建镜像需要的Dockerfile。这只是默认行为,实际上Dockerfile的文件名并不要求必须为Dockerfile,而且并不要求必须位于上下文目录中,比如可以用“-f ../Dockerfile.php”参数指定某个文件作为Dockerfile 。
当然,一般大家习惯性的会使用默认的文件名Dockerfile,以及会将其置于镜像构建上下文目录中。

docker build的用法

docker build的基本语法为:docker build [选项] <上下文路径/URL/->,从语法中可以很明显地看出该命令支持多种构建方式.

1.从本地文件系统构建

$ docker build -t nginx:v3 .

上述构建操作包含2个参数:
"-t"指定镜像标签名称
"."指定构建上下文路径

2.从Git仓库构建

$ docker build https://github.com/nuccch/docker_test#:8.14

这行命令指定了构建所需的Git仓库,并且指定默认的master分支,构建目录为/8.14/,然后Docker就会自己去 git clone这个项目、切换到指定分支、并进入到指定目录后开始构建。
说明: 从Git仓库中构建Docker镜像是在实际项目中使用得比较多的方式,开发者只需要将代码提交到指定Git仓库的指定分支,测试只需要从该仓库的分支上构建Docker镜像即可.

3.用给定的tar压缩包构建

$ docker build http://server/context.tar.gz

如果所给出的URL不是个Git仓库,而是个tar压缩包,那么Docker引擎会下载这个包,并自动解压缩,以其作为上下文,开始构建。

4.从标准输入中读取Dockerfile进行构建

$ docker build - < Dockerfile

或者

$ cat Dockerfile | docker build -

如果标准输入传入的是文本文件,则将其视为Dockerfile ,并开始构建。
注意: 这种形式由于直接从标准输入中读取Dockerfile的内容,它没有上下文,因此不可以存在像其他方法那样可以将本地文件COPY进镜像之类的事情。

5.从标准输入中读取上下文压缩包进行构建

$ docker build - < context.tar.gz

如果发现标准输入的文件格式是gzip,bzip2以及xz的话,将会使其为上下文压缩包,直接将其展开,将里面视为上下文,并开始构建。

【参考】
http://blog.daocloud.io/principle-of-docker-image/ 深入分析 Docker 镜像原理
https://yq.aliyun.com/articles/68477 Docker镜像原理和最佳实践