Asp.Net Core使用System.Drawing.Common部署到docker報錯問題

原文: Asp.Net Core使用System.Drawing.Common部署到docker報錯問題

Asp.Net Core 2.1發佈後,正式支持System.Drawing.Common繪圖了,能夠用來作一些圖片驗證碼之類的功能。可是把網站部署到docker容器裏運行會遇到不少問題,也是很是鬧心的,本文記錄這些問題,但願幫到有須要的人。html

建立網站

前提條件:安裝最新版VS2017和Net Core SDK 2.1。linux

 

首先新建網站,選擇Asp.Net Core 2.1 Web應用程序(模型視圖控制器),不勾選docker,我習慣自行編寫Dockerfile。git

 

指定網站訪問端口5000。github

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                //必須指定端口,不然在Win10命令行運行端口是5000,在CentOS docker運行端口是80
                .UseUrls("http://*:5000")
                .UseStartup<Startup>();

  

爲了調試方便,把隱私要求和https要求先屏蔽。web

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輸出所有調試信息,便於查找錯誤。docker

{
  "Logging": {
    "LogLevel": {
      "Default": "Debug"
    }
  },
  "AllowedHosts": "*"
}

  

調試運行一下,確認網站沒問題。shell

 

把Home控制器的Index頁面改一下,簡單粗暴一點,就顯示一個圖片好了。json

@{
    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。bash

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,注意把文件屬性「複製到輸出目錄」設置爲「若是較新則複製」。

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

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目錄,把容器跑起來。

[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能夠看到容器調試信息,發生了錯誤。

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爲

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

 

瀏覽網站,仍然沒法顯示圖片,可是錯誤內容變了。

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。

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長這樣。

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

相關文章
相關標籤/搜索