Docker,已经成为了,容器化部署的标准,越来越多的团队,都在用Docker,打包和部署应用,Docker,确实,带来了很多便利,一次构建,到处运行,环境一致,部署方便,等等。

但是,很多人,构建Docker镜像的时候,都没有注意优化,构建出来的镜像,很大,几百MB,甚至几个GB,不仅,占用存储空间,而且,拉取和推送,很慢,部署效率低,还有,安全漏洞,等等问题,影响了,Docker的使用体验。

其实,Docker镜像,有很多优化的方法和工具,能把镜像,优化得很小,很安全,很高效,今天,就来推荐一些,Docker镜像优化的工具,帮助大家,优化Docker镜像,提升部署效率,降低安全风险。

一、为什么要优化Docker镜像

先说说,为什么,要优化Docker镜像,优化了,有什么好处。

1. 减小镜像大小,节省存储空间: 未优化的镜像,通常,很大,几百MB,甚至几个GB,而优化后的镜像,可能,只有几十MB,甚至几MB,能节省,大量的存储空间,不管是,镜像仓库,还是,服务器本地,都能,节省很多空间。

2. 加快拉取和推送速度,提升部署效率: 镜像小了,拉取和推送的速度,就快了,部署的时候,能更快地,拉取镜像,启动容器,提升部署效率,特别是,在服务器很多,或者,网络不好的情况下,差距,很明显。

3. 减少安全漏洞,提高安全性: 未优化的镜像,通常,包含了,很多,不需要的软件包,库,工具,这些,都可能,有安全漏洞,增加了,攻击面,优化后的镜像,只包含,运行应用,必需的内容,攻击面小了,安全漏洞,也少了,更安全。

4. 提升构建速度,提高开发效率: 优化Dockerfile,用好构建缓存,多阶段构建,等等,能提升,镜像构建的速度,开发的时候,能更快地,构建镜像,测试,提高开发效率。

5. 降低成本: 镜像小了,存储空间,网络流量,都省了,成本,也就降低了,特别是,用云服务的团队,镜像仓库存储,网络流量,都是,要钱的,镜像小了,能省不少钱。

所以,优化Docker镜像,还是,很有必要的,不管是,从效率,安全,还是,成本的角度,都值得,去做。

二、镜像瘦身工具

首先,推荐一些,镜像瘦身的工具,能把镜像,变得很小。

1. Alpine Linux: Alpine Linux,是一个,轻量级的Linux发行版,很小,基础镜像,只有,5MB左右,而且,包含了,常用的工具,包管理器,apk,能很方便地,安装需要的软件包。

很多,官方镜像,都有,Alpine版本,比如,node:alpine,python:alpine,openjdk:alpine,nginx:alpine,等等,用Alpine版本的基础镜像,比,用Debian或者Ubuntu版本的,小很多,通常,能小,50%以上。

比如,node:14,Debian版本,大概,900MB,而,node:14-alpine,只有,100MB左右,差距,很大。

使用方法,很简单,Dockerfile里,基础镜像,用alpine版本就行:

FROM node:14-alpine

Alpine,用的是musl libc,而不是,glibc,所以,有些,依赖glibc的软件,可能,在Alpine上,运行不了,或者,有问题,这时候,要注意,测试一下,大部分情况,都是没问题的。

2. distroless: distroless,是Google,推出的,极简的基础镜像,只包含,运行应用,必需的内容,没有,包管理器,没有,shell,没有,常用的工具,甚至,连ls、cat这些命令,都没有,所以,镜像,非常小,而且,攻击面,非常小,很安全。

distroless,有,各种语言的版本,比如,gcr.io/distroless/java,gcr.io/distroless/python,gcr.io/distroless/nodejs,等等,适合,生产环境,对安全要求高的场景。

比如,Java应用,用distroless基础镜像,最终的镜像,可能,只有,100MB左右,而,用openjdk:8,可能,有,500MB以上,差距,很大。

