Docker秘籍 之 创建史上最小Docker容器

2 篇文章 0 订阅
2 篇文章 0 订阅

作者:Adriaan de Jonge July 4, 2014

译者:Stone Feng

原文链接:http://blog.xebia.com/2014/07/04/create-the-smallest-possible-docker-container/

译文链接:http://blog.csdn.net/stonefeng/article/details/38714129

注:此文参加Docker中文社区译文悬赏活动,Docker中文社区转载时可适当修改本译文。

【转载必须在正文中标注并保留原文链接、译文链接、作者、译者信息、以及本条要求。】

译者按:

  史上最小Docker容器长什么样子?怎么创建的?做什么用?来看此文!

scratch镜像

  如果你使用Docker,你很快就会发现,当你采用预配置的容器时,需要下载的内容很大。一个简单的Ubuntu容器很容易就超过了200MB,而且当其中附带安装好的软件时,这个体积还要增加。在某些情况下,你其实不需要Ubuntu所带的所有东西。例如,假如你想运行一个用Go语言编写的简单web server(经过权衡,server一词不翻译更自然——译者),那么就不需要任何工具。

  我搜索了用来作为起始点的最小容器,发现了这个:docker pull scratch

  这个scratch(有草稿的意思,这里可以看做专用词汇,不翻译)镜像是极好的,几乎完美!简洁,体积小,速度快。它没有bug、安全漏洞、迟钝的代码、或者技术过失。这是因为它基本上是空的,除了一点儿由Docker添加的元数据。实际上,你可以用下面的命令(参见:Docker文档)自己创建这个scratch镜像:

tar cv --files-from /dev/null | docker import - scratch

  所以,这就是体积最小的Docker镜像了。此文结束!嗯……或者还有些什么东西可说的?比如,怎么使用这个scratch镜像?这还真有点儿挑战性。

scratch镜像添加内容

  在这个“空”镜像上能运行什么?回答是:没有依赖的可执行程序。这样的程序你有吗?

  我经常用PythonJavaJavaScript写程序。这些语言/平台都需要安装运行时。最近,我开始探索Go(或者你可以称为GoLang)平台。这平台看上去是静态链接的。我编译了一个简单的Hello World web server,并用scratch容器运行。下面列出了代码:

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容器里不包括Go编译器,我无法在里面编译我的webserver。我在Mac上工作,我也无法编译一个linux可执行码(实际上,是可以跨平台编译Go语言源码到另外平台的,但这将是另外一篇博文的素材)。

  所以我需要一个包含Go编译器的Docker容器。让我们这样做:

docker run -ti google/golang /bin/bash

  用这个容器编译Go web server,并提交到GitHub库:

go get github.com/adriaandejonge/helloworld

  go get命令是go build命令的变体,可以取来远程依赖包并构建。通过下面命令运行构建结果:

$GOPATH/bin/helloworld

  这能正常工作。但这还不是我们想要的。我们想让hello world容器在scratch容器里运行。所以,实际上我们需要一个带有如下内容的Dockerfile

FROM scratch

ADD bin/helloworld /helloworld

CMD ["/helloworld"]

  然后运行它。不幸的是,我们运行google/golang容器的方式无法构建这个Dockerfile。所以,我们需要一个在这个容器里面访问Docker的方法。

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可执行代码在容器里的路径,而不需要硬编码。然而,当你在苹果系统上使用boot2docker时,要小心使用这个命令。如果docker可执行码并没有安装在boot2docker虚拟机而是其他地方时,这个命令是找不到docker可执行码的。找到的是被添加到容器里的在boot2docker虚拟机中的可执行码。所以,你可以用/usr/local/bin/docker硬编码代替$(which docker)。类似地,你如果运行不同的系统,你有机会用/var/run/docker.sock来调整到不同的位置。

  现在你能在$GOPATH目录(在这个例子里指向/gopath)下,使用google/golang容器里的Dockerfile了。我把这个Dockerfile 传到了GitHub上,这样你可以使用下面的代码将其从Go构建目录拷贝到希望的位置:

cp $GOPATH/src/github.com/adriaandejonge/helloworld/Dockerfile $GOPATH

  你需要拷贝这个,因为编译好的二进制码位于$GOPATH/bin,并且也不可能在构建Dockerfile的时候包括父目录里的文件。所以下面执行这个:

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 命令看到可执行码的动态连接库:

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)

  所以,为了在web服务器李允星Hello World,我们需要告诉Go编译器执行静态链接。

Go里创建静态链接的可执行码

  为了创建静态链接可执行码,我们需要告诉Gocgo编译器而非go编译器。命令如下:

CGO_ENABLED=0 go get -a -ldflags '-s' github.com/adriaandejonge/helloworld

CGO_ENABLED环境变量告诉Go使用cgo编译器而非go编译器。-a标记告诉Go重新构建所有依赖。否则,你让然会使用动态链接依赖库。并且-ldflags '-s' 标记尤其漂亮,它大体上能缩减可执行文件50%的体积。你不用cgo编译器也可以做到这点,缩减的体积源于去掉了调试信息。

  保险起见,重新运行一下ldd命令:

ldd $GOPATH/bin/helloworld 

  应该返回:

not a dynamic executable

  你还可以重新运行从scratch创建带可执行码的Docker容器的步骤:

docker build -t adejonge/helloworld $GOPATH

  如果一切正常,应该返回类似下面的内容: 

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 rm -f helloworld

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传到了一个叫做adriaandejonge/hellobuild的单独的GitHub库里。可以用下面的命令构建:

docker build -t adejonge/hellobuild github.com/adriaandejonge/hellobuild

  -t flag指定镜像名称为adejonge/hellobuild 并且隐含标记为最新。这些名字能让你将来删除他们的时候容易一点儿。下一步,你可以用这个镜像创建容器,记得要使用本文之前提过的标记:

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新容器,基于adejonge/helloworld镜像的,跟前面的做法一样:

docker run -ti --name helloworld adejonge/helloworld

  由于所有步骤都在同样的命令行下运行,并没打开Docker容器里面的bash shell,所以你可以吧这些步骤添加到bash脚本里并自动运行它。为了方便,我把这些bash脚本添加到了hellobuild GitHub

  同样,如果你想越过这些步骤直接尝试这个最小的Docker容器,你可以用这个构建好的镜像,我把它放在了Docker Hub库:

docker pull adejonge/helloworld

  用docker images -a命令将显示大小为3.6MB。当然,如果你创建比我这个Go web server更小的可执行码,你的容器可以做得更小。用C或者汇编你可以做到这一点。然而,你无法比scratch镜像更小了。


  喜欢此文?不喜欢此文?敬请留言,欢迎转发,多多交流。——译者

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值