镜像是 Docker 容器的基石,容器是镜像的实例,有了镜像才能启动容器。
镜像到底包含什么呢?容器为什么是轻量级的虚拟化呢?
首先从一个最小的镜像hello-world讲起,hello-world镜像仅有1.85kB,根据经验它肯定是不包括Linux的内核的,因为现在Linux内核大小至少100MB以上。
这么小的镜像,它能运行,是一个完整的镜像,他是怎么构建出来的呢?
这个镜像的构建文件仅有三行,第一行从空白镜像开始构建,第二行拷贝二进制hello程序,第三行运行hello程序。
/hello 就是文件系统的全部内容,连最基本的 /bin,/usr, /lib, /dev 都没有。
hello-world 虽然是一个完整的镜像,但它并没有什么实际用途。通常来说,我们希望镜像能提供一个基本的操作系统环境,用户可以根据需要安装和配置软件。这样的镜像我们称作 base 镜像。
什么是base镜像?Base镜像指不依赖其他镜像,从scratch构建,其他镜像可以以之为基础镜像进行扩展。
能称作 base 镜像的通常都是各种 Linux 发行版的 Docker 镜像,比如 Ubuntu, Debian, CentOS 等。
一个CentOS镜像为什么还不到200MB?
Linux 操作系统由内核空间和用户空间组成。内核空间是 kernel,Linux 刚启动时会加载 bootfs 文件系统,之后 bootfs 会被卸载掉,rootfs被加载。用户空间的文件系统是 rootfs,包含我们熟悉的 /dev, /proc, /bin 等目录。
不同 Linux 发行版的区别主要就是 rootfs。比如 Ubuntu 14.04 使用 upstart 管理服务,apt 管理软件包;而 CentOS 7 使用 systemd 和 yum。这些都是用户空间上的区别,Linux kernel 差别不大。
对于 base 镜像来说,底层直接用 Host 的 kernel,自己只需要提供 rootfs 就行了。而对于一个精简的 OS,rootfs 可以很小,只需要包括最基本的命令、工具和程序库就可以了。
相比其他 Linux 发行版,CentOS 的 rootfs 已经算臃肿的了,alpine 还不到 10MB。
如下,在CentOS 7宿主机上运行 Ubuntu 16.04容器,看到的容器内核和宿主机的内核相同,容器复用了宿主机的内核:
如下图所示,镜像是分层存储:
为什么镜像采用分层结构呢?
最大的好处就是资源共享,比如有多个镜像从同一个base镜像构建而来,宿主机上只需要有一份base镜像就可以了,多个镜像共用同一个base镜像。
多个容器共用同一个base镜像,当某个容器修改配置时,其他容器也会修改吗?
答案是不会,因为最上面的容器层是可写的,下面的其他层(镜像层)都是只读,当需要修改下层的文件时,会先复制此文件到上面的容器层,然后再修改。容器层记录对镜像的修改,所有镜像层都是只读的,不会被容器修改,所以镜像可以被多个容器共享。
Docker支持两种方法构建镜像,一是手动构建,二是通过Dockerfile构建。
- 通过Docker commit命令手动构建镜像
# docker run -ti --name ubuntu-vi ubuntu:16.04 bash
# apt-get update && apt-get install -y vim
# 开启另一终端,提交构建的镜像
# docker commit ubuntu-vi ubuntu-with-vi
- 通过Dockerfile构建镜像
# 编写Dockerfile
# vi Dockerfile
FROM ubuntu:16.04
RUN apt-get update && apt-get install -y vim
CMD ["/bin/bash"]
# 构建镜像
# docker build -t ubuntu-with-vi-dockerfile
Docker 并不建议用户通过这种方式构建镜像。原因如下:
- 这是一种手工创建镜像的方式,容易出错,效率低且可重复性弱。比如要在 debian base 镜像中也加入 vi,还得重复前面的所有步骤。
- 使用者并不知道镜像是如何创建出来的,里面是否有恶意程序。也就是说无法对镜像进行审计,存在安全隐患。docker history 会显示镜像的构建历史,也就是 Dockerfile 的执行过程。手工创建镜像的方式无法获取history。
通过两种方式构建好的镜像如下:
Dockerfile常用指令如下:
FROM 指定基础镜像 MAINTAINER 设置镜像维护者 COPY 将文件从构建上下文复制到镜像 ADD 同COPY,如果源文件是压缩文件自动解压缩 ENV 设置环境变量 EXPOSE 指定容器监听的端口 VOLUME 定义匿名卷 WORKDIR 为RUN、CMD、ENTRYPOINT等命令设置工作目录 RUN 在容器中运行指定命令 CMD 指定容器启动时运行的命令,可被替换 ENTRYPOINT指定容器启动时运行的命令 HEALTHCHECK 健康检查
我们通常使用 RUN 指令安装应用和软件包,构建镜像。如果 Docker 镜像的用途是运行应用程序或服务,比如运行一个 MySQL,应该优先使用 Exec 格式的 ENTRYPOINT 指令。CMD 可为 ENTRYPOINT 提供额外的默认参数,同时可利用 docker run 命令行替换默认参数。 如果想为容器设置默认的启动命令,可使用 CMD 指令。用户可在 docker run 命令行中替换此默认命令。
编译好的镜像只能在本地使用,如果想给其他人使用,最好是将容器上传到镜像仓库。容器仓库又分为公共仓库和私有仓库,hub.docker.com 是 Docker 公司提供的公有仓库,所有用户均可拉取公用仓库中的镜像,为了安全起见,通常公司会搭建自己的私有仓库。
如下是容器镜像的生命周期,包括镜像的构建、镜像打标签、推送到镜像仓库、从镜像仓库拉取镜像、镜像导入、导出、镜像删除等操作:
容器镜像常用命令小结:
docker images 显示镜像列表 docker history 显示镜像构建历史 docker commit 从容器创建新镜像 docker build 从 Dockerfile 构建镜像 docker tag 给镜像打 tag docker pull 从 registry 下载镜像 docker push 将镜像上传到 registry docker rmi 删除 Docker host 中的镜像 docker search 搜索 Docker Hub 中的镜像