使用方法,通常,配合多阶段构建,第一阶段,用完整的镜像,构建应用,第二阶段,用distroless镜像,只拷贝,构建好的应用:

# 第一阶段,构建
FROM maven:3.6-jdk-8 AS build
COPY . /app
WORKDIR /app
RUN mvn package -DskipTests

# 第二阶段,运行
FROM gcr.io/distroless/java:8
COPY --from=build /app/target/app.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]

distroless镜像,因为,没有shell,所以,进入容器调试,不太方便,但是,生产环境,通常,不需要,进入容器调试,所以,还是,很适合的。

3. docker-slim: docker-slim,是一个,专门用来,瘦身Docker镜像的工具,能自动分析,镜像里的内容,去掉,不需要的文件,库,工具,等等,把镜像,变得很小,而且,不需要,修改Dockerfile,很方便。

docker-slim,支持,很多语言和框架,比如,Node.js、Python、Java、Go、Ruby、PHP,等等,能自动识别,应用需要哪些文件,哪些库,然后,去掉,不需要的,瘦身效果,很明显,通常,能把镜像,减小,50%-90%。

比如,一个,Node.js应用,原来的镜像,500MB,用docker-slim瘦身之后,可能,只有,50MB,效果,很惊人。

使用方法,也很简单:

# 安装docker-slim
# 下载二进制文件,或者,用brew install docker-slim

# 瘦身镜像
docker-slim build --target myimage:latest --tag myimage:slim

docker-slim,会自动分析,镜像,然后,生成,一个瘦身版的镜像,myimage:slim,很小,而且,功能,和原来的镜像,一样。

当然,docker-slim,不是,100%准确的,有时候,可能会,去掉,一些,应用需要的文件,导致,应用运行不了,所以,瘦身之后,一定要,测试一下,确保,应用能正常运行,如果,有问题,可以,用--include-path参数,指定,需要保留的文件。

4. 多阶段构建(Multi-stage builds): 多阶段构建,是Docker 17.05,引入的特性,能在,一个Dockerfile里,定义多个阶段,每个阶段,可以,用不同的基础镜像,然后,把,一个阶段的产物,拷贝到,另一个阶段,这样,最终的镜像,只包含,运行应用,必需的内容,不包含,构建时,需要的工具,源码,依赖,等等,能大大减小,镜像大小。

比如,一个,Java应用,构建的时候,需要,Maven,JDK,源码,等等,但是,运行的时候,只需要,JRE,和,构建好的jar包,用多阶段构建,第一阶段,用Maven镜像,构建应用,第二阶段,用JRE镜像,只拷贝,构建好的jar包,最终的镜像,就小很多。

例子:

# 第一阶段,构建
FROM maven:3.6-jdk-8 AS build
COPY . /app
WORKDIR /app
RUN mvn package -DskipTests

# 第二阶段,运行
FROM openjdk:8-jre-alpine
COPY --from=build /app/target/app.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]

这样,最终的镜像,是基于openjdk:8-jre-alpine的,只有,JRE和jar包,很小,而,构建时的Maven,JDK,源码,依赖,等等,都不会,出现在最终的镜像里。

多阶段构建,是Docker镜像优化,最常用,也最有效的方法之一,建议,大家,都用起来,特别是,编译型语言,比如,Java、Go、C++,等等,用多阶段构建,效果,很明显。

三、镜像分析工具

接下来,推荐一些,镜像分析工具,能帮你,分析镜像里的内容,看看,镜像里,有什么,哪些文件,占空间大,哪些,可以去掉,等等。

1. dive: dive,是一个,非常好用的,Docker镜像分析工具,能让你,探索镜像里的,每一层,每个文件,看看,每个文件的大小,权限,等等,还能,对比,两个镜像的差异,很方便,找到,镜像里,大的文件,不需要的文件,然后,优化。

dive,是命令行工具,界面,很友好,用键盘操作,能,浏览镜像的文件系统,查看,每层的变化,等等。

