本文以 WOL 的.NET 项目为例,介绍了 Dockerfile 的基础知识和编写要点,旨在帮助读者更好地理解和掌握如何为 .NET 应用创建和优化 Dockerfile。
前面我们已经勾选了 Docker 容器化支持,项目已经生成了一个默认的 Dockerfile。但在实际项目中,我们需要根据项目的实际需求和环境来定制化 Dockerfile,以便更好地利用 Docker 的优势。本文将以 WOL (Wake On LAN) 项目为例,详细介绍如何编写一个针对 .NET 应用的 Dockerfile 文件,并分享一些实用的编写技巧。
以下是 .NET8 中 Web API 项目的默认模板,在这个默认模板中,我们可以看到一个典型的多阶段构建过程。
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["WebApplication3/WebApplication3.csproj", "WebApplication3/"]
RUN dotnet restore "./WebApplication3/./WebApplication3.csproj"
COPY . .
WORKDIR "/src/WebApplication3"
RUN dotnet build "./WebApplication3.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./WebApplication3.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "WebApplication3.dll"]
根据默认模板,我们先来复习一下 Dockerfile 的基本结构:
AS
为这个阶段设置别名,如 FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 8080
和 EXPOSE 8081
COPY ["WebApplication3/WebApplication3.csproj", "WebApplication3/"]
RUN dotnet restore "./WebApplication3/./WebApplication3.csproj"
ARG BUILD_CONFIGURATION=Release
ENTRYPOINT ["dotnet", "WebApplication3.dll"]
在默认模板中,我们还可以看到一个典型的多阶段构建过程。多阶段构建可以减小最终生成的镜像大小,提高构建速度。在上述示例中,我们使用了四个阶段,分别命名为 base、build、publish 和 final,用于不同的构建和运行阶段。
一般来说使用默认的 Dockerfile 就可以了,但是有时候我们需要根据项目的特定需求和环境对 Dockerfile 进行调整和优化。
项目可能需要一些额外的系统依赖或者工具。可以通过 RUN 指令和包管理器(如 apt-get、yum 等)来安装这些依赖。有时我们可以在编程中就能发现并意识到,但是更多的时候需要我们在容器化后对项目做好测试工作,确保所有功能都能正常运行。
比如在 WOL 项目的工具类中有写这样一个检查设备是否在线的函数:
internal static bool Ping(string iP)
{
// 检查IP是否在线
Ping ping = new Ping();
PingReply pingReply = ping.Send(iP,100);
return pingReply.Status == IPStatus.Success;
}
写起来倒是很简单,也很省事。但是如果不知道其运行原理的话,就会在容器化后遇到问题。这个函数使用了 System.Net.NetworkInformation.Ping
类来检查目标 IP 地址是否在线,但是其依然是使用了系统的 Ping
工具来处理。Docker 的 Base 镜像都是最简版本,为了轻便,都是不包含 Ping 工具的。
为了解决这个问题,我们就需要将缺失的依赖安装恢复,Debian 的 Ping 工具包为 Iputils-ping
,在 final
阶段通过 apt 安装即可。
在上一节中,我们讨论了安装额外依赖的必要性。为了加速镜像构建过程中的软件包安装,我们可以考虑修改安装源。通常,我们会在物理机上更改镜像源以加速软件包的安装。同样的方法也适用于容器镜像的构建过程。如果不更改安装源,一个镜像的构建可能会花费数小时,这对于开发者来说是难以忍受的。
在使用 mcr.microsoft.com/dotnet/aspnet:8.0
镜像时,底层基于 Debian 系统。为了加速软件包的安装,我们可以更改安装源。在这个例子中,我们将安装源更改为清华大学的镜像源。以下是如何在 Dockerfile 中进行更改的示例:
sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list.d/debian.sources && \
apt-get update
这段代码使用 sed
命令修改了 /etc/apt/sources.list.d/debian.sources
文件中的安装源,将其从 deb.debian.org
更改为了 mirrors.tuna.tsinghua.edu.cn
。接下来,使用 apt-get update
命令更新软件包列表。
通过这样的设置,我们可以加速后续软件包的下载和安装过程,从而提高整体镜像构建速度。
在执行 apt update
和安装软件包之后,我们需要注意清理系统中产生的缓存和临时文件。这些文件会增加镜像的大小,而在大多数情况下,它们在运行时并不需要。因此,我们可以在 Dockerfile 中添加相应的指令来清理这些垃圾文件,从而减小最终生成的镜像大小。
在使用 apt-get
安装软件包后,可以使用以下命令清理缓存:
apt-get clean
rm -rf /var/lib/apt/lists/*
这段代码首先使用 apt-get clean
命令清理软件包缓存,然后使用 rm -rf /var/lib/apt/lists/*
删除下载的软件包列表。这样一来,我们就可以在构建过程中有效地减小镜像大小。
前面已节我们已经按照了系统的 ping 工具,这样我们就可以正常的使用 System.Net.NetworkInformation.Ping
类来检查目标 IP 地址是否在线。但是在 Linux 系统中,Ping 的实现依赖于 ICMP 协议,而在容器中执行 ICMP 请求通常需要特殊权限,直接容器化运行项目会发现下面的问题:
ping: socktype: SOCK_RAW
ping: socket: Operation not permitted
ping: => missing cap_net_raw+p capability or setuid?
这是因为为了提高安全性,我们已经将应用的运行用户更改为了 app。为了解决这个问题,我们可以在 Dockerfile 中为更改 ping 命令的权限。
chmod u+s /bin/ping
处理到这里,Dockerfile 的文件就基本解决完毕。但是在实际测试中我们会发现,API 接口中返回的中文会出现乱码的情况:
我们可以看到,由配置文件返回的中文信息并没有出现乱码,而通过代码直接返回的中文出现了乱码的情况。这里是因为默认的代码文件编码是 GB18030 ,我们只需要将其改为 UTF-8 ,然后重新制备镜像即可解决这种硬编码导致的乱码问题。
通过以上的问题重现和处理,我们最终的项目 Dockerfile 如下:
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["WakeOnLan/WakeOnLan.csproj", "WakeOnLan/"]
RUN dotnet restore "WakeOnLan/WakeOnLan.csproj"
COPY . .
WORKDIR "/src/WakeOnLan"
RUN dotnet build "WakeOnLan.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "WakeOnLan.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=true
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list.d/debian.sources \
&& apt-get update \
&& apt-get install -y --no-install-recommends iputils-ping \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& chmod u+s /bin/ping
WORKDIR /app
USER app
EXPOSE 8080
COPY --from=publish /app/publish .
ENTRYPOINT ["./WakeOnLan"]
最后总结在编写 Dockerfile 时需要注意的一些要点:
使用多阶段构建:多阶段构建可以减小最终生成的镜像大小,提高构建速度。在上述示例中,我们使用了三个阶段,分别用于编译、发布和运行应用程序。
使用官方镜像:尽量使用官方提供的基础镜像,以确保镜像的安全性和可靠性。
优化镜像层:尽量将不会经常变动的文件放在前面,以充分利用镜像层缓存,提高构建速度。
安装额外依赖:根据项目实际需求,安装额外的依赖和工具,确保所有功能正常运行。
更改安装源:更改镜像源以加速软件包的下载和安装过程,减少构建时间。
清理缓存和临时文件:清理缓存和临时文件,减小生成的镜像大小。
解决权限问题:确保容器化应用程序的安全性,遵循最小权限原则,避免使用 root 用户运行容器。设置运行时用户并调整文件和目录的权限。
充分测试:对容器化后的项目进行充分的测试工作,以确保所有功能都能正常运行。
通过关注这些要点,我们可以针对实际项目编写和调整 Dockerfile,以便更好地利用 Docker 的优势。同时,对容器化后的项目进行充分的测试工作也是至关重要的。