Aws-lambda-dotnet: AWS Lambdaはlibgdiplusを使用したSystem.Drawing呼び出しをサポートしていますか?

作成日 2018年04月18日  ·  7コメント  ·  ソース: aws/aws-lambda-dotnet

ちょっと、そこ、

最近、AWS Lambda .NET Core 2APIにキャプチャ生成を実装しました。 一度は実行されていたと誓ったかもしれませんが、今は単に失敗しています。 .NETCoreの現在のSystem.Drawing実装を調べました。 ただし、私が読んでいることから、GDI +への明示的な依存関係により、このライブラリはクロスプラットフォーム互換ではありません。 さらに調査すると、Monoチームによって最初に開発されたCompatCore.System.Drawingがあります。 おそらく、これはSystem.Drawingのクロスプラットフォームバージョンであり、LinuxベースのGDI +の実装を使用できます。 ただし、これを使用しようとすると、次のエラー/スタックトレースが発生します。

System.TypeInitializationException: The type initializer for 'System.Drawing.GDIPlus' threw an exception. ---> System.DllNotFoundException: Unable to load DLL 'gdiplus': The specified module or one of its dependencies could not be found.
 (Exception from HRESULT: 0x8007007E)
   at System.Drawing.GDIPlus.GdiplusStartup(UInt64& token, GdiplusStartupInput& input, GdiplusStartupOutput& output)
   at System.Drawing.GDIPlus..cctor()
   --- End of inner exception stack trace ---
   at System.Drawing.GDIPlus.GdipCreateBitmapFromScan0(Int32 width, Int32 height, Int32 stride, PixelFormat format, IntPtr scan0, IntPtr& bmp)
   at System.Drawing.Bitmap..ctor(Int32 width, Int32 height, PixelFormat format)
   at Ivy.Captcha.Services.CaptchaGenerationService.GenerateCaptchaImage(Int32 captchaCharLength, Int32 width, Int32 height)
   at IAGE.Api.Data.Services.CaptchaApiService.GenerateCaptcha() in D:\Workspaces\iam-global-education\src\Api\IAGE.Api.Data\Services\CaptchaApiService.cs:line 63
   at lambda_method.lambda_method(Closure , Object , Object[] )
   at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__10.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__22.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__17.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__15.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Builder.RouterMiddleware.d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.d__6.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware.d__7.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Elmah.Io.AspNetCore.ElmahIoMiddleware.d__6.MoveNext()

次のスレッドによると: https

runtime.linux-x64.CoreCompat.System.Drawing NuGetパッケージを追加することで、libgdiplus DLLrefを含めることができたはずです。 しかし、それは私には役に立たなかった。 多くの人がSystem.Cryptography.x509Certificatesで見たのと同様の問題になることを期待して、ライブラリ参照のさまざまな組み合わせを試してきました。 しかし、私はこれが機能することを可能にする組み合わせをまだ見つけていません。

他の誰かがSystem.Drawing機能をAWSLambdaで起動して実行することができましたか?

guidance

最も参考になるコメント

はいMagick.NETは、箱から出してすぐにクロスプラットフォームで動作するように数か月更新されました。 私がしなければならなかったのはそれをプロジェクトに追加することだけでした。 以下は、それを使用して画像のサムネイルを作成するために作成したLambda関数です。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Amazon.Lambda.Core" Version="1.0.0" />
    <PackageReference Include="Amazon.Lambda.Serialization.Json" Version="1.1.0" />
    <PackageReference Include="Amazon.Lambda.S3Events" Version="1.0.2" />
    <PackageReference Include="AWSSDK.S3" Version="3.3.16.2" />
    <PackageReference Include="AWSXRayRecorder.Handlers.AwsSdk" Version="2.1.0-beta" />
    <PackageReference Include="Magick.NET-Q16-AnyCPU" Version="7.4.2" />
  </ItemGroup>

  <ItemGroup>
    <DotNetCliToolReference Include="Amazon.Lambda.Tools" Version="2.1.1" />
  </ItemGroup>

</Project>
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

