Spring Boot Rest API deployed in AWS Lambda (Serverless) Git lab CI/CD

Spring Boot Rest API deployed in AWS Lambda (Serverless) Git lab CI/CD

Just to begin AWS Lambda is server less architecture, which allows you to run the code without managing the server and cost is pay per use, usually its based on number of invocation and execution time.

Usually  lambda  is preferred for small task or to have platform specific trigger. But when we need to maintain the entire application it becomes tedious, For example if we do it  via console, First we need to create the function and upload the code to S3, and set up the HTTP gateway trigger. so each time when you update the code it becomes very difficult and time consuming,  just imagine for 50 URLs of a project. so when we are starting with lambda Function or any server less design, it better to start with  Automated deployment  as bare minimum instead of   spending most of the time  in deploying or playing inside the Cloud console.

So when develop any hobby project, minimum CI & CD pipelines is mandatory for me. And I prefer GitLab as my CI tool. So this preference led  me to find easier ways to deploy the the java application in to AWS lambda. So found these tools Micronaut as web framework, server less Framework for managing deployment and GitLab for CI & CD.

I am an spring boot advocate, I use spring boot in most of my applications, so this example is from on of my  experiments so lets begin this  example

I have used few additional tools to make my life easier to manage my lambda function Those are

  1. Serverless framework is Open Source, lets you develop and deploy serverless applications to AWS, Azure, GCP & more. and it has very good documentation to understand  to begin with.
  2. GIT Lab CI/CD  As this is integrated with my repo provider so I did not to use another CI tool.
  3. And used this dependency in my pom
<dependency>
	<groupId>com.amazonaws.serverless</groupId>
	<artifactId>aws-serverless-java-container-springboot2</artifactId>
	<version>1.5.2</version>
</dependency>

Step 1: Create the Dummy controller or two in Spring boot (Latest version this was working is 2.2.6.RELEASE)



import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/health")
public class HealthController {

    @GetMapping("/check")
    public HealthTO main() {
        return new HealthTO();
    }

}

Step 2 : Lets us create Entry point which can handle the request from AWS lambda This is main Application Class.


@SpringBootApplication
@EnableWebMvc
public class EntryPoint  implements RequestStreamHandler {

    private static final SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;

    static {
        try {
            handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(EntryPoint.class);
        } catch (ContainerInitializationException e) {
            // if we fail here. We re-throw the exception to force another cold start
            e.printStackTrace();
            throw new RuntimeException("Could not initialize Spring Boot application", e);
        }
    }

    public static void main(String[] args) {
        SpringApplication.run(EntryPoint.class, args);
    }

    @Bean
    public HandlerMapping handlerMapping() {
        return new RequestMappingHandlerMapping();
    }

    /*
     * Create required HandlerAdapter, to avoid several default HandlerAdapter instances being created
     */
    @Bean
    public HandlerAdapter handlerAdapter() {
        return new RequestMappingHandlerAdapter();
    }


    @Override
    public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
        handler.proxyStream(inputStream, outputStream, context);
    }
}

To optimise the cold start up time by manually using ComponentScan annotations for required components. and also using `@import` instead of `Autowired`

Step 3 Changing Build mechanism

Here i have attached the pom, instead of using spring boot maven plugin, we are using shade plugin to remove tomcat from the dependency

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.6.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.ashrithgn.example</groupId>
	<artifactId>service</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>app_name_service</name>
	<description>Handels gooles auth</description>
	<properties>
		<java.version>11</java.version>
	</properties>
	<dependencies>
	
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>com.amazonaws.serverless</groupId>
			<artifactId>aws-serverless-java-container-springboot2</artifactId>
			<version>1.5.2</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<finalName>app_name</finalName>
		<plugins>
			<!--<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
							<exclude>
								<groupId>org.apache.tomcat.embed</groupId>>
							</exclude>
					</excludes>
				</configuration>
			</plugin>-->
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-shade-plugin</artifactId>
				<version>2.3</version>
				<configuration>
					<createDependencyReducedPom>false</createDependencyReducedPom>
				</configuration>
				<executions>
					<execution>
						<phase>package</phase>
						<goals>
							<goal>shade</goal>
						</goals>
						<configuration>
							<artifactSet>
								<excludes>
									<exclude>org.apache.tomcat.embed:*</exclude>
								</excludes>
							</artifactSet>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>

</project>

And package the application as jar upload the jar in AWS lambda manually or use serverless to manage the lambda and create the API gateway.

Step 4: Setting up Serverless Framework

Installing  the serverless (on ubuntu)

sudo apt install nodejs
sudo apt install npm
sudo npm install -g serverless

Preparing serverless.yaml

This is yaml file  more  or like steps and rules for the deployment. create in the root of the project, or the place where ever the artifacts are accessible.

service: <project-name> # provide the project name
provider:
  name: aws
  runtime: java8

package:
  artifact: <path of the jar file> # provide path of the jar file

functions:
  get_news_categories:
    handler: <package-name>.EntryPoint
    events:
    - http:
        path: health/check
        method: GET # Http metodod [GET,POST,etc]
        cors: true # allows cors

Adding Aws Credentials to system environment  variable

export AWS_ACCESS_KEY_ID=<VALUE>
export AWS_SECRET_ACCESS_KEY=<****************>

Make sure the AWs use has these permission policy IAMFullAccess, AmazonS3FullAccess, CloudWatchLogsFullAccess , AmazonAPIGatewayAdministrator, AWSCloudFormationFullAccess, AWSLambda_FullAccess.

CI & CD : I am using the the GitLab CI, instead u can use any CI tool. For build pipeline use `mvn clean package shade:shade` to generate the artifact. to deploy deploy u can use `serverless deploy --stage production --verbose`.

Git lab CI & CD example

image: node:latest

stages:
  - package
  - deploy

mvn_build:
  stage: package
  image: maven:3-jdk-11
  script: "mvn package shade:shade"
  artifacts:
    paths:
      - target/<jarname>.jar
production:
  stage: deploy
  before_script:
    - npm config set prefix /usr/local
    - npm install -g serverless
  script:
    - serverless deploy --stage production --verbose
  environment: production

Also :

AWS Lambda APIs using Micronaut along with basic CI & CD (Gitlab CI) steps.
This article is about writing AWS lambda function using the Micronaut Java framework and deploying it as an AWS lambda function with help of the GitLab CI.