Docker for Devs 系列包括以下6篇文章,这是第二篇。
在这个系列教程的第一部分中,我们为应用程序创建了一个的 基础 Docker 镜像并运行了该镜像的实例(称为容器)。我们也见证了 Grayskull 的力量......我的意思是,Docker!(PS: Grayskull 出自《He-Man and the Masters of the Universe》,上个世纪八十年代的一部动画)
我们完成了所有典型应用程序的配置和运行,但不是从我们的本地主机,而是在一个容器内运行。接下来的 Docker for Developers 系列中,我们将试着配置一个可编辑的应用程序开发环境镜像。
我们在本教程的这一部分中的目标是生成一个代表我们应用程序开发版本的镜像,并为它配置一个(可运行)容器所需的必要组件,这样我们就能对文件系统进行更改并将其反映在容器中。
让我们在我们的应用程序的根目录中创建一个新的Docker镜像文件。以下是派生镜像的说明:
# dev.dockerfile
FROM express-prod-i
ENV NODE_ENV=development
CMD [“./initialize.sh”]
我们做了什么?
我们创建了一个新的docker镜像文件:
我们不会在创建镜像时初始化应用程序,而是将其移至容器中。因此,应用程序启动步骤(例如,"npm install")将在每次容器启动时执行。
1. 在项目根目录下创建一个名为 "initialize.sh" 的文件
2. 将以下内容粘贴到 "initialize.sh" 中
npm install
node bin/www
3. 从终端/命令提示符进入项目根目录并运行以下命令,以使 bash shell 脚本可执行:
chmod +x initialize.sh
注意:请记住,这些容器正在基于 Linux 的环境中运行,因此运行 chmod system 命令会将指定文件的权限设置为可执行文件(本例中为initialize.sh)。
我们做了什么?
还记得吗,我们在基本的 express-prod-i 镜像中指定了运行 "npm install" 命令,该命令将安装 NPM 软件包作为容器的一部分。但在这里,我们:
现在,我们拥有了一个新的 Docker 镜像文件,我们已经准备好创建一个镜像了。
就像我们在上一篇教程中所做的那样,让我们创建一个新的镜像:
docker build -t express-dev -i -f dev.dockerfile。
我们做了什么?
运行 docker images
,我们可以看到所有运行着的新旧镜像:
我们现在有一个镜像,代表我们的应用程序的开发版本,它以我们的生产环境版本为基础。现在,我们想在运行那个容器的同时,挂载数据卷(Volume)。
一直以来,您可能一直在想如何编辑源代码,并且如果源代码驻留在容器中,它会反映在正在运行的容器中,对吗?那也是我们要完成的主要目标之一,不是吗?
我之前提到,镜像是一堆不同的只读分层文件系统。每层添加或替换下面的层。我也提到容器是镜像的一个运行实例。但事实上不止于此,容器为镜像的底层只读文件系统提供了一个读写层。
为了将这些只读层和读写层合并在一起,Docker 使用了 Union File System(联合文件系统)。但通过容器的状态变化并不会反映在镜像中,任何文件更改都严格保存在容器中。这就带来了一个问题:当一个容器脱机时,在容器实例化的底层镜像中任何改变都不会被保存。
因此,为了持久化容器所做的更改(也有其他好处),Docker 开发了 Volume,通常被称作数据卷。简而言之,数据卷是存在于 Union File System 之外的目录或文件,通常位于主机文件系统上。
现在我们有了一个表示应用程序开发版本的镜像,我们准备在主机上创建一个容器,其中包含指向应用程序源代码本地目录的 数据卷:
重要提示:如果你已经在容器外运行了应用程序(例如,
node bin/www
),与我们在 shell 脚本 initialization.sh 中设置的命令相同,并且你的文件夹根目录下有一个本地的 node_modules 目录,请现在删除他们。
docker run –name express-dev-app -p 7000:3000 -v $(pwd):/var/app express-dev-i
注意:赋给 volume -v 标志的值被拆分为由 (:) 分隔的主机目录,后面跟着容器 WORKDIR 工作目录。这只与我们的设置有关,但指定的容器目录不一定是 WORKDIR 目录。
我们做了什么?
$(pwd)
代表主机上的“当前工作目录”到容器 "/var/app" 中的一个目录(指定为 Dockerfile 中的 WORKDIR)。提示:当容器被移除时,默认情况下不会删除数据卷。但是,您可以使用 docker remove(rm)指定 -v 标志来删除关联卷:
docker rm -v [容器的名称或ID]
。
如果一切按计划进行,您应该能在终端/命令提示符中看到 npm install
的结果和正在安装的 node modules 列表。我特意遗漏了这个被分开的 -d 标志,这样就可以观察到了。
我们可以通过运行 docker ps
命令列出正在运行的容器,来验证是否有问题导致容器停止运行。如果没有列出,可以将
ALL -a 标志添加到上述命令中,以显示所有容器,并查看是否有“express-dev-app”容器列出的退出错误。
在我们继续之前,我们可以通过使用下面的 INSPECT
命令来查看有关装载量的信息,这个命令会向我们显示大量的容器信息:
docker inspect express-dev-app
我们做了什么?
这大概你一直在等待的时刻吧!我们将单刀直入,看看我们如何在本地进行源代码更改,并将其反映在容器中。
重要提示:请务必查看第6步,了解关于安装的本地源代码和容器的一些重要提示,命令和解释。
1. 在浏览器中进入http://localhost:7000
2. 在根目录中,导航到 /views 目录并打开 index.jade 文件
3. 找到行
p Welcome to #{title}
4. 修改为
p Welcome to #{title} running in a container
5. 回到浏览器中,刷新URL
我们做了什么?
我们不需要重建,甚至无需重新启动容器,就能看到我们对这个 express 应用的前端进行的简单而重要的改动被反映在了容器中。
还记得吗,我们在创建最后一个容器之前删除了本地应用程序根目录中可能存在的任何 node_modules 文件夹。但是,如果你再查看一下,会发现 node_modules 文件夹依然存在。
这是因为托管运行 node.js 应用程序所需的更改(例如安装所有依赖的 node 模块),会通过我们挂载的卷在本地反映出来。
我们可以通过连接到正在运行的容器来验证。在容器上打开一个 bash shell 并检查有关工作目录的信息。
我们没有以脱机模式启动容器,因此您需要停止正在运行的容器,并使用docker start
命令重启,如上一个教程中所示。
或者您需要打开一个新的终端/命令提示符并通过:
docker exec -it express-dev-app /bin/sh
ls -l
我们做了什么?
ls -l
来显示目录内容实际上显示了本地卷挂载主机目录的内容。我们在 Docker for Developer 教程中完成的看起来很简单,但是非常高效。我们将我们的应用程序设置模块化,到一个包含应用程序必要设置的容器,同时保持对我们运行在容器中的应用程序源代码的控制。
本篇教程中,我们只是初步地在应用程序开发中应用 Docker 容器化技术。在下一个教程中,我们将抛开这些简单的例子,通过在容器中使用和运行支持热重载的通用(同构)React.js 应用程序,进行更深入的实践。