您的位置:首页 > 运维架构 > Docker

Docker 使用镜像

2017-11-28 16:52 260 查看

获取镜像

Docker Hub 上有大量的高质量的镜像可以用,这里我们就说一下怎么获取这些 镜像并运行。

获取镜像的命令是 docker pull 。其命令格式为:

docker pull [选项] [Docker Registry地址]<仓库名>:<标签>


docker pull centos:7


运行

有了镜像后,我们就可以以这个镜像为基础启动一个容器来运行。以上面的 centos:7 为例,如果我们打算启动里面的 bash 并且进行交互式操作的话,可以执行下面的命令。

$ docker run -it --rm centos:7 bash


列出镜像

要想列出已经下载下来的镜像,可以使用

$ docker images


$ docker images ls


镜像体积

这里标识的所占用空间和在 Docker Hub 上看到的镜像大小不同。 比如, ubuntu:16.04 镜像大小,在这里是 127 MB ,但是在 Docker Hub 显示的却是 50 MB。这是因为 Docker Hub 中显示的体积是压缩后的体积。在镜像下载和上传过程中镜像是 保持着压缩状态的,因此 Docker Hub 所显示的大小是网络传输中更关心的流量大小。而 docker images 显示的是镜像下载到本地后,展开的大小,准确说,是展开后的各层所占空 间的总和,因为镜像到本地后,查看空间的时候,更关心的是本地磁盘空间占用的大小。

另外一个需要注意的问题是, docker images 列表中的镜像体积总和并非是所有镜像实际硬 盘消耗。由于 Docker 镜像是多层存储结构,并且可以继承、复用,因此不同镜像可能会因为 使用相同的基础镜像,从而拥有共同的层。由于 Docker 使用 Union FS,相同的层只需要保 存一份即可,因此实际镜像硬盘占用空间很可能要比这个列表镜像大小的总和要小的多。

中间层镜像

默认的 docker images 列表中只会显示顶层镜像,如果希 望显示包括中间层镜像在内的所有镜像的话,需要加 -a 参数。

$ docker images -a


列出部分镜像

$ docker images ubuntu


以特定格式显示

# docker image ls


使用 commit 制作镜像

镜像是多层存储,每一层是在前一层的基础上进行的修改; 而容器同样也是多层存储,是在以镜像为基础层,在其基础上加一层作为容器运行时的存储层。

启动一个镜像

$ docker run --name webserver -d -p 8889:80 nginx


这条命令会用 nginx 镜像启动一个容器,命名为 webserver ,并且映射了 80 端口,这样我们可以用浏览器去访问这个 nginx 服务器。

我们可以使用 docker exec 命令进入容器,修改其内容。

$ docker exec -it webserver bash
root@3729b97e8226:/# echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
root@3729b97e8226:/# exit
exit


我们修改了容器的文件,也就是改动了容器的存储层。我们可以通过 docker diff 命令看到具体的改动。

$ docker diff webserver


docker commit 的语法格式为:

docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]


我们可以用下面的命令将容器保存为镜像:

$ docker commit \
--author "xiao <jeiker@126.com>" \
--message "修改了默认网页" \
webserver \
nginx:v2
sha256:07e33465974800ce65751acc279adc6ed2dc5ed4e0838f8b86f0c87aa1795214


–author 是指定修改的作者

–message 则是记录本次修改的内容。

这点和 git版本控制相似,不过这里这些信息可以省略留空。

docker images 中看到这个新定制的镜像

可以用 docker history 具体查看镜像内的历史记录

$ docker history nginx:v2


新的镜像定制好后,我们可以来运行这个镜像。

docker run --name web2 -d -p 8880:80 nginx:v2


慎用 docker commit

使用 docker commit 命令虽然可以比较直观的帮助理解镜像分层存储的概念,但是实际环境 中并不会这样使用。

首先,如果仔细观察之前的
docker diff webserver
的结果,你会发现除了真正想要修改的
/usr/share/nginx/html/index.html
文件外,由于命令的执行,还有很多文件被改动或添加 了。这还仅仅是最简单的操作,如果是安装软件包、编译构建,那会有大量的无关内容被添

