形象塑造最佳实践

预计阅读时间:9分钟

安全扫描

生成映像后,最好使用docker scan命令对其进行扫描以查看是否存在安全漏洞。Docker已与Snyk合作提供漏洞扫描服务。

例如,要扫描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 命令,您可以看到用于创建图像中每个图层的命令。

  1. 使用docker image history命令查看getting-started您在本教程前面创建的图像中的图层。

     docker image history getting-started
    

    您应该得到看起来像这样的输出(日期/ ID可能不同)。

     IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
     a78a40cbf866        18 seconds ago      /bin/sh -c #(nop)  CMD ["node" "src/index.j…    0B                  
     f1d1808565d6        19 seconds ago      /bin/sh -c yarn install --production            85.4MB              
     a2c054d14948        36 seconds ago      /bin/sh -c #(nop) COPY dir:5dc710ad87c789593…   198kB               
     9577ae713121        37 seconds ago      /bin/sh -c #(nop) WORKDIR /app                  0B                  
     b95baba1cfdb        13 days ago         /bin/sh -c #(nop)  CMD ["node"]                 0B                  
     <missing>           13 days ago         /bin/sh -c #(nop)  ENTRYPOINT ["docker-entry…   0B                  
     <missing>           13 days ago         /bin/sh -c #(nop) COPY file:238737301d473041…   116B                
     <missing>           13 days ago         /bin/sh -c apk add --no-cache --virtual .bui…   5.35MB              
     <missing>           13 days ago         /bin/sh -c #(nop)  ENV YARN_VERSION=1.21.1      0B                  
     <missing>           13 days ago         /bin/sh -c addgroup -g 1000 node     && addu…   74.3MB              
     <missing>           13 days ago         /bin/sh -c #(nop)  ENV NODE_VERSION=12.14.1     0B                  
     <missing>           13 days ago         /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B                  
     <missing>           13 days ago         /bin/sh -c #(nop) ADD file:e69d441d729412d24…   5.59MB   
    

    每条线代表图像中的一层。此处的显示在底部显示底部,在顶部显示最新层。使用此功能,您还可以快速查看每个图层的大小,从而帮助诊断较大的图像。

  2. 您会注意到其中几行被截断了。如果添加该--no-trunc标志,则将获得完整的输出(是的,有趣的是,如何使用截断的标志来获得未截断的输出,是吗?)

     docker image history --no-trunc getting-started
    

层缓存

既然您已经看到了分层的实际效果,则有一个重要的课程可以学习,以帮助减少容器映像的构建时间。

图层更改后,还必须重新创建所有下游图层

让我们看一下我们又用了一次的Dockerfile ...

# syntax=docker/dockerfile:1
FROM node:12-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]

回到映像历史记录输出,我们看到Dockerfile中的每个命令都成为映像中的新层。您可能还记得,当我们对映像进行更改时,必须重新安装yarn依赖项。有没有办法解决这个问题?每次构建时都将相同的依赖项运送出去没有多大意义,对吗?

