• 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。

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

      

    为了调试方便,把隐私要求和https要求先屏蔽。

    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输出全部调试信息,便于查找错误。

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

      

    调试运行一下,确认网站没问题。

    把Home控制器的Index页面改一下,简单粗暴一点,就显示一个图片好了。

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

    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

  • 相关阅读:
    js实现input button从不可用变为可用
    eclipse工程名出现小红叉的解决办法
    jQuery的dataTables插件实现中文排序
    禁用鼠标选中DOM
    获取当前JS所在文件夹
    解决方案:在移动端输入框在软键盘弹出后位置矫正
    MUI版本升级更新程序IOS和andriod
    JavaScript输入表单数据正则验证规则
    Windows安裝PHP環境
    CentOS下安装PHP
  • 原文地址:https://www.cnblogs.com/sunnytrudeau/p/9384620.html
Copyright © 2020-2023  润新知