加进来,如果不小心清理,将会导致镜像极为臃肿。

此外,使用 docker commit 意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为黑 箱镜像.

换句话说,就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像,别人根 本无从得知。而且,即使是这个制作镜像的人,过一段时间后也无法记清具体在操作的。

虽 然 docker diff 或许可以告诉得到一些线索,但是远远不到可以确保生成一致镜像的地步。

这种黑箱镜像的维护工作是非常痛苦的。

使用 Dockerfile 定制镜像

从刚才的 docker commit 的学习中,我们可以了解到,镜像的定制实际上就是定制每一层所 添加的配置、文件。

如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚 本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问 题、体积的问题就都会解决。这个脚本就是 Dockerfile。

Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层, 因此每一条指令的内容,就是描述该层应当如何构建。

在一个空白目录中,建立一个文本文件,并命名为 Dockerfile :

$ mkdir mynginx

$ cd mynginx

$ touch Dockerfile


其内容为:

FROM nginx

RUN echo '<h1>Hello, Dockerfile!</h1>' > /usr/share/nginx/html/index.html


这个 Dockerfile 很简单,一共就两行.

就用到了两条指令:

FROM

RUN

FROM 指定基础镜像

所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。

就像我们之前运行了一个 nginx 镜像的容器,再进行修改一样,基础镜像是必须指定的。而 FROM 就是指定基础镜 像,因此一个 Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令。

就像建房子需要地基一样。

在 Docker Store 上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如 nginx 、 redis 、 mongo 、 mysql 、 httpd 、 php 、 tomcat 等;

也有一些方便开发、构建、运行各种语言应用的镜像,如 node 、 openjdk 、 python 、 ruby 在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。

如果没有找到对应服务的镜像,官方镜像中还提供了一些更为基础的操作系统镜像,如 ubuntu 、 debian 、 centos 、 fedora 、 alpine 等,这些操作系统的软件库为我们提供了更广阔的扩展空间。

除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch。这个镜像 是虚拟的概念,并不实际存在,它表示一个空白的镜像。

如果你以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作 为镜像第一层开始存在。

RUN 执行命令

RUN 指令是用来执行命令行命令的。

由于命令行的强大能力, RUN 指令在定制镜像时是最常用的指令之一。

shell 格式:RUN <命令>,就像直接在命令行中输入的命令一样。

exec 格式:RUN [“可执行文件”, “参数1”, “参数2”],这更像是函数调用中的格式。

Dockerfile 中每一个指令都会建立一层, RUN 也不例外。每一个 RUN 的行为, 就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束 后, commit 这一层的修改,构成新的镜像。

Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过 127 层。

首先,之前所有的命令只有一个目的,就是编译、安装 redis 可执行文件。因此没有必要建立 很多层,这只是一层的事情。因此,这里没有使用很多个 RUN 对一一对应不同的命令,而是 仅仅使用一个 RUN 指令,并使用 && 将各个所需命令串联起来。将之前的 7 层,简化为了 1 层。在撰写 Dockerfile 的时候,要经常提醒自己,这并不是在写 Shell 脚本,而是在定义每 一层该如何构建。

并且,这里为了格式化还进行了换行。Dockerfile 支持 Shell 类的行尾添加 \ 的命令换行方 式,以及行首 # 进行注释的格式。良好的格式,比如换行、缩进、注释等,会让维护、排障 更为容易,这是一个比较好的习惯。

此外,还可以看到这一组命令的最后添加了清理工作的命令,删除了为了编译构建所需要的 软件,清理了所有下载、展开的文件,并且还清理了 apt 缓存文件。这是很重要的一步,我 们之前说过,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。 因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清 理掉。

很多人初学 Docker 制作出了很臃肿的镜像的原因之一,就是忘记了每一层构建的最后一定要 清理掉无关文件。

构建镜像

