前言
上篇博文说到使用 Visual Studio Tools for Docker 帮助我们生成 Dockerfile, 现在我们讨论下生成的 Dockerfile 的优劣.
一, 以往 Dockerfile 构建模式
(1)发布 API 项目
新建 web API 项目, 项目名称为 API
在项目所在目录输入指令: dotnet publish --runtime Ubuntu.16.04-x64
(2)创建镜像
在发布目录新建 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 参考: 里面有各个指令的详细介绍.
二, multi-stage builds(多阶段构建)
在多阶段构建的过程中, 我们在 Dockerfile 使用多个 FROM 指令, 每个 FROM 指令使用不同的基础镜像构成了不同阶段. 你可以选择从上一个阶段的产物 (artifacts) 复制到下一个阶段, 从而确保不会把不需要的东西带到下一阶段. 这种方法可以有效减小 Docker 镜像的大小.
默认情况下, 这些阶段没有被命名, 可以通过它们的整数引用它们, 第一个 FROM 指令从 0 开始. 然而, 我们也可以以 as <NAME > 的方式命名每个阶段.
参考官网:
以下我们用 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. 使用多阶段构建镜像.
来源: https://www.cnblogs.com/FireworksEasyCool/p/10221299.html