containerizing,有时也叫Dockerizing,is the process of taking an application and configuring it to run as a container.
容器通常是关于apps,特别是,它们使得app的构建、传送(将image推送到Docker仓库)、运行变的简单。
将应用程序装箱的过程如下:
1、编写应用程序代码;
2、创建Dockerfile描述app、它的依赖以及如何运行它;
3、将Dockerfile输入到docker image 构建命令;
4、Docker将应用程序构建成Docker image。
上述过程的图示如下:
接下来,将以一个简单的单容器 Node.js web app为例来进行装箱,主要包含如下步骤:
• 获取app代码
• 检查Dockerfile
• 装箱app
• 运行app
• 测试app
• 使用多阶段构建转移到生产环境
• 一些最佳实践
注:使用网上环境,地址:https://labs.play-with-docker.com/
1、获取app代码
从GitHubs上将https://github.com/nigelpoulton/psweb.git克隆到本地,psweb目录下包含了所有的应用程序源代码。如下图所示:
2、检查Dockerfile
psweb目录下有一个文件叫Dockerfile,这个文件描述了应用程序,并告诉Docker如何把应用程序构建进镜像。
包含应用程序的目录被称之为构建上下文,将Dockerfile放在构建上下文的根目录是一个通用实践。
注意 Dockerfile以大写字母D开始,是一个单词,dockerfile和Docker file都是无效的。
Dockerfile的主要目的有两个:描述应用程序和告诉Docker如何将应用程序装箱(创建一个包含应用程序的镜像)。此外,还可以弥合开发和运营之间的鸿沟!
上述Dockerfile文件里的内容可解释为:以alpine镜像开始,增加nigelpoulton@hotmail.com作为维护者(设置元数据,不会产生新的层),安装Node.js和NPM,拷贝应用程序代码,设置工作目录(设置元数据,不会产生新的层),安装依赖,记录app网络端口(设置元数据,不会产生新的层),设置app.js作为运行的缺省应用程序。如下图所示:
所有的Dockerfile都是以 FROM开始,这个是镜像的基础层,其他的作为附加层。
3、装箱app
Dockerfile编写完成后,就可以开始构建镜像。 构建完成后,用docker image ls查看构建的镜像,应用程序已被装箱。
可以将镜像推送到Docker Hub仓库。前提是你必须得有一个Docker Hub账号.
在推送镜像到仓库前,必须以特定的方式来对镜像进行打标签。当推送镜像时,Docker需要下列所有信息:Registry、 Repository、Tag
一般情况下,不需要指定Registry(默认docker.io)和Tag(默认latest),然而,Docker对于Repository也不需要一个缺省值,它从推送的镜像中获取“REPOSITORY”值。
对于上述构建的镜像,docker image push将尝试和 推送镜像到docker.io/web:latest,但由于没有访问web仓库的权限,所有的镜像都存在于二级命名空间,因此需要重新打标签,命令为 docker image tag <current-tag> <new-tag> ,这将会额外增加一个标签,并不会覆盖原始的。
4、运行app
输入命令:docker container run -d --name c1 -p 80:8080 web:latest,来运行app,如下图所示:
5、测试app
在浏览器输入地址,出现如下界面,则app启动成功,正在运行。
6、使用多阶段构建转移到生产环境
对于Docker镜像来说,大是糟糕的。大意味着更多潜在的脆弱性和可能更大的攻击面。
Docker镜像应该小,它的目的是传送仅包含所需原料的生产镜像在生产环境运行app。
编写Dockerfile的方式会影响镜像的大小,因为每一个RUN指令都会在镜像里增加一层。因此最佳实践是在每一个RUN指令里包含多个命令。
另一个问题是,我们不会清除我们自己。针对镜像,我们会运行一些命令在拉取build-time工具,当我们传送镜像到生产环境时,这些工具遗留在镜像里,这不是理想的。
对于上述问题,大多数情况下使用 builder pattern,但大多数都需要行为准则并且增加了复杂度。
builder pattern要求至少两个Dockerfile:一个生产环境,一个开发环境。在开发环境中,编写Dockerfile.dev,可以从一个大的基础镜像开始拉取任何需要的构建工具,然后构建app。然后从Dockerfile.dev构建镜像,创建容器并运行它。在生产环境中,编写Dockerfile.prod,从一个较小的基础镜像构建新镜像,仅拷贝应用程序。所有的任何事都需要使用脚本来进行粘合。
这个方法是可行的,但是以复杂度为代价的。
多阶段构建可以解决上述问题。
多阶段构建是关于在不增加复杂性的情况下优化构建的。
在多阶段构建中,我们需要一个单一的Dockerfile,包含多个FROM指令。每一个FROM指令是一个新的构建阶段 ,可以轻松复制前述阶段的产物。
Dockerfile文件内容如下:
从上述Dockerfile看,有3个FROM指令,每一个FROM指令构成一个独特的构建阶段,比如 阶段 0 叫storefront,阶段 1 叫appserver,阶段 2叫production。
storefront阶段:拉取node:latest镜像,设置工作目录,拷贝APP代码,使用两个RUN指令执行npm,此镜像有3个层,且size大。因为包含了大量的构建东西而非代码。
appserver阶段:拉取maven:latest镜像,通过两个COPY和两个RUN指令增加了4个层,这产生了非常多的构建工具和很少的实际生产 代码。
production阶段:拉取java:8-jdk-alpine镜像,此比node和maven镜像小很多。它增加了用户,设置工作目录,从storefront阶段产生的镜像里拷贝一些app代码。此外,设置了一个不同的工作目录,从appserver阶段产生的镜像里拷贝一些应用程序代码,最后,给镜像设置了当它作为容器启动时的主运行程序。
对上述的Dockerfile进行构建,在上图中输入:"docker image build -t multi:stage .",完成后,输入 docker image ls查看,如下:
其中node:latest是由storefront阶段拉取的,maven:latest是由appserver阶段拉取的,两个<none>:<none>镜像分别由storefront阶段和appserver阶段产生的(分别增加Dockerfile里描述的层所组成)。java:8-jdk-alpine是multi:stage阶段拉取的,multi:stage是由最后的构建阶段所产生的镜像,从上述看,此两个镜像比storefront阶段和appserver阶段产生的镜像大小小很多。
7、 一些最佳实践
- 利用构建缓存(Leverage the build cache)
- 压缩镜像(Squash the image,Docker遵循一个正常的过程来构建镜像,最后增加了一个步骤将所有的东西压缩成单一层)
- 使用no-install-recommends(Use no-install-recommends)【使用apt包管理器时,需要在apt-get install命令后使用no-install-recommends标志,这使得apt仅安装主要依赖,其他的不安装】
- 在Windows下,不要从MSI包进行安装(Do not install from MSI packages (Windows))
• docker image build :读取Dockerfile文件,将应用程序装箱,-t 标签标记镜像,可以声明Dockerfile文件中的名称和位置。• FROM 指令:为新镜像声明base镜像。
• RUN指令:允许在运行镜像中的指令,其创建了一个新层。每一个RUN指令创建一个单一新层。
• COPY指令:向镜像中添加文件,并作为一个新层。通常向镜像中拷贝应用程序。
• EXPOSE指令:记录应用程序使用的网络端口。
• ENTRYPOINT指令:当镜像作为容器启动时,将默认应用程序设置为运行。
• 其他的Dockerfile指令包括LABEL, ENV, ONBUILD, HEALTHCHECK, CMD灯。