using Amazon.Lambda.Core;
using Amazon.Lambda.S3Events;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.XRay.Recorder.Core;
using ImageMagick;

// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]

namespace ImageResizer
{
    public class Function
    {
        IAmazonS3 S3Client { get; set; }

        /// <summary>
        /// Default constructor. This constructor is used by Lambda to construct the instance. When invoked in a Lambda environment
        /// the AWS credentials will come from the IAM role associated with the function and the AWS region will be set to the
        /// region the Lambda function is executed in.
        /// </summary>


        public Function()
        {
            Amazon.XRay.Recorder.Handlers.AwsSdk.AWSSDKHandler.RegisterXRayForAllServices();
            S3Client = new AmazonS3Client();
        }

        /// <summary>
        /// Constructs an instance with a preconfigured S3 client. This can be used for testing the outside of the Lambda environment.
        /// </summary>
        /// <param name="s3Client"></param>
        public Function(IAmazonS3 s3Client)
        {
            this.S3Client = s3Client;
        }

        HashSet<string> SupportedImageTypes { get; } = new HashSet<string> { ".png", ".jpg", ".jpeg" };

        /// <summary>
        /// This method is called for every Lambda invocation. This method takes in an S3 event object and can be used 
        /// to respond to S3 notifications.
        /// </summary>
        /// <param name="evnt"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public async Task FunctionHandlerAsync(S3Event evnt, ILambdaContext context)
        {
            foreach (var record in evnt.Records)
            {
                var bucket = record.S3.Bucket.Name;
                var key = record.S3.Object.Key;

                if (key.StartsWith("thumbnails"))
                {
                    context.Logger.LogLine($"Object s3://{bucket}/{key} is already a thumbnail");
                    continue;
                }
                if (!SupportedImageTypes.Contains(Path.GetExtension(key.ToLower())))
                {
                    context.Logger.LogLine($"Object s3://{bucket}/{key} is not a supported image type");
                    continue;
                }

                context.Logger.LogLine($"Processing s3://{bucket}/{key}");

                MemoryStream resizedImageStream;
                using (var response = await this.S3Client.GetObjectAsync(bucket, key))
                {
                    AWSXRayRecorder.Instance.BeginSubsegment("Magick Image Resize");
                    try
                    {
                        using (MagickImageCollection collection = new MagickImageCollection(response.ResponseStream))
                        {
                            foreach (MagickImage image in collection)
                            {
                                image.Resize(200, 200);
                            }
                            context.Logger.LogLine($"   Image resized");

                            resizedImageStream = new MemoryStream();
                            collection.Write(resizedImageStream);
                            resizedImageStream.Position = 0;
                        }
                    }
                    catch(Exception e)
                    {
                        AWSXRayRecorder.Instance.AddException(e);
                        throw;
                    }
                    finally
                    {
                        AWSXRayRecorder.Instance.EndSubsegment();
                    }
                }

                var index = key.LastIndexOf('/');
                var thumbnailKey = "thumbnails/" + (index != -1 ? key.Substring(index + 1) : key);
                await this.S3Client.PutObjectAsync(new PutObjectRequest
                {
                    BucketName = bucket,
                    Key = thumbnailKey,
                    InputStream = resizedImageStream
                });

                context.Logger.LogLine($"   Thumbnail saved to s3://{bucket}/{thumbnailKey}");
            }
        }
    }
}

全てのコメント7件

LinuxでSystem.Drawingにlibgdiplusを使用することについて聞いたことがありますが、試していません。 Lambdaでの画像操作には、最新バージョンのMagick.NET-Q16-AnyCPUを使用しています。 Captchaに必要と思われる描画は行っていませんが、いくつかの

私の問題は、libgdiplusがLambdaLinuxインスタンスのデフォルトのインストールではないことです。 おそらく、runtime.linux-x64.CoreCompat.System.Drawingは、アプリケーションの起動時にapt-getを介してこのライブラリをインストールするためのスクリプトを実行するだけのNuGetパッケージでした。 ただし、以下のリンクでこのプロジェクトのコードを掘り下げた後、これがどのように機能するのか、またはなぜ失敗するのかを正確に理解することができませんでした。

