D02-镜像相关知识点
2 Docker镜像
2.1Base镜像
2.2构建镜像: docker import; docker commit ; dockerfile
2.3 dockerfile
2.4 分发镜像
2.1 Base镜像
base 镜像有两层含义:
- 
不依赖其他镜像,从 scratch 构建。 
- 
其他镜像可以之为基础进行扩展。 
所以,能称作 base 镜像的通常都是各种 Linux 发行版的 Docker 镜像,比如 Ubuntu, Debian, CentOS 等。
以 CentOS 为例考察 base 镜像包含哪些内容。
下载镜像:
docker pull centos
查看镜像信息:

镜像大小不到 200MB。
等一下!
一个 CentOS 才 200MB ?
平时我们安装一个 CentOS 至少都有几个 GB,怎么可能才 200MB !
相信这是几乎所有 Docker 初学者都会有的疑问,包括我自己。下面我们来解释这个问题。
Linux 操作系统由内核空间和用户空间组成。
如下图所示:

rootfs
内核空间是 kernel,Linux 刚启动时会加载 bootfs 文件系统,之后 bootfs 会被卸载掉。
用户空间的文件系统是 rootfs,包含我们熟悉的 /dev, /proc, /bin 等目录。
对于 base 镜像来说,底层直接用 Host 的 kernel,自己只需要提供 rootfs 就行了。
而对于一个精简的 OS,rootfs 可以很小,只需要包括最基本的命令、工具和程序库就可以了。相比其他 Linux 发行版,CentOS 的 rootfs 已经算臃肿的了,alpine 还不到 10MB。
我们平时安装的 CentOS 除了 rootfs 还会选装很多软件、服务、图形桌面等,需要好几个 GB 就不足为奇了。
base 镜像提供的是最小安装的 Linux 发行版。
下面是 CentOS 镜像的 Dockerfile 的内容:

第二行 ADD 指令添加到镜像的 tar 包就是 CentOS 7 的 rootfs。在制作镜像时,这个 tar 包会自动解压到 / 目录下,生成 /dev, /proc, /bin 等目录。
注:可在 Docker Hub 的镜像描述页面中查看 Dockerfile 。
支持运行多种 Linux OS
不同 Linux 发行版的区别主要就是 rootfs。
比如 Ubuntu 14.04 使用 upstart 管理服务,apt 管理软件包;而 CentOS 7 使用 systemd 和 yum。这些都是用户空间上的区别,Linux kernel 差别不大。
所以 Docker 可以同时支持多种 Linux 镜像,模拟出多种操作系统环境。

