Docker镜像优化
前言
上篇博文说到使用Visual Studio Tools for Docker帮助我们生成Dockerfile,现在我们讨论下生成的Dockerfile的优劣。
一、以往Dockerfile构建模式
(1)发布API项目
新建Web API项目,项目名称为API
在项目所在目录输入指令:dotnet publish --runtime ubuntu.16.04-x64
(2)创建镜像
在发布目录publish文件下新建Dockerfile文件,黏贴以下代码
# 声明使用的基础镜像 FROM microsoft/dotnet:2.1-sdk # 设置工作目录 WORKDIR /app # 将本地应用拷贝到 容器/app 目录下 COPY ./ ./ # 设置导出端口 EXPOSE 80 # 指定应用入口点 API.dll代表的是主程序文件 ENTRYPOINT ["dotnet", "API.dll"]
在Dockerfile所在的目录下输入指令生成镜像:docker build -t api .
不要忘记后面有一个点 .

生成api:latest镜像成功
参考博客操作步骤:https://www.cnblogs.com/bluesummer/p/8087326.html
(3)分析镜像
我们来查看镜像信息,输入:docker images

发现api:latest镜像的大小为1.83GB,大得有点夸张。
现在我们来分析这个Dockerfile:
FROM microsoft/dotnet:2.1-sdk
FROM:指定所创建的基础镜像,如果本地不存在,则默认去Docker Hub下载指定镜像。任何Dockerfile中的第一条指令必须为FROM指令。并且,如果在同一个Dockerfile中创建多个镜像,可以使用多个FROM指令。
文中Dockerfile基于microsoft/dotnet:2.1-sdk镜像,而图中可看到,microsoft/dotnet:2.1-sdk镜像大小已经达到1.73GB了,所以最后生成的api:latest镜像大小为1.83GB也不足为怪。
那问题来了,我们应该采用哪个镜像作为基础镜像,来创建我们自己的镜像呢?
查阅了微软官网文档说明:
microsoft/dotnet:<version>-sdk包含带有.NET Core 和命令行工具 (CLI) 的.NET Core SDK。此镜像将映射到开发方案,可使用此镜像进行本地开发、调试和单元测试。
microsoft/dotnet:<version>-runtime包含.NET Core(runtime和库),并且针对在生产环境中运行.NET Core 应用进行了优化。
我们修改Dockerfile的FROM指令为
FROM microsoft/dotnet:2.1-aspnetcore-runtime
其他指令保持不变,新建一个api:1.0.0的镜像。

