Today, I spent some time trying to publish a .NET 6 AWS Serverless Application that consisted of:
- A few serverless functions that I wanted to run using the default CPU architecture, x64 (or using AWS terminology, x86_64)
- A few other serverless functions that I wanted to run on arm64 architecture (on Graviton2 CPUs)
I made sure that each a configuration for functions targeting arm64 differed by setting the following non-default property in the serverless.template
file:
// (...)
"Properties": {
"Runtime": "dotnet6",
"MemorySize": 512,
"Architectures": [
"arm64"
],
// (...)
}
Code language: JSON / JSON with Comments (json)
A problem: Internal Server Error from arm64 lambdas
Unfortunately, after I published the application, I noticed that:
- The functions that targeted the default x86_64 architecture worked flawlessly
- The functions that targeted arm64 responded with Internal Server Error. Looking at the logs in CloudWatch I noticed the exception:
Unhandled exception. System.BadImageFormatException: Could not load file or assembly 'Amazon.Lambda.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. An attempt was made to load a program with an incorrect format.
Code language: JavaScript (javascript)
Such an error might suggest a mismatch between the runtime environment supported by the application and the actual runtime environment.
What happened when I targeted different architectures in a single project?
The hint came from the publish operation logs. I noticed the tooling used the following command to prepare the deployment package for the first of my functions (which was x64):
dotnet publish "D:\Projekty\Benchmark.AwsServerless\Benchmark.AwsServerless." --output "D:\Projekty\Benchmark.AwsServerless\Benchmark.AwsServerless.\bin\Release\net6.0\publish" --configuration "Release" --framework "net6.0" /p:GenerateRuntimeConfigurationFiles=true --runtime linux-x64 --self-contained False
What happened next was that the compiled artifacts got packed into a ZIP package and uploaded to AWS S3 storage. However, the same artifacts were set to be re-used by subsequent functions as well. Even those configured to target arm64! Hence the mismatch in runtime.
How I solved the issue
This looks like an assumption hard-coded in the tooling that architecture is the same for all Lambdas within the project.
If that’s the case, the easiest solution seems to just split the C# project into two separate projects, one containing only x64 Lambdas, and the other containing only ARM64 Lambdas. But the projects can still share a code via a class library.
When I did that, the deployment tooling properly auto-detected the linux-arm64
target in the arm64 project, and no other code changes were needed beside the one in the serverless.template
file mentioned earlier.