spring-bootaws-lambdaterraformterraform-provider-awslocalstack

Springboot lambda on localstack with terraform, docker: Error loading class StreamLambdaHandler: Metaspace","errorType":"java.lang.OutOfMemoryError


I have created a rest api lambda using spring boot. When I create a jar file from this and deploy it to localstack with terraform, i can call the api and it works as expected.

But when I instead create a docker image from my code and adopt terraform to use that image_uri I get the following error when I call the api.

 Lambda runtime initialization error for function arn:aws:lambda:eu-west-2:000000000000:function:restapi: b'{"errorMessage":"Error loading class com.example.lambda.StreamLambdaHandler: Metaspace","errorType":"java.lang.OutOfMemoryError"}'

And this is the terraform:

variable "STAGE" {
  type    = string
  default = "local"
}

variable "AWS_REGION" {
  type    = string
  default = "eu-west-2"
}

variable "IMG_URI" {
  type    = string
  default = "localhost:4510/com.example-restapi-lambda:1.0.0"
}

variable "FUNCTION_NAME" {
  type    = string
  default = "restapi"
}


variable "FUNCTION_HANDLER" {
  type    = string
  default = "com.example.lambda.StreamLambdaHandler"
}


provider "aws" {
  access_key                  = "test_access_key"
  secret_key                  = "test_secret_key"
  region                      = var.AWS_REGION
  s3_force_path_style         = true
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true

  endpoints {
    apigateway       = var.STAGE == "local" ? "http://localhost:4566" : null
    cloudformation   = var.STAGE == "local" ? "http://localhost:4566" : null
    cloudwatch       = var.STAGE == "local" ? "http://localhost:4566" : null
    cloudwatchevents = var.STAGE == "local" ? "http://localhost:4566" : null
    iam              = var.STAGE == "local" ? "http://localhost:4566" : null
    lambda           = var.STAGE == "local" ? "http://localhost:4566" : null
    s3               = var.STAGE == "local" ? "http://localhost:4566" : null
  }
}

resource "aws_iam_role" "lambda-execution-role" {
  name = "lambda-execution-role-${var.FUNCTION_NAME}"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}

resource "aws_lambda_function" "restApiLambdaFunction" {
  image_uri     = var.IMG_URI
  function_name = var.FUNCTION_NAME
  role          = aws_iam_role.lambda-execution-role.arn
  handler       = var.FUNCTION_HANDLER
  # handler = "org.springframework.cloud.function.adapter.aws.FunctionInvoker"
  runtime = "java11"
  timeout = 60
  environment {
    variables = {
      MAIN_CLASS    = "com.example.lambda.AWSLambdaApp"
      # JAVA_OPTS = "-Xmx5g"
    }
  }
}

resource "aws_api_gateway_rest_api" "rest-api" {
  name = "RestApi-${var.FUNCTION_NAME}"
}

resource "aws_api_gateway_resource" "proxy" {
  rest_api_id = aws_api_gateway_rest_api.rest-api.id
  parent_id   = aws_api_gateway_rest_api.rest-api.root_resource_id
  path_part   = "{proxy+}"
}

resource "aws_api_gateway_method" "proxy" {
  rest_api_id   = aws_api_gateway_rest_api.rest-api.id
  resource_id   = aws_api_gateway_resource.proxy.id
  http_method   = "ANY"
  authorization = "NONE"
}

resource "aws_api_gateway_integration" "proxy" {
  rest_api_id = aws_api_gateway_rest_api.rest-api.id
  resource_id = aws_api_gateway_method.proxy.resource_id
  http_method = aws_api_gateway_method.proxy.http_method

  integration_http_method = "POST"
  type                    = "AWS_PROXY"
  uri                     = aws_lambda_function.restApiLambdaFunction.invoke_arn
}

resource "aws_api_gateway_deployment" "rest-api-deployment" {
  depends_on  = [aws_api_gateway_integration.proxy]
  rest_api_id = aws_api_gateway_rest_api.rest-api.id
  stage_name  = var.STAGE
}

resource "aws_cloudwatch_event_rule" "warmup" {
  name                = "warmup-event-rule-${var.FUNCTION_NAME}"
  schedule_expression = "rate(10 minutes)"
}

resource "aws_cloudwatch_event_target" "warmup" {
  target_id = "warmup"
  rule      = aws_cloudwatch_event_rule.warmup.name
  arn       = aws_lambda_function.restApiLambdaFunction.arn
  input     = "{\"httpMethod\": \"SCHEDULE\", \"path\": \"warmup\"}"
}

resource "aws_lambda_permission" "warmup-permission" {
  statement_id  = "AllowExecutionFromCloudWatch"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.restApiLambdaFunction.function_name
  principal     = "events.amazonaws.com"
  source_arn    = aws_cloudwatch_event_rule.warmup.arn
}

The closest thing i have seen as a solution is passing JAVA_OPTS to a docker image increasing the available memory but not sure how to do that via a terraform. Although not sure if that would solve the problem. Any guidance would be greatly appreciated.


Solution

  • There is a memory_size option in the aws_lambda_function resource. If not defined it will default to 128MB. It probably needs to be increased in order to avoid OOM error.