每次都花大量时间下载依赖,构建超大Docker镜像?来看高手怎么做

未匹配的标注

如果你也因为不确定docker容器的安全性而惴惴不安;如果你也经常因为构建docker镜像而花费大量时间下载依赖,构建出特别大的镜像,那真的推荐你把这篇文章看完。

安全扫描

当你构建完一个镜像后,一个好的习惯是:使用 docker scan 命令扫描安全漏洞。Docker与Synk合作,提供漏洞扫描服务。

比如说,要扫描之前在教程中创建的 getting-started 镜像,你可以输入:

docker scan getting-started

扫描使用了不断更新的漏洞数据库,所以你看到提示应该和我这边的不太一样,不过大致是这样的:

输出的列表会列出漏洞的类型,详细信息的网址,以及最重要的:哪个版本修复了漏洞。

你也可以从 docker scan 文档中了解命令的其它选项。

在命令行上扫描新构建的镜像的同时,你也可以配置 Docker Hub 来自动扫描新推送的镜像,随后在 Docker Hub 和 Docker Desktop 中看到扫描结果。

镜像分层

你知道么?你还可以查看是什么构成了一个镜像。使用 docker image history 命令,你能看到是什么命令创建了镜像内部的哪个分层。

1、使用 docker image history 命令查看 getting-started 镜像中的分层,对,就是之前教程中创建的那个。

docker image history getting-started

你会看到类似下面的输出内容(日期或ID都可能会不一样):

每一行都代表了镜像中的一个分层。这里展示的内容,基础的分层在下面,最新的分层在上面。通过这个命令,你也可以快速查看每个分层的大小,帮你诊断超大镜像的问题。

2、此外,你还会发现有些行被截断了。如果想要获取完整输出不截断的话,需要加上 –no-trunc 旗标。

docker image history –no-trunc getting-started

不过在 Windows 的 Power shell 上输出的内容有些糟糕,不易阅读。为此我也在 docker cli 的仓库提了 issue ,等待官方的回复吧。

分层缓存

既然你已经见识过分层了,那就轮到重要的一课了。教你如何减少容器镜像的构建时间。

一旦某个分层改变后,所有的下游分层都必须重建。

让我们再看一眼我们之前使用的 Dockerfile

FROM node:12-alpine

WORKDIR /app

COPY . .

RUN yarn install –production

CMD [“node”, “src/index.js”]

回到镜像历史的输出,我们看到 Dockerfile 中的每个命令都会转变为镜像中的分层。你可能还记得,当我们对镜像做修改的时候,yarn依赖必须重装一遍。有什么办法可以解决这个问题么?每次构建都要发布一遍相同的依赖有点说不过去了,对吧?

要解决这个问题,我们需要重新构建我们的 Dockerfile ,来实现依赖缓存。对于 Node 应用来说,依赖都是定义在 package.json 文件中的。那么,如果我们先只把那个文件拷贝过来,安装依赖,再拷贝剩余的其它文件呢?这样的话,只有 package.json 变更的情况下,才会重建yarn依赖。这样不是更合理么?

1、更新Dockerfile,先把package.json文件拷贝进来,安装依赖,然后再把其它文件拷贝进来。

2、创建 .dockerignore 文件,和 Dockerfile 放在同一个路径下,内容如下:

node_modules

.dockerignore 文件帮我们选择性地拷贝仅与镜像相关的文件。在 Dockerfile reference 中能够看到更多相关信息。文件创建以后,node_moduels 文件夹就会被第2个 COPY 步骤忽略了。不然的话,它很可能会被拷贝进容器,覆盖 *RUN *步骤的命令所创建的依赖。关于Node.js应用为什么建议这么做,以及其它最佳实践,参考 《将一个Node.js网页应用容器化》一文。

3、使用 docker build 构建一个新镜像。

docker build -t getting-started .

能够看到类似这样的输出:

所有的层都被重建了。没毛病,因为我们把 Dockerfile 给改过了。注意,这次我们的 RUN 步骤花了39.6秒。

4、现在,对 src/static/index.html 文件做个小改动,比如把

改成 “牛到不行的待办任务APP”。