使用方法:

# 安装
# Mac: brew install dive
# Linux: 下载二进制文件
# Windows: 下载二进制文件

# 分析镜像
dive myimage:latest

运行后,就能看到,镜像的分层信息,文件系统,每个文件的大小,等等,很直观,能帮你,找到,优化的空间。

dive,还能,评估镜像的效率,看看,有没有,浪费的空间,比如,重复的文件,大的文件,在后面的层,被删除了,等等,给一个,分数,让你,知道,镜像的优化空间,有多大。

2. microbadger: microbadger,是一个,在线的,Docker镜像分析工具,能分析,Docker Hub上的,公开镜像,看看,镜像的大小,分层,标签,更新时间,等等,还能,看到,镜像里,安装了哪些包,有哪些,安全漏洞,等等。

使用方法,很简单,打开microbadger的网站,输入,镜像名,就能,看到分析结果,比如,nginx:latest,就能看到,这个镜像,有多大,有多少层,每层,有什么,安装了哪些包,等等,很方便。

microbadger,适合,分析,公开的镜像,看看,这个镜像,质量怎么样,有没有,安全漏洞,要不要,用这个镜像,等等。

3. docker history: docker history,是Docker,自带的命令,能查看,镜像的,构建历史,每层,执行了什么命令,大小,是多少,等等,能帮你,找到,哪一层,占空间大,然后,优化。

使用方法:

docker history myimage:latest

运行后,就能看到,镜像的,每一层,创建时间,创建者,大小,命令,等等,很直观,比如,你看到,某一层,执行了apt-get install,占了,200MB,就可以,想想,能不能,去掉,不需要的包,或者,用,更小的基础镜像。

docker history,虽然,简单,但是,很实用,能快速,找到,镜像里,大的层,优化的方向。

四、安全扫描工具

接下来,推荐一些,镜像安全扫描工具,能扫描,镜像里的,安全漏洞,配置问题,等等,帮你,提高,镜像的安全性。

1. Trivy: Trivy,是一个,非常好用的,容器镜像安全扫描工具,能扫描,镜像里的,操作系统包,应用依赖,等等,的安全漏洞,还能,扫描,配置文件,IaC,等等,功能,很强大,而且,速度快,使用简单,很受欢迎。

Trivy,支持,很多操作系统,比如,Alpine、Debian、Ubuntu、CentOS、RHEL,等等,也支持,很多语言的依赖,比如,Node.js、Python、Java、Go、Ruby、PHP,等等,能扫描,依赖里的,安全漏洞。

使用方法,很简单:

# 安装
# Mac: brew install trivy
# Linux: 下载二进制文件,或者,用apt/yum安装
# Windows: 下载二进制文件

# 扫描镜像
trivy image myimage:latest

运行后,Trivy,会扫描镜像,然后,输出,漏洞列表,包括,漏洞ID,严重程度,包名,版本,修复版本,等等,很详细,能帮你,快速,找到,需要修复的漏洞。

Trivy,还能,生成报告,比如,HTML报告,JSON报告,等等,也能,集成到CI/CD流程里,自动扫描,镜像,如果,有高危漏洞,就,阻止部署,很方便。

Trivy,是现在,最流行的,容器安全扫描工具之一,推荐大家,都用起来。

2. Clair: Clair,是CoreOS,推出的,容器镜像安全扫描工具,也是,很流行的,能扫描,镜像里的,安全漏洞,支持,很多操作系统,API,很完善,能,集成到,镜像仓库,CI/CD,等等。

Clair,的架构,稍微复杂一点,需要,部署,Clair服务,数据库,等等,不像Trivy,一个二进制文件,就能用,但是,Clair,功能,也很强大,适合,企业级,大规模的场景。

很多,镜像仓库,比如,Harbor,Quay,等等,都集成了,Clair,能自动扫描,上传的镜像,很方便。

