【教程】如何创建尽可能小的Docker容器

本文讲的是【教程】如何创建尽可能小的Docker容器,【编者的话】本文作者以一个使用Go语言编写的Web服务为例,重点介绍了如何通过Scratch创建一个尽可能小的Docker容器。在尝试过程中,作者也发现了很多问题,也逐一得到解决,感兴趣的读者一定要看看作者解决问题的思路。本文看点包括如何从Docker内部调用Docker、创建Docker容器的Docker容器、Go语言创建静态链接的可执行文件。

当在使用Docker的时候,如果想使用预先配置好的容器,就需要下载很大的镜像包。一个简单的Ubuntu的容器就有200多兆,如果安装了相关的软件,还会更大。在很多情况下,你并不需要Ubuntu容器内的所有功能模块,例如,如果你只想运行简单的Go语言编写的Web服务,而它并不需要任何其他工具。

我一直在寻找尽可能小的容器,然后发现了这个:
docker pull scratch
Scratch镜像很赞,它简洁、小巧而且快速, 它没有bug、安全漏洞、延缓的代码或技术债务。这是因为它基本上是空的。除了有点儿被Docker添加的metadata (译注:元数据为描述数据的数据)。你可以用以下命令创建这个scratch镜像(官方文档上有描述):
tar cv --files-from /dev/null | docker import - scratch

这是它,非常小的一个Docker镜像。到此结束!

...或许我们还可以来探讨更多的东西。例如,如何使用scratch镜像呢?这又带来了一些挑战。

为Scratch镜像创建内容

我们可以在一个空的Scratch镜像里运行什么?无依赖的可执行文件。你有没有不需要依赖的可执行文件吗?

我曾经用Python、Java和JavaScript编写过代码。这些语言/平台需要安装运行环境。最近,我开始研究Go(如果你喜欢话用GoLang)语言平台。看起来Go是静态链接的。所以我尝试编写一个简单的hello world Web服务器,并在Scratch容器中运行它。下面是Hello World Web服务器的代码:

package main
import (
"fmt"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello World from Go in minimal Docker container")
}
func main() {
http.HandleFunc("/", helloHandler)
fmt.Println("Started, serving at 8080")
err := http.ListenAndServe(":8080", nil)
if err != nil {
    panic("ListenAndServe: " + err.Error())
}
}

很显然,我不能在Scratch容器内编译我的Web服务,因为此容器内没Go编译器。并且,因为我的工作是在Mac上,我也不能编译的Linux二进制。 (其实,交叉编译GoLang源到不同的平台是可能的,但是这是另一篇文章的资料)

因此,首先我需要一个包含Go编译器的Docker容器。先从简单的开始:
docker run -ti google/golang /bin/bash

在这个容器内,我可以构建Go Web服务,我已经将代码提交到GitHub仓库
go get github.com/adriaandejonge/helloworld
go get命令和go buildy欧典想,它允许获取远程代码包并构建远程依赖。你可以通过运行可执行文件来启动服务:
$GOPATH/bin/helloworld
很棒,它执行了。但这不是我们期待的,我们想让hello world Web服务运行在Scratch容器内。所以,我们需要编写Dockerfile:

FROM scratch
ADD bin/helloworld /helloworld
CMD ["/helloworld"]

然后启动。不幸的是,我们使用google/golang容器的方式是没有办法建立这个Dockerfile的。因此,我们首先需要找到一种可以从容器内访问Docker的方法。

从容器内调用Docker

使用Docker的时候,你迟早会有从Docker内部控制Docker的需求。有许多方法可以做到这一点。你可以使用递归的方式,在Docker内运行Docker。然而,这似乎过于复杂,并且又回到了原点:容量大的容器。

你还可以用一些额外的命令参数来提供访问外部Docker给实例:
docker run -v /var/run/docker.sock:/var/run/docker.sock -v $(which docker):$(which docker) -ti google/golang /bin/bash