5、再次使用 docker build -t getting-started . 构建镜像。这一次,输出的内容会有些不同:

*RUN *步骤只用了0.0秒。我们可以看到缓存了的步骤,前面都会加 CACHED 前缀。构建速度大大提升。同时,推拉镜像以及更新镜像都会快得多。

多阶段构建

这篇教程中,不会深入讨论关于多阶段构建的内容,不过多阶段构建确实是一个强大的工具。优点包括:

1、把构建时依赖和运行时依赖区分开

2、减少镜像大小,只发布应用运行所需的东西

以Maven/Tomcat为例

构建 Java 应用的时候,需要 JDK 来把源代码编译成 Java 字节码。不过,生产环境中,并不需要JDK。另外,你可能会使用类似 Maven 或 Gradle 来帮忙构建应用。这些工具在最终镜像中,其实也用不着。这个时候,多阶段构建就能起到作用了。

FROM maven AS build

WORKDIR /app

COPY . .

RUN mvn package

FROM tomcat

COPY –from=build /app/target/file.war /usr/local/tomcat/webapps

这个例子中,我们使用了一个阶段,叫作 build,这个阶段主要是使用Maven来执行真正的 Java 构建。第2个阶段从FROM tomcat 开始,把文件从 build 阶段拷贝过来。最终的镜像只会创建最后一个阶段,这个行为可以使用 –target 旗标覆盖。

以 React 为例

构建React应用的时候,我们需要一个Node环境,以便把 JS 代码(通常是JSX),SASS样式表等等编译成静态的HTML,JS 和 CSS。如果我们不是在做服务器端渲染的话,生产环境构建甚至不需要Node环境。为什么不把静态资源发布到一个静态 nginx 容器呢?

FROM node:12 AS build

WORKDIR /app

COPY package* yarn.lock ./

RUN yarn install

COPY public ./public

COPY src ./src

RUN yarn run build

FROM nginx:alpine

COPY –from=build /app/build /usr/share/nginx/html

这里,我们使用的是 node:12 镜像来执行构建(同时最大化了分层缓存),然后把输出文件拷贝进了 nginx 容器。怎么样,还不错吧?

回顾

通过对镜像的结构有一个初步了解,我们可以更快地构建镜像,发布更少的变更。扫描镜像给我们吃了定心丸,就目前而言,容器的运行和分发是安全的。多阶段构建帮助我们减少了镜像的容量。通过把构建时依赖和运行时依赖分离开,增加了最终镜像的安全性。

然后呢?

尽管我们的教程结束了,但是仍旧有很多关于容器的东西需要学习!我们这里不会展开太多,不过接下来还是有一些其它领域要去拓展的。

容器编排

生产环境中运行容器是艰难的。你不想登录到每台机器中去,仅仅是为了运行 docker run 或 docker-compose up。为什么不?如果容器挂了怎么办?跨多台机器规模化怎么办?容器编排可以解决这个问题。例如 Kubernetes,Swarm,Nomad,和 ECS 这些工具,都会以类似的方式解决这个问题。

通用的理念是,你会拥有几个”管理员“,管理员会接收到想要的状态。这个状态可以是:我想要给我的网页应用运行2个实例,暴露80端口。管理员接收到状态后,会查看集群中的所有机器,并把任务委派给”员工“节点。管理员会时刻关心着变更,比如容器退出,管理员就会做一些工作,让真实状态能够反映想要的状态。

云原生计算基金会项目

CNCF是一个是拥有许多开源项目的中立组织,开源项目包括 Kubernetes,Prometheus,Envoy,Linkerd,NATS等等。可以在他们的项目页面看到已毕业和孵化中的项目,可以在他们的landscape页面看到组织的全景图。有着非常多的,围绕监控、日志、安全、镜像注册表、消息传递等等的项目!

所以,如果你刚刚接触容器和云原生应用开发,欢迎联系社区,提出问题,保持学习。你能够加入让我们特别高兴!

docker官方教程系列到此就告一段落了。

如果有任何问题,或者建议,欢迎评论区留言或私信给我。

我们下一期,不见不散。

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
秦晓武
讨论数量: 0
发起讨论 只看当前版本


暂无话题~