Why Docker

最近因为一些工作原因接触了 Docker,之前学习 Go 的时候就一直听说 Docker 的大名,搞得我以为 Docker 是 Google 开发的。

进入正题,本文主要是是描述一下 Docker 到底是什么。

相信大家能看到的最多的描述是这样的:Docker 是将应用与它所处的环境全部打包来保证它可以在任何地方运行。

这个描述不能算错,但是它其实不够精确。在我看来,Docker 更像是 Git 一样的版本控制工具。只不过他控制的层面是系统级的。

为什么说 Docker 很像 Git 呢。因为 Docker 和 Git 有很多的共同点。

首先,Docker 和 Git 的每次提交都是永久保存的,并且之后的提交不会改变已提交的内容,而是在已提交内容之上提交修改。

其次,Docker 和 Git 一样,你可以从任意的一个提交中 “fork” 出来进行自己的修改,从而构建一个自己的 Docker 镜像。

和 Git 不同的是,大多数时候 Docker 都不是通过 把本机的所有内容打包 来构建镜像。而是从官方提供的某些精简系统上 “fork” 出去,从头开始搭建一个应用。这也是为什么我开头说 Docker 并不是一个环境打包工具的原因。

官方推荐的较为常用的精简系统是Alpine Linux 而其他的例如 Debian 也是非常常见的,另外大多数常见的应用,如 Nginx, MongoDB, Mysql, Nodejs, WordPress 等等都是有官方提供的封装完毕的包,可以直接拿来使用。

好耶,我们一开始就有了一个基底,接下来呢?

接下来,我们需要在基底上构建我们自己的应用,在 Docker 中一般使用 run 命令,指定一个 镜像(image),然后使用它生成一个容器(container),image 和 container 的关系就像类与实例一样,唯一有些区别的是,你可以将一个 container 固化成一个 image 来复用。

在 Docker 中,我们可以像这样新建一个容器并打开其中的 bash(想打开其他终端程序只需要修改最后的命令即可):

有了 bash,一切皆有可能,根据需要构建自己的应用吧。构建完成后只需要使用 commit 命令将其固化就可以生成一个新的镜像。

有没有觉得这种方式非常不优雅?我知道这种方式不是你想要的,你想要一种可以写成配置的操作方式,而不是傻乎乎的每次都要进去开 bash 跑脚本。没错,Docker 也确实提倡使用配置文件来动态的构建镜像。我骗了你:p,上面的方案一般是用来调试一个已经启动完成了的 container 的(例如修改 nginx 的配置)。

一般我们会使用 DckerFile 来构建一个全新的应用,DockerFile 通常由几个部分构成,下面我给出一个简单的 demo,更复杂的就请自己读文档啦。

这份配置实际上是很容易理解的,但是可能有些概念性的东西需要再提一下。

首先是 FROM 命令,这代表你选择了谁作为基底去构建应用,本例中选择了官方包 nodejs 版本为 9,具体的基底可以在 DockerHub 查到,一般的基底都会附上一份 DockerFile 以供参考这份基底是如何生成的。

其次是 WORKDIR 这个概念,这里的 WORKDIR 并不是指当前的工作目录,而是指容器中的目录,也就是说,使用 WORKDIR 就相当于在容器中执行 mkdir -p && cd 的功能。

然后就是 RUN 命令,看起来似乎很简单对吧?就是很直白的执行一行 shell 嘛。但是这里有一个坑,每一个 RUN 命令都是完全独立的。什么意思呢?我们要从 RUN 的原理入手。首先,希望你还没有忘记 DockerFile 是用来做什么的,没错 RUN 命令就是生成镜像的关键,每一次的 RUN 命令结束就会生成一个镜像,这一点在构建的时候就会体现出来,在使用 DockerFile 构建的时候你会看到很多类似这样的输出。

我们可以看到 RUN 命令生成了一个 hash 为 4fa0c5415cdf 的 container,并且在容器中执行完 cp 命令后,将 container 提交为一个新的 image(04f420201cfb)。

我们知道了 RUN 的原理,那么坑在哪呢。坑就是,虽然 RUN 是用来执行 shell 的,但是请不要写出这样的 DockerFile

原因应该很直观了,每一次 RUN 命令结束之后,都会直接 commit,构建新的镜像,同时当前目录都会重置到 WORKDIR 上,所以如果你真的要切换目录,请使用这样的写法

同时,由于每次的 RUN 都会产生一个新的 image,所以能够统一的操作尽量将他统一起来,否则最终镜像的大小可能会超出你的想象。如果你 clone 过几万个 commit 的 Git 项目,你应该知道我在说什么。当然,你也不要为了减小大小把所有的操作都串在一起,因为如果文件没有改动,Docker 是会直接缓存上次的镜像的,这样打包速度会成倍的提升,毕竟一般打包一次就是半个小时的人生。所以之间的权衡自己衡量一下就好。

COPY 命令就很直观了,将本地的文件拷贝进 docker 镜像中,没什么好说的。EXPOSE 命令用于暴露容器端口,现在暂时不去展开。值得一提的是 CMD 命令,我们可以看出,CMD 命令也是执行了一个 shell。那么他和 RUN 命令区别在哪?答案是,RUN 命令用于构建镜像,而 CMD 命令则是启动镜像时自动执行的。

简单来说,我们构建镜像的时候希望把 打包/编译/设置环境变量 等等工作都完成,但是我们肯定不会希望在打包的时候就直接启动程序开始监听端口了。前者就是 RUN 的工作,后者则是 CMD 的范畴。

到这里 Docker 本身的部分就完全结束了。

发表评论

电子邮件地址不会被公开。

This site uses Akismet to reduce spam. Learn how your comment data is processed.