docker镜像分层
2024-01-21 00:46 youxin 阅读(57) 评论(0) 编辑 收藏 举报镜像分层
您知道可以查看镜像是如何组成的吗?使用docker image history命令,您可以查看镜像中的每个层次的创建命令。
1.使用docker image history命令查看您在教程中创建的getting-started镜像中的层次。
docker image history getting-started
您应该会得到类似以下的输出(日期/ID可能不同)。
IMAGE CREATED CREATED BY SIZE COMMENT
05bd8640b718 53 minutes ago CMD ["node" "src/index.js"] 0B buildkit.dockerfile.v0
<missing> 53 minutes ago RUN /bin/sh -c yarn install --production # b… 83.3MB buildkit.dockerfile.v0
<missing> 53 minutes ago COPY . . # buildkit 4.59MB buildkit.dockerfile.v0
<missing> 55 minutes ago WORKDIR /app 0B buildkit.dockerfile.v0
<missing> 10 days ago /bin/sh -c #(nop) CMD ["node"] 0B
<missing> 10 days ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B
<missing> 10 days ago /bin/sh -c #(nop) COPY file:4d192565a7220e13… 388B
<missing> 10 days ago /bin/sh -c apk add --no-cache --virtual .bui… 7.85MB
<missing> 10 days ago /bin/sh -c #(nop) ENV YARN_VERSION=1.22.19 0B
<missing> 10 days ago /bin/sh -c addgroup -g 1000 node && addu… 152MB
<missing> 10 days ago /bin/sh -c #(nop) ENV NODE_VERSION=18.12.1 0B
<missing> 11 days ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 11 days ago /bin/sh -c #(nop) ADD file:57d621536158358b1… 5.29MB
每一行代表镜像中的一个层次。这里的显示从底部显示基础层次,最新层次在顶部。使用这个命令,您还可以快速查看每个层次的大小,有助于诊断大型镜像。
2.您会注意到其中一些行被截断了。如果添加--no-trunc标志,您将获得完整的输出(是的...有趣的是您使用截断标志来获得未截断的输出,不是吗?)
docker image history --no-trunc getting-started
层缓存
Layer Caching
现在你已经看到了层叠的作用,有一个重要的教训可以帮助减少容器映像的构建时间。
一旦一个层发生变化,所有下游的层也必须重新创建
让我们再次查看我们使用的 Dockerfile...
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]
回到映像历史输出,我们可以看到 Dockerfile 中的每个命令都会成为映像中的一个新层。你可能还记得,当我们对映像进行更改时,必须重新安装 yarn 依赖项。有没有办法解决这个问题呢?每次构建时都携带相同的依赖关系似乎没有太多意义,对吗?
为了解决这个问题,我们需要重新构建我们的 Dockerfile,以支持依赖项的缓存。对于基于 Node 的应用程序,这些依赖项在 package.json 文件中定义。那么,如果我们首先只复制该文件,安装依赖项,然后再复制其他所有内容呢?然后,只有在 package.json 发生更改时,我们才重新创建 yarn 依赖项。有道理吧?
To fix this, we need to restructure our Dockerfile to help support the caching of the dependencies. For Node-based applications, those dependencies are defined in the package.json
file. So, what if we copied only that file in first, install the dependencies, and then copy in everything else? Then, we only recreate the yarn dependencies if there was a change to the package.json
. Make sense?
1.更新 Dockerfile 以首先复制 package.json,然后安装依赖项,最后复制其他所有内容。
FROM node:18-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --production
COPY . .
CMD ["node", "src/index.js"]
2.在与 Dockerfile 相同的文件夹中创建一个名为 .dockerignore 的文件,并包含以下内容。
node_modules
.dockerignore 文件是一种选择性复制仅与映像相关的文件的简便方法。你可以在这里阅读更多关于这个的内容。在这种情况下,应该在第二个 COPY 步骤中省略 node_modules 文件夹,因为否则它可能会覆盖由 RUN 步骤中的命令创建的文件。有关为 Node.js 应用程序建议使用这种方法以及其他最佳实践的更多详细信息,请参阅它们有关 Docker 化 Node.js web 应用程序的指南。
3.使用 docker build 构建一个新的映像。
docker build -t getting-started .
你应该会看到如下输出...
[+] Building 16.1s (10/10) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 175B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/node:18-alpine 0.0s
=> [internal] load build context 0.8s
=> => transferring context: 53.37MB 0.8s
=> [1/5] FROM docker.io/library/node:18-alpine 0.0s
=> CACHED [2/5] WORKDIR /app 0.0s
=> [3/5] COPY package.json yarn.lock ./ 0.2s
=> [4/5] RUN yarn install --production 14.0s
=> [5/5] COPY . . 0.5s
=> exporting to image 0.6s
=> => exporting layers 0.6s
=> => writing image sha256:d6f819013566c54c50124ed94d5e66c452325327217f4f04399b45f94e37d25 0.0s
=> => naming to docker.io/library/getting-started 0.0s
你会看到所有的层都被重新构建了。这很好,因为我们对 Dockerfile 进行了相当大的更改。
4.现在,对 src/static/index.html 文件进行更改(比如将 <title> 更改为 "The Awesome Todo App")。
5.再次使用 docker build -t getting-started . 构建 Docker 映像。这次,你的输出应该有所不同。
[+] Building 1.2s (10/10) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 37B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/node:18-alpine 0.0s
=> [internal] load build context 0.2s
=> => transferring context: 450.43kB 0.2s
=> [1/5] FROM docker.io/library/node:18-alpine 0.0s
=> CACHED [2/5] WORKDIR /app 0.0s
=> CACHED [3/5] COPY package.json yarn.lock ./ 0.0s
=> CACHED [4/5] RUN yarn install --production 0.0s
=> [5/5] COPY . . 0.5s
=> exporting to image 0.3s
=> => exporting layers 0.3s
=> => writing image sha256:91790c87bcb096a83c2bd4eb512bc8b134c757cda0bdee4038187f98148e2eda 0.0s
=> => naming to docker.io/library/getting-started 0.0s
首先,你应该注意到构建速度大大加快了!你会看到有几个步骤正在使用先前缓存的层。所以,太好了!我们正在使用构建缓存。推送和拉取这个映像以及对它的更新也将快得多。太好了!
多阶段构建
Multi-Stage Builds
虽然在本教程中我们不会深入讨论它,但多阶段构建是一个非常强大的工具,它通过使用多个阶段来创建镜像来帮助我们。它们提供了几个优点,包括:
- 将构建时的依赖项与运行时的依赖项分开
- 通过仅运送应用程序运行所需的内容来减小镜像的总大小
- Separate build-time dependencies from runtime dependencies
- Reduce overall image size by shipping only what your app needs to run
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构建。在第二阶段(从FROM tomcat开始),我们从构建阶段复制文件。最终镜像只是最后一个阶段被创建的镜像(可以使用--target标志进行覆盖)。
React示例
在构建React应用程序时,我们需要一个Node环境来编译JS代码(通常是JSX)、SASS样式表等,以生成静态HTML、JS和CSS。尽管如果我们不执行服务器端渲染,甚至在生产构建中都不需要Node环境。为什么不将静态资源运送到一个静态的nginx容器中呢?
FROM node:18 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:18镜像来执行构建(最大化层缓存),然后将输出复制到一个nginx容器中。很酷,对吧?
总结
通过了解镜像的结构,我们可以更快地构建镜像并减少更改的数量。扫描镜像使我们有信心运行和分发的容器是安全的。多阶段构建还帮助我们减小镜像的总大小,并通过将构建时的依赖项与运行时的依赖项分开来增加最终容器的安全性。
下一步做什么?
虽然我们已经完成了我们的研讨会,但关于容器还有很多要学习的内容!我们不会在这里深入探讨,但以下是一些可以接下来研究的领域!
容器编排
在生产环境中运行容器是有挑战的。你不希望只是登录到一台机器上运行docker run或docker compose up。为什么呢?如果容器停止运行会发生什么?如何在多台机器上进行扩展?容器编排解决了这个问题。像Kubernetes、Swarm、Nomad和ECS等工具都以略有不同的方式来解决这个问题。
一般的想法是你有“管理器”接收预期状态。这个状态可能是“我想运行两个实例的我的Web应用程序并暴露端口80。”然后管理器会查看集群中的所有机器,并将工作委派给“工作”节点。管理器会监视变化(比如容器退出),然后努力使实际状态反映预期状态。
Cloud Native Computing Foundation项目
CNCF是各种开源项目的供应商中立的家园,包括Kubernetes、Prometheus、Envoy、Linkerd、NATS等等!你可以在这里查看已毕业和孵化的项目,以及整个CNCF景观。有很多项目可以帮助解决监控、日志记录、安全性、镜像注册、消息传递等问题!
所以,如果你是新手容器领域和云原生应用开发,欢迎!请加入社区,提问,并继续学习!我们很高兴有你的加入!
https://zhuanlan.zhihu.com/p/667867199