赞
踩
有没有试问过自己,你的 docker 镜像是不是尽可能的小?有没有优化空间?为什么需要更小的镜像?
首先较大的镜像意味着如下几点:
所有我们尝试下常规项目中是否可以优化我们的镜像大小
开始前我们需要一个示例项目,这里就用dotnet new webapi -o Test创建一个 net api 项目,的如下文件
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 2022-04-01 8:53 Controllers
d---- 2022-04-01 8:53 obj
d---- 2022-04-01 8:53 Properties
-a--- 2022-04-01 8:53 127 appsettings.Development.json
-a--- 2022-04-01 8:53 151 appsettings.json
dotnet publish -c Release -o ./publish
使用 ASP.NET Core Runtime 创建一个 docker 镜像,Dockerfile 文件内容如下
FROM mcr.microsoft.com/dotnet/aspnet:6.0
EXPOSE 80 443
WORKDIR /app
COPY publish .
ENTRYPOINT ["dotnet", "Test.dll"]
docker build --pull --rm -f "Dockerfile" -t test:latest "."
如想查看应用是否可以正常运行,可尝试运行docker run -p 8080:80 test命令创建容器,并访问http://localhost:8080/Weather即可看到 json 数据
[
{
"date": "2022-04-02T01:20:36.7772078+00:00",
"temperatureC": -14,
"temperatureF": 7,
"summary": "Cool"
}
//....
]
执行docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test latest 84c40efd5e10 6 minutes ago 212MB
212MB,一个简单的应用构建的镜像高达 212MB 是不是有点太大了
让我们开查看下是什么让这个镜像如此之大:
docker history test
IMAGE CREATED CREATED BY SIZE COMMENT
84c40efd5e10 10 minutes ago ENTRYPOINT ["dotnet" "Test.dll"] 0B buildkit.dockerfile.v0
<missing> 10 minutes ago COPY publish . # buildkit 4.18MB buildkit.dockerfile.v0
<missing> 10 minutes ago WORKDIR /app 0B buildkit.dockerfile.v0
<missing> 10 minutes ago EXPOSE map[443/tcp:{} 80/tcp:{}] 0B buildkit.dockerfile.v0
<missing> 2 days ago /bin/sh -c #(nop) COPY dir:d0b9b7817ce7b36ce… 20.3MB
<missing> 2 days ago /bin/sh -c #(nop) ENV ASPNET_VERSION=6.0.3 … 0B
<missing> 2 days ago /bin/sh -c ln -s /usr/share/dotnet/dotnet /u… 24B
<missing> 2 days ago /bin/sh -c #(nop) COPY dir:7ed68022dc665c2bf… 70.6MB
<missing> 2 days ago /bin/sh -c #(nop) ENV DOTNET_VERSION=6.0.3 0B
<missing> 2 days ago /bin/sh -c #(nop) ENV ASPNETCORE_URLS=http:… 0B
<missing> 2 days ago /bin/sh -c apt-get update && apt-get ins… 36.2MB
<missing> 3 days ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 3 days ago /bin/sh -c #(nop) ADD file:966d3669b40f5fbae… 80.4MB
基础镜像和 Linux 库占用 116M,Net 占用 90.9MB,App 占用 4.18MB
从上面的步骤我们得知仅仅一个 4MB 的应用,构建出来的镜像竟然高达 200+MB,现在我们正式尝试将镜像优化到尽可能的小
通过自包含部署 net 应用,可以将程序、第三方库、以及运行时一起发布,这样就可以选择更小的基础镜像构建
dotnet publish --runtime alpine-x64 -c Release --self-contained true -o ./publish
基础镜像有很多,根据你的需求选择不同的镜像,这里我选择 Alpine Linux
Alpine Linux 是一个独立的、非商业的、通用的 Linux 发行版,专为重视安全性、简单性和资源效率的高级用户而设计。
修改 Dockerfile,并执行构建命令docker build --pull --rm -f "Dockerfile" -t test:latest "."
FROM alpine:3.15.3
RUN apk add --no-cache libstdc++ libintl
EXPOSE 80 443
WORKDIR /app
COPY publish .
ENTRYPOINT ["./Test"]
此时再次执行 docker images 查看镜像大小
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
test latest 30daa834b245 3 seconds ago 104MB
可以看到现在只有 104MB
验证镜像是否可执行docker run -p 8080:80 test查看程序是否可以正常运行
不幸的是得到了如下错误
Error loading shared library libstdc++.so.6: No such file or directory (needed by ./Test)
Error loading shared library libgcc_s.so.1: No such file or directory (needed by ./Test)
Error relocating ./Test: _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7reserveEm: symbol not found
Error relocating ./Test: _ZNKSt5ctypeIcE13_M_widen_initEv: symbol not found
Error relocating ./Test: _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE14_M_replace_auxEmmmc: symbol not found
Error relocating ./Test: _ZdlPv: symbol not found
...篇幅原因,省去多余日志
根据错误日志得知缺少 libstdc++ 和 libintl,我们在镜像里安装对应的依赖,因此修改 Dockerfile 内容如下,并再次执行docker run -p 8080:80 test查看程序是否可以正常运行
FROM alpine:3.15.3
RUN apk add --no-cache libstdc++ libintl
EXPOSE 80 443
WORKDIR /app
COPY publish .
ENTRYPOINT ["./Test"]
依然不幸的得到如下异常
Process terminated. Couldn't find a valid ICU package installed on the system. Please install libicu using your package manager and try again. Alternatively you can set the configuration flag System.Globalization.Invariant to true if you want to run with no globalization support. Please see https://aka.ms/dotnet-missing-libicu for more information. at System.Environment.FailFast(System.String) at System.Globalization.GlobalizationMode+Settings..cctor() at System.Globalization.CultureData.CreateCultureWithInvariantData() at System.Globalization.CultureData.get_Invariant() at System.Globalization.CultureInfo..cctor() at System.Globalization.CultureInfo.get_CachedCulturesByName() at System.Globalization.CultureInfo.GetCultureInfo(System.String) at System.Reflection.RuntimeAssembly.GetLocale() at System.Reflection.RuntimeAssembly.GetName(Boolean) at System.Reflection.Assembly.GetName() at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.Configure(System.Action`2<Microsoft.AspNetCore.Hosting.WebHostBuilderContext,Microsoft.AspNetCore.Builder.IApplicationBuilder>) at Microsoft.AspNetCore.Hosting.WebHostBuilderExtensions.Configure(Microsoft.AspNetCore.Hosting.IWebHostBuilder, System.Action`2<Microsoft.AspNetCore.Hosting.WebHostBuilderContext,Microsoft.AspNetCore.Builder.IApplicationBuilder>) at Microsoft.AspNetCore.Builder.WebApplicationBuilder+<>c__DisplayClass6_0.<.ctor>b__1(Microsoft.AspNetCore.Hosting.IWebHostBuilder) at Microsoft.Extensions.Hosting.GenericHostBuilderExtensions+<>c__DisplayClass0_0.<ConfigureWebHostDefaults>b__0(Microsoft.AspNetCore.Hosting.IWebHostBuilder) at Microsoft.Extensions.Hosting.GenericHostWebHostBuilderExtensions.ConfigureWebHost(Microsoft.Extensions.Hosting.IHostBuilder, System.Action`1<Microsoft.AspNetCore.Hosting.IWebHostBuilder>, System.Action`1<Microsoft.Extensions.Hosting.WebHostBuilderOptions>) at Microsoft.Extensions.Hosting.GenericHostWebHostBuilderExtensions.ConfigureWebHost(Microsoft.Extensions.Hosting.IHostBuilder, System.Action`1<Microsoft.AspNetCore.Hosting.IWebHostBuilder>) at Microsoft.Extensions.Hosting.GenericHostBuilderExtensions.ConfigureWebHostDefaults(Microsoft.Extensions.Hosting.IHostBuilder, System.Action`1<Microsoft.AspNetCore.Hosting.IWebHostBuilder>) at Microsoft.AspNetCore.Builder.WebApplicationBuilder..ctor(Microsoft.AspNetCore.Builder.WebApplicationOptions, System.Action`1<Microsoft.Extensions.Hosting.IHostBuilder>) at Microsoft.AspNetCore.Builder.WebApplication.CreateBuilder(System.String[]) at Program.<Main>$(System.String[])
看日志需要icu,该问题有两个解决方法
dockerfile 中将icu和libstdc++ libintl一起安装
FROM alpine:3.15.3
#由于icu官方源在国内下载很慢,所以使用国内代替
RUN echo "http://mirrors.aliyun.com/alpine/v3.15/main/" > /etc/apk/repositories && apk add --no-cache libstdc++ libintl icu
EXPOSE 80 443
WORKDIR /app
COPY publish .
ENTRYPOINT ["./Test"]
设置环境变量DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1(也可以在 dockerfile 中设置环境变量)
FROM alpine:3.15.3
RUN apk add --no-cache libstdc++ libintl
EXPOSE 80 443
WORKDIR /app
COPY publish .
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
ENTRYPOINT ["./Test"]
这里为了方便我就直接在 docker 命令里加环境变量的方式
docker run -e DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 -p 8080:80 test
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: /app/
自 net6 开始,发布的时候可以进行裁剪,就是将不必要的依赖不进行打包发布,但可能回导致一些问题,发布时使用PublishTrimmed
dotnet publish --runtime alpine-x64 -c Release -p:PublishTrimmed=true -o ./publish
此处未使用–self-contained true 是因为这个默认值为 true,详见官方文档
再次构建镜像并查看镜像大小
REPOSITORY TAG IMAGE ID CREATED SIZE
test latest 344730e17472 36 minutes ago 56.8MB
这次得到了仅 56.8MB 的镜像
我已经迫不及待的想验证下是否可以正常运行
执行docker run -e DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 -p 8080:80 test
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: /app/
一切正常!很棒~
但此时如果代码中有时区相关的代码,如获取当前时间,你会发现时间由于缺少时区导致时间不准
这是因为 alpine 中没有时区相关库,需要修改 dockerfile 内容即可
FROM alpine:3.15.3
ENV TZ=Asia/Shanghai DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
RUN echo "http://mirrors.aliyun.com/alpine/v3.15/main/" > /etc/apk/repositories \
&& apk add --no-cache libstdc++ libintl tzdata zeromq \
&& ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \
&& echo $TZ > /etc/timezone
EXPOSE 80 443
WORKDIR /app
COPY publish .
ENTRYPOINT ["./Test"]
我们回顾下我们做了什么
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。