可以看到,镜像大小瞬间小了很多。但是我们还不满足,因为原本microsoft/dotnet:2.1-aspnetcore-runtime镜像才253MB,而我们的镜像是353MB。
WORKDIR /app
WORKDIR:为后续RUN、CMD和ENTRYPOINT指令设置工作目录。可以使用多个WORKDIR,后续命令如果参数是相对路径,则会基于之前命令指定路径。例如:
WORKDIR /app
WORKDIR publish
WORKDIR api
最终路径为:/app/publish/api
这个指令在这里看上去应该优化不了。
COPY ./ ./
COPY:格式为COPY <src> <dest>。复制本地主机的<src>(为Dockerfile所在的目录的相对路径)下的内容到容器中的<dest>下。目标路径不存在,则自动创建。
./ ./ 就是将本地Dockerfile所在的目录的文件和文件夹都复制到镜像中的/app目录下。
注意区分以下两条指令:
COPY test relativeDir/ # adds "test" to `WORKDIR`/relativeDir/
COPY test /absoluteDir/ # adds "test" to /absoluteDir/
想了下,这里应该是不合理的,把全部文件都复制过去,这肯定会造成镜像变大。
又想了下,这个已经是我们发布过的文件,那还能怎么办。问题先放这里,等会再解决。
EXPOSE 80
EXPOSE:声明镜像内服务所监听的端口。
ENTRYPOINT ["dotnet", "API.dll"]
ENTRYPOINT:指定镜像的默认入口命令,该入口命令会在启动容器是作为跟命令执行,所有传入值作为该命令的参数。支持两种格式:
ENTRYPOINT [“executable”,”param1”,”param2”] (exec调用执行)
ENTRYPOINT command param1 param2 (shell中执行)
每个Dockerfile里若出现多个ENTRYPOINT,只有放后面的那个ENTRYPOINT有效。
Dockerfile参考:https://docs.docker.com/engine/reference/builder/#usage 里面有各个指令的详细介绍。
二、multi-stage builds(多阶段构建)
在多阶段构建的过程中,我们在Dockerfile使用多个FROM指令,每个FROM指令使用不同的基础镜像构成了不同阶段。你可以选择从上一个阶段的产物(artifacts)复制到下一个阶段,从而确保不会把不需要的东西带到下一阶段。这种方法可以有效减小Docker镜像的大小。
默认情况下,这些阶段没有被命名,可以通过它们的整数引用它们,第一个FROM指令从0开始。然而,我们也可以以as <NAME>的方式命名每个阶段。
参考官网:https://docs.docker.com/develop/develop-images/multistage-build/
以下我们用Visual Studio Tools for Docker生成的Dockerfile进行介绍。
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base WORKDIR /app EXPOSE 80 FROM microsoft/dotnet:2.1-sdk AS build WORKDIR /src COPY ["API/API.csproj", "API/"] RUN dotnet restore "API/API.csproj" COPY . . WORKDIR "/src/API" RUN dotnet build "API.csproj" -c Release -o /app FROM build AS publish RUN dotnet publish "API.csproj" -c Release -o /app FROM base AS final WORKDIR /app COPY --from=publish /app . ENTRYPOINT ["dotnet", "API.dll"]
它分为四个阶段,分别是base、build、publish和final。
base阶段:上面已经分析了这里不再详述。
build阶段:
FROM microsoft/dotnet:2.1-sdk AS build以microsoft/dotnet:2.1-sdk为基础镜像
WORKDIR /src工作目录为/src
COPY ["API/API.csproj", "API/"]把Dockerfile所在目录的API/API.csproj文件复制到容器的/src/API/中
RUN dotnet restore "API/API.csproj"在当前镜像的基础上执行dotnet restore "API/API.csproj",把API项目的依赖项和工具还原,并输出结果。
dotnet restore这条命令是使用 NuGet 还原依赖项以及在 project 文件中指定项目特殊的工具执行对依赖项和工具的还原。
COPY . .
WORKDIR "/src/API"切换工作目录到/src/API,可以用WORKDIR API替代,但明显第一种方法更直观。
RUN dotnet build "API.csproj" -c Release -o /app执行dotnet build "API.csproj" -c Release -o /app,以Release模式生成API项目及其所有依赖项并把生成的二进制文件输出到/app目录。
publish阶段:
FROM build AS publish以上一阶段build为基础镜像
RUN dotnet publish "API.csproj" -c Release -o /app执行dotnet publish "API.csproj" -c Release -o /app,以Release模式把API应用程序及其依赖项打包到/app目录以部署到托管系统。
final阶段:
FROM base AS final以上阶段base为基础镜像
WORKDIR /app以/app为工作目录
COPY --from=publish /app .把publish阶段生成的/app目录下的文件和文件夹复制到/app目录。
这样做的原因是,上阶段的产物是不会带到下一阶段。
现在可以解释为什么使用Visual Studio Tools for Docker不用发布也能生成可运行的镜像了,它实时 (JIT) 编译,提高启动性能。而且它只获取了程序运行所需要的文件放到镜像中。
我们生成一个最新的镜像