上图 Debian 和 BusyBox(一种嵌入式 Linux)上层提供各自的 rootfs,底层共用 Docker Host 的 kernel。
这里需要说明的是:
- 
base 镜像只是在用户空间与发行版一致,kernel 版本与发行版是不同的。 
 例如 CentOS 7 使用 3.x.x 的 kernel,如果 Docker Host 是 Ubuntu 16.04(比如我们的实验环境),那么在 CentOS 容器中使用的实际是是 Host 4.x.x 的 kernel。![http://7xo6kd.com1.z0.glb.clouddn.com/upload-ueditor-image-20170502-1493736942590076830.png]() 
 ① Host kernel 为 4.4.0-31
 ② 启动并进入 CentOS 容器
 ③ 验证容器是 CentOS 7
 ④ 容器的 kernel 版本与 Host 一致
- 
容器只能使用 Host 的 kernel,并且不能修改。 
 所有容器都共用 host 的 kernel,在容器中没办法对 kernel 升级。如果容器对 kernel 版本有要求(比如应用只能在某个 kernel 版本下运行),则不建议用容器,这种场景虚拟机可能更合适。
2.2 镜像的分层结构
Docker 支持通过扩展现有镜像,创建新的镜像。
实际上,Docker Hub 中 99% 的镜像都是通过在 base 镜像中安装和配置需要的软件构建出来的。比如我们现在构建一个新的镜像,Dockerfile 如下:

① 新镜像不再是从 scratch 开始,而是直接在 Debian base 镜像上构建。
② 安装 emacs 编辑器。
③ 安装 apache2。
④ 容器启动时运行 bash。
构建过程如下图所示:

可以看到,新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层。
问什么 Docker 镜像要采用这种分层结构呢?
最大的一个好处就是 - 共享资源。
比如:有多个镜像都从相同的 base 镜像构建而来,那么 Docker Host 只需在磁盘上保存一份 base 镜像;
同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。
而且镜像的每一层都可以被共享,我们将在后面更深入地讨论这个特性。
这时可能就有人会问了:如果多个容器共享一份基础镜像,当某个容器修改了基础镜像的内容,比如 /etc 下的文件,这时其他容器的 /etc 是否也会被修改?
答案是不会!
修改会被限制在单个容器内。
这就是我们接下来要学习的容器 Copy-on-Write 特性。
可写的容器层
当容器启动时,一个新的可写层被加载到镜像的顶部。
这一层通常被称作“容器层”,“容器层”之下的都叫“镜像层”。

所有对容器的改动 - 无论添加、删除、还是修改文件都只会发生在容器层中。
只有容器层是可写的,容器层下面的所有镜像层都是只读的。
下面我们深入讨论容器层的细节。
镜像层数量可能会很多,所有镜像层会联合在一起组成一个统一的文件系统。
如果不同层中有一个相同路径的文件,比如 /a,上层的 /a 会覆盖下层的 /a,也就是说用户只能访问到上层中的文件 /a。
在容器层中,用户看到的是一个叠加之后的文件系统。
- 
添加文件 
 在容器中创建文件时,新文件被添加到容器层中。
- 
读取文件 在容器中读取某个文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后打开并读入内存。 
- 
修改文件 在容器中修改已存在的文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后修改之。 
- 
删除文件 在容器中删除文件时,Docker 也是从上往下依次在镜像层中查找此文件。找到后,会在容器层中记录下此删除操作。 
只有当需要修改时才复制一份数据,这种特性被称作 Copy-on-Write。可见,容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改。
这样就解释了我们前面提出的问题:容器层记录对镜像的修改,所有镜像层都是只读的,不会被容器修改,所以镜像可以被多个容器共享。
理解了镜像的原理和结构,下一节我们学习如何构建镜像。
2.3 构建镜像
对于 Docker 用户来说,最好的情况是不需要自己创建镜像。几乎所有常用的数据库、中间件、应用软件等都有现成的 Docker 官方镜像或其他人和组织创建的镜像,我们只需要稍作配置就可以直接使用。
使用现成镜像的好处除了省去自己做镜像的工作量外,更重要的是可以利用前人的经验。特别是使用那些官方镜像,因为 Docker 的工程师知道如何更好的在容器中运行软件。
当然,某些情况下我们也不得不自己构建镜像,比如:
- 
找不到现成的镜像,比如自己开发的应用程序。 
- 
需要在镜像中加入特定的功能,比如官方镜像几乎都不提供 ssh。 
所以本节我们将介绍构建镜像的方法。同时分析构建的过程也能够加深我们对前面镜像分层结构的理解。
Docker 提供了两种构建镜像的方法:
- 手动制作系统镜像
- 
docker commit 命令 
- 
Dockerfile 构建文件 
2.3.1 手动制作系统镜像(将一台物理机或vm转换为docker镜像)
使用官方的构建脚本,在github上官方源码里面就有。
cd /images/
git clone https://github.com/dotcloud/docker.git
cd /images/docker/contrib
来看看下载的源码里都有什么,进入contrib目录
 
[root@node-pc1 contrib]# ll total 168 drwxr-xr-x 18 root root 4096 4月 27 11:44 ./ drwxr-xr-x 37 root root 4096 4月 27 11:44 ../ drwxr-xr-x 2 root root 4096 4月 27 11:44 apparmor/ drwxr-xr-x 4 root root 4096 4月 27 11:44 builder/ -rwxr-xr-x 1 root root 10257 4月 27 11:44 check-config.sh* drwxr-xr-x 4 root root 4096 4月 27 11:44 desktop-integration/ drwxr-xr-x 2 root root 4096 4月 27 11:44 docker-device-tool/ -rwxr-xr-x 1 root root 3140 4月 27 11:44 dockerize-disk.sh* -rwxr-xr-x 1 root root 2864 4月 27 11:44 docker-machine-install-bundle.sh* -rwxr-xr-x 1 root root 3874 4月 27 11:44 download-frozen-image-v1.sh* -rwxr-xr-x 1 root root 11735 4月 27 11:44 download-frozen-image-v2.sh* -rw-r--r-- 1 root root 190 4月 27 11:44 editorconfig drwxr-xr-x 2 root root 4096 4月 27 11:44 gitdm/ drwxr-xr-x 2 root root 4096 4月 27 11:44 httpserver/ drwxr-xr-x 7 root root 4096 4月 27 11:44 init/ -rwxr-xr-x 1 root root 1596 4月 27 11:44 mac-install-bundle.sh* drwxr-xr-x 2 root root 4096 4月 27 11:44 mkimage/ -rwxr-xr-x 1 root root 1656 4月 27 11:44 mkimage-alpine.sh* -rw-r--r-- 1 root root 2788 4月 27 11:44 mkimage-archarm-pacman.conf -rw-r--r-- 1 root root 2702 4月 27 11:44 mkimage-arch-pacman.conf -rwxr-xr-x 1 root root 3468 4月 27 11:44 mkimage-arch.sh* -rwxr-xr-x 1 root root 1911 4月 27 11:44 mkimage-crux.sh* -rwxr-xr-x 1 root root 1832 4月 27 11:44 mkimage-pld.sh* -rwxr-xr-x 1 root root 3270 4月 27 11:44 mkimage.sh* -rwxr-xr-x 1 root root 3641 4月 27 11:44 mkimage-yum.sh* drwxr-xr-x 2 root root 4096 4月 27 11:44 nnp-test/ -rwxr-xr-x 1 root root 1473 4月 27 11:44 nuke-graph-directory.sh* -rwxr-xr-x 1 root root 477 4月 27 11:44 project-stats.sh* -rw-r--r-- 1 root root 247 4月 27 11:44 README.md -rwxr-xr-x 1 root root 2023 4月 27 11:44 report-issue.sh* drwxr-xr-x 2 root root 4096 4月 27 11:44 reprepro/ -rw-r--r-- 1 root root 45 4月 27 11:44 REVIEWERS drwxr-xr-x 3 root root 4096 4月 27 11:44 selinux-fedora-24/ drwxr-xr-x 3 root root 4096 4月 27 11:44 selinux-oraclelinux-7/ drwxr-xr-x 5 root root 4096 4月 27 11:44 syntax/ drwxr-xr-x 2 root root 4096 4月 27 11:44 syscall-test/ drwxr-xr-x 2 root root 4096 4月 27 11:44 udev/ drwxr-xr-x 2 root root 4096 4月 27 11:44 vagrant-docker/
在线查看脚本:
https://raw.githubusercontent.com/docker/docker/master/contrib/mkimage-yum.sh
 
./mkimage-yum.sh -y /etc/yum.conf centos7 执行如上脚本,成功后,就可以看到镜像了docker images】,概要说明一下,主要是如下几步: 1.tmp目录下建立临时目录和文件系统 2.使用yum安装相关的软件包 3.软件包安装和信息定制 4.tar打包 5.清理 建立目录结构[rootfs] target=$(mktemp -d --tmpdir $(basename $0).XXXXXX) set -x mkdir -m 755 "$target"/dev mknod -m 600 "$target"/dev/console c 5 1 mknod -m 600 "$target"/dev/initctl p mknod -m "$target"/dev/full c 1 7 mknod -m "$target"/dev/null c 1 3 mknod -m "$target"/dev/ptmx c 5 2 mknod -m "$target"/dev/random c 1 8 mknod -m "$target"/dev/tty c 5 0 mknod -m "$target"/dev/tty0 c 4 0 mknod -m "$target"/dev/urandom c 1 9 mknod -m "$target"/dev/zero c 1 5 # amazon linux yum will fail without vars set if [ -d /etc/yum/vars ]; then mkdir -p -m 755 "$target"/etc/yum cp -a /etc/yum/vars "$target"/etc/yum/ fi 软件包安装和信息定制 yum -c "$yum_config" --installroot="$target" --releasever=/ --setopt=tsflags=nodocs \ --setopt=group_package_types=mandatory -y groupinstall Core yum -c "$yum_config" --installroot="$target" -y clean all ... 删除非必要文件目录 yum -c "$yum_config" --installroot="$target" -y clean all cat > "$target"/etc/sysconfig/network <<EOF NETWORKING=yes HOSTNAME=localhost.localdomain EOF # effectively: febootstrap-minimize --keep-zoneinfo --keep-rpmdb --keep-services "$target". # locales rm -rf "$target"/usr/{{lib,share}/locale,{lib,lib64}/gconv,bin/localedef,sbin/build-locale-archive} # docs and man pages rm -rf "$target"/usr/share/{man,doc,info,gnome/help} # cracklib rm -rf "$target"/usr/share/cracklib # i18n rm -rf "$target"/usr/share/i18n # yum cache rm -rf "$target"/var/cache/yum mkdir -p --mode=0755 "$target"/var/cache/yum # sln rm -rf "$target"/sbin/sln # ldconfig rm -rf "$target"/etc/ld.so.cache "$target"/var/cache/ldconfig mkdir -p --mode=0755 "$target"/var/cache/ldconfig 打包 tar --numeric-owner -c -C "$target" . docker import - $name:$version docker run -i -t $name:$version echo success 清理 rm -rf "$target" 如果我们需要自己的安全增强软件等定制,只需要在第2步将我们的内容合并进去即可 生成的镜像可以作为我们工作的基础,当然你也可以push到开源社区
参考上述脚本,使用tar命令,打包虚拟机;导入为镜像:
 
mkdir /saveFS cd saveFS tar -cvpf saved_xgp72.tar --directory=/ --exclude=proc --exclude=sys --exclude=dev/pts --exclude=oracle/oracle_install --exclude=var/cache/yum --exclude=usr/share/man --exclude=usr/share/doc --exclude=usr/share/info --exclude=usr/share/gnome/help --exclude=saveFS . cat aved_xgp72.tar | docker import - xgpdb7:v1
(脚本仍有优化空间)
2.3.2 docker commit
docker commit 命令是创建新镜像最直观的方法,其过程包含三个步骤:
- 
运行容器 docker run 
- 
修改容器 实际操作 
- 
将容器保存为新的镜像 docker commit 
举个例子:在 ubuntu base 镜像中安装 vi 并保存为新镜像。
- 
第一步, 运行容器 ![http://7xo6kd.com1.z0.glb.clouddn.com/upload-ueditor-image-20170507-1494146962627054699.png]() -it参数的作用是以交互模式进入容器,并打开终端。412b30588f4a是容器的内部 ID。
- 
安装 vim ![http://7xo6kd.com1.z0.glb.clouddn.com/upload-ueditor-image-20170507-1494146962772083062.png]() 
 确认 vim 没有安装。![http://7xo6kd.com1.z0.glb.clouddn.com/upload-ueditor-image-20170507-1494146962884055877.png]() 安装 vi。 安装 vi。
- 
保存为新镜像 
 在新窗口中查看当前运行的容器。![http://7xo6kd.com1.z0.glb.clouddn.com/upload-ueditor-image-20170507-1494146962959041762.png]() 
  silly_goldberg 是 Docker 为我们的容器随机分配的名字。
执行 docker commit 命令将容器保存为镜像。
  
新镜像命名为 ubuntu-with-vi。
查看新镜像的属性。
从 size 上看到镜像因为安装了软件而变大了。
从新镜像启动容器,验证 vi 已经可以使用。
以上演示了如何用 docker commit 创建新镜像。然而,Docker 并不建议用户通过这种方式构建镜像。原因如下:
- 
这是一种手工创建镜像的方式,容易出错,效率低且可重复性弱。比如要在 debian base 镜像中也加入 vi,还得重复前面的所有步骤。 
- 
更重要的:使用者并不知道镜像是如何创建出来的,里面是否有恶意程序。也就是说无法对镜像进行审计,存在安全隐患。 
既然 docker commit 不是推荐的方法,我们干嘛还要花时间学习呢?
原因是:即便是用 Dockerfile(推荐方法)构建镜像,底层也 docker commit 一层一层构建新镜像的。
学习 docker commit 能够帮助我们更加深入地理解构建过程和镜像的分层结构。
docker commit -m "201803: install jiezhi ,add ump;201804 add run.sh update by jdw" -a daweij u000-ump 172.28.2.2:4000/ump:20180413
2.3.3 Dockerfile
Dockerfile 是一个文本文件,记录了镜像构建的所有步骤。
使用docker,必须掌握dockerfile的各类执行与编写方法,会读也会写。
参考Dockerfile 常用指令 - 每天5分钟玩转 Docker 容器技术(16)
下面列出了 Dockerfile 中最常用的指令,完整列表和说明可参看官方文档。
FROM
指定 base 镜像。
MAINTAINER
设置镜像的作者,可以是任意字符串。
COPY
将文件从 build context 复制到镜像。
COPY 支持两种形式:
- 
COPY src dest 
- 
COPY ["src", "dest"] 
注意:src 只能指定 build context 中的文件或目录。
ADD
与 COPY 类似,从 build context 复制文件到镜像。不同的是,如果 src 是归档文件(tar, zip, tgz, xz 等),文件会被自动解压到 dest。
ENV
设置环境变量,环境变量可被后面的指令使用。例如:
...
ENV MY_VERSION 1.3
RUN apt-get install -y mypackage=$MY_VERSION
...
EXPOSE
指定容器中的进程会监听某个端口,Docker 可以将该端口暴露出来。我们会在容器网络部分详细讨论。
VOLUME
将文件或目录声明为 volume。我们会在容器存储部分详细讨论。
WORKDIR
为后面的 RUN, CMD, ENTRYPOINT, ADD 或 COPY 指令设置镜像中的当前工作目录。
RUN
在容器中运行指定的命令。
CMD
容器启动时运行指定的命令。
Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效。CMD 可以被 docker run 之后的参数替换。
ENTRYPOINT
设置容器启动时运行的命令。
Dockerfile 中可以有多个 ENTRYPOINT 指令,但只有最后一个生效。CMD 或 docker run 之后的参数会被当做参数传递给 ENTRYPOINT。
下面我们来看一个较为全面的 Dockerfile:

注:Dockerfile 支持以“#”开头的注释。
构建镜像:

① 构建前确保 build context 中存在需要的文件。
② 依次执行 Dockerfile 指令,完成构建。
运行容器,验证镜像内容:

① 进入容器,当前目录即为 WORKDIR。
如果 WORKDIR 不存在,Docker 会自动为我们创建。
② WORKDIR 中保存了我们希望的文件和目录:
目录 bunch:由 ADD 指令从 build context 复制的归档文件 bunch.tar.gz,已经自动解压。
文件 tmpfile1:由 RUN 指令创建。
文件 tmpfile2:由 COPY 指令从 build context 复制。
③ ENV 指令定义的环境变量已经生效。
----------------------------
RUN、CMD 和 ENTRYPOINT 这三个 Dockerfile 指令看上去很类似,很容易混淆。本节将通过实践详细讨论它们的区别。
简单的说:
- 
RUN 执行命令并创建新的镜像层,RUN 经常用于安装软件包。 
- 
CMD 设置容器启动后默认执行的命令及其参数,但 CMD 能够被 docker run后面跟的命令行参数替换。 如 /bin/bash
- 
ENTRYPOINT 配置容器启动时运行的命令。 
下面我们详细分析。
Shell 和 Exec 格式
我们可用两种方式指定 RUN、CMD 和 ENTRYPOINT 要运行的命令:Shell 格式和 Exec 格式,二者在使用上有细微的区别。
Shell 格式
<instruction> <command>
例如:
RUN apt-get install python3
CMD echo "Hello world"
ENTRYPOINT echo "Hello world"
当指令执行时,shell 格式底层会调用 /bin/sh -c <command> 。
例如下面的 Dockerfile 片段:
ENV name Cloud Man
ENTRYPOINT echo "Hello, $name"
执行 docker run <image> 将输出:
Hello, Cloud Man
注意环境变量 name 已经被值 Cloud Man 替换。
下面来看 Exec 格式。
Exec 格式
<instruction> ["executable", "param1", "param2", ...]
例如:
RUN ["apt-get", "install", "python3"]
CMD ["/bin/echo", "Hello world"]
ENTRYPOINT ["/bin/echo", "Hello world"]
当指令执行时,会直接调用 <command>,不会被 shell 解析。
例如下面的 Dockerfile 片段:
ENV name Cloud Man
ENTRYPOINT ["/bin/echo", "Hello, $name"]
运行容器将输出:
Hello, $name
注意环境变量“name”没有被替换。
如果希望使用环境变量,照如下修改
ENV name Cloud Man
ENTRYPOINT ["/bin/sh", "-c", "echo Hello, $name"]
运行容器将输出:
Hello, Cloud Man
CMD 和 ENTRYPOINT 推荐使用 Exec 格式,因为指令可读性更强,更容易理解。RUN 则两种格式都可以。
RUN
RUN 指令通常用于安装应用和软件包。
RUN 在当前镜像的顶部执行命令,并通过创建新的镜像层。Dockerfile 中常常包含多个 RUN 指令。
RUN 有两种格式:
- 
Shell 格式:RUN 
- 
Exec 格式:RUN ["executable", "param1", "param2"] 
下面是使用 RUN 安装多个包的例子:
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion
注意:apt-get update 和 apt-get install 被放在一个 RUN 指令中执行,这样能够保证每次安装的是最新的包。如果 apt-get install 在单独的 RUN 中执行,则会使用 apt-get update 创建的镜像层,而这一层可能是很久以前缓存的。
CMD
CMD 指令允许用户指定容器的默认执行的命令。
此命令会在容器启动且 docker run 没有指定其他命令时运行。
- 
如果 docker run 指定了其他命令,CMD 指定的默认命令将被忽略。 
- 
如果 Dockerfile 中有多个 CMD 指令,只有最后一个 CMD 有效。 
CMD 有三种格式:
- 
Exec 格式:CMD ["executable","param1","param2"] 
 这是 CMD 的推荐格式。
- 
CMD ["param1","param2"] 为 ENTRYPOINT 提供额外的参数,此时 ENTRYPOINT 必须使用 Exec 格式。 
- 
Shell 格式:CMD command param1 param2 
Exec 和 Shell 格式前面已经介绍过了。
第二种格式 CMD ["param1","param2"] 要与 Exec 格式 的 ENTRYPOINT 指令配合使用,其用途是为 ENTRYPOINT 设置默认的参数。我们将在后面讨论 ENTRYPOINT 时举例说明。
下面看看 CMD 是如何工作的。Dockerfile 片段如下:
CMD echo "Hello world"
运行容器 docker run -it [image] 将输出:
Hello world
但当后面加上一个命令,比如 docker run -it [image] /bin/bash,CMD 会被忽略掉,命令 bash 将被执行:
root@10a32dc7d3d3:/#
ENTRYPOINT
ENTRYPOINT 指令可让容器以应用程序或者服务的形式运行。
ENTRYPOINT 看上去与 CMD 很像,它们都可以指定要执行的命令及其参数。
不同的地方在于 ENTRYPOINT 不会被忽略,一定会被执行,即使运行 docker run 时指定了其他命令。
ENTRYPOINT 有两种格式:
- 
Exec 格式:ENTRYPOINT ["executable", "param1", "param2"] 这是 ENTRYPOINT 的推荐格式。 
- 
Shell 格式:ENTRYPOINT command param1 param2 
在为 ENTRYPOINT 选择格式时必须小心,因为这两种格式的效果差别很大。
Exec 格式
ENTRYPOINT 的 Exec 格式用于设置要执行的命令及其参数,同时可通过 CMD 提供额外的参数。
ENTRYPOINT 中的参数始终会被使用,而 CMD 的额外参数可以在容器启动时动态替换掉。
比如下面的 Dockerfile 片段:
ENTRYPOINT ["/bin/echo", "Hello"]
CMD ["world"]
当容器通过 docker run -it [image] 启动时,输出为:
Hello world
而如果通过 docker run -it [image] CloudMan 启动,则输出为:
Hello CloudMan
Shell 格式
ENTRYPOINT 的 Shell 格式会忽略任何 CMD 或 docker run 提供的参数。
最佳实践
- 
使用 RUN 指令安装应用和软件包,构建镜像。 
- 
如果 Docker 镜像的用途是运行应用程序或服务,比如运行一个 MySQL,应该优先使用 Exec 格式的 ENTRYPOINT 指令。CMD 可为 ENTRYPOINT 提供额外的默认参数,同时可利用 docker run 命令行替换默认参数。 
- 
如果想为容器设置默认的启动命令,可使用 CMD 指令。用户可在 docker run 命令行中替换此默认命令。 
到这里,我们已经具备编写 Dockerfile 的能力了。如果大家还觉得没把握,推荐一个快速掌握 Dockerfile 的方法:去 Docker Hub 上参考那些官方镜像的 Dockerfile。
一个栗子
 
# Pull base image FROM ubuntu:13.10 MAINTAINER daweij "email.." # update source RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe"> /etc/apt/sources.list RUN apt-get update # Install curl RUN apt-get -y install curl # Install JDK 7 RUN cd /tmp && curl -L 'http://download.oracle.com/otn-pub/java/jdk/7u65-b17/jdk-7u65-linux-x64.tar.gz' -H 'Cookie: oraclelicense=accept-securebackup-cookie; gpw_e24=Dockerfile' | tar -xz RUN mkdir -p /usr/lib/jvm RUN mv /tmp/jdk1.7.0_65/ /usr/lib/jvm/java-7-oracle/ # Set Oracle JDK 7 as default Java RUN update-alternatives --install /usr/bin/java java /usr/lib/jvm/java-7-oracle/bin/java 300 RUN update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/java-7-oracle/bin/javac 300 ENV JAVA_HOME /usr/lib/jvm/java-7-oracle/ # Install tomcat7 RUN cd /tmp && curl -L 'http://archive.apache.org/dist/tomcat/tomcat-7/v7.0.8/bin/apache-tomcat-7.0.8.tar.gz' | tar -xz RUN mv /tmp/apache-tomcat-7.0.8/ /opt/tomcat7/ ENV CATALINA_HOME /opt/tomcat7 ENV PATH $PATH:$CATALINA_HOME/bin ADD tomcat7.sh /etc/init.d/tomcat7 RUN chmod 755 /etc/init.d/tomcat7 # Expose ports. EXPOSE 8080 # Define default command. ENTRYPOINT service tomcat7 start && tail -f /opt/tomcat7/logs/catalina.out
 
export JAVA_HOME=/usr/lib/jvm/java-7-oracle/ export TOMCAT_HOME=/opt/tomcat7 case $1 in start) sh $TOMCAT_HOME/bin/startup.sh ;; stop) sh $TOMCAT_HOME/bin/shutdown.sh ;; restart) sh $TOMCAT_HOME/bin/shutdown.sh sh $TOMCAT_HOME/bin/startup.sh ;; esac exit 0
2.4 镜像分发:tag使用最佳实践
一个特定镜像的名字由两部分组成:repository 和 tag。
[image name] = [repository]:[tag]
tag 常用于描述镜像的版本信息,可以是任意字符串。
千万别被 latest tag 给误导了。latest 其实并没有什么特殊的含义。当没指明镜像 tag 时,Docker 会使用默认值 latest,仅此而已。
虽然 Docker Hub 上很多 repository 将 latest 作为最新稳定版本的别名,但这只是一种约定,而不是强制规定。
所以我们在使用镜像时最好还是避免使用 latest,明确指定某个 tag,比如 httpd:2.3,ubuntu:xenial。
tag 使用最佳实践
借鉴软件版本命名方式能够让用户很好地使用镜像。
一个高效的版本命名方案可以让用户清楚地知道当前使用的是哪个镜像,同时还可以保持足够的灵活性。
每个 repository 可以有多个 tag,而多个 tag 可能对应的是同一个镜像。
下面通过例子为大家介绍 Docker 社区普遍使用的 tag 方案。
假设我们现在发布了一个镜像 myimage,版本为 v1.9.1。那么我们可以给镜像打上四个 tag:1.9.1、1.9、1 和 latest。

我们可以通过 docker tag 命令方便地给镜像打 tag。
docker tag myimage-v1.9.1 myimage:1
docker tag myimage-v1.9.1 myimage:1.9
docker tag myimage-v1.9.1 myimage:1.9.1
docker tag myimage-v1.9.1 myimage:latest
过了一段时间,我们发布了 v1.9.2。这时可以打上 1.9.2 的 tag,并将 1.9、1 和 latest 从 v1.9.1 移到 v1.9.2。

命令为:
docker tag myimage-v1.9.2 myimage:1
docker tag myimage-v1.9.2 myimage:1.9
docker tag myimage-v1.9.2 myimage:1.9.2
docker tag myimage-v1.9.2 myimage:latest
之后,v2.0.0 发布了。这时可以打上 2.0.0、2.0 和 2 的 tag,并将 latest 移到 v2.0.0。

命令为:
docker tag myimage-v2.0.0 myimage:2
docker tag myimage-v2.0.0 myimage:2.0
docker tag myimage-v2.0.0 myimage:2.0.0
docker tag myimage-v2.0.0 myimage:latest
这种 tag 方案使镜像的版本很直观,用户在选择非常灵活:
- 
myimage:1 始终指向 1 这个分支中最新的镜像。 
- 
myimage:1.9 始终指向 1.9.x 中最新的镜像。 
- 
myimage:latest 始终指向所有版本中最新的镜像。 
- 
如果想使用特定版本,可以选择 myimage:1.9.1、myimage:1.9.2 或 myimage:2.0.0。 
Docker Hub 上很多 repository 都采用这种方案,所以大家一定要熟悉。
仓库分为hub和私有仓库,在第一节(D01-容器生态系统)中已有介绍。
下面是镜像的常用操作子命令:
images 显示镜像列表
history 显示镜像构建历史
commit 从容器创建新镜像
build 从 Dockerfile 构建镜像
tag 给镜像打 tag
pull 从 registry 下载镜像
push 将 镜像 上传到 registry
rmi 删除 Docker host 中的镜像
search 搜索 Docker Hub 中的镜像
import 将压缩包导入为镜像
docker save将镜像导出为tar包
docker export 将容器导出为tar包
 
                    
                

 


 安装 vi。
安装 vi。
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号