3. Aqua Security: Aqua Security,是一个,企业级的,容器安全平台,功能,很全面,包括,镜像扫描,运行时安全,网络隔离,合规,等等,能,全方位地,保护,容器环境的安全,适合,对安全要求高的,企业用户。

Aqua,是商业产品,有,免费版,也有,企业版,功能,很强大,但是,价格,也不便宜,适合,企业用户。

4. Docker Bench for Security: Docker Bench for Security,是Docker,官方的,安全检查脚本,能检查,Docker主机,和,容器的,安全配置,看看,有没有,不符合,安全最佳实践的地方,比如,Docker daemon的配置,容器的配置,网络配置,等等,给你,安全建议。

使用方法:

git clone https://github.com/docker/docker-bench-security.git
cd docker-bench-security
./docker-bench-security.sh

运行后,会检查,Docker环境的安全配置,然后,输出,检查结果,哪些,通过了,哪些,有警告,哪些,有问题,以及,修复建议,很详细,能帮你,提高,Docker环境的安全性。

五、构建优化工具

接下来,推荐一些,构建优化工具,能提升,镜像构建的速度,和,效率。

1. BuildKit: BuildKit,是Docker,新一代的,镜像构建引擎,比,传统的,Docker构建,速度更快,功能更强,支持,并行构建,缓存挂载,秘密信息管理,前端扩展,等等,能大大提升,构建效率。

BuildKit,从Docker 18.09开始,集成在Docker里,只需要,设置环境变量,DOCKER_BUILDKIT=1,就能,启用BuildKit:

DOCKER_BUILDKIT=1 docker build -t myimage:latest .

或者,在Docker daemon的配置里,默认启用BuildKit。

BuildKit,的优势:

  • 并行构建: 能并行执行,Dockerfile里,不相关的步骤,提升构建速度。
  • 更好的缓存: 更智能的缓存机制,能更好地,利用缓存,提升构建速度。
  • 缓存挂载: 能挂载,缓存目录,比如,apt缓存,npm缓存,maven缓存,等等,下次构建的时候,能复用,大大提升,依赖下载的速度。
  • 秘密信息管理: 能安全地,传递,秘密信息,比如,token,密码,等等,不会,留在镜像的层里。
  • 前端扩展: 支持,不同的前端,比如,Dockerfile,Buildpacks,等等,更灵活。

用BuildKit,构建镜像,通常,能比,传统的构建,快,30%-50%,特别是,有很多依赖,或者,多阶段构建的场景,差距,更明显,推荐大家,都用起来。

2. buildx: buildx,是Docker的,一个插件,能扩展,Docker build的功能,支持,多架构构建,BuildKit,等等,能,一次构建,生成,多个架构的镜像,比如,amd64,arm64,等等,很方便,现在,越来越多的服务器,是ARM架构的,比如,Apple Silicon,AWS Graviton,等等,多架构镜像,越来越重要。

buildx,使用方法:

# 创建builder
docker buildx create --name mybuilder --use
docker buildx inspect --bootstrap

# 构建多架构镜像,并推送
docker buildx build --platform linux/amd64,linux/arm64 -t myimage:latest --push .

这样,就能,一次构建,生成,amd64和arm64,两个架构的镜像,推送到,镜像仓库,用户,不管是,什么架构的机器,都能,拉取到,对应架构的镜像,很方便。

buildx,底层,也是用BuildKit,所以,也有,BuildKit的,所有优势,构建速度快,功能强。

3. Kaniko: Kaniko,是Google,推出的,在容器里,构建Docker镜像的工具,不需要,Docker daemon,就能,构建镜像,适合,在Kubernetes,或者,其他,没有Docker daemon的环境里,构建镜像,比如,CI/CD流水线,在容器里,运行,不需要,特权模式,更安全。

Kaniko,的使用方法,和Docker build,类似,读取Dockerfile,然后,构建镜像,推送到,镜像仓库:

