docker镜像

1. base镜像

  • base 镜像有两层含义:

    (1)不依赖其他镜像,从scratch构建

    (2)其他镜像可以以之为基础进行扩展。

    所以,能称作 base 镜像的通常都是各种Linux发行版的 Docker 镜像,比如 Ubuntu、Debian、CentOS 等。

docker pull ubuntu

image

Ubuntu镜像只有78M左右,下边解释为什么会这么小?

  • Linux 操作系统由内核空间和用户空间组成

image

  1. rootfs

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

  2. base 镜像提供的是最小安装的 Linux 发行版

    CentOS 镜像的Dockerfile 的内容

    FROM scratch
    ADD centos-7-docker.tar.xz /
    CMD ["/bin/bash"]
    

    第二行 ADD 指令添加到镜像的tar包就是CentOS7的rootfs。在制作镜像时,这个 tar包会自动解压到/目录下,生成/dev、/proc、/bin 等目录。

    注:可在DockerHub的镜像描述页面中查看 Dockerfile。

  3. 支持运行多种 Linux OS
    不同 Linux 发行版的区别主要就是 rootfs。比如 Ubuntu14.04使用upstart 管理服务,apt管理软件包;而 CentOS7使用 systemd和 yum。这些都是用户空间上的区别,Linux kerel 差别不大。所以 Docker 可以同时支持多种 Linux 镜像,模拟出多种操作系统环境。

image

上图 Debian 和 BusyBox(一种嵌入式 Linux)上层提供各自的 rootfs,底层共用 Docker Host 的 kernel.

(1) base 镜像只是在用户空间与发行版一致,kernel 版本与发行版是不同的。例如CentOS7使用3.x.x的kenrel,如果 Docker Host是 Ubuntu 16.04,那么在CentOS容器中使用的实际上是Host4.xx的kernel。

(2) 容器只能使用Host的kernel,并且不能修改。所有容器都共用host的kernel,在容器中没办法对kernel升级。如果容器对kernel版本有要求(比如应用只能在某个kernel版本下运行),则不建议用容器,这种场景虚拟机可能更合适。

2. 镜像的分层结构

Docker 支持通过扩展现有镜像,创建新的镜像。实际上,Docker Hub中99%的镜像都是通过在 base 镜像中安装和配置需要的软件构建出来的。比如现在构建一个新的镜像,Dockerfile如下所示。

