
기본 구성
사이드 프로젝트에서 Batch Job이 필요할 때, 기존에는 EC2에 띄워 놓은 instance에서 @Scheduled를 사용해서 주기적으로 실행하는 방식을 사용했습니다. 당연하지만 이렇게 하면 Instance가 늘어나면 Batch가 중복 실행되고, 조금이라도 트래픽이 많아지면 Batch가 서비스를 방해하게 됩니다.
그래서 어떻게 할까 고민하다가 AWS Lambda를 Cloudwatch를 통해서 주기적으로 실행하면 되지 않을까 생각이 들었습니다.
이게 그냥 되는 건 아니고 이렇게 하기 위해서는 여러 단계가 필요합니다.
- 실행하자마자 내가 원하는 Batch Job을 실행해 주는 Docker Image 만들기
- 이 Docker Image를 AWS의 Elastic Container Registry에 업로드하기
- Lambda에서 ECR의 이미지를 사용하게 구성하기
- CloudWatch를 구성하고 Cron Expression 작성하기
이렇게만 하면 될 거라 생각하고 시작했지만... 아쉽게도 그렇지 않고 여러 문제가 있었습니다. 이런 문제들을 어떻게 해결했는지 소개하고, 이 모든 문제를 해결한 뒤의 후기를 들려 드리겠습니다.
Spring Boot에서 Batch 전용 Docker Image 만들기
제가 원하는 구성은 Docker Image를 실행하기만 하면 그에 해당하는 Batch Job이 실행되는 겁니다. 물론 Batch Job의 개수만큼의 Docker Image를 만들어도 되지만 이렇게 하면 귀찮기도 하고, Batch Job의 개수가 늘어날 때마다 Docker Image도 늘어나게 되는 말도 안 되는 상황이 됩니다.
그래서 Batch용 Docker Image 1개를 만들고, Docker Container를 실행할 때에 Batch Job 이름을 넘겨주면 그 Batch Job을 실행해 주는 구성을 생각했습니다. 우선 저는 기존 Spring Boot 프로젝트에서 새로운 package를 만들고, 거기에 Batch Job을 실행하는데 필요한 최소한의 Gradle Dependency만 추가해 줬습니다.