https://github.com/CoreCompat/CoreCompat/tree/master/native/runtime.linux-x64.CoreCompat.System.Drawing

今のところ、解決策を探し続けます。 ただし、そのMagick.NETリンクは、一部のCaptchaイメージを生成する有効な方法のように思われるため、フォールバックとして保持するようにします。 クロスプラットフォームの互換性を高めるために開発されたように見えるので、ネイティブのSystem.Drawingの使用を検討しましたが、インストールされたGDI +ライブラリを何らかの方法で参照しているようです。

このMagick.NETパッケージがLambdaで正常に使用されているのを見たことがありますか?

はいMagick.NETは、箱から出してすぐにクロスプラットフォームで動作するように数か月更新されました。 私がしなければならなかったのはそれをプロジェクトに追加することだけでした。 以下は、それを使用して画像のサムネイルを作成するために作成したLambda関数です。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Amazon.Lambda.Core" Version="1.0.0" />
    <PackageReference Include="Amazon.Lambda.Serialization.Json" Version="1.1.0" />
    <PackageReference Include="Amazon.Lambda.S3Events" Version="1.0.2" />
    <PackageReference Include="AWSSDK.S3" Version="3.3.16.2" />
    <PackageReference Include="AWSXRayRecorder.Handlers.AwsSdk" Version="2.1.0-beta" />
    <PackageReference Include="Magick.NET-Q16-AnyCPU" Version="7.4.2" />
  </ItemGroup>

  <ItemGroup>
    <DotNetCliToolReference Include="Amazon.Lambda.Tools" Version="2.1.1" />
  </ItemGroup>

</Project>
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

using Amazon.Lambda.Core;
using Amazon.Lambda.S3Events;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.XRay.Recorder.Core;
using ImageMagick;

// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]

namespace ImageResizer
{
    public class Function
    {
        IAmazonS3 S3Client { get; set; }

        /// <summary>
        /// Default constructor. This constructor is used by Lambda to construct the instance. When invoked in a Lambda environment
        /// the AWS credentials will come from the IAM role associated with the function and the AWS region will be set to the
        /// region the Lambda function is executed in.
        /// </summary>


        public Function()
        {
            Amazon.XRay.Recorder.Handlers.AwsSdk.AWSSDKHandler.RegisterXRayForAllServices();
            S3Client = new AmazonS3Client();
        }

        /// <summary>
        /// Constructs an instance with a preconfigured S3 client. This can be used for testing the outside of the Lambda environment.
        /// </summary>
        /// <param name="s3Client"></param>
        public Function(IAmazonS3 s3Client)
        {
            this.S3Client = s3Client;
        }

        HashSet<string> SupportedImageTypes { get; } = new HashSet<string> { ".png", ".jpg", ".jpeg" };

        /// <summary>
        /// This method is called for every Lambda invocation. This method takes in an S3 event object and can be used 
        /// to respond to S3 notifications.
        /// </summary>
        /// <param name="evnt"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public async Task FunctionHandlerAsync(S3Event evnt, ILambdaContext context)
        {
            foreach (var record in evnt.Records)
            {
                var bucket = record.S3.Bucket.Name;
                var key = record.S3.Object.Key;

                if (key.StartsWith("thumbnails"))
                {
                    context.Logger.LogLine($"Object s3://{bucket}/{key} is already a thumbnail");
                    continue;
                }
                if (!SupportedImageTypes.Contains(Path.GetExtension(key.ToLower())))
                {
                    context.Logger.LogLine($"Object s3://{bucket}/{key} is not a supported image type");
                    continue;
                }

                context.Logger.LogLine($"Processing s3://{bucket}/{key}");

                MemoryStream resizedImageStream;
                using (var response = await this.S3Client.GetObjectAsync(bucket, key))
                {
                    AWSXRayRecorder.Instance.BeginSubsegment("Magick Image Resize");
                    try
                    {
                        using (MagickImageCollection collection = new MagickImageCollection(response.ResponseStream))
                        {
                            foreach (MagickImage image in collection)
                            {
                                image.Resize(200, 200);
                            }
                            context.Logger.LogLine($"   Image resized");

                            resizedImageStream = new MemoryStream();
                            collection.Write(resizedImageStream);
                            resizedImageStream.Position = 0;
                        }
                    }
                    catch(Exception e)
                    {
                        AWSXRayRecorder.Instance.AddException(e);
                        throw;
                    }
                    finally
                    {
                        AWSXRayRecorder.Instance.EndSubsegment();
                    }
                }

                var index = key.LastIndexOf('/');
                var thumbnailKey = "thumbnails/" + (index != -1 ? key.Substring(index + 1) : key);
                await this.S3Client.PutObjectAsync(new PutObjectRequest
                {
                    BucketName = bucket,
                    Key = thumbnailKey,
                    InputStream = resizedImageStream
                });

                context.Logger.LogLine($"   Thumbnail saved to s3://{bucket}/{thumbnailKey}");
            }
        }
    }
}