在讲到下一步之前,请重新运行Go编译器,因为重新启动一个容器Docker会忘记之前的编译内容:
go get github.com/adriaandejonge/helloworld

当启动容器时,-v 参数在Docker容器内创建一个卷,并允许提供从Docker上的文件作为输入。/var/run/docker.sock是Unix套接字,允许访问Docker服务器。$(which docker)可以为容器提供Docker可执行文件的路径。但是,当在Apple的boot2docker上运行Docker时,使用该命令需要注意,如果Docker可执行文件被安装在不同的路径上相对于安装在boot2docker的虚拟机,这将会导致不匹配错误:它将是boot2docker虚拟服务器内的可执行文件被导入容器内。所以,你可能要替换$(which docker)/usr/local/bin/docker。同样,如果你运行在不同的系统,/var/run/docker.sock有一个不同的位置,你需要相应地调整。

现在,你可以在 google/golang容器内使用在$GOPATH路径下的Dockerfile,例子中,它指向/gopath 。其实,我已经提交Dockerfile到GitHub上。因此,你可以在Go build目录中复制它,命令如下:
cp $GOPATH/src/github.com/adriaandejonge/helloworld/Dockerfile $GOPATH 
编译好的二进制文件位于$GOPATH/bin 目录下,当构建Dockerfile时它不可能从父目录中include文件。所以在复制后,下一步是:
docker build -t adejonge/helloworld $GOPATH
如果一切顺利,那么,Docker会有类似输出:

Successfully built 6ff3fd5a381d

然后您可以运行容器:
docker run -ti --name hellobroken adejonge/helloworld
但不幸的是,Docker会输出类似于:

2014/07/02 17:06:48 no such file or directory

那么到底是怎么回事?我们的Scratch容器内已经有静态链接的可执行文件。难道我们犯了什么错误?

事实证明,Go不是静态链接库的,或者至少不是所有的库。在Linux下,我们可以看到动态链接库用以下命令:
ldd $GOPATH/bin/helloworld

其中输入类似以下内容:

linux-vdso.so.1 => (0x00007fff039fe000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f61df30f000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f61def84000)
/lib64/ld-linux-x86-64.so.2 (0x00007f61df530000)

所以,在我们才可以运行的Hello World Web服务器之前,我们需要告诉Go编译器真正的做静态链接。

Go语言创建静态链接的可执行文件

为了创建静态链接的可执行文件,我们需要使用cgo编译器,而不是Go编译器。命令如下:
CGO_ENABLED=0 go get -a -ldflags '-s' github.com/adriaandejonge/helloworld
CGO_ENABLED 环境变量表示使用cgo编译器,而不是Go编译器。-a参数表示要重建所有的依赖。否则,还是以动态链接依赖为结果。-ldflags -s一个不错的额外标志,它可以缩减生成的可执行文件约50%的大小,没有cgo编译器你也可以使用该命令,50%是除去了调试信息的结果。

重新运行ldd命令:
ldd $GOPATH/bin/helloworld
现在应该有类似输出:

not a dynamic executable

然后重新运行用Scratch镜像构建Docker容器那一步:
docker build -t adejonge/helloworld $GOPATH 
如果一切顺利,Docker会有类似输出:

Successfully built 6ff3fd5a381d

接着运行容器:
docker run -ti --name helloworld adejonge/helloworld
而这个时候会输出:

Started,serving at 8080

目前为止,有许多步骤,会有很多错误的余地。让我们退出google/golang 容器:
<Press Ctrl-C> exit

您可以检查容器和镜像的存在或不存在:
docker ps -a docker images -a

并且您可以清理Docker:
docker rm -f hello world docker rmi -f adejonge/helloworld

创建Docker容器的Docker容器

到目前为止我们已经敲了这么多命令,我们可以把这些步骤写在Dockerfile中,Docker会帮我们自动处理:
FROM google/golang RUN CGO_ENABLED=0 go get -a -ldflags '-s' github.com/adriaandejonge/helloworld
RUN cp /gopath/src/github.com/adriaandejonge/helloworld/Dockerfile /gopath
CMD docker build -t adejonge/helloworld gopath