FROM debian
RUN apt-get install emacs
RUN atp-get install apache2
CMD[”/bin/bash"]

image

可以看到,新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层。

  • 为什么 Docker 镜像要采用分层结构呢?

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

    如果多个容器共享一份基础镜像,当某个容器修改了基础镜像的内容,比如 /etc下的文件,这时其他容器的/etc是否也会被修改?
    答案是不会!修改会被限制在单个容器内。这是由容器Copy-on-Write特性决定的。

2.1 可写的容器层

当容器启动时,一个新的可写层被加载到镜像的顶部。容器层之下的都叫镜像层。这一层通常被称作容器层

image

  • 所有对容器的改动,无论添加、删除,还是修改文件都只会发生在容器层中。只有容器层是可写的,容器层下面的所有镜像层都是只读的。
  • 镜像层数量可能会很多,所有镜像层会联合在一起组成一个统一的文件系统。如果不同层中有一个相同路径的文件,比如,上层的/a会覆盖下层的/a,也就是说用户只能访问到上层中的文件 /a。在容器层中,用户看到的是一个叠加之后的文件系统。
    (1) 添加文件。在容器中创建文件时,新文件被添加到容器层中。
    (2) 读取文件。在容器中读取某个文件时,Docker会从上往下依次在各镜像层中查找此文件。一旦找到,打开并读入内存。
    (3) 修改文件。在容器中修改已存在的文件时,Docker会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后修改之。
    (4) 删除文件。在容器中删除文件时,Docker也是从上往下依次在镜像层中查找此文件。找到后,会在容器层中记录下此删除操作。
  • 只有当需要修改时才复制一份数据,这种特性被称作Copy-on-Write。可见,容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改。
  • 容器层记录对镜像的修改,所有镜像层都是只读的,不会被容器修改,所以镜像可以被多个容器共享。

3. 构建镜像

  • 对于 Docker 用户来说,最好的情况是不需要自己创建镜像。几乎所有常用的数据库、中间件、应用软件等都有现成的 Docker 官方镜像或其他人和组织创建的镜像,我们只需要稍作配置就可以直接使用。

  • 使用现成镜像的好处除了省去自己做镜像的工作量外,更重要的是可以利用前人的经验。特别是使用那些官方镜像,因为 Docker 的工程师知道如何更好地在容器中运行软件。当然,某些情况下我们也不得不自己构建镜像,比如:
    (1) 找不到现成的镜像,比如自己开发的应用程序
    (2) 需要在镜像中加入特定的功能,比如官方镜像几乎都不提供 ssh。

    介绍构建镜像的方法。同时分析构建的过程也能够加深对前面镜像分层结构的理解。
    Docker 提供了两种构建镜像的方法: docker commit 命令与 Dockerfile 构建文件。

3.1 docker commit

  • docker commit 命令是创建新镜像最直观的方法,其过程包含三个步骤:

    • 运行容器。
    • 修改容器。
    • 将容器保存为新的镜像。

    举个例子: 在Ubuntu base 镜像中安装vim并保存为新镜像。

(1) 运行容器

docker run -it ubuntu # -it参数的作用是以交互模式进入容器,并打开终端

image

(2) 安装vi

确认vim没有安装

image

安装vim

image

(3) 保存为新镜像

在新窗口中查看当前运行的容器

image

goofy_darwin是Docker随机分配的名字,执行 docker commit 命令将容器保存成新镜像

docker commit goofy_darwin ubuntu-with-vi

image

从size 上看到镜像因为安装了软件而变大了从新镜像启动容器,验证vi已经可以使用。

3.2 Dockerfile

Dockerfle 是一个文本文件,记录了镜像构建的所有步骤。

  • 创建Dockerfile文件
  • 写入对应的命令
FROM ubuntu
RUN apt-get update && apt-get install -y vim

执行构建命令

docker build -t ubuntu-with-vi-dockerfile .

image

-t 将新镜像命名为 ubuntu-with-vi-dockerfle,命令末尾的.指明 build context 为当前目录。Docker 默认会从 build context 中查找 Dockerfile 文件,我们也可以通过-f参数指定 Dockerfile 的位置。

镜像构建过程:

  • 首先Docker将build context中的所有文件发送给 Docker daemon。build context 为镜像构建提供所需要的文件或目录。
  • Dockerfile 中的 ADDCOPY 等命令可以将 build context 中的文件添加到镜像。build context 为当前目录 root,该目录下的所有文件子目录都会被发送给 Docker daemon。所以,使用 build context 就得小心了,不要将多余文件放到 build context,特别不要把 /,/usr 作为 build context,否则构建过程会相当缓慢甚至失败。
  • 执行 FROM,将 Ubuntu 作为 base 镜像。Ubuntu镜像D为3db8720ecbf5
  • 执行 RUN,安装 vim,有过之前docker安装过对应的镜像层,就会使用缓存中带有的,using caching
    可以说明这个问题
  • 镜像构建成功。

3.3 查看镜像分层结构

ubuntu-with-vi-dockerfile是通过在 base镜像的顶部添加一个新的镜像层而得到的

image

这个新镜像层的内容由 RUN apt-get update && apt-get install-y vim 生成。可以通过 docker history 命令验证

image

docker history 会显示镜像的构建历史,也就是 Dockerfle 的执行过程。ubuntu-with-vi-dockerfile与Ubuntu 镜像相比,确实只是多了顶部的一层 1ec0e62547a1由 apt-get 命令创建,大小为108MB。docker history 也向我们展示了镜像的分层结构,每层由上至下排列。

注: missing表示无法获取IMAGEID,通常从DockerHub 下载的镜像会有这个问题

3.4 镜像的缓存特性

Docker 会缓存已有镜像的镜像层,构建新镜像时,如果某镜像层已经存在,就直接使用,无须重新创建。详见3.2 docker file构建docker镜像过程

如果希望在构建镜像时不使用缓存,可以在 docker build 命令中加上--no-cache 参数。

Dockerfile 中每一个指令都会创建一个镜像层,上层是依赖于下层的。无论什么时候,只要某一层发生变化,其上面所有层的缓存都会失效。如果我们改变Dockerfle指令的执行顺序,或者修改或添加指令,都会使缓存失效。举例说明,比如交换前面RUN和COPY的顺序,就到导致重新下载镜像层。

4.分发镜像

有几种可用的方法分发镜像:
(1) 用相同的 Dockerfile 在其他 host 构建镜像。
(2) 将镜像上传到公共Registry(比如 Docker Hub),Host 直接下载使用。
(3) 搭建私有的 Registry 供本地 Host 使用。

4.1为镜像命名

无论采用何种方式保存和分发镜像,首先都得给镜像命名。当我们执行 docker build 命令时已经为镜像取了个名字,例如:

docker build -t ubuntu-with-vi

这里的ubuntu-with-vi 就是镜像的名字,通过 docker images 可以査看镜像的信息

image

ubuntu-with-vi对应的是 REPOSITORY,而且还有一个叫latest的 TAG。实际上一个特定镜像的名字由两部分组成: repository和tag

[image name] = [repository]:[tag]

如果执行 docker build 时没有指定 tag,会使用默认值 latest。其效果相当于:

docker build -t ubuntu-with-vi:latest

tag 常用于描述镜像的版本信息。

image

4.2 tag 使用最佳实践

借鉴软件版本命名方式能够让用户很好地使用镜像。一个高效的版本命名方案可以让用户清楚地知道当前使用的是哪个镜像,同时还可以保持足够的灵活性。每个repository可以有多个tag,而多个tag 可能对应的是同一个镜像。

下面通过例子介绍 Docker 社区普遍使用的 tag 方案。
假设我们现在发布了一个镜像myimage,版本为1.9.1,那么我们可以给镜像打上4个tag: 1.9.11.91latest

过了一段时间,我们发布了v1.9.2。这时可以打上1.9.2的tag,并将1.9、1和latest 从v1.9.1 移到 v1.9.2。

image

4.3 使用公共 Registry

保存和分发镜像的最直接方法就是使用 Docker Hub。Docker Hub是Docker 公司维护的公共Registry。用户可以将自己的镜像保存到 DockerHub 免费的repository中。如果不希望别人访问自己的镜像,也可以购买私有 repository。除了 Docker Hub,quay.io 是另一个公共 Registry,提供与 Docker Hub 类似的服务。如何用 DockerHub存取我们的镜像,
(1) 首先得在 Docker Hub 上注册一个账号。

docker login -u xxx # xxx表示的是用户名

(2) 在Docker Host 上登录。

(3)修改镜像的 repository,使之与 Docker Hub 账号匹配。
Docker Hub为了区分不同用户的同名镜像,镜像的registry中要包含用户名,完整格式为:[username]/xxx:tag。我们通过 docker tag 命令重命名镜像。

docker tag ubuntu xxx/ubuntu:v1
docker push xxx/ubuntu:v1 # 将镜像推送到Docker Hub 中去

Docker 会上传镜像的每一层。因为如果镜像实际上跟官方的镜像一模一样,Docker Hub上已经有了全部的镜像层,所以真正上传的数据很少。同样的,如果我们的镜像是基于 base 镜像的,也只有新增加的镜像层会被上传。如果想上传同一 repository中所有镜像,省略tag 部分就可以了

4.4.搭建本地 Registry

Docker Hub 虽然非常方便,但还是有些限制,比如:

(1) 需要Imteret 连接,而且下载和上传速度慢。
(2) 上传到 Docker Hub的镜像任何人都能够访问,虽然可以用私有repository,但不是免费的。
(3) 因安全原因很多组织不允许将镜像放到外网。

解决方案就是搭建本地的 Registry。
Docker 已经将Registy开源了,同时在 Docker Hub上也有官方的镜像 registry。下面就在 Docker 中运行自己的 registry。

docker run -d -p 5000:5000 -v /root/registry:/var/lib/registry registry:2

镜像名称由repository和tag两部分组成。而repository 的完整格式为:

[registry-host]:port/username/xxx 

只有 Docker Hub 上的镜像可以省略 registry-host:[port]

docker push registry.example.net:5000/xxx/ubuntu:v1 # 上传镜像到registry
docker pull registry.example.net:5000/xxx/ubuntu:v1 # 从registry拉取镜像到本地
docker rmi xxx # 删除本地镜像,一个镜像有多个tag,只有当最后一个tag删除是镜像才会被删除

image

docker search xxx # 搜索 docker hub中的镜像

posted on 2024-03-06 23:25  ccblblog  阅读(3)  评论(0编辑  收藏  举报

导航