为了解决这个问题,我们需要重组Dockerfile来帮助支持依赖项的缓存。对于基于节点的应用程序,那些依赖项在package.json文件中定义。因此,如果我们仅先复制该文件,安装依赖项,然后再复制其他所有内容,该怎么办?然后,仅当更改时,才重新创建yarn依赖项package.json。有道理?

  1. 更新Dockerfile以package.json首先复制,安装依赖项,然后复制其他所有内容。

     # syntax=docker/dockerfile:1
     FROM node:12-alpine
     WORKDIR /app
     COPY package.json yarn.lock ./
     RUN yarn install --production
     COPY . .
     CMD ["node", "src/index.js"]
    
  2. .dockerignore在与Dockerfile相同的文件夹中创建一个文件,其内容如下。

     node_modules
    

    .dockerignore文件是选择性地仅复制与图像相关的文件的简便方法。您可以在此处了解更多信息 。在这种情况下,该node_modules文件夹应在COPY第二步中省略,因为否则,它可能会覆盖该RUN步骤中由该命令创建的文件。有关为何建议对Node.js应用程序和其他最佳做法进行推荐的更多详细信息,请参阅其有关对Node.js Web应用程序进行Docker化的指南 。

  3. 使用建立新图像docker build

     docker build -t getting-started .
    

    您应该看到这样的输出...

     Sending build context to Docker daemon  219.1kB
     Step 1/6 : FROM node:12-alpine
     ---> b0dc3a5e5e9e
     Step 2/6 : WORKDIR /app
     ---> Using cache
     ---> 9577ae713121
     Step 3/6 : COPY package.json yarn.lock ./
     ---> bd5306f49fc8
     Step 4/6 : RUN yarn install --production
     ---> Running in d53a06c9e4c2
     yarn install v1.17.3
     [1/4] Resolving packages...
     [2/4] Fetching packages...
     info fsevents@1.2.9: The platform "linux" is incompatible with this module.
     info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
     [3/4] Linking dependencies...
     [4/4] Building fresh packages...
     Done in 10.89s.
     Removing intermediate container d53a06c9e4c2
     ---> 4e68fbc2d704
     Step 5/6 : COPY . .
     ---> a239a11f68d8
     Step 6/6 : CMD ["node", "src/index.js"]
     ---> Running in 49999f68df8f
     Removing intermediate container 49999f68df8f
     ---> e709c03bc597
     Successfully built e709c03bc597
     Successfully tagged getting-started:latest
    

    您会看到所有层都已重建。很好,因为我们对Dockerfile进行了很多更改。

  4. 现在,对src/static/index.html文件进行更改(例如将更<title>改为“ The Awesome Todo App”)。

  5. 现在docker build -t getting-started .再次使用构建Docker映像。这次,您的输出应该看起来有些不同。

     Sending build context to Docker daemon  219.1kB
     Step 1/6 : FROM node:12-alpine
     ---> b0dc3a5e5e9e
     Step 2/6 : WORKDIR /app
     ---> Using cache
     ---> 9577ae713121
     Step 3/6 : COPY package.json yarn.lock ./
     ---> Using cache
     ---> bd5306f49fc8
     Step 4/6 : RUN yarn install --production
     ---> Using cache
     ---> 4e68fbc2d704
     Step 5/6 : COPY . .
     ---> cccde25a3d9a
     Step 6/6 : CMD ["node", "src/index.js"]
     ---> Running in 2be75662c150
     Removing intermediate container 2be75662c150
     ---> 458e5c6f080c
     Successfully built 458e5c6f080c
     Successfully tagged getting-started:latest
    

    首先,您应该注意到构建速度要快得多!并且,您将看到步骤1-4都具有 Using cache。所以,万岁!我们正在使用构建缓存。推和拉该图像以及对其进行更新也将更快。万岁!

多阶段构建

虽然我们在本教程中不会做太多的介绍,但是多阶段构建是一个非常强大的工具,可以帮助使用多个阶段来创建映像。它们有几个优点:

  • 将构建时依赖项与运行时依赖项分开
  • 通过运送您的应用程序需要运行的内容来减小整体图像尺寸

Maven / Tomcat示例

在构建基于Java的应用程序时,需要JDK将源代码编译为Java字节码。但是,生产中不需要该JDK。另外,您可能正在使用Maven或Gradle之类的工具来帮助构建应用程序。在我们的最终图像中也不需要这些。多阶段构建帮助。

# syntax=docker/dockerfile:1
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应用程序时,我们需要一个Node环境来将JS代码(通常为JSX),SASS样式表等编译为静态HTML,JS和CSS。如果我们不进行服务器端渲染,则甚至不需要在生产环境中使用Node环境。为什么不将静态资源运送到静态nginx容器中?

# syntax=docker/dockerfile:1
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容器中。酷吧?

回顾

通过稍微了解图像的结构,我们可以更快地构建图像,并减少更改。扫描图像使我们确信我们正在运行和分发的容器是安全的。多阶段构建还可以通过将构建时依赖项与运行时依赖项分开来帮助我们减小整体映像大小并提高最终容器的安全性。

入门设置方向快速启动简介概念容器码头