安全扫描
镜像构建完成之后,最好使用 docker scan 命令对其进行扫描以查找安全漏洞。Docker 与 Snyk 合作提供漏洞扫描服务。
例如,要扫描我们自己创建的 todo-app 镜像,只需执行以下命令:
docker scan todo-app
扫描命令使用了一个不断更新的漏洞数据库,如果没有发现漏洞,类似如下输出:
➜ ~ docker scan todo-appDocker Scan relies upon access to Snyk, a third party provider, do you consent to proceed using Snyk? (y/N)yTesting todo-app......✓ Tested 16 dependencies for known vulnerabilities, no vulnerable paths found.For more free scans that keep your images secure, sign up to Snyk at https://dockr.ly/3ePqVcp➜ ~
如果发现了漏洞,类似如下输出:
✗ Low severity vulnerability found in freetype/freetypeDescription: CVE-2020-15999Info: https://snyk.io/vuln/SNYK-ALPINE310-FREETYPE-1019641Introduced through: freetype/freetype@2.10.0-r0, gd/libgd@2.2.5-r2From: freetype/freetype@2.10.0-r0From: gd/libgd@2.2.5-r2 > freetype/freetype@2.10.0-r0Fixed in: 2.10.0-r1✗ Medium severity vulnerability found in libxml2/libxml2Description: Out-of-bounds ReadInfo: https://snyk.io/vuln/SNYK-ALPINE310-LIBXML2-674791Introduced through: libxml2/libxml2@2.9.9-r3, libxslt/libxslt@1.1.33-r3, nginx-module-xslt/nginx-module-xslt@1.17.9-r1From: libxml2/libxml2@2.9.9-r3From: libxslt/libxslt@1.1.33-r3 > libxml2/libxml2@2.9.9-r3From: nginx-module-xslt/nginx-module-xslt@1.17.9-r1 > libxml2/libxml2@2.9.9-r3Fixed in: 2.9.9-r4
列出了漏洞类型、了解更多信息的 URL 等等,最重要的是哪个版本修复了该漏洞,以便我们及时更新到安全的版本。
更多关于漏洞扫描的信息可以查看 docker scan documentation
除了在命令行上扫描新生成的镜像外,还可以 配置 Docker Hub 来自动扫描所有新推送的镜像,然后在 Docker Hub 和 Docker Desktop 中查看安全扫描结果。类似下图的显示:
镜像分层
可以通过
docker image history命令查看镜像的创建历史,镜像中每个图层的都执行了哪些命令。docker image history todo-app
你应该能看到类似如下的输出:
IMAGE CREATED CREATED BY SIZE COMMENTa78a40cbf866 18 seconds ago /bin/sh -c #(nop) CMD ["node" "src/index.j… 0Bf1d1808565d6 19 seconds ago /bin/sh -c yarn install --production 85.4MBa2c054d14948 36 seconds ago /bin/sh -c #(nop) COPY dir:5dc710ad87c789593… 198kB9577ae713121 37 seconds ago /bin/sh -c #(nop) WORKDIR /app 0Bb95baba1cfdb 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 todo-app
层缓存
现在我们已经知道了镜像是分层的,再记住一点:
一旦某一层有更新,该层以及它的所有下层也必须重新构建。利用好这一特性将有助于减少构建镜像所需花费的时间。
回顾之前创建的 Dockerfile 文件内容FROM node:12-alpineWORKDIR /appCOPY . .RUN yarn install --productionCMD ["node", "src/index.js"]
对照上一步执行
docker image history的输出结果,会发现 Dockerfile 文中的每一行命令都对应到镜像中的一个层。你应该还记得,在之前的章节当我们只对源码做一小点文本的修改,但是在更新镜像时,同样必须重新执行每个层的命令,比如重新之下 yarn install 安装依赖等等。显然每次都执行 yarn 命令安装依赖是多余的,有没有解决方法?
要解决这个问题,需要对 Dockerfile 文件进行修改,调整几条命令的执行顺序就可以利用镜像的缓存功能避免执行多余的 yarn install 操作了。对于基于 Node 的应用程序,这些依赖项是在 package.json 文件中定义的。因此,我们只需先把该文件复杂到容器,再安装依赖,然后复制剩下的内容。这样调整之后,只有当package.json文件有变动时才会执行 yarn install 操作。如上所述调整我们的 Dockerfile 文件:增加一行
COPY package.json yarn.lock ./;同时将COPY . .移到RUN yarn install --production之后FROM node:12-alpineWORKDIR /appCOPY package.json yarn.lock ./RUN yarn install --productionCOPY . .CMD ["node", "src/index.js"]
在与 Dockerfile 相同的目录下新创建一个名为
.dockerignore的文件,内容如下:node_modules
.dockerignore文件可以告诉 Docker 在构建镜像时忽略某些文件或文件夹,由于node_modules文件夹是在执行 yarn install 命令时自动生成的,因此它不需要作为镜像的固有内容,忽略它才是对的,而且还可以减少镜像的大小。点击 这里 查看更多关于.dockerignore的信息。再次使用
docker build -t todo-app .命令构建新镜像
会看到类似如下输出:Sending build context to Docker daemon 219.1kBStep 1/6 : FROM node:12-alpine---> b0dc3a5e5e9eStep 2/6 : WORKDIR /app---> Using cache---> 9577ae713121Step 3/6 : COPY package.json yarn.lock ./---> bd5306f49fc8Step 4/6 : RUN yarn install --production---> Running in d53a06c9e4c2yarn 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---> 4e68fbc2d704Step 5/6 : COPY . .---> a239a11f68d8Step 6/6 : CMD ["node", "src/index.js"]---> Running in 49999f68df8fRemoving intermediate container 49999f68df8f---> e709c03bc597Successfully built e709c03bc597Successfully tagged todo-app:latest
这一次你会看到所有层都已重建,因为我们更改了 Dockerfile 文件。
- 现在我们要再一次修改源码,将
src/static/index.html文件第 11 行<title>标签的内容改为 “The Awesome Todo App” 再次使用
docker build -t todo-app .命令构建镜像。这次的输出应该看起来有些不同了:Sending build context to Docker daemon 219.1kBStep 1/6 : FROM node:12-alpine---> b0dc3a5e5e9eStep 2/6 : WORKDIR /app---> Using cache---> 9577ae713121Step 3/6 : COPY package.json yarn.lock ./---> Using cache---> bd5306f49fc8Step 4/6 : RUN yarn install --production---> Using cache---> 4e68fbc2d704Step 5/6 : COPY . .---> cccde25a3d9aStep 6/6 : CMD ["node", "src/index.js"]---> Running in 2be75662c150Removing intermediate container 2be75662c150---> 458e5c6f080cSuccessfully built 458e5c6f080cSuccessfully tagged todo-app:latest
这次应该能明显的感觉到构建速度快了很多!因为有好几个层使用了缓存。
多阶段构建
Maven/Tomcat 示例
在构建基于 Java 的应用程序时,需要使用 JDK 将 Java 源代码编译为 Java 字节码。然而, 在生产环境中不需要完整的 JDK,只需 JRE 即可。另外,你可能正在使用 Maven 或 Gradle 之类的工具来帮助构建应用程序,在最终的镜像中也不需要这类开发阶段使用到的工具。使用多阶段构建可以解决这些问题。
FROM maven AS buildWORKDIR /appCOPY . .RUN mvn packageFROM TomcatCOPY --from=build /app/target/file.war /usr/local/tomcat/webapps
在此示例中,我们使用一个称为
build的阶段,通过 Maven 构建 Java 应用程序。第二个阶段从FROM tomcat开始,我们从build这个第一阶段的结果中复制文件。默认情况下最终的镜像内容由最后一个阶段创建(可以使用--target标志自己指定)。React 示例
在构建 React 应用程序时,我们需要一个 Node 环境来编译 JS 代码、SASS 样式、以及更多的静态 HTML、JS 和 CSS。如果我们不需要服务器端渲染,则在生产环境中我们甚至都不需要 Node 环境,只需将编译好之后的静态资源放到镜像中即可,如下所示:
FROM node:12 AS buildWORKDIR /appCOPY package* yarn.lock ./RUN yarn installCOPY public ./publicCOPY src ./srcRUN yarn run buildFROM nginx:alpineCOPY --from=build /app/build /usr/share/nginx/html
回顾
在本节中,先通过对我们的镜像进行安全扫描,以确保我们正在运行和分享的镜像是安全的。然后了解了一些镜像的结构及特点,对我们应用程序的 Dockerfile 文件稍作修改,以便更快地构建镜像。最后通过多阶段构建,将开发环境和生产环境需要的依赖区分开,不止可以减少镜像的大小,还能提高容器的安全性。
原始资料:Image building tips