好了,让我们再回到之前定制的 nginx 镜像的 Dockerfile 来。现在我们明白了这个 Dockerfile 的内容,那么让我们来构建这个镜像吧。

在 Dockerfile 文件所在目录执行:

$ docker build -t nginx:v3 .


在这里我们指定了最终镜像的名称 -t nginx:v3 ,构建成功后,我们可以像之前运行 nginx:v2 那样来运行这个镜像,其结果会和 nginx:v2 一样。

Sending build context to Docker daemon 2.048 kB
Step 1 : FROM nginx
---> 9e7424e5dbae
Step 2 : RUN echo '<h1>Hello, Dockerfile!</h1>' > /usr/share/nginx/html/index.html
---> Running in cd78a57c5464
---> 7b9af65ffe24
Removing intermediate container cd78a57c5464
Successfully built 7b9af65ffe24


查看构建完成后的镜像

# docker images nginx


命令格式:

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


镜像构建上下文(Context)

如果注意,会看到 docker build 命令最后有一个 . 。 .表示当前目录,而 Dockerfile 就在当前目录,因此不少初学者以为这个路径是在指定 Dockerfile 所在路径,这么理解其实是不准确的。如果对应上面的命令格式,你可能会发现,这是在指定上下文路径。

那么什么是上下文呢?

首先我们要理解 docker build 的工作原理。

Docker 在运行时分为 Docker 引擎(也就是服 务端守护进程)和客户端工具。

Docker 的引擎提供了一组 REST API,被称为 Docker Remote API,而如 docker 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交 互,从而完成各种功能。

因此,虽然表面上我们好像是在本机执行各种 docker 功能,但实 际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也因为这种 C/S 设计, 让我们操作远程服务器的 Docker 引擎变得轻而易举。

CS模式图

当我们进行镜像构建的时候,并非所有定制都会通过 RUN 指令完成,经常会需要将一些本地 文件复制进镜像,比如通过 COPY 指令、 ADD 指令等。而 docker build 命令构建镜像,其 实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端/服务 端的架构中,如何才能让服务端获得本地文件呢?

这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径, docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。

如果在 Dockerfile 中这么写:

COPY ./package.json /app/


这并不是要复制执行 docker build 命令所在的目录下的 package.json ,也不是复制 Dockerfile 所在目录下的 package.json ,而是复制 上下文(context) 目录下的package.json

现在就可以理解刚才的命令 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 ,而且并不要求必须位于上下文目录中,比如可以用 -f ../Dockerfile.php 参数指定某个文件作为Dockerfile 。

其它 docker build 的用法

直接使用Git创建进行构建

$ docker build https://github.com/twang2218/gitlab-ce-zh.git#:8.14[/code] 
这行命令指定了构建所需的 Git repo,并且指定默认的 master 分支,构建目录为 /8.14/ , 然后 Docker 就会自己去 git clone 这个项目、切换到指定分支、并进入到指定目录后开始 构建。

用指定tar 压缩包构建

$ docker build http://server/context.tar.gz[/code] 
如果所给出的 URL 不是个 Git repo,而是个tar压缩包,那么 Docker 引擎会下载这个包,并自动解压缩,以其作为上下文,开始构建。

Docker 1.13+ 推荐

$ docker image build


删除本地镜像

如果要删除本地的镜像,可以使用 docker rmi 命令,其格式为:

docker rmi [选项] <镜像1> [<镜像2> ...]


注意 docker rm 命令是删除容器,不要混淆。

用 ID、镜像名、摘要删除镜像.

我们可以用镜像的完整 ID,也称为 长 ID ,来删除镜像。

但是人工输入就太累了,所以更多的时候是用 短 ID 来删除镜像。

docker images 默认列出的就已经是短 ID 了,

一般取前3个字符以上,只要足够区分于别的镜像就可以了。

$ docker rmi 501


我们也可以用镜像名 ,也就是 <仓库名>:<标签> ,来删除镜像。

我们需要删除所有仓库名为 redis 的镜像:

$ docker rmi $(docker images -q redis)


Docker 1.13+ 推荐

$ docker image rm
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  docker