mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-01-22 23:00:57 +08:00
PRF:20180719 Building tiny container images.md
Part2
This commit is contained in:
parent
57761992c3
commit
2881b5897b
@ -4,13 +4,13 @@
|
||||
|
||||
![](https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/containers_scale_performance.jpg?itok=R7jyMeQf)
|
||||
|
||||
[Docker][1] 近几年的爆炸性发展让大家逐渐了解到容器和容器镜像的概念。尽管 Linux 容器技术在很早之前就已经出现,这项技术近来的蓬勃发展却还是要归功于 Docker 对用户友好的命令行界面以及使用 Dockerfile 格式轻松构建镜像的方式。纵然 Docker 大大降低了入门容器技术的难度,但构建一个兼具功能强大、体积小巧的容器镜像的过程中,有很多技巧需要了解。
|
||||
[Docker][1] 近几年的爆炸性发展让大家逐渐了解到容器和容器镜像的概念。尽管 Linux 容器技术在很早之前就已经出现,但这项技术近来的蓬勃发展却还是要归功于 Docker 对用户友好的命令行界面以及使用 Dockerfile 格式轻松构建镜像的方式。纵然 Docker 大大降低了入门容器技术的难度,但构建一个兼具功能强大、体积小巧的容器镜像的过程中,有很多技巧需要了解。
|
||||
|
||||
### 第一步:清理不必要的文件
|
||||
|
||||
这一步和在普通服务器上清理文件没有太大的区别,而且要清理得更加仔细。一个小体积的容器镜像在传输方面有很大的优势,同时,在磁盘上存储大量不必要的数据也是对资源的一种浪费。这个原则对于所有服务器来说都是合适的。
|
||||
这一步和在普通服务器上清理文件没有太大的区别,而且要清理得更加仔细。一个小体积的容器镜像在传输方面有很大的优势,同时,在磁盘上存储不必要的数据的多个副本也是对资源的一种浪费。因此,这些技术对于容器来说应该比有大量专用内存的服务器更加需要。
|
||||
|
||||
清理容器镜像中的缓存文件可以有效缩小镜像体积。下面的对比是分别使用 `dnf` 安装 [Nginx][2] 构建的镜像和使用 `yum` 安装 Nginx 后清理缓存文件构建的镜像:
|
||||
清理容器镜像中的缓存文件可以有效缩小镜像体积。下面的对比是使用 `dnf` 安装 [Nginx][2] 构建的镜像,分别是清理和没有清理 yum 缓存文件的结果:
|
||||
|
||||
```
|
||||
# Dockerfile with cache
|
||||
@ -41,11 +41,11 @@ cache: 464 MB
|
||||
no-cache: 271 MB
|
||||
```
|
||||
|
||||
从上面的结果来看,清理缓存文件的效果相当显著。和清除了 `yum` 缓存文件的容器镜像相比,不清除 `dnf` 元数据和缓存文件构建出来的容器镜像体积接近前者的两倍。除此以外,包管理器缓存文件、Ruby gem 临时文件、nodejs 缓存文件,甚至是下载的源码 tarball 最好都全部清理掉。
|
||||
从上面的结果来看,清理缓存文件的效果相当显著。和清除了元数据和缓存文件的容器镜像相比,不清除的镜像体积接近前者的两倍。除此以外,包管理器缓存文件、Ruby gem 的临时文件、nodejs 缓存文件,甚至是下载的源码 tarball 最好都全部清理掉。
|
||||
|
||||
### 层:一个潜在的隐患
|
||||
|
||||
很不幸(当你往下读,你会发现这是不幸中的万幸),根据容器层的概念,不能简单地向 Dockerfile 中写一句 `RUN rm -rf /var/cache/yum` 就完事儿了。因为 Dockerfile 的每一条命令都以一个层的形式存储,并一层层地叠加。所以,如果你是这样写的:
|
||||
很不幸(当你往下读,你会发现这是不幸中的万幸),根据容器中的层的概念,不能简单地向 Dockerfile 中写一句 `RUN rm -rf /var/cache/yum` 就完事儿了。因为 Dockerfile 的每一条命令都以一个层的形式存储,并一层层地叠加。所以,如果你是这样写的:
|
||||
|
||||
```
|
||||
RUN dnf install -y nginx
|
||||
@ -53,9 +53,9 @@ RUN dnf clean all
|
||||
RUN rm -rf /var/cache/yum
|
||||
```
|
||||
|
||||
你的容器镜像就会包含三层,而 `RUN dnf install -y nginx` 这一层仍然会保留着那些缓存文件,然后在另外两层中被移除。但缓存仍然是存在的,只是因为你把一个文件系统挂载在另外一个文件系统顶部,文件仍然在那里,只不过你见不到也访问不到它们而已。
|
||||
你的容器镜像就会包含三层,而 `RUN dnf install -y nginx` 这一层仍然会保留着那些缓存文件,然后在另外两层中被移除。但缓存实际上仍然是存在的,当你把一个文件系统挂载在另外一个文件系统之上时,文件仍然在那里,只不过你见不到也访问不到它们而已。
|
||||
|
||||
在上一节的示例中,你会看到正确的做法是将几条命令连接起来,在产生缓存文件的同一层里把缓存文件清理掉:
|
||||
在上一节的示例中,你会看到正确的做法是将几条命令链接起来,在产生缓存文件的同一条 Dockerfile 指令里把缓存文件清理掉:
|
||||
|
||||
```
|
||||
RUN dnf install -y nginx \
|
||||
@ -63,26 +63,26 @@ RUN dnf install -y nginx \
|
||||
&& rm -rf /var/cache/yum
|
||||
```
|
||||
|
||||
这样就把几条命令连成了一条命令,在最终的镜像中只占用一个层。这样只会浪费一点缓存的好处,稍微多耗费一点点构建容器镜像的时间,但被清理掉的缓存文件就不会留存在最终的镜像中了。作为一个折中方法,只需要把一些相关的命令(例如 `yum install` 和 `yum clean all`、下载文件、解压文件、移除 tarball 等等)连接成一个命令,就可以在最终的容器镜像中节省出大量体积,Docker 层也能更好地发挥作用。
|
||||
这样就把几条命令连成了一条命令,在最终的镜像中只占用一个层。这样只会浪费一点缓存的好处,稍微多耗费一点点构建容器镜像的时间,但被清理掉的缓存文件就不会留存在最终的镜像中了。作为一个折衷方法,只需要把一些相关的命令(例如 `yum install` 和 `yum clean all`、下载文件、解压文件、移除 tarball 等等)连接成一个命令,就可以在最终的容器镜像中节省出大量体积,你也能够利用 Docker 的缓存加快开发速度。
|
||||
|
||||
层还有一个更隐蔽的特性。每一层都记录了文件的更改,这里的更改并不仅仅指文件是否存在、文件内容是否被改动,而是包括文件属性在内的所有更改。因此即使是对文件使用了 `chmod` 操作也会被在新的层创建文件的副本。
|
||||
层还有一个更隐蔽的特性。每一层都记录了文件的更改,这里的更改并不仅仅已有的文件累加起来,而是包括文件属性在内的所有更改。因此即使是对文件使用了 `chmod` 操作也会被在新的层创建文件的副本。
|
||||
|
||||
下面是一次 `docker images` 命令的输出内容。其中容器镜像 `layer_test_1` 是仅在 CentOS 基础镜像中增加了一个 1GB 大小的文件后构建出来的镜像,而容器镜像 `layer_test_2` 是使用了 `FROM layer_test_1` 语句,仅仅再执行一条 `chmod u+x` 命令后构建出来的镜像。
|
||||
下面是一次 `docker images` 命令的输出内容。其中容器镜像 `layer_test_1` 是在 CentOS 基础镜像中增加了一个 1GB 大小的文件后构建出来的镜像,而容器镜像 `layer_test_2` 是使用了 `FROM layer_test_1` 语句创建出来的,除了执行一条 `chmod u+x` 命令没有做任何改变。
|
||||
|
||||
```
|
||||
layer_test_2 latest e11b5e58e2fc 7 seconds ago 2.35 GB
|
||||
layer_test_1 latest 6eca792a4ebe 2 minutes ago 1.27 GB
|
||||
```
|
||||
|
||||
如你所见,`layer_test_2` 镜像比 `layer_test_1` 镜像大了 1GB 以上。尽管 `layer_test_2` 基于 `layer_test_1` 且,只比 `layer_test_1` 多出一层,但恰好就在这多出来的一层中包含了一个额外的 1GB 的文件。在构建容器镜像的过程中,如果在单独一层中进行移动、更改、删除文件,都会出现类似的结果。
|
||||
如你所见,`layer_test_2` 镜像比 `layer_test_1` 镜像大了 1GB 以上。尽管事实上 `layer_test_1` 只是 `layer_test_2` 的前一层,但隐藏在这第二层中有一个额外的 1GB 的文件。在构建容器镜像的过程中,如果在单独一层中进行移动、更改、删除文件,都会出现类似的结果。
|
||||
|
||||
### 专用镜像和公用镜像
|
||||
|
||||
有这么一个亲身经历:我们的项目重度依赖于 [Ruby on Rails][3],于是我们开始使用容器。一开始我们就建立了一个 Ruby 的基础镜像供所有的团队使用,为了简单起见(实际上这样并不好),我们使用 [rbenv][4] 将 Ruby 最新的 4 个版本都安装到了这个镜像当中,目的是让开发人员只用这个镜像就可以将使用不同版本 Ruby 的应用程序迁移到容器中。我们当时还认为这是一个有点大但兼容性相当好的镜像,因为这个镜像可以同时满足各个团队的使用。
|
||||
有这么一个亲身经历:我们的项目重度依赖于 [Ruby on Rails][3],于是我们开始使用容器。一开始我们就建立了一个官方的 Ruby 的基础镜像供所有的团队使用,为了简单起见(“这就是我们自己在服务器上瞎鼓捣的想法”),我们使用 [rbenv][4] 将 Ruby 最新的 4 个版本都安装到了这个镜像当中,目的是让开发人员只用这个单一的镜像就可以将使用不同版本 Ruby 的应用程序迁移到容器中。我们当时还认为这是一个有点大但兼容性相当好的镜像,因为这个镜像可以同时满足各个团队的使用。
|
||||
|
||||
实际上这是费力不讨好的。如果将不同版本应用程序安装在独立的镜像中,可以很轻松地实现镜像的自动化维护。同时,还可以在应用程序接近生命周期结束前提前做好预防措施,以免产生不可控的后果。庞大的公用镜像也会对资源造成浪费,当我们后来将这个庞大的镜像按照 Ruby 版本进行拆分之后,每个基于这些基础镜像构建出来的应用镜像体积都会比原来缩小很多,节省了大量的服务器存储资源。
|
||||
实际上这是费力不讨好的。如果将略微不同的版本的应用程序安装在独立的镜像中,可以很轻松地实现镜像的自动化维护。同时,选择特定版本的特定镜像,还有助于在引入破坏性改变,在应用程序接近生命周期结束前提前做好预防措施,以免产生不可控的后果。庞大的公用镜像也会对资源造成浪费,当我们后来将这个庞大的镜像按照 Ruby 版本进行拆分之后,我们最终得到了共享一个基础镜像的多个镜像,如果它们都放在一个服务器上,会额外多占用一点空间,但是要比安装了多个版本的巨型镜像要小得多。
|
||||
|
||||
这个例子也不是说不能追求镜像的兼容性,但仅对于这个例子来说,分拆成多个小的专用镜像无疑能够节省存储资源和维护成本,同时不同的团队也能够根据特定的需求来做定制化的配置。
|
||||
这个例子也不是说构建一个灵活的镜像是没用的,但仅对于这个例子来说,从一个公共镜像创建根据用途而构建的镜像最终将节省存储资源和维护成本,而在受益于维护公共基础镜像的好处的同时,每个团队也能够根据需要来做定制化的配置。
|
||||
|
||||
### 从零开始:将你需要的内容添加到空白镜像中
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user