安全检查
当你已经构建了一个镜像,最好使用 docker scan 命令进行安全漏洞检查。Docker 已经与 Synk 合作,去提供安全漏洞检查服务。
你必须登录 Docker Hub 才能检查你的镜像。使用 docker scan --login ,然后使用 docker scan <image-name> 检查镜像。
例如,你可以检查之前构建的 getting-started 镜像,只需要键入。
docker scan getting-started
输出类似如下:
? Low severity vulnerability found in freetype/freetype
Description: CVE-2020-15999
Info: https://snyk.io/vuln/SNYK-ALPINE310-FREETYPE-1019641
Introduced through: freetype/freetype@2.10.0-r0, gd/libgd@2.2.5-r2
From: freetype/freetype@2.10.0-r0
From: gd/libgd@2.2.5-r2 > freetype/freetype@2.10.0-r0
Fixed in: 2.10.0-r1
? Medium severity vulnerability found in libxml2/libxml2
Description: Out-of-bounds Read
Info: https://snyk.io/vuln/SNYK-ALPINE310-LIBXML2-674791
Introduced through: libxml2/libxml2@2.9.9-r3, libxslt/libxslt@1.1.33-r3, nginx-module-xslt/nginx-module-xslt@1.17.9-r1
From: libxml2/libxml2@2.9.9-r3
From: libxslt/libxslt@1.1.33-r3 > libxml2/libxml2@2.9.9-r3
From: nginx-module-xslt/nginx-module-xslt@1.17.9-r1 > libxml2/libxml2@2.9.9-r3
Fixed in: 2.9.9-r4
输出列出了漏洞的类别,一个 URL 去了解更多,解决漏洞的相关库版本。
这里有一些其他选项,你可以阅读 docker scan 文档。
除了在命令行检查你新构建的镜像,你也可以配置 Docker Hub 去自动检查所有上传的镜像,你可以同时在 Docker Hub 和 Docker Desktop 看见结果。
镜像层次
使用 docker image history 命令,你可以看见创建镜像每一层的命令。
- 使用
docker image history 命令去看 getting-started 镜像的层次。
docker image history getting-started
你应该得到如下输出。
IMAGE CREATED CREATED BY SIZE COMMENT
e569a14f58f5 2 days ago EXPOSE map[3000/tcp:{}] 0B buildkit.dockerfile.v0
<missing> 2 days ago CMD ["node" "src/index.js"] 0B buildkit.dockerfile.v0
<missing> 2 days ago RUN /bin/sh -c yarn install --production
<missing> 2 days ago COPY . .
<missing> 2 days ago WORKDIR /app 0B buildkit.dockerfile.v0
<missing> 2 days ago RUN /bin/sh -c apk add --no-cache python2 g+… 223MB buildkit.dockerfile.v0
<missing> 4 weeks ago /bin/sh -c
<missing> 4 weeks ago /bin/sh -c
<missing> 4 weeks ago /bin/sh -c
<missing> 4 weeks ago /bin/sh -c apk add --no-cache --virtual .bui… 7.84MB
<missing> 4 weeks ago /bin/sh -c
<missing> 4 weeks ago /bin/sh -c addgroup -g 1000 node && addu… 77.6MB
<missing> 4 weeks ago /bin/sh -c
<missing> 3 months ago /bin/sh -c
<missing> 3 months ago /bin/sh -c
每一行都代表镜像的一层。你可以看见每层的大小,去帮助处理大型镜像。
- 你会注意到有几行是截断的,如果你添加
--no-trunc 标签,你将会得到全部输出。
docker image history --no-trunc getting-started
层缓存
现在你已经实际看见层次了,这里有个重要的方法去帮助减少构建镜像的次数
一旦一个层被改变,所有下游层不得不被重新构建。
下面我们看一个 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 依赖即可。
- 更新 Dockerfile 去首先复制
package.json ,安装依赖,然后复制所有文件。
FROM node:12-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --production
COPY . .
CMD ["node", "src/index.js"]
- 在与 Dockerfile 相同文件夹下,创建一个文件
.dockerignore ,并添加如下内容。
node_modules
.dockerignore 文件是一个简单的方法去仅仅复制镜像相关文件。你可以在这了解更多。在这个案例中,node_modules 文件夹应该在第二个 COPY 步骤被忽略,因为他可能会重写在 RUN 步骤所创建的文件。
如果你想了解更多 Node.js 应用的细节,你可以看这篇文档。
- 使用
docker build 构建一个新的镜像。
docker build -t getting-started .
输出如下:
[+] Building 18.6s (10/10) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 212B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 53B 0.0s
=> [internal] load metadata for docker.io/library/node:12-alpine 0.0s
=> [internal] load build context 0.5s
=> => transferring context: 3.29kB 0.4s
=> [1/5] FROM docker.io/library/node:12-alpine 0.0s
=> CACHED [2/5] WORKDIR /app 0.0s
=> [3/5] COPY package.json yarn.lock ./ 0.0s
=> [4/5] RUN yarn install --production 16.7s
=> [5/5] COPY . . 0.1s
=> exporting to image 1.2s
=> => exporting layers 1.1s
=> => writing image sha256:8d86e3a560798b86a9e0be58cfa7120c353686d6673433189ffe1a8634d442db 0.0s
=> => naming to docker.io/library/getting-started 0.0s
我们看到镜像的所有层次都被重新构建。
- 下面,对
src/static/index.html 文件做一个改变(例如改变 title 为 The Awesome Todo App )。 - 再次使用
docker build -t getting-started . 构建 Docker 镜像,这次的输出和之前有点不同。
[+] Building 0.2s (10/10) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 32B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 34B 0.0s
=> [internal] load metadata for docker.io/library/node:12-alpine 0.0s
=> [1/5] FROM docker.io/library/node:12-alpine 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 3.44kB 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.0s
=> exporting to image 0.1s
=> => exporting layers 0.0s
=> => writing image sha256:3da77ddc82b27f14cf76726efd1e856da84d5cd8344b17c885282abbe827b624 0.0s
=> => naming to docker.io/library/getting-started 0.0s
你会明显发现,构建更快了,这是因为我们使用了构建缓存。
多阶段构建
多阶段构建是一个有力的工具去构建镜像,优势如下:
- 将构建时依赖和运行时依赖分离。
- 通过发送你的应用需要运行的依赖去减小整个镜像的大小。
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 ),我们从 build 阶段复制文件。最终的镜像只是最后被创建的阶段(可以使用 --target 进行重写)。
React 实例
当我们构建 React 应用时,我们需要一个 Node 环境去编译 JS 代码(常是 JSX),SASS stylesheets 和更多在静态 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 容器。
|