我提交了这个Dockerfile到另一个GitHub库。它可以用这个命令构建:
docker build -t adejonge/hellobuild github.com/adriaandejonge/hellobuild

-t表示镜像的标签名为adejonge/hellobuild和隐式标签名为latest。这些名称会在之后的删除镜像中用到。

接下来,你可以创建容器用刚才提供的标签:
docker run -v /var/run/docker.sock:/var/run/docker.sock -v $(which docker):$(which docker) -ti --name hellobuild adejonge/hellobuild

提供--name hellobuild 参数使得在运行后更容易移除容器。事实上,你现在就可以这样做,因为在运行此命令后,你已经创建了adejonge/helloworld的镜像:
docker rm -f hellobuild docker rmi -f adejonge/hellobuild 
现在你可以运行新的helloworld容器:
docker run -ti --name helloworld adejonge/helloworld
因为所有这些步骤都出自同一命令行运行,而无需在Docker容器内打开bash shell,你可以将这些步骤添加到一个bash脚本,并自动运行。我已经将bash脚本提交到了GitHub库

另外,如果你想尝试一个尽可能小的容器,但是又不想遵循博客中的步骤,你也可以用我提交到Docker Hub库的镜像
docker pull adejonge/helloworld

docker images -a你可以看到大小为3.6MB。当然,如果你能创建一个比我使用 Go 编写的 Web 服务还小的可执行文件,那就可以让它更小。使用 C 语言或者是汇编,你可以这样做到。尽管如此,你不可能使得它比 scratch 镜像还小。

原文链接Create The Smallest Possible Docker Container(翻译:田浩浩 审校:李颖杰)

===========================
译者介绍
田浩浩,悉尼大学USYD硕士研究生,目前在珠海从事Android应用开发工作。业余时间专注Docker的学习与研究,希望通过DockerOne把最新最优秀的译文贡献给大家,与读者一起畅游Docker的海洋。

原文发布时间为:2014-12-27

本文作者:田浩浩

本文来自合作伙伴DockerOne,了解相关信息可以关注DockerOne。

原文标题:【教程】如何创建尽可能小的Docker容器

时间: 2024-12-25 12:14:11

【教程】如何创建尽可能小的Docker容器的相关文章

创建尽可能小的 Docker 容器

当我们在使用 Docker 的时候,你会很快注意到你正在下载很多 MB 作为你的预先配置的容器.一个简单的 Ubuntu 容器很容易超过 200 MB,并且随着在上面安装软件,尺寸在逐渐增大.在某些情况下,你不需要任何事情都使用 Ubuntu .例如,如果你只是简单的想运行一个 web 服务,使用 GO 编写的,没有必要围绕它使用任何工具. 我一直在寻找尽可能小的容器入手,并且发现了一个: docker pull scratch scratch 镜像是完美的,真正的完美!它简洁,小巧以及快速.它

Docker容器:更小不一定更好

本文讲的是Docker容器:更小不一定更好,[编者的话]按正常逻辑来说,我们应该选择体积较小的Docker容器.然而事实是,体积小却并不一定能带来性能上的优势.本文将介绍一个使用了一个体积稍大一点的容器,从而将性能提高30倍以上的例子. 按道理来说,我们应该选择体积较小的Docker容器.然而事实是,体积小却并不一定能带来性能上的优势.本文将介绍一个使用了一个体积稍大一点的容器,从而将性能提高30倍以上的例子. 摘要 当使用grep来处理大量的数据的时候,busybox中带的grep速度慢的让人

如何在Docker容器中运行GUI程序

如何在Docker容器中运行GUI程序 各位,今天我们将学习如何在Docker之中运行GUI程序.我们可以轻易地在Docker容器中运行大多数GUI程序且不出错.Docker是一个开源项目,提供了一个打包.分发和运行任意程序的轻量级容器的开放平台.它没有语言支持.框架或者打包系统的限制,并可以运行在任何地方.任何时候,从小型的家用电脑到高端的服务器都可以运行.这让人们可以打包不同的包用于部署和扩展网络应用,数据库和后端服务而不必依赖于特定的栈或者提供商. 下面是我们该如何在Docker容器中运行