다음으로 전용 Dockerfile을 만들어 줍니다.
FROM openjdk:8-jdk
WORKDIR /패키지_이름
COPY . .
RUN ./gradlew :패키지_이름:clean :패키지_이름:build
RUN mv ./batch/build/libs/*.jar /JAR_파일_이름.jar
ENTRYPOINT ["/bin/sh", "-c", "java -Dspring.profiles.active=api,live -jar /JAR_파일_이름.jar ${JOB_NAME}"]
Dockerfile을 이렇게 구성하면 Lambda의 환경 변수에 있는 JOB_NAME의 값에 따라 다른 종류의 Batch Job이 실행될 수 있게 돼 Batch Job 개수 별로 다른 Docker Image를 만들지 않아도 됩니다.
마지막으로 CI/CD에서 batch용 Dockerfile도 Docker Image로 만들고, 이걸 AWS의 EKR에 올려 주기만 하면 됩니다. API 서버와 Batch는 서로 dependency가 없으니 이건 Github CI/CD 기준으로 다른 Job으로 구성해도 됩니다.
계속 INIT Timeout이 난다면?
AWS Lambda는 기본적으로 Batch를 위해 있는 게 아니라 특정 이벤트가 발생했을 때 잠깐 깨어나서 계산을 처리하고 다시 잠드는 용도로 있습니다. 그렇기 때문에 당연히 빨리 깨어나서 일하고 다시 잠드는 특성을 가져야 해, 깨어나는 시간 즉 INIT을 10초로 제한하고 있습니다...
Understanding the Lambda execution environment lifecycle - AWS Lambda
The 10-second timeout doesn't apply to functions that are using provisioned concurrency or SnapStart. For provisioned concurrency and SnapStart functions, your initialization code can run for up to 15 minutes. The time limit is 130 seconds or the configure
docs.aws.amazon.com

하지만 Spring을 Batch 용도로 사용할 때 Spring Container를 띄운다면 당연히 10초는 우습게 넘게 되고, Spring Container를 시작하는 중에 계속 INIT Timeout이 나게 됩니다.

이런 경우 Spring Container 시작을 INIT으로 보지 않고, 그냥 Docker Container가 시작하는 걸 INIT으로 보게 하고 싶을 수 있습니다. (그렇게 안 하면 계속 무한 재시작만 할 뿐입니다...)
이걸 위해서는 Docker Container가 시작되고 main 함수가 실행될 때 직접 Lambda의 API를 호출해서 INIT 신호를 보내면 됩니다. 자세한 내용은 문서에 나와 있습니다.
Using the Lambda runtime API for custom runtimes - AWS Lambda
Using the Lambda runtime API for custom runtimes AWS Lambda provides an HTTP API for custom runtimes to receive invocation events from Lambda and send response data back within the Lambda execution environment. This section contains the API reference for t
docs.aws.amazon.com
Kotlin에서는 아래와 같이 함수를 만들어...
fun sendCompleteInitSignalToLambda() {
val runtimeApi = System.getenv("AWS_LAMBDA_RUNTIME_API")
val connection = URL("http://$runtimeApi/2018-06-01/runtime/invocation/next").openConnection() as HttpURLConnection
connection.requestMethod = "GET"
connection.connect()
}
main이 시작하자마자 호출하면 됩니다.
fun main(args: Array<String>) {
sendCompleteInitSignalToLambda()
}
VPC에 연결하면서 인터넷에 접근해야 하는데, NAT Gateway는 사용하기 싫다면?
Lambda를 Batch로 활용한다면 보통 무슨 계산을 하고 DB에 저장해야 할 겁니다. 그리고 DB는 보통 VPC의 Private Subnet 안에 있겠죠... 그럼 당연히 Lambda도 VPC 안에 넣고, Private Subnet에는 아니더라도 Public Subnet에 넣고 DB의 연결을 허용하는 SG에 넣습니다. 그럼 당연히 인터넷 연결이 될 거라 생각할 겁니다. 왜냐하면 IGW가 달려 있으니깐!
근데 VPC를 할당하는 페이지에 가면 싸한 문구를 발견합니다.

그리고 이 문서에 가서 내용을 읽어 보면 Lambda는 Public Subnet에 넣어도 인터넷에 접근하기 위해서는 NAT Gateway를 사용해야 한다는 겁니다...
Enable internet access for VPC-connected Lambda functions - AWS Lambda
Enable internet access for VPC-connected Lambda functions By default, Lambda functions run in a Lambda-managed VPC that has internet access. To access resources in a VPC in your account, you can add a VPC configuration to a function. This restricts the fun
docs.aws.amazon.com
그리고 당연하게도 NAT Gateway는 무료가 아닙니다...
Pricing for NAT gateways - Amazon Virtual Private Cloud
Pricing for NAT gateways When you provision a NAT gateway, you are charged for each hour that your NAT gateway is available and each gigabyte of data that it processes. For more information, see Amazon VPC Pricing. The following strategies can help you red
docs.aws.amazon.com
이쯤에서 저는 약간의 분노를 느꼈습니다. 굳이 이렇게 할 이유가 없는데 AWS가 NAT Gateway를 사용하게 하려고 이런 식으로 구성했다고 느꼈기 때문입니다. 그래서 포기하지 않고 약간의 검색을 한 결과 다행히 이런 게시글을 찾았습니다.
Why can't an AWS lambda function inside a public subnet in a VPC connect to the internet?
I've followed the tutorial here to create a VPC with public and private subnets. Then I set up an AWS lambda function inside the public subnet to test if it could connect to the outside internet. ...
stackoverflow.com
결론만 말하자면 NAT Gateway를 부착하지 않아도 Lambda의 Network Interface에 Elastic IP를 넣으면 문제없이 인터넷에 접근 가능하다는 겁니다!
후기
- 처음에 AWS Lambda를 Batch로 사용하겠다는 아이디어가 떠올랐을 땐 굉장히 간단할 거라 생각했습니다.
- 하지만 모든 과정에서 내가 원래 Lambda를 써야 하는 용도를 거스르고 있다는 생각이 들었습니다.
- 예전에 AWS를 따로 공부한다는 게 이해가 안 갔는데 이렇게 AWS 특유의 여러 정책들을 마주하다 보니 AWS를 따로 공부한다는게 무슨 말인지 점점 이해가 갔습니다.
- 사실 굉장히 마음에 안 들었습니다. 점점 제 서버가 Cloud Agnostic 하지 않고 AWS에 종속된다고 느꼈기 때문입니다. 이 경험을 계기로 K8S나 Terraform에 대한 관심이 엄청 생겼습니다.
'👨💻 프로그래밍 > 📦 Backend' 카테고리의 다른 글
| 애플 로그인, Server-to-Server Event 대응하기! (0) | 2025.12.26 |
|---|---|
| Transaction Outbox Pattern (0) | 2025.10.03 |
| Spring Cache에서 RDB를 원천으로 사용하기 (0) | 2025.08.16 |
| MySQL에서 Index가 사용되지 않는 대표적인 5가지 경우와 이유 알아보기 (0) | 2025.07.20 |
| MySQL의 실행 계획을 보기 위한 최소한의 지식 (0) | 2025.07.06 |