From 92250f29d82ee783643beaa54a9523db91bd7bde Mon Sep 17 00:00:00 2001 From: bjkim Date: Fri, 10 Apr 2026 19:22:33 +0900 Subject: [PATCH] =?UTF-8?q?[ADD]=20AWS=20=ED=94=84=EB=A1=9C=EB=8D=95?= =?UTF-8?q?=EC=85=98=20=EC=84=A4=EC=A0=95=20=ED=8C=8C=EC=9D=BC=20=EB=B0=8F?= =?UTF-8?q?=20FileSystemStorageProvider=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80,=20EKS=20=EB=B0=B0=ED=8F=AC=EB=A5=BC=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20Kubernetes=20=EB=A7=A4=EB=8B=88=ED=8E=98?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kubernetes-aws.yaml | 57 ++++++++++ .../storage/FileSystemStorageProvider.java | 105 ++++++++++++++++++ src/main/resources/application-aws.properties | 40 +++++++ src/main/resources/application.properties | 6 +- 4 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 kubernetes-aws.yaml create mode 100644 src/main/java/kr/re/etri/autoflow/service/storage/FileSystemStorageProvider.java create mode 100644 src/main/resources/application-aws.properties diff --git a/kubernetes-aws.yaml b/kubernetes-aws.yaml new file mode 100644 index 0000000..2f4d727 --- /dev/null +++ b/kubernetes-aws.yaml @@ -0,0 +1,57 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: autoflow-server + namespace: autoflow +spec: + replicas: 1 + selector: + matchLabels: + app: autoflow-server + template: + metadata: + labels: + app: autoflow-server + spec: + containers: + - name: autoflow-server + # [수정] 외부 레지스트리 주소를 제거하고 로컬 태그만 사용 + image: autoflow-server:latest + # [추가] 외부에서 이미지를 다운로드하지 않고 로컬 이미지를 사용하도록 강제 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8080 + env: + - name: RDS_HOSTNAME + # [수정 필요] Outpost 내부 RDS의 Private IP 또는 DNS를 입력하세요. + value: "INTERNAL_RDS_IP_HERE" + - name: RDS_USERNAME + value: "admin" + - name: RDS_PASSWORD + valueFrom: + secretKeyRef: + name: autoflow-secrets + key: rds-password + - name: JWT_SECRET + valueFrom: + secretKeyRef: + name: autoflow-secrets + key: jwt-secret + - name: S3_BUCKET_NAME + # [수정 필요] Outpost 내 생성한 S3 버킷 명을 입력하세요. + value: "autoflow-outpost-bucket" +--- +apiVersion: v1 +kind: Service +metadata: + name: autoflow-server-svc + namespace: autoflow +spec: + # Outpost EKS 환경에 따라 LoadBalancer 또는 NodePort를 선택하세요. + type: LoadBalancer + selector: + app: autoflow-server + ports: + - protocol: TCP + port: 80 + targetPort: 8080 diff --git a/src/main/java/kr/re/etri/autoflow/service/storage/FileSystemStorageProvider.java b/src/main/java/kr/re/etri/autoflow/service/storage/FileSystemStorageProvider.java new file mode 100644 index 0000000..4bf6a3c --- /dev/null +++ b/src/main/java/kr/re/etri/autoflow/service/storage/FileSystemStorageProvider.java @@ -0,0 +1,105 @@ +package kr.re.etri.autoflow.service.storage; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +@Service("storageProvider") +@ConditionalOnProperty(name = "storage.provider", havingValue = "filesystem") +public class FileSystemStorageProvider implements StorageProvider { + + @Value("${storage.local.base-path:./storage}") + private String basePath; + + @Value("${storage.local.default-bucket:mlpipeline}") + private String defaultBucket; + + private String getTargetDir(String bucketName) { + String bucket = (bucketName == null || bucketName.isEmpty()) ? defaultBucket : bucketName; + return Paths.get(basePath, bucket).toString(); + } + + @Override + public void uploadFile(String bucketName, String objectName, InputStream is, String contentType, long size, String type) throws Exception { + String targetDir = getTargetDir(bucketName); + Path targetPath = Paths.get(targetDir, objectName); + + Files.createDirectories(targetPath.getParent()); + Files.copy(is, targetPath, StandardCopyOption.REPLACE_EXISTING); + } + + @Override + public void uploadFileToDefault(String objectName, InputStream is, String contentType, long size) throws Exception { + uploadFile(null, objectName, is, contentType, size, null); + } + + @Override + public byte[] downloadFile(String bucketName, String objectName, String type) throws Exception { + String targetDir = getTargetDir(bucketName); + Path targetPath = Paths.get(targetDir, objectName); + + return Files.readAllBytes(targetPath); + } + + @Override + public byte[] downloadFileFromDefault(String objectName) throws Exception { + return downloadFile(null, objectName, null); + } + + @Override + public File downloadFileToServer(String bucketName, String objectName, String localPath, String type) throws Exception { + String targetDir = getTargetDir(bucketName); + Path sourcePath = Paths.get(targetDir, objectName); + + File localDir = new File(localPath); + if (!localDir.exists()) localDir.mkdirs(); + + String fileName = Paths.get(objectName).getFileName().toString(); + Path destinationPath = Paths.get(localPath, fileName); + + Files.copy(sourcePath, destinationPath, StandardCopyOption.REPLACE_EXISTING); + + return destinationPath.toFile(); + } + + @Override + public String readYamlText(String bucketName, String objectName, String type) throws Exception { + byte[] bytes = downloadFile(bucketName, objectName, type); + return new String(bytes, StandardCharsets.UTF_8); + } + + @Override + public String readYamlTextFromDefault(String objectName) throws Exception { + return readYamlText(null, objectName, null); + } + + @Override + public void deleteFile(String bucketName, String objectName, String type) throws Exception { + String targetDir = getTargetDir(bucketName); + Path targetPath = Paths.get(targetDir, objectName); + + Files.deleteIfExists(targetPath); + } + + @Override + public void deleteFileFromDefault(String objectName) throws Exception { + deleteFile(null, objectName, null); + } + + @Override + public String getFileUrl(String bucketName, String objectName, String type) { + // 로컬 파일 경로를 URL로 반환하거나 상대 경로로 반환 + // 프론트엔드에서 접근 가능한 경로여야 할 경우 추가적인 서블릿 설정이 필요할 수 있음 + String targetDir = getTargetDir(bucketName); + return Paths.get(targetDir, objectName).toAbsolutePath().toUri().toString(); + } +} diff --git a/src/main/resources/application-aws.properties b/src/main/resources/application-aws.properties new file mode 100644 index 0000000..f1b9b9d --- /dev/null +++ b/src/main/resources/application-aws.properties @@ -0,0 +1,40 @@ +# AWS Production Configuration +server.port = 8080 +server.servlet.context-path=/autoflow-server-mgmt + +spring.profiles.active=prod + +# RDS MariaDB Configuration +# Replace with your RDS endpoint +spring.datasource.url=jdbc:mariadb://${RDS_HOSTNAME}:3306/autoflow +spring.datasource.driver-class-name=org.mariadb.jdbc.Driver +spring.datasource.username=${RDS_USERNAME} +spring.datasource.password=${RDS_PASSWORD} + +spring.jpa.database-platform=org.hibernate.dialect.MariaDBDialect +spring.jpa.hibernate.ddl-auto=none +spring.sql.init.mode=never + +# App Properties (JWT) - Should be injected via env variables in K8s +cuuva.app.jwtCookieName=cuuva-jwt +cuuva.app.jwtRefreshCookieName=cuuva-jwt-refresh +cuuva.app.jwtSecret=${JWT_SECRET} + +# Storage Provider (S3) +storage.provider=s3 +cloud.aws.region.static=ap-northeast-2 +cloud.aws.s3.bucket=${S3_BUCKET_NAME} +# IAM Role should be used instead of hardcoded keys in EKS +# cloud.aws.credentials.access-key=${AWS_ACCESS_KEY} +# cloud.aws.credentials.secret-key=${AWS_SECRET_KEY} + +# Swagger UI +springdoc.swagger-ui.path=/swagger-ui +springdoc.swagger-ui.config-url=/autoflow-server-mgmt/v3/api-docs/swagger-config +springdoc.swagger-ui.url=/autoflow-server-mgmt/v3/api-docs +springdoc.swagger-ui.doc-expansion=none +springdoc.swagger-ui.disable-swagger-default-url=true + +# Multipart limit +spring.servlet.multipart.max-file-size=500MB +spring.servlet.multipart.max-request-size=500MB diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 5f4c61c..6f51392 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -50,9 +50,13 @@ spring.servlet.multipart.max-request-size=500MB springdoc.swagger-ui.tags-sorter=alpha -# Storage Provider (minio or s3) +# Storage Provider (minio, s3, or filesystem) storage.provider=minio +# Local FileSystem ?? +storage.local.base-path=./storage +storage.local.default-bucket=mlpipeline + # MinIO ?? minio.endpoint=http://192.168.10.135:31795 minio.access-key=minio