发现它和microsoft/dotnet:2.1-aspnetcore-runtime镜像一样大,这下满足了,毕竟我们没写什么代码到项目中。
三、优化Docker镜像的方向
1.精简镜像用途,尽量让每个镜像的用途都比较集中、单一,避免构造大而复杂,功能多的镜像。
2.选用合适的基础镜像。
3.在Dockerfile中写上注释,方便维护和他人使用。
4.正确使用版本号,如1.0.1。
5.使用多阶段构建镜像。
Docker镜像优化的更多相关文章
- 前端 Docker 镜像体积优化
如果 2019 年技术圈有十大流行词,容器化肯定占有一席之地,随着 Docker 的风靡,前端领域应用到 Docker 的场景也越来越多,本文主要来讲述下开源的分布式图数据库 Nebula Graph ...
- 使用Visual Studio 2017构建.Net Core的Docker镜像
1 Docker 镜像优化 微软在为开发人员生成 Docker 镜像时,提供以下三种主要方案: 用于开发 .NET Core 应用的 镜像 用于构建生成 .NET Core 应用的 镜像 用于运行 ...
- Docker容器技术-优化Docker镜像
一.优化Docker镜像 1.降低部署时间 一个大的Docker应用是如何影响在新Docker宿主机上的部署时间. (1)编写Dockerfile创建一个大Docker镜像 [root@bogon ~ ...
- 优化 ASP.NET Core Docker 镜像的大小
在这容器化的世界里,我们已经很少直接通过文件发布来运行asp.net core程序了.现在大多数情况下,我们都会使用docker来运行程序.在使用docker之前,我们往往需要打包我们的应用程序.as ...
- Dockerfile 自动制作 Docker 镜像(三)—— 镜像的分层与 Dockerfile 的优化
Dockerfile 自动制作 Docker 镜像(三)-- 镜像的分层与 Dockerfile 的优化 前言 a. 本文主要为 Docker的视频教程 笔记. b. 环境为 CentOS 7.0 云 ...
- 优化 Docker 镜像大小常见方法
平时我们构建的 Docker 镜像通常比较大,占用大量的磁盘空间,随着容器的大规模部署,同样也会浪费宝贵的带宽资源.本文将介绍几种常用的方法来优化 Docker 镜像大小,这里我们使用 Docker ...
- NodeJS 服务 Docker 镜像极致优化指北
这段时间在开发一个腾讯文档全品类通用的 HTML 动态服务,为了方便各品类接入的生成与部署,也顺应上云的趋势,考虑使用 Docker 的方式来固定服务内容,统一进行制品版本的管理.本篇文章就将我在服务 ...
- 阿里mysql同步工具otter的docker镜像
https://github.com/dearplain/otter_manager https://github.com/dearplain/otter_node 本人开发的小巧docker镜像,根 ...
- 用dockerfile创建jmeter的docker镜像
网上多是创建docker镜像是从jmeter官方下载jmeter的tgz包 今天我们用本地已经下载好的tgz包. 以下是dockerfile FROM java:8 ENV http_proxy &q ...
随机推荐
- mysql 查询优化~sql优化通用
一 简介:今天我们来探讨下SQL语句的优化基础 二 基础规则: 一 通用: 1 避免索引字段使用函数 2 避免发生隐式转换 3 order by字段需要走索引,否则会发生filesor ...
- SpringAOP+注解实现简单的日志管理
今天在再次深入学习SpringAOP之后想着基于注解的AOP实现日志功能,在面试过程中我们也经常会被问到:假如项目已经上线,如何增加一套日志功能?我们会说使用AOP,AOP也符合开闭原则:对代码的修改 ...
- Spring+CXF整合来管理webservice(服务器启动发布webservice)
Spring+CXF整合来管理webservice 实现步骤: 1. 添加cxf.jar 包(集成了Spring.jar.servlet.jar ),spring.jar包 ,serv ...
- SciPy模块应用
1.图像模糊 图像的高斯模糊是非常经典的图像卷积例子.本质上,图像模糊就是将(灰度)图像I 和一个高斯核进行卷积操作:,其中是标准差为σ的二维高斯核.高斯模糊通常是其他图像处理操作的一部分,比如图像 ...
- 如何读取Linux键值,输入子系统,key,dev/input/event,dev/event,C语言键盘【转】
转自:https://blog.csdn.net/lanmanck/article/details/8423669 相信各位使用嵌入式的都希望直接读取键值,特别是芯片厂家已经提供input驱动的情况下 ...
- Linux系统调用的运行过程【转】
本文转自:http://blog.csdn.net/kernel_learner/article/details/7331505 在Linux中,系统调用是用户空间访问内核的唯一手段,它们是内核唯一的 ...
- 搭建RDA交叉编译器
apt-get install subversion //安装版本控制系统,便于管理文件目录 apt-get install make atp-get install gcc =======set e ...
- 巧用CASE WHEN 验证用户登录信息
最近逛博客园的时候偶然看到一个很巧妙的SQL,巧妙利用CASE WHEN 实现一个简单的 SQL 同时验证用户帐号是否存在.密码是否正确.晓菜鸟之前的做法都是根据用户名和密码一起验证,如果验证失败直接 ...
- Linux安全配置步骤简述
一.磁盘分区 1.如果是新安装系统,对磁盘分区应考虑安全性: 1)根目录(/).用户目录(/home).临时目录(/tmp)和/var目录应分开到不同的磁盘分区: 2)以上各目录所在分区的磁 ...
- PYTHON-文件指针的移动,移动和函数基础
# 文件内指针的移动 #大前提:文件内指针的移动是Bytes为单位的,唯独t模式下的read读取内容个数是以字符为单位 # f.seek(指针移动的字节数,模式控制): 控制文件指针的移动# 模式控制 ...