基础的 Docker 容器网络命令

各位好,今天我们将学习一些Docker容器的基础命令.Docker 是一个开源项目,提供了一个可以打包.装载和运行任何应用的轻量级容器的开放平台.它没有语言支持.框架和打包系统的限制,从小型的家用电脑到高端服务器,在何时何地都可以运行.它可以使部署和扩展web应用程序.数据库和后端服务像搭积木一样容易,而不依赖特定技术栈或提供商.Docker适用于网络环境,它正应用于数据中心.ISP和越来越多的网络服务. 因此,这里有一些你在管理Docker容器的时候会用到的一些命令. 1. 找到Docker接

使用OpenStack管理Docker容器(一)

本文讲的是使用OpenStack管理Docker容器(一),[编者的话]本文将讲述如何使用OpenStack创建并管理Docker,有3种流行的使用方法,使用的分别是Nova Docker驱动,Heat Docker插件,以及Magnum.这篇文章分成2部分,第一部分,将主要介绍Nova Docker驱动的用法.第二部分,是关于Heat Docker插件和Magnum.这是序列文章的第一部分. 在这篇文章中,我将介绍一些不同的方法,这些方法是关于OpenStack如何创建,以及管理Docker容

微容器:更小的,更轻便的Docker容器

本文讲的是微容器:更小的,更轻便的Docker容器,[编者的话]本文介绍了微容器的概念和好处,并用一些例子介绍了如何构建微镜像,从scratch到Alpine Linux,并推荐了一些已有的基础微镜像,方便为几乎所有主流语言的应用构建微镜像.本文也指出了构建微镜像的基本原理:将构建时依赖和运行时依赖分开,构建时所用的镜像包含所有构建所用的工具,它可以比较大,但运行时的基础镜像应该仅包含运行时依赖.使用微容器,no going back! Docker 使你能把你的应用和应用的依赖打包到一个良好的

如何交互式地创建一个Docker容器

如何交互式地创建一个Docker容器 大家好,今天我们来学习如何使用一个docker镜像交互式地创建一个Docker容器.当我们从镜像中启动一个Docker进程,Docker就会获取该镜像及其父镜像,并重复这个过程,直到到达基础镜像.然后联合文件系统(UFS)会在其顶层添加一个读写层.读写层被称之为容器,它包含了一些关于父镜像信息及一些其他的信息,如唯一ID,网络配置和资源限制等.容器是有状态的,其状态可以从 运行态 切换到 退出态.一个处于 运行态的容器包含了在CPU上面运行的进程树,于其它在

docker如何创建一个运行后台进程的容器并同时提供shell终端

只看标题还不是很明显,本文实现docker的这样一种比较常用的功能:通过docker run启动一个容器后,容器中已经运行了一个后台进程(这里以监听80端口的nginx为例),同时进入一个shell终端可供操作,而不受限于只能在前台运行nginx与运行shell终端之间的一种.这个例子实现了,那么其他类似的运行多任务docker就可以以此类推.另外本文还提供了一种在docker容器内部安装软件(vim)的方法,对于定制自己需要的镜像大有帮助. 你可能需要先阅读docker专题(2):docker

docker学习(5) 在mac中创建mysql docker容器

github上有一个专门的docker-libary项目,里面有各种各样常用的docker镜像,可以做为学习的示例,今天研究下其中mysql镜像的用法,国内镜像daocloud.io也能找到mysql的镜像,但根据其参考文档在mac上尝试了数次,将mysql数据库文件存储在mac本机时,启动总是报错,大意是docker容器运行时,容器的当前用户mysql,由于权限不足无法mac本机上创建文件,stackoverflow上有人解决了这个问题,参考其解决方法,重新整理了下Dockerfile文件,内