Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .github/workflows/deploy-lambda-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Deploy Lambda to Dev

on:
workflow_dispatch: # 수동 실행
push:
branches:
- develop # develop 브랜치 푸시 시 자동 실행

jobs:
deploy:
name: Deploy Lambda Dev
runs-on: ubuntu-latest

env:
S3_BUCKET: sopt-makers-app
STACK_NAME: app-dev
AWS_REGION: ap-northeast-2

steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up JDK 21
uses: actions/setup-java@v3
with:
distribution: 'corretto'
java-version: '21'

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }}
aws-region: ${{ env.AWS_REGION }}

- name: copy application.yml files
run: |
mkdir -p ./src/main/resources
mkdir -p ./src/test/resources
echo "${{ secrets.APPLICATION_DEV_YML }}" > ./src/main/resources/application-dev.yml
echo "${{ secrets.APPLICATION_LAMBDA_YML }}" > ./src/main/resources/application-lambda.yml
echo "${{ secrets.APPLICATION_TEST_YML }}" > ./src/test/resources/application-test.yml

- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Build Lambda JAR
run: ./gradlew clean lambdaJar -x test

- name: Upload JAR to S3
run: |
# 빌드된 ZIP 파일 찾기
JAR_FILE=$(ls build/distributions/*-lambda.zip | head -1)

# 타임스탬프 생성
TIMESTAMP=$(date +"%Y%m%d-%H%M%S")
S3_KEY="lambda/${{ env.STACK_NAME }}-${TIMESTAMP}.zip"

# S3 업로드
aws s3 cp "$JAR_FILE" "s3://${{ env.S3_BUCKET }}/$S3_KEY"

echo "S3_KEY=$S3_KEY" >> $GITHUB_ENV

- name: Install SAM CLI
uses: aws-actions/setup-sam@v2

- name: Deploy with SAM
working-directory: ./lambda
run: |
sam deploy \
--config-env dev \
--stack-name ${{ env.STACK_NAME }} \
--no-fail-on-empty-changeset \
--parameter-overrides \
S3Bucket=${{ env.S3_BUCKET }} \
S3Key=${{ env.S3_KEY }} \
Profile="dev,lambda"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ application-local.yml
application-prod.yml
application-dev.yml
application-test.yml
application-lambda.yml
docker-compose.yml

### HTTP 관련
Expand Down
41 changes: 40 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ dependencies {
// jwt
implementation 'com.nimbusds:nimbus-jose-jwt:9.37.3'
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
implementation 'org.springframework.boot:spring-boot-starter-webflux'

// test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand All @@ -104,6 +103,11 @@ dependencies {
// slack
implementation 'com.slack.api:slack-api-client:1.30.0'

// AWS Lambda Dependencies for JAR deployment
implementation 'com.amazonaws.serverless:aws-serverless-java-container-springboot3:2.1.5'
implementation 'com.amazonaws:aws-lambda-java-core:1.4.0'
implementation 'com.amazonaws:aws-lambda-java-events:3.16.1'

}
tasks.named('test') {
useJUnitPlatform()
Expand All @@ -118,4 +122,39 @@ dependencyManagement {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:$springCloudVersion"
mavenBom "software.amazon.awssdk:bom:2.20.0"
}
}

// Lambda ZIP 빌드 설정
task lambdaJar(type: Zip) {
dependsOn bootJar
archiveClassifier = 'lambda'
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
zip64 = true // 대용량 ZIP 파일 지원

// lib 디렉토리 구조로 패키징
into('lib') {
from(jar)
from(configurations.runtimeClasspath) {
// Lambda에서 불필요한 파일 제외
exclude "org/apache/tomcat/embed/**"
exclude "META-INF/*.SF"
exclude "META-INF/*.DSA"
exclude "META-INF/*.RSA"
exclude "META-INF/MANIFEST.MF"
exclude "**/module-info.class"
}
}
}

// Lambda 빌드를 기본 빌드에 포함
build.dependsOn lambdaJar

// JAR 설정
jar {
enabled = true
archiveClassifier = ''
}

bootJar {
enabled = true
}
59 changes: 59 additions & 0 deletions lambda/lambda-deploy-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/bin/bash

# 간단한 Lambda JAR 배포 스크립트
set -e # 에러 발생시 중단

# 설정
ENV=${1:-dev}
S3_BUCKET="sopt-makers-app"
STACK_NAME="app-${ENV}"
AWS_REGION="ap-northeast-2"
TARGET_PROFILE="${ENV},lambda"

# 색상 정의
GREEN='\033[0;32m'
NC='\033[0m' # No Color

echo "🚀 Lambda JAR 배포 시작 (환경: $ENV)"

# 0. S3에서 yml 파일 가져오기
#echo "📥 S3에서 설정 파일 다운로드 중..."
#aws s3 cp s3://${S3_BUCKET}/dev/deploy/application-lambda-dev.yml src/main/resources/application-lambda-dev.yml

# 1. JAR 빌드
echo "📦 JAR 빌드 중..."
./gradlew clean lambdaJar -x test