Magickを始めたばかりで、うまくいくと思います。 比較的多様な画像操作APIにアクセスできるだけでなく、クロスプラットフォームでも十分にサポートされているようです。 提案をありがとうございました。もう少し開発努力をしてください。問題なくこのハードルをクリアできるはずです。

GDI +の実装をどのように稼働させることができたのか興味があります。 実行時に実行する前に、適切なlibgdiplusライブラリをLambdaにインストールする方法がわかりません。

私が見ているところによると、ImageMagick DrawablesAPIはAWSLambdaと適切にクロスプラットフォーム互換ではないようです。 ライブラリを実装してDraw()関数を実行すると、次のエラーが発生します。

ImageMagick.MagickDrawErrorException: NonconformingDrawingPrimitiveDefinition `text' @ error/draw.c/DrawImage/3402
   at ImageMagick.MagickExceptionHelper.Check(IntPtr exception)
   at ImageMagick.DrawingWand.NativeDrawingWand.Render()
   at ImageMagick.MagickImage.Draw(IEnumerable`1 drawables)
   at Ivy.Captcha.Magick.Services.MagickCaptchaGenerationService.GenerateCaptchaImage(Int32 captchaCharLength, Int32 width, Int32 height)
   at IAGE.Api.Data.Services.CaptchaApiService.GenerateCaptcha() in D:\Workspaces\iam-global-education\src\Api\IAGE.Api.Data\Services\CaptchaApiService.cs:line 63
   at lambda_method.lambda_method(Closure , Object , Object[] )
   at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__10.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__14.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__22.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__17.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.d__15.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Builder.RouterMiddleware.d__4.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.d__6.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware.d__7.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Elmah.Io.AspNetCore.ElmahIoMiddleware.d__6.MoveNext()

何をしても、Lambdaで描画機能を動作させることができないようです。 System.Drawing(Microsoft)、CoreCompat.System.Drawing(Mono)、およびImageMagick(サードパーティ)を使用しようとしましたが、すべて役に立たないようです。 すべての画像生成ライブラリには、AWSLambdaを使いこなすのに苦労しているいくつかの主要なシステム依存関係があるようです。 成功した場合は、引き続き実験と報告を行います。

Dockerコンテナーをカスタマイズする方法と同様に、将来的にLambdaランタイムをカスタマイズできる可能性はありますか? これはすべて、libgdiplusをLambdaインスタンスにインストールする簡単な実行で解決できます。

私はばかなので、単にGoogle ReCaptchaを使用できたのに、AWSLambdaサーバーでCaptchaイメージを生成するべきではありませんでした。 このように、私のAWS Lambdaは検証コードのみを処理し、いかなる形式の描画機能も参照する必要はありません。

将来的には、AWSLambdaに何らかの形の描画機能を実装する方法が必要です。 しかし、私はこの問題をバックバーナーに置き、当分の間、これを私の側のひどい設計決定にチョークで書きます。

おそらく、Lambdaのデプロイメントレイヤーは、ある時点で.Net Coreで適切なサポートが利用可能になるまで、この問題を解決できる可能性があります。

https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html

このページは役に立ちましたか?
0 / 5 - 0 評価