docker run --rm -v "$HOME"/.config/gcloud:/root/.config/gcloud \
  -v "$PWD":/workspace \
  gcr.io/kaniko-project/executor:latest \
  --dockerfile /workspace/Dockerfile \
  --destination myimage:latest \
  --context dir:///workspace/

Kaniko,适合,在Kubernetes里,做CI/CD,构建镜像,不需要,Docker daemon,更安全,更轻量。

4. Buildpacks: Buildpacks,是一个,自动构建镜像的工具,不需要,写Dockerfile,Buildpacks,能自动检测,应用的类型,比如,Node.js,Python,Java,等等,然后,自动,构建镜像,很方便,适合,不想写Dockerfile,或者,不懂Docker的开发者。

Buildpacks,最初,是Heroku,推出的,后来,Pivotal和Heroku,一起,推出了,Cloud Native Buildpacks,成为了,CNCF的项目,越来越流行。

使用方法:

# 安装pack
# Mac: brew install buildpacks/tap/pack
# Linux: 下载二进制文件

# 构建镜像
pack build myimage:latest --builder paketobuildpacks/builder:base

这样,不需要Dockerfile,pack,就能,自动检测,应用类型,构建镜像,很方便,而且,构建出来的镜像,质量,也很高,小,安全,有,最佳实践。

Buildpacks,适合,微服务,多语言,团队,不想,维护很多Dockerfile的场景,能大大提升,开发效率。

六、镜像清理工具

最后,推荐一些,镜像清理工具,能清理,不需要的镜像,容器,卷,等等,节省存储空间。

1. docker system prune: docker system prune,是Docker,自带的命令,能清理,停止的容器,悬空的镜像,悬空的构建缓存,未使用的网络,等等,能快速,清理,Docker占用的空间。

使用方法:

# 清理停止的容器,悬空镜像,悬空构建缓存,未使用网络
docker system prune

# 加上-a,清理所有未使用的镜像,不只是悬空的
docker system prune -a

# 加上--volumes,清理未使用的卷
docker system prune --volumes

# 加上-f,强制清理,不提示
docker system prune -af

docker system prune,简单实用,能快速,清理,Docker占用的空间,推荐,定期执行,或者,加到,定时任务里,定期清理。

2. docker-gc: docker-gc,是Spotify,推出的,Docker垃圾回收工具,能自动清理,过期的容器和镜像,比,docker system prune,更灵活,能配置,保留时间,保留数量,等等,适合,服务器上,自动清理,Docker占用的空间。

docker-gc,能配置:

  • 容器,超过多长时间,就清理。
  • 镜像,超过多长时间,没有被使用,就清理。
  • 保留,最近的,多少个镜像。
  • 排除,某些镜像,不清理。
  • 等等。

使用方法,可以,用Docker运行,或者,安装成,定时任务,定期执行:

docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -e DRY_RUN=1 spotify/docker-gc

先,用DRY_RUN=1,测试一下,看看,会清理哪些,确认没问题,再,正式运行。

docker-gc,适合,服务器上,长期运行,自动清理,Docker占用的空间,很方便。

七、镜像优化最佳实践

最后,总结一下,Docker镜像优化的,最佳实践:

1. 用小的基础镜像: 尽量用,小的基础镜像,比如,Alpine,distroless,等等,不要用,Debian,Ubuntu,这种,大的基础镜像,除非,有特殊需求。

2. 用多阶段构建: 编译型语言,或者,构建时,需要很多工具的,都要用,多阶段构建,第一阶段,构建,第二阶段,只拷贝,运行时,需要的内容,大大减小,镜像大小。

3. 合并RUN指令: Dockerfile里,每个RUN指令,都会,创建一层,所以,尽量,把多个命令,合并到,一个RUN指令里,减少层数,而且,能在,同一个RUN里,安装包,然后,清理缓存,减小,层的大小。

比如:

# 不好的写法,多个RUN,每层都有apt缓存
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git