# 2. S3 업로드
JAR_FILE=$(ls build/distributions/*-lambda.zip | head -1)
TIMESTAMP=$(date +"%Y%m%d-%H%M%S")
S3_KEY="lambda/${STACK_NAME}-${TIMESTAMP}-lambda.zip"

echo "☁️ S3 업로드 중..."
echo " 파일: $JAR_FILE"
echo " S3 경로: s3://${S3_BUCKET}/${S3_KEY}"
aws s3 cp "$JAR_FILE" "s3://${S3_BUCKET}/${S3_KEY}"

# 3. SAM으로 배포
echo "🔄 SAM 배포 중..."
cd lambda

sam deploy \
--config-env ${ENV} \
--stack-name ${STACK_NAME} \
--no-fail-on-empty-changeset \
--parameter-overrides \
"S3Bucket=${S3_BUCKET} S3Key=${S3_KEY} Profile=${TARGET_PROFILE}"

cd ..

echo -e "${GREEN}✅ 배포 완료!${NC}"

# API 엔드포인트 출력
API_ENDPOINT=$(aws cloudformation describe-stacks \
--stack-name ${STACK_NAME} \
--query "Stacks[0].Outputs[?OutputKey=='ApiEndpoint'].OutputValue" \
--output text \
--region ${AWS_REGION})

echo -e "${GREEN}🌐 API Endpoint: ${API_ENDPOINT}${NC}"
21 changes: 21 additions & 0 deletions lambda/samconfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
version = 0.1

[default.build.parameters]
cached = true
parallel = true

[dev.deploy.parameters]
stack_name = "app-dev"
region = "ap-northeast-2"
capabilities = "CAPABILITY_IAM"
confirm_changeset = false
template = "template-dev.yaml"
resolve_s3 = true

[prod.deploy.parameters]
stack_name = "app-prod"
region = "ap-northeast-2"
capabilities = "CAPABILITY_IAM"
confirm_changeset = true # 운영은 변경사항 확인
template = "template-prod.yaml"
resolve_s3 = true
79 changes: 79 additions & 0 deletions lambda/template-dev.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
Sopt makers App Backend DEV - Spring Boot JAR on AWS Lambda with SnapStart

Globals:
Function:
Timeout: 300 # 최대 실행 시간 (초)
MemorySize: 3072 # 메모리 (MB) - 프로젝트 크기에 따라 조정
Runtime: java21 # Java 버전

Parameters:
S3Bucket:
Type: String
Default: ""
Description: S3 bucket containing the Lambda JAR
S3Key:
Type: String
Default: ""
Description: S3 key (path) to the Lambda JAR file
Profile:
Type: String
Default: "lambda,dev"
Description: Spring profile to use


Resources:
ApiFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub "${AWS::StackName}-api"
CodeUri:
Bucket: !Ref S3Bucket
Key: !Ref S3Key
Handler: org.sopt.app.LambdaHandler
SnapStart:
ApplyOn: PublishedVersions # SnapStart 활성화
AutoPublishAlias: live
Environment:
Variables:
SPRING_PROFILES_ACTIVE: !Ref Profile
TZ: Asia/Seoul
JAVA_TOOL_OPTIONS: "-XX:+TieredCompilation -XX:TieredStopAtLevel=1"
Policies:
- AWSLambdaBasicExecutionRole
- Statement:
- Effect: Allow
Action:
- s3:GetObject
Resource: !Sub "arn:aws:s3:::${S3Bucket}/*"
Events:
ApiProxy:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /{proxy+}
Method: ANY
ApiRoot:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /
Method: ANY

ApiGateway:
Type: AWS::Serverless::Api
Properties:
StageName: dev
EndpointConfiguration: REGIONAL
Cors:
AllowOrigin: "'*'"
AllowHeaders: "'*'"
AllowMethods: "'*'"
AllowCredentials: "'false'"

Outputs:
ApiEndpoint:
Description: "API Gateway endpoint URL"
Value: !Sub "[https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/dev](https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/dev)"
5 changes: 1 addition & 4 deletions src/main/java/org/sopt/app/AppApplication.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
package org.sopt.app;

import jakarta.annotation.PostConstruct;
import java.util.TimeZone;
import org.sopt.app.common.external.auth.AuthClientProperty;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableAsync;

import java.util.TimeZone;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableJpaAuditing // JPA Auditing(감시, 감사) 기능을 활성화 하는 어노테이션 createdDate, modifiedDate 저장 활성화
@EnableAsync
@EnableScheduling
@SpringBootApplication
@EnableConfigurationProperties(AuthClientProperty.class)
public class AppApplication {
Expand Down
40 changes: 40 additions & 0 deletions src/main/java/org/sopt/app/LambdaHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.sopt.app;

import com.amazonaws.serverless.exceptions.ContainerInitializationException;
import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
import com.amazonaws.serverless.proxy.model.AwsProxyResponse;
import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class LambdaHandler implements RequestStreamHandler {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

말씀하신 대로 람다에서는 스케줄이 보장되지 않고, 현재는 솝탬프 점수 정합성 스케줄러만 있다 하셨는데

람다 프로파일에서라도 spring.task.scheduling.enabled=false
로 명시적으로 스케줄러 빈을 제외하는건 어떻게 생각하시나요

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은데요?? 반영해두겠습니다~


private static SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;

static {
try {
handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(AppApplication.class);

// Swagger
SpringBootLambdaContainerHandler.getContainerConfig().addBinaryContentTypes(
"image/png",
"image/jpeg",
"image/gif",
"application/octet-stream"
);
} catch (ContainerInitializationException e) {
throw new RuntimeException("Could not initialize Spring Boot application", e);
} catch (Exception e) {
throw new RuntimeException("Could not initialize Lambda handler", e);
}
}

@Override
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
throws IOException {
handler.proxyStream(inputStream, outputStream, context);
}
}
14 changes: 14 additions & 0 deletions src/main/java/org/sopt/app/common/config/SchedulingConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.sopt.app.common.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.EnableScheduling;

@Configuration
@EnableScheduling
@Profile("!lambda")
public class SchedulingConfig {
public SchedulingConfig(){
System.out.println("✅ [DEBUG] 스케줄러 빈이 생성되었습니다! (람다가 아님)");
}
}
Loading