Asp.Net Core使用System.Drawing.Common部署到docker报错问题
Asp.Net Core 2.1发布后,正式支持System.Drawing.Common绘图了,可以用来做一些图片验证码之类的功能。但是把网站部署到docker容器里运行会遇到很多问题,也是非常闹心的,本文记录这些问题,希望帮到有需要的人。
创建网站
前提条件:安装最新版VS2017和Net Core SDK 2.1。
首先新建网站,选择Asp.Net Core 2.1 Web应用程序(模型视图控制器),不勾选docker,我习惯自行编写Dockerfile。
指定网站访问端口5000。
1 2 3 4 5 | public static IWebHostBuilder CreateWebHostBuilder( string [] args) => WebHost.CreateDefaultBuilder(args) //必须指定端口,否则在Win10命令行运行端口是5000,在CentOS docker运行端口是80 .UseUrls( "http://*:5000" ) .UseStartup<Startup>(); |
为了调试方便,把隐私要求和https要求先屏蔽。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public void ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. //options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler( "/Home/Error" ); //app.UseHsts(); } //app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseCookiePolicy(); app.UseMvc(routes => { routes.MapRoute( name: "default" , template: "{controller=Home}/{action=Index}/{id?}" ); }); } |
修改appsettings.json输出全部调试信息,便于查找错误。
1 2 3 4 5 6 7 8 | { "Logging" : { "LogLevel" : { "Default" : "Debug" } }, "AllowedHosts" : "*" } |
调试运行一下,确认网站没问题。
把Home控制器的Index页面改一下,简单粗暴一点,就显示一个图片好了。
1 2 3 4 5 6 | @{ ViewData["Title"] = "Home Page"; } < h4 >System.Drawing.Common绘图</ h4 > < img src="Home/GetImg" alt="pic" class="img-thumbnail" width="400" height="200" /> |
NuGet安装System.Drawing.Common。
给Home控制器增加一个绘图函数GetImg。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public IActionResult GetImg() { _logger.LogInformation($ "{DateTime.Now:yyyy-MM-dd HH:mm:ss:fff}, start create image" ); string msg = $ "{DateTime.Now:HH:mm:ss}, 您好 drawing from .NET Core" ; Image image = new Bitmap(400, 200); Graphics graph = Graphics.FromImage(image); graph.Clear(Color.Azure); Pen pen = new Pen(Brushes.Black); graph.DrawLines(pen, new Point[] { new Point(10, 10), new Point(380, 180) }); graph.DrawString(msg, new Font( new FontFamily( "微软雅黑" ), 12, FontStyle.Bold), Brushes.Blue, new PointF(10, 90)); //把图片保存到内存文件流 MemoryStream ms = new MemoryStream(); image.Save(ms, ImageFormat.Png); ; byte [] buf = ms.GetBuffer(); _logger.LogInformation($ "{DateTime.Now:yyyy-MM-dd HH:mm:ss:fff}, finish create image" ); return File(buf, "image/png" ); } |
调试运行一下,是没问题的。
部署网站到docker
编写Dockerfile,注意把文件属性“复制到输出目录”设置为“如果较新则复制”。
1 2 3 4 5 | FROM microsoft/dotnet:2.1-aspnetcore-runtime WORKDIR /app COPY . /app EXPOSE 5000 ENTRYPOINT [ "dotnet" , "NetCoreDraw.dll" ] |
重新编译网站,以文件夹方式发布到默认的bin\Release\PublishOutput。在Windows控制台进入发布目录,输入dotnet NetCoreDraw.dll运行网站,浏览器访问http://localhost:5000/,确认没问题。
编写docker-compose.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | version: '3' services: myweb: container_name: myweb image: mywebimage build: context: ./PublishOutput dockerfile: Dockerfile ports: - "5000:5000" environment: - ASPNETCORE_ENVIRONMENT=Production - TZ=Asia/Shanghai restart: always |
我用CentOS 7.3虚拟机做试验,安装好docker环境,用Xshell访问虚拟机,用Xftp把PublishOutput文件夹、docker-compose.yml拖到Home下面。
进入Home目录,把容器跑起来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | [root@localhost home] # docker-compose up Creating network "home_default" with the default driver Building myweb Step 1 /5 : FROM microsoft /dotnet :2.1-aspnetcore-runtime 2.1-aspnetcore-runtime: Pulling from microsoft /dotnet be8881be8156: Pull complete f854db899319: Pull complete 4591fd524b8e: Pull complete 65f224da8749: Pull complete Digest: sha256:a43b729b84f918615d4cdce92a8bf59e3e4fb2773b8491a7cf4a0d728886eeba Status: Downloaded newer image for microsoft /dotnet :2.1-aspnetcore-runtime ---> fcc3887985bb Step 2 /5 : WORKDIR /app Removing intermediate container aba36715acfc ---> 25bc5bb6871f Step 3 /5 : COPY . /app ---> 9baaa790a82f Step 4 /5 : EXPOSE 5000 ---> Running in 269408c67989 Removing intermediate container 269408c67989 ---> fbd444c44d20 Step 5 /5 : ENTRYPOINT [ "dotnet" , "NetCoreDraw.dll" ] ---> Running in 2a9ba559b137 Removing intermediate container 2a9ba559b137 ---> b1bb1dccd49a Successfully built b1bb1dccd49a Successfully tagged mywebimage:latest WARNING: Image for service myweb was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`. Creating myweb ... done Attaching to myweb myweb | warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35] myweb | No XML encryptor configured. Key {1f87d27e-c6b1-435f-bab6-aebacd6d6817} may be persisted to storage in unencrypted form. myweb | Hosting environment: Production myweb | Content root path: /app myweb | Now listening on: http: // [::]:5000 myweb | Application started. Press Ctrl+C to shut down. |
在浏览器访问我的虚拟机里的网站http://192.168.41.129:5000/,图片显示不出来。
在Xshell可以看到容器调试信息,发生了错误。
1 2 3 4 5 6 7 8 9 10 | myweb | 2018-07-29 09:50:04:753, start create image myweb | info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2] myweb | Executed action NetCoreDraw.Controllers.HomeController.GetImg (NetCoreDraw) in 18.5988ms myweb | fail: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[1] myweb | An unhandled exception has occurred while executing the request. myweb | System.TypeInitializationException: The type initializer for 'Gdip' threw an exception. ---> System.DllNotFoundException: Unable to load shared library 'libdl' or one of its dependencies. In order to help diagnose loading problems, consider setting the LD_DEBUG environment variable: liblibdl: cannot open shared object file : No such file or directory myweb | at Interop.Libdl.dlopen(String fileName, Int32 flag) myweb | at System.Drawing.SafeNativeMethods.Gdip.LoadNativeLibrary() myweb | at System.Drawing.SafeNativeMethods.Gdip..cctor() myweb | --- End of inner exception stack trace --- |
找不到库文件libdl,网上有类似的问题和解决方案,就是建立一个文件连接。
https://q.cnblogs.com/q/107946/
但是aspnetcore容器中的库文件位置,跟正式发行版本的CentOS和Ubuntu不太一样,得进入容器去找。在容器运行的时候,通过Xshell的复制功能,再开一个终端窗口,进入容器。
docker exec -it myweb /bin/bash
aspnetcore容器不支持locate命令,我对Linux系统文件位置不熟,只好硬着头皮挨个看了一遍,还好目录不多,最后确定在这里。
root@544f7be29f68:/# ls lib/x86_64-linux-gnu/libdl*
lib/x86_64-linux-gnu/libdl-2.24.so lib/x86_64-linux-gnu/libdl.so.2
所以修改Dockerfile为
1 2 3 4 5 6 7 8 | FROM microsoft /dotnet :2.1-aspnetcore-runtime RUN ln -s /lib/x86_64-linux-gnu/libdl-2 .24.so /lib/x86_64-linux-gnu/libdl .so WORKDIR /app COPY . /app EXPOSE 5000 ENTRYPOINT [ "dotnet" , "NetCoreDraw.dll" ] |
重新编译、发布网站到虚拟机,重新运行编译运行容器。
[root@localhost home]# docker-compose up --build
浏览网站,仍然无法显示图片,但是错误内容变了。
1 2 3 4 5 6 7 | myweb | fail: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[1] myweb | An unhandled exception has occurred while executing the request. myweb | System.TypeInitializationException: The type initializer for 'Gdip' threw an exception. ---> System.DllNotFoundException: Unable to load DLL 'libgdiplus' : The specified module could not be found. myweb | at System.Runtime.InteropServices.FunctionWrapper`1.get_Delegate() myweb | at System.Drawing.SafeNativeMethods.Gdip.GdiplusStartup(IntPtr& token, StartupInput& input, StartupOutput& output) myweb | at System.Drawing.SafeNativeMethods.Gdip..cctor() myweb | --- End of inner exception stack trace --- |
这次是找不到libgdiplus,网上也有关于这个问题的解决方案。
https://q.cnblogs.com/q/103863/
因为需要在容器跑起来的时候安装一些东西,所以要更换为国内的源,提高速度。参考
https://www.cnblogs.com/OMango/p/8519980.html
把Dockerfile改为这样了,注意更新源之后要apt-get update一下,否则会报错Unable to locate package libgdiplus。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | FROM microsoft /dotnet :2.1-aspnetcore-runtime RUN ln -s /lib/x86_64-linux-gnu/libdl-2 .24.so /lib/x86_64-linux-gnu/libdl .so RUN echo "deb http: //mirrors .aliyun.com /debian wheezy main contrib non- free \ deb-src http: //mirrors .aliyun.com /debian wheezy main contrib non- free \ deb http: //mirrors .aliyun.com /debian wheezy-updates main contrib non- free \ deb-src http: //mirrors .aliyun.com /debian wheezy-updates main contrib non- free \ deb http: //mirrors .aliyun.com /debian-security wheezy /updates main contrib non- free \ deb-src http: //mirrors .aliyun.com /debian-security wheezy /updates main contrib non- free " > /etc/apt/sources .list RUN apt-get update RUN apt-get install libgdiplus -y && ln -s libgdiplus.so gdiplus.dll WORKDIR /app COPY . /app EXPOSE 5000 ENTRYPOINT [ "dotnet" , "NetCoreDraw.dll" ] |
再次运行,终于看到图片了,但是汉字没有显示出来。
参考上面的文章,把字体文件复制到容器中,再安装字体相关的功能即可。最终Dockerfile长这样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | FROM microsoft /dotnet :2.1-aspnetcore-runtime RUN ln -s /lib/x86_64-linux-gnu/libdl-2 .24.so /lib/x86_64-linux-gnu/libdl .so RUN echo "deb http: //mirrors .aliyun.com /debian wheezy main contrib non- free \ deb-src http: //mirrors .aliyun.com /debian wheezy main contrib non- free \ deb http: //mirrors .aliyun.com /debian wheezy-updates main contrib non- free \ deb-src http: //mirrors .aliyun.com /debian wheezy-updates main contrib non- free \ deb http: //mirrors .aliyun.com /debian-security wheezy /updates main contrib non- free \ deb-src http: //mirrors .aliyun.com /debian-security wheezy /updates main contrib non- free " > /etc/apt/sources .list RUN apt-get update RUN apt-get install libfontconfig1 -y RUN apt-get install libgdiplus -y && ln -s libgdiplus.so gdiplus.dll COPY . /fonts/msyh .ttc /usr/share/fonts/dejavu WORKDIR /app COPY . /app EXPOSE 5000 ENTRYPOINT [ "dotnet" , "NetCoreDraw.dll" ] |
把Win10的微软雅黑字体文件msyh.ttc放到项目的fonts目录下,设置文件属性“复制到输出目录”设置为“如果较新则复制”。
再次运行容器,搞定了。
Asp.Net Core网站跨平台部署到Linux容器运行是一个飞跃性的技术进步,但是时不时会碰到一些跟Linux系统相关的问题,总感觉是又爱又恨,心太累。
源代码
https://github.com/woodsun2018/NetCoreDraw
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步