- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章如何正确且快速构建Docker优质的安全镜像由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
可能每个人都听说过Docker,并且大多数开发人员都熟悉并使用过Docker,诸如构建Docker镜像之类的基本操作。一般而言,构建镜像非常简单,只需运行docker built -t name:tag .,但其实还有很多其他可优化的东西,尤其是在优化构建过程和所创建的最终镜像方面.
因此,在本文中,我们将研究如何优化Docker镜像的构建过程,以使我们能够在最短构建时间内构建最小、最安全的满足生产需求的Docker镜像.
缓存以加快构建速度 。
镜像的构建时间大都花在系统软件包和应用程序依赖包的下载和安装。但是,这些通常不会经常变更,因此推荐进行缓存.
从系统包和工具开始——通常在FROM后运行,以确保已将其缓存。无论您使用哪个Linux发行版作为基本镜像,都应该得到如下所示的结果:
FROM ... # any viable base image like centos:8, ubuntu:21.04 or alpine:3.12.3 。
。
# RHEL/CentOS 。
RUN yum install ... 。
# Debian 。
RUN apt-get install ... 。
# Alpine 。
RUN apk add ... 。
。
# Rest of the Dockerfile (COPY, RUN, CMD...) 。
另外,您甚至可以将这些相关命令提取到独立的Dockerfile以构建自己的基础镜像。然后可以将该镜像推送到镜像仓库,以便您和其他人可以在其他的Dockerfile中引用.
这样,您无需再去担心系统包以及相关的依赖项,除非您需要升级它们或添加与删除某些内容.
在系统包之后,我们通常要安装应用程序依赖项。这些可能是来自Maven存储库中的Java库(默认存储在.m2目录中),JavaScript模块node_modules或Python库venv.
与系统依赖项相比,这些更改的频率更高,但不足以保证每次构建都能进行完整的重新下载和重新安装。但是如果对应Dockerfile写得不好,您会注意到,即使未修改依赖项,也不会使用缓存:
FROM ... # any viable base image like python:3.8, node:15 or openjdk:15.0.1 。
。
# Copy everything at once 。
COPY . . 。
。
# Java 。
RUN mvn clean package 。
# Or Python 。
RUN pip install -r requirements.txt 。
# Or JavaScript 。
RUN npm install 。
# ... 。
CMD [ "..." ] 。
这是为什么?问题出在COPY . .,Docker在构建的每个步骤中都使用缓存,直到它遇到新的或已修改的命令/层.
在这种情况下,当我们将所有内容复制到镜像中时—包括未更改的依赖关系列表以及已修改的源代码.
Docker会继续进行并重新下载且重新安装所有依赖关系。因为修改过源码文件,它不再能够在该层使用缓存。为避免这种情况,我们必须分两个步骤复制文件:
FROM ... # any viable base image like python:3.8, node:15 or openjdk:15.0.1 。
。
COPY pom.xml ./pom.xml # Java 。
COPY requirements.txt ./requirements.txt # Python 。
COPY package.json ./package.json # JavaScript 。
。
RUN mvn dependency:go-offline -B # Java 。
RUN pip install -r requirements.txt # Python 。
RUN npm install # JavaScript 。
。
COPY ./src ./src/ 。
# Rest of Dockerfile (build application; set CMD...) 。
首先,我们添加列出所有应用程序依赖项的文件并安装它们。如果此文件没有更改,则将缓存所有更改。只有这样,我们才能将其余(修改过的)源码复制到镜像中,并运行应用程序代码的测试和构建。对于更多的“高级”方法,我们使用Docker的BuildKit及其实验功能进行相同的操作:
# syntax=docker/dockerfile:experimental 。
。
FROM ... # any viable base image like python:3.8, openjdk:15.0.1 。
COPY pom.xml ./pom.xml # Java 。
COPY requirements.txt ./requirements.txt # Python 。
。
RUN --mount=type=cache,target=/root/.m2 mvn dependency:go-offline -B # Java 。
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt # Python 。
上面的代码显示了如何使用命令--mount选项RUN来选择缓存目录。如果您要显式使用非默认缓存位置,这将很有帮助.
但是,如果要使用此功能,则必须包括指定语法版本的标题行(如上所述),并使用来运行构建,比如:DOCKER_BUILDKIT=1 docker build name:tag ..
在这些文档(https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/syntax.md#run---mounttypecache)中可以找到有关实验功能的更多信息.
到目前为止,所有内容仅适用于本地构建—对于CI,情况则不同,并且通常每个工具/提供程序都会有所不同,但对于其中的任何一个,您将需要一些持久性卷来存储缓存/依赖项 。例如,对于Jenkins,您可以在代理中使用存储.
对于在Kubernetes上运行的Docker构建(无论是使用JenkinsX,Tekton还是其他),您将需要Docker守护进程,该守护进程可以在Docker(DinD)中使用Docker进行部署,DinD是在Docker容器中运行的Docker守护进程.
至于构建本身,您将需要一个连接到DinD socket的pod(容器)来运行docker build命令.
为了演示和简化操作,我们可以使用以下pod进行操作:
apiVersion: v1 。
kind: Pod 。
metadata: 。
name: docker-build 。
spec: 。
containers: 。
- name: dind # Docker in Docker container 。
image: docker:19.03.3-dind 。
securityContext: 。
privileged: true 。
env: 。
- name: DOCKER_TLS_CERTDIR 。
value: '' 。
volumeMounts: 。
- name: dind-storage 。
mountPath: /var/lib/docker 。
- name: docker # Builder container 。
image: docker:19.03.3-git 。
securityContext: 。
privileged: true 。
command: ['cat'] 。
tty: true 。
env: 。
- name: DOCKER_BUILDKIT 。
value: '1' 。
- name: DOCKER_HOST 。
value: tcp://localhost:2375 。
volumes: 。
- name: dind-storage 。
emptyDir: {} 。
- name: docker-socket-volume 。
hostPath: 。
path: /var/run/docker.sock 。
type: File 。
上面的容器由2个容器组成—一个用于DinD,一个用于镜像构建。要使用构建容器运行构建,可以访问其shell,克隆一些存储库并运行构建流程:
~ $ kubectl exec --stdin --tty docker-build -- /bin/sh # Open shell session 。
~ # git clone https://github.com/username/reponame.git # Clone some repository 。
~ # cd reponame 。
~ # docker build --build-arg BUILDKIT_INLINE_CACHE=1 -t name:tag --cache-from username/reponame:latest . 。
... 。
=> importing cache manifest from martinheinz/python-project-blueprint:flask 。
... 。
=> => writing image sha256:... 。
=> => naming to docker.io/library/name:tag 。
=> exporting cache 。
=> => preparing build cache for export 。
最终docker build使用了一些新选项—--cache-from image:tag,来告诉Docker它应该使用(远程)仓库中的指定镜像作为缓存源。这样,即使缓存的层未存储在本地文件系统中,我们也可以利用缓存的优点.
另一个选项----build-arg BUILDKIT_INLINE_CACHE=1用于在创建缓存元数据时将其写入镜像。这必须用于--cache-from工作,有关更多信息,请参阅文档(https://docs.docker.com/engine/reference/commandline/build/#specifying-external-cache-sources).
最小镜像 。
快速构建确实很让人高兴,但是如果您拥有真正的“thick”图像,则仍然需要花费很长的时间才能push/pull它们,而且胖镜像很可能还包含许多无用的库,工具以及诸如此类的东西,这些都使镜像变得更加臃肿.
易受攻击,因为它会造成更大的攻击面.
制作更小的镜像的最简单方法是使用Alpine Linux之类的基础镜像,而不是基于Ubuntu或RHEL的镜像。另一个好的方法是使用多步骤Docker构建,其中您使用一个镜像进行构建(第一个FROM命令),而使用另一个更小的镜像来运行应用程序(第二个/最后一个FROM),例如:
# 332.88 MB 。
FROM python:3.8.7 AS builder 。
。
COPY requirements.txt /requirements.txt 。
RUN /venv/bin/pip install --disable-pip-version-check -r /requirements.txt 。
。
# only 16.98 MB 。
FROM python:3.8.7-alpine3.12 as runner 。
。
# copy only the dependencies installation from the 1st stage image 。
COPY --from=builder /venv /venv 。
COPY --from=builder ./src /app 。
。
CMD ["..."] 。
上面显示了我们首先在基本的Python 3.8.7镜像中准备了应用程序及其依赖项,该镜像很大,为332.88 MB。在此处,我们安装了应用程序所需的虚拟环境和库.
然后,我们切换到更小的基于Alpine的镜像,该镜像仅为16.98 MB。我们将先前创建的整个虚拟环境以及源代码复制到该镜像。这样,我们最终得到的图像要小得多,镜像层更少,同时也有更少的不必要的工具和二进制文件.
要记住的另一件事是我们在每次构建过程中产生的层数。FROM,COPY,RUN以及CMD是都会生成新的层。至少在RUN的情况下,我们可以通过将所有RUN命令合并成这样的一个命令来轻松地减少它创建的层的数量:
# Bad, Creates 4 layers 。
RUN yum --disablerepo=* --enablerepo="epel" 。
RUN yum update 。
RUN yum install -y httpd 。
RUN yum clean all -y 。
。
# Good, creates only 1 layer 。
RUN yum --disablerepo=* --enablerepo="epel" && \ 。
yum update && \ 。
yum install -y httpd && \ 。
yum clean all -y 。
我们可以更进一步,完全摆脱可能很重的基础镜像。为此,我们将使用特殊的FROM scratch信号通知Docker应使用最小的基本镜像,而下一个命令将是最终镜像的第一层.
这对于以二进制文件运行且不需要大量工具的应用程序特别有用,例如Go,C ++或Rust应用程序。但是,这种方法要求二进制文件是静态编译的,因此它不适用于Java或Python之类的语言。FROM scratchDockerfiles的示例可能像这样:
FROM golang as builder 。
。
WORKDIR /go/src/app 。
COPY . . 。
# Static build is required so that we can safely use 'scratch' base image 。
RUN CGO_ENABLED=0 go install -ldflags '-extldflags "-static"' 。
。
FROM scratch 。
COPY --from=builder /go/bin/app /app 。
ENTRYPOINT ["/app"] 。
很简单,对吧?借助这种Dockerfile,我们可以生成仅约3MB的镜像.
锁定版本 。
速度和大小是大多数人关注的两件事,而镜像的安全性成为人们的事后考虑。有几种简单的方法可以将镜像锁定下来,并限制攻击者可以利用的攻击面.
最基本的建议是锁定所有库、包、工具和基本镜像的版本,这不仅对安全性很重要,而且对镜像的稳定性也很重要。如果您对镜像使用最新标记,或者您没有在Python的requirements.txt或JavaScript的package.json中指定版本,您在构建期间下载的镜像/库可能与应用程序代码不兼容,或者使容器暴露于漏洞中.
当您想将所有内容锁定到特定版本时,还应该定期更新所有这些依赖项,以确保您拥有所有可用的最新安全补丁程序和修补程序.
即使您真的很努力地避免所有依赖中的任何漏洞,仍然会有一些您错过或尚未修复/发现的漏洞。所以,为了减轻任何可能的攻击的影响,最好避免以根用户身份运行容器.
因此,应该在Dockerfiles中包含用户1001,以表示从Dockerfiles创建的容器应该并且可以作为非根用户(理想情况下是任意用户)运行。当然,这可能需要您修改应用程序并选择正确的基本镜像,因为一些常见的基本映像(如nginx)需要根权限(例如,由于特权端口).
通常很难在Docker镜像中找到与避免漏洞,但是如果镜像仅包含运行应用程序所需的最低限度,则可能会更容易一些。Google发行的Distroless(https://github.com/GoogleContainerTools/distroless)是一个这样的镜像.
将Distroless镜像修剪到甚至没有shell或软件包管理器的程度,这使得它们比Debian或基于Alpine的镜像在安全性方面要好得多。如果您使用的是多步骤Docker构建,那么大多数情况下,切换到Distroless runner映像非常简单:
FROM ... AS builder 。
。
# Build the application ... 。
。
# Python 。
FROM gcr.io/distroless/python3 AS runner 。
# Golang 。
FROM gcr.io/distroless/base AS runner 。
# NodeJS 。
FROM gcr.io/distroless/nodejs:10 AS runner 。
# Rust 。
FROM gcr.io/distroless/cc AS runner 。
# Java 。
FROM gcr.io/distroless/java:11 AS runner 。
。
# Copy application into runner and set CMD... 。
。
# More examples at https://github.com/GoogleContainerTools/distroless/tree/master/examples 。
除了最终镜像及其容器中可能存在的漏洞外,我们还必须考虑用于构建镜像的Docker守护程序和容器运行时。因此,与我们的所有镜像一样,我们不应允许Docker与root用户一起运行,而应使用所谓的rootless模式.
这个文档(https://docs.docker.com/engine/security/rootless/)是关于如何在Docker中进行设置的完整指南,如果您不想调整配置,那么您可能要考虑切换到podman,podman默认情况下在rootless 和 daemonless 下运行的.
结论 。
容器和Docker已有很长的历史了,每个人都可以学习和使它用,而不仅仅是简简单单的使用。本文中的技巧和示例应该可以提高您的Docker知识并改善所使用的Docker镜像质量.
但是,在构建Docker镜像之外,还有许多其他事情可以改善我们处理镜像和容器的方式。例如,应用seccomp策略,使用cgroups或可能使用完全不同的容器运行时与引擎来限制资源消耗.
原文地址:https://mp.weixin.qq.com/s?__biz=MzU0OTg5MzMzOQ==&mid=2247489920&idx=1&sn=7a1b0ef4b9494af6f94647f1d35d1ccc&chksm=fba9bc34ccde3522159cd5e913916e514c7b649e09c92b0c8ae89cb8c3be9a2069be34ec98f0&mpshare=1& 。
最后此篇关于如何正确且快速构建Docker优质的安全镜像的文章就讲到这里了,如果你想了解更多关于如何正确且快速构建Docker优质的安全镜像的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我需要将 OCI 镜像 list 转换为 Docker v2.2 镜像格式,反之亦然。但我找不到两者之间的任何区别,是否有任何实际区别或它们相同? 最佳答案 Docker 镜像 list V 2,模式
LNMP 是代表 Linux 系统下的 Nginx、Mariadb、PHP 相结合而构建成的动态网站服务器架构。下面使用docker制作分布式lnmp 镜像。 1、docker 分布式 lnmp
你好,我创建了一个基础镜像;但是,每当我运行 docker build . 时,我都看不到成功构建 我的 docker 文件 FROM centos:7 ARG user=john ARG home=
我想要我的 iPhone 应用程序中有一个功能,可以将图像转换为类似镜像的方式。 就像如果有一个左手举起的人的图像,那么转换后的图像必须有右手举起的同一个人。 任何代码或链接将不胜感激 预先感谢您的帮
我们希望将一个Elasticsearch集群放置在kubernetes集群的顶部(当前有2个节点,但是我们计划增加它)。 是否可以通过使集群中的每个节点包含相同数据的方式配置elasticsearch
我试图了解 docker 如何在文件系统上存储图像和图层。构建图像时,图层出现在 /var/lib/docker/image/overlay2/layerdb 中,图像出现在 /var/lib/doc
所以我最近开始使用 docker,因为我认为让我的网站 dockerised 会很好。我有一个 super 简单的 docker-compose.yml 文件,其中仅包含 wordpress:late
我有一个 docker 镜像,叫它 dockerimage/test。每次我更新它时,我都会增加一个标签,所以 dockerimage/test:1、dockerimage/test:2 等等。 当我
我开始使用 Docker,我发现我可以将主图像存储库放在不同的磁盘上(符号链接(symbolic link)/var/lib/docker 到其他位置)。 但是,现在我想看看是否有办法将它拆分到多个磁
显然应用程序打包和部署似乎有两种做法 创建 Docker 镜像并部署它 从头开始构建和部署应用程序。 我对如何使用选项 1) 感到困惑。前提是你获取一个 docker 镜像并在任何平台上重复使用它。但
我有一个 UIView具有透明背景和一些按钮。我想捕获 View 的绘图,将其缩小,然后在屏幕上的其他位置重新绘制(镜像)它。 (在另一个 View 之上。)按钮可以更改,因此它不是静态的。 最好的方
我正在为一个项目编写测试,我想测试和验证一个 docker 镜像构建。但我不想推送图像。 我希望图像构建在 CI(如 taskcluster)上并运行测试。 最佳答案 您需要使用 taskcluste
我想复制每个 html 页面中的代码,同时添加一些更改: 例子: Any text (even if includes :., 输出: Any text (even if includes :.,
我使用三星 ARM Cortex A9 Exynos4412 板。我在板上启动“linux + Qt”img。但是板上没有包管理器,也没有 make 、 gcc 命令。在/bin 文件中有文件 Bus
是否有可能以某种方式设置一个 git 存储库,该存储库像通常的 --mirror 一样用于 pull 入它,但在将从推送到另一个存储库时没有强制? 最佳答案 您可以像这样添加 --no-force 来
背景 最近在巡检过程中,发现harbor存储空间使用率已经达到了80%。于是,去看了一下各项目下的镜像标签数。发现有个别项目下的镜像标签数竟然有好几百个。细问之下得知,该项目目前处于调试阶段
以下均在centos 7进行的操作 docker安装 ? 1
我知道如何删除 N 天前创建的旧 Docker 镜像。 See here 但我真正想做的是删除过去 N 天未使用的旧 Docker 镜像。 目标是保留经常使用的图像,即使在我进行清理时没有容器实际使用
我有一个自定义的 docker 镜像,已经构建好了。没有可用的 Dockerfile。在容器内部,可以使用自定义用户,而不是 root,比如 test。此用户已附加到组 test。这是容器的默认用户。
我有一个开发数据库,我想将其提交到 docker 镜像中,然后推送到私有(private)存储库并用于本地开发和 CI 构建。 数据库保存为SQL备份,我可以通过将备份文件映射到官方镜像的/doc
我是一名优秀的程序员,十分优秀!