# 好的写法,合并成一个RUN,最后清理缓存
RUN apt-get update && apt-get install -y \
    curl \
    git \
    && rm -rf /var/lib/apt/lists/*

4. 利用构建缓存: Docker构建,会缓存,每一层,如果,这一层的内容,没变,就会,用缓存,所以,要把,不经常变的内容,放在前面,经常变的内容,放在后面,比如,先拷贝,依赖文件,安装依赖,再拷贝,源码,这样,依赖没变的话,就能,用缓存,不用,重新安装依赖。

比如,Node.js应用:

# 不好的写法,每次源码变了,都要重新安装依赖
COPY . .
RUN npm install

# 好的写法,先拷贝package.json,安装依赖,再拷贝源码
COPY package*.json ./
RUN npm install
COPY . .

5. 清理不需要的文件: 构建的时候,会产生,很多,不需要的文件,比如,缓存,临时文件,文档,示例,测试代码,等等,要,及时清理,不要,留在镜像里。

比如,apt安装后,清理/var/lib/apt/lists/*,npm安装后,清理~/.npm,maven安装后,清理~/.m2,等等。

6. 用.dockerignore: 用.dockerignore文件,排除,不需要,拷贝到镜像里的文件,比如,.git,node_modules,dist,build,测试文件,文档,等等,减少,构建上下文的大小,也避免,把不需要的文件,拷贝到镜像里。

比如,.dockerignore:

.git
node_modules
dist
build
*.log
*.md
test

7. 不要用latest标签: 构建镜像的时候,不要用latest标签,要用,具体的版本号,或者,git commit hash,这样,能追溯,镜像是,哪个版本的代码构建的,出了问题,也能,快速定位,而且,拉取镜像的时候,也不会,因为latest变了,拉取到,不一样的镜像。

8. 扫描安全漏洞: 构建镜像后,要用,安全扫描工具,比如,Trivy,扫描一下,看看,有没有,安全漏洞,及时修复,特别是,生产环境的镜像,一定要,扫描,确保,没有高危漏洞。

9. 最小权限原则: 容器里,不要用root用户运行应用,要创建,普通用户,用普通用户运行,这样,即使,应用被攻破了,攻击者,也只有,普通用户的权限,危害,小很多。

比如:

RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser

10. 定期更新基础镜像: 基础镜像,也会,有安全漏洞,要,定期更新基础镜像,重新构建应用镜像,确保,基础镜像,是最新的,没有,已知的安全漏洞。

写在最后

Docker镜像优化工具推荐:提升效率的利器。

Docker镜像优化,是DevOps的重要环节,做好了,能大大提升,部署效率,降低成本,提高安全性,值得,每个用Docker的团队,重视。

本文,推荐了,很多,Docker镜像优化的工具,包括,镜像瘦身工具(Alpine、distroless、docker-slim、多阶段构建)、镜像分析工具(dive、microbadger、docker history)、安全扫描工具(Trivy、Clair、Aqua、Docker Bench for Security)、构建优化工具(BuildKit、buildx、Kaniko、Buildpacks)、镜像清理工具(docker system prune、docker-gc),等等,每个工具,都有,它的特点和适用场景,大家可以,根据自己的需求,选择合适的工具。

当然,工具,只是,辅助,更重要的是,要有,优化的意识,和,最佳实践,写Dockerfile的时候,就要,注意优化,用小的基础镜像,多阶段构建,合并RUN,利用缓存,清理不需要的文件,等等,这样,构建出来的镜像,自然,就小,就安全,就高效。

希望,这篇文章,能帮助大家,更好地,优化Docker镜像,提升部署效率,降低安全风险,让Docker,用得,更顺手,更高效。

最后,用一句话结尾:

"Docker镜像优化,不是,一次性的工作,而是,一个持续的过程,不断地,优化,迭代,才能,让镜像,保持,小,安全,高效,为业务,提供,更好的支撑。"

祝大家,都能,构建出,小而美,安全,高效的Docker镜像,工作顺利,部署无忧!