From f18a976ca96324221580174d0b4a0b6d695f28b9 Mon Sep 17 00:00:00 2001 From: userjin2123 Date: Thu, 6 Nov 2025 10:21:59 +0900 Subject: [PATCH 01/16] =?UTF-8?q?feat=20:=20deploy=20=EB=B0=8F=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=9D=BC=EB=B6=80=20=EB=B3=80=EA=B2=BD,=20?= =?UTF-8?q?=EA=B8=80=EB=A1=9C=EB=B2=8C=20exception=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=95=A1=EC=B8=84=EC=97=90=EC=9D=B4=ED=84=B0=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=EB=8A=94=20=EA=B7=B8=EB=83=A5=20=EB=8B=A4=EC=8B=9C=20?= =?UTF-8?q?throw?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.prod.yml => docker-compose.yml | 10 ++- monew-api/build.gradle | 3 + .../exception/GlobalExceptionHandler.java | 7 +- .../src/main/resources/logback-spring.xml | 7 ++ monew-batch/build.gradle | 3 + .../monew_batch/MonewBatchApplication.java | 12 ++-- .../user/scheduler/DeletionScheduler.java | 3 +- .../src/main/resources/application-prod.yml | 2 +- .../src/main/resources/prometheus.yml | 68 +++++++++++++++++-- 9 files changed, 99 insertions(+), 16 deletions(-) rename docker-compose.prod.yml => docker-compose.yml (96%) diff --git a/docker-compose.prod.yml b/docker-compose.yml similarity index 96% rename from docker-compose.prod.yml rename to docker-compose.yml index bfb0964..70ddb83 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.yml @@ -136,10 +136,11 @@ services: # Prometheus (Metrics Collector) prometheus: - image: prom/prometheus:latest + build: + context: . + dockerfile: Dockerfile.prom + image: monew-prometheus:-latest container_name: monew-prometheus - volumes: - - ./monew-monitor/src/main/resources/prometheus.yml:/etc/prometheus/prometheus.yml:ro ports: - "9090:9090" networks: @@ -148,6 +149,9 @@ services: # Grafana (Visualization) grafana: + build: + context: . + dockerfile: Dockerfile.grafana image: grafana/grafana:latest container_name: monew-grafana environment: diff --git a/monew-api/build.gradle b/monew-api/build.gradle index b84ba3a..c4f5378 100644 --- a/monew-api/build.gradle +++ b/monew-api/build.gradle @@ -19,4 +19,7 @@ dependencies { implementation 'org.springframework.security:spring-security-crypto' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9' implementation 'org.apache.commons:commons-text:1.10.0' // 유사도 계산용 + + implementation("org.springframework.boot:spring-boot-starter-actuator") + runtimeOnly("io.micrometer:micrometer-registry-prometheus") } \ No newline at end of file diff --git a/monew-api/src/main/java/com/monew/monew_api/common/exception/GlobalExceptionHandler.java b/monew-api/src/main/java/com/monew/monew_api/common/exception/GlobalExceptionHandler.java index 00b7400..5688edd 100644 --- a/monew-api/src/main/java/com/monew/monew_api/common/exception/GlobalExceptionHandler.java +++ b/monew-api/src/main/java/com/monew/monew_api/common/exception/GlobalExceptionHandler.java @@ -56,7 +56,12 @@ public ResponseEntity handleValidationExceptions(MethodArgumentNo } @ExceptionHandler(Exception.class) - public ResponseEntity handleUnexpectedException(Exception e, HttpServletRequest request) { + public ResponseEntity handleUnexpectedException(Exception e, HttpServletRequest request) throws Exception { + + if (request.getRequestURI().startsWith("/actuator")) { + throw e; + } + log.error("[서버 내부 오류] 예외 타입: {}, 메시지: {}, URI: {}", e.getClass().getSimpleName(), e.getMessage(), diff --git a/monew-api/src/main/resources/logback-spring.xml b/monew-api/src/main/resources/logback-spring.xml index 45c63f7..3b23cdf 100644 --- a/monew-api/src/main/resources/logback-spring.xml +++ b/monew-api/src/main/resources/logback-spring.xml @@ -32,8 +32,15 @@ + + + ${LOG_PATTERN} + + + + diff --git a/monew-batch/build.gradle b/monew-batch/build.gradle index 98d2cc3..7997905 100644 --- a/monew-batch/build.gradle +++ b/monew-batch/build.gradle @@ -25,4 +25,7 @@ dependencies { runtimeOnly 'org.postgresql:postgresql' runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.batch:spring-batch-test' + + implementation("org.springframework.boot:spring-boot-starter-actuator") + runtimeOnly("io.micrometer:micrometer-registry-prometheus") } \ No newline at end of file diff --git a/monew-batch/src/main/java/com/monew/monew_batch/MonewBatchApplication.java b/monew-batch/src/main/java/com/monew/monew_batch/MonewBatchApplication.java index c092f5c..bf2a146 100644 --- a/monew-batch/src/main/java/com/monew/monew_batch/MonewBatchApplication.java +++ b/monew-batch/src/main/java/com/monew/monew_batch/MonewBatchApplication.java @@ -11,10 +11,14 @@ import java.util.TimeZone; @SpringBootApplication( - scanBasePackages = { - "com.monew.monew_batch", - "com.monew.monew_api.article.repository", - } + scanBasePackages = { + "com.monew.monew_batch", + "com.monew.monew_api.article.repository", + }, + exclude = { + org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration.class, + org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration.class + } ) @EntityScan(basePackages = "com.monew.monew_api") @EnableJpaRepositories(basePackages = "com.monew.monew_api") diff --git a/monew-batch/src/main/java/com/monew/monew_batch/user/scheduler/DeletionScheduler.java b/monew-batch/src/main/java/com/monew/monew_batch/user/scheduler/DeletionScheduler.java index 9480e84..b2e4c53 100644 --- a/monew-batch/src/main/java/com/monew/monew_batch/user/scheduler/DeletionScheduler.java +++ b/monew-batch/src/main/java/com/monew/monew_batch/user/scheduler/DeletionScheduler.java @@ -22,7 +22,8 @@ public class DeletionScheduler { * [요구사항] Soft delete 후 1일 경과한 사용자를 영구 삭제 * [프로토타입] 5초마다 체크하여 5분 경과한 사용자 삭제 */ - @Scheduled(fixedDelay = 5000) +// @Scheduled(fixedDelay = 50000) + @Scheduled(cron = "0 10 5 * * *", zone = "Asia/Seoul") public void runUserDeletionJob() throws Exception { log.info("==== Starting User Deletion Job ===="); diff --git a/monew-batch/src/main/resources/application-prod.yml b/monew-batch/src/main/resources/application-prod.yml index e39c3ae..f4763f4 100644 --- a/monew-batch/src/main/resources/application-prod.yml +++ b/monew-batch/src/main/resources/application-prod.yml @@ -66,4 +66,4 @@ aws: monew: api: - url: ${MONEW_API_URL} # 배포 후 추가 필요 \ No newline at end of file + url: ${http://monew-api.monew.local:8080} \ No newline at end of file diff --git a/monew-monitor/src/main/resources/prometheus.yml b/monew-monitor/src/main/resources/prometheus.yml index 606a695..f24307e 100644 --- a/monew-monitor/src/main/resources/prometheus.yml +++ b/monew-monitor/src/main/resources/prometheus.yml @@ -1,18 +1,74 @@ +#global: +# scrape_interval: 10s # 10초마다 메트릭 수집 + +# local +#scrape_configs: +# - job_name: 'monew-api' +# metrics_path: '/actuator/prometheus' +# static_configs: +# - targets: ['host.docker.internal:8080'] +# +# - job_name: 'monew-batch' +# metrics_path: '/actuator/prometheus' +# static_configs: +# - targets: ['host.docker.internal:8081'] +# +# - job_name: 'monew-monitor' +# metrics_path: '/actuator/prometheus' +# static_configs: +# - targets: ['host.docker.internal:8082'] + +# 배포 +#scrape_configs: +# - job_name: 'monew-api' +# metrics_path: '/api/actuator/prometheus' +# static_configs: +# - targets: ['monew-app-alb-721921608.ap-northeast-2.elb.amazonaws.com'] +# scheme: http +# +# - job_name: 'monew-batch' +# metrics_path: '/batch/actuator/prometheus' +# static_configs: +# - targets: ['monew-app-alb-721921608.ap-northeast-2.elb.amazonaws.com'] +# scheme: http +# +# - job_name: 'monew-monitor' +# metrics_path: '/monitor/actuator/prometheus' +# static_configs: +# - targets: ['monew-app-alb-721921608.ap-northeast-2.elb.amazonaws.com'] +# scheme: http + global: scrape_interval: 10s # 10초마다 메트릭 수집 +# (로컬용은 그대로 주석 유지) + +# 배포 scrape_configs: + # 0) Prometheus 자기 자신 (프리픽스 사용하므로 metrics_path 변경) + - job_name: 'prometheus' + metrics_path: /prometheus/metrics + static_configs: + - targets: ['localhost:9090'] + scheme: http + + # 1) monew-api - job_name: 'monew-api' - metrics_path: '/actuator/prometheus' + metrics_path: /actuator/prometheus static_configs: - - targets: ['host.docker.internal:8080'] + - targets: ['monew-alb-721921608.ap-northeast-2.elb.amazonaws.com'] + scheme: http + # 2) monew-batch - job_name: 'monew-batch' - metrics_path: '/actuator/prometheus' + metrics_path: /batch/actuator/prometheus static_configs: - - targets: ['host.docker.internal:8081'] + - targets: ['monew-alb-721921608.ap-northeast-2.elb.amazonaws.com'] + scheme: http + # 3) monew-monitor - job_name: 'monew-monitor' - metrics_path: '/actuator/prometheus' + metrics_path: /monitor/actuator/prometheus static_configs: - - targets: ['host.docker.internal:8082'] \ No newline at end of file + - targets: ['monew-alb-721921608.ap-northeast-2.elb.amazonaws.com'] + scheme: http \ No newline at end of file From 9b66faec6d8cebffd8712ca821b70aff42033845 Mon Sep 17 00:00:00 2001 From: userjin2123 Date: Thu, 6 Nov 2025 10:22:09 +0900 Subject: [PATCH 02/16] =?UTF-8?q?feat=20:=20deploy=20=EB=B0=8F=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=9D=BC=EB=B6=80=20=EB=B3=80=EA=B2=BD,=20?= =?UTF-8?q?=EA=B8=80=EB=A1=9C=EB=B2=8C=20exception=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=95=A1=EC=B8=84=EC=97=90=EC=9D=B4=ED=84=B0=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=EB=8A=94=20=EA=B7=B8=EB=83=A5=20=EB=8B=A4=EC=8B=9C=20?= =?UTF-8?q?throw?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 248 +++++++++++++++++++++++++++++++++++ Dockerfile.grafana | 145 ++++++++++++++++++++ Dockerfile.prom | 7 + 3 files changed, 400 insertions(+) create mode 100644 .github/workflows/deploy.yml create mode 100644 Dockerfile.grafana create mode 100644 Dockerfile.prom diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..df71529 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,248 @@ +name: CD - Deploy to ECS + +on: + push: + branches: + - release + workflow_dispatch: + +env: + AWS_REGION: ap-northeast-2 + ECS_CLUSTER: monew-cluster + +jobs: + build-and-push: + runs-on: ubuntu-latest + strategy: + matrix: + service: + - name: api + dockerfile: Dockerfile.api + ecr_repo: monew-api + ecs_service: monew-api-service + task_definition: monew-api-task + - name: batch + dockerfile: Dockerfile.batch + ecr_repo: monew-batch + ecs_service: monew-batch-service + task_definition: monew-batch-task + - name: monitor + dockerfile: Dockerfile.monitor + ecr_repo: monew-monitor + ecs_service: monew-monitor-service + task_definition: monew-monitor-task + - name: prometheus + dockerfile: Dockerfile.prom + ecr_repo: monew-prometheus + ecs_service: monew-prometheus-service + task_definition: monew-prometheus-task + - name: grafana + dockerfile: Dockerfile.grafana + ecr_repo: monew-grafana + ecs_service: monew-grafana-service + task_definition: monew-grafana-task + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + # ======================================== + # ECR 로그인 (Public 또는 Private) + # ======================================== + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + + # ======================================== + # Docker 이미지 빌드 & 푸시 + # ======================================== + - name: Build, tag, and push ${{ matrix.service.name }} + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + IMAGE_TAG: ${{ github.sha }} + run: | + docker build \ + -f ${{ matrix.service.dockerfile }} \ + -t $ECR_REGISTRY/${{ matrix.service.ecr_repo }}:$IMAGE_TAG \ + -t $ECR_REGISTRY/${{ matrix.service.ecr_repo }}:latest \ + . + + docker push $ECR_REGISTRY/${{ matrix.service.ecr_repo }}:$IMAGE_TAG + docker push $ECR_REGISTRY/${{ matrix.service.ecr_repo }}:latest + + # ======================================== + # ECS 배포 - API & 모니터링 (병렬) + # ======================================== + deploy-main: + runs-on: ubuntu-latest + needs: build-and-push + strategy: + matrix: + service: + - name: api + ecs_service: monew-api-service + task_definition: monew-api-task + ecr_repo: monew-api + - name: monitor + ecs_service: monew-monitor-service + task_definition: monew-monitor-task + ecr_repo: monew-monitor + - name: prometheus + ecs_service: monew-prometheus-service + task_definition: monew-prometheus-task + ecr_repo: monew-prometheus + - name: grafana + ecs_service: monew-grafana-service + task_definition: monew-grafana-task + ecr_repo: monew-grafana + + steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + + # ======================================== + # 태스크 정의 업데이트 + # ======================================== + - name: Update task definition + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + IMAGE_TAG: ${{ github.sha }} + run: | + # 현재 태스크 정의 가져오기 + TASK_DEFINITION=$(aws ecs describe-task-definition \ + --task-definition ${{ matrix.service.task_definition }} \ + --query 'taskDefinition' \ + --output json) + + # 새 이미지로 업데이트 + NEW_TASK_DEFINITION=$(echo $TASK_DEFINITION | jq \ + --arg IMAGE "$ECR_REGISTRY/${{ matrix.service.ecr_repo }}:$IMAGE_TAG" \ + '.containerDefinitions[0].image = $IMAGE | + del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)') + + # 새 태스크 정의 등록 + NEW_TASK_ARN=$(aws ecs register-task-definition \ + --cli-input-json "$NEW_TASK_DEFINITION" | \ + jq -r '.taskDefinition.taskDefinitionArn') + + echo "NEW_TASK_ARN=$NEW_TASK_ARN" >> $GITHUB_ENV + + # ======================================== + # 서비스 재시작 + # ======================================== + - name: Update ECS service + run: | + aws ecs update-service \ + --cluster ${{ env.ECS_CLUSTER }} \ + --service ${{ matrix.service.ecs_service }} \ + --task-definition ${{ env.NEW_TASK_ARN }} \ + --desired-count 1 \ + --force-new-deployment + + echo "✅ ${{ matrix.service.name }} service deployed!" + + # ======================================== + # 배포 상태 확인 (선택사항) + # ======================================== + - name: Wait for service stability + run: | + echo "Waiting for service to be stable..." + aws ecs wait services-stable \ + --cluster ${{ env.ECS_CLUSTER }} \ + --services ${{ matrix.service.ecs_service }} + + echo "🚀 ${{ matrix.service.name }} deployment completed!" + + # ======================================== + # ECS 배포 - Batch (API 안정화 후) + # ======================================== + deploy-batch: + runs-on: ubuntu-latest + needs: deploy-main # ⭐ API 배포 완료 후 실행 + strategy: + matrix: + service: + - name: batch + ecs_service: monew-batch-service + task_definition: monew-batch-task + ecr_repo: monew-batch + + steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + + # ======================================== + # 태스크 정의 업데이트 + # ======================================== + - name: Update task definition + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + IMAGE_TAG: ${{ github.sha }} + run: | + # 현재 태스크 정의 가져오기 + TASK_DEFINITION=$(aws ecs describe-task-definition \ + --task-definition ${{ matrix.service.task_definition }} \ + --query 'taskDefinition' \ + --output json) + + # 새 이미지로 업데이트 + NEW_TASK_DEFINITION=$(echo $TASK_DEFINITION | jq \ + --arg IMAGE "$ECR_REGISTRY/${{ matrix.service.ecr_repo }}:$IMAGE_TAG" \ + '.containerDefinitions[0].image = $IMAGE | + del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)') + + # 새 태스크 정의 등록 + NEW_TASK_ARN=$(aws ecs register-task-definition \ + --cli-input-json "$NEW_TASK_DEFINITION" | \ + jq -r '.taskDefinition.taskDefinitionArn') + + echo "NEW_TASK_ARN=$NEW_TASK_ARN" >> $GITHUB_ENV + + # ======================================== + # 서비스 재시작 + # ======================================== + - name: Update ECS service + run: | + aws ecs update-service \ + --cluster ${{ env.ECS_CLUSTER }} \ + --service ${{ matrix.service.ecs_service }} \ + --task-definition ${{ env.NEW_TASK_ARN }} \ + --desired-count 1 \ + --force-new-deployment + + echo "✅ ${{ matrix.service.name }} service deployed!" + + # ======================================== + # 배포 상태 확인 (선택사항) + # ======================================== + - name: Wait for service stability + run: | + echo "Waiting for service to be stable..." + aws ecs wait services-stable \ + --cluster ${{ env.ECS_CLUSTER }} \ + --services ${{ matrix.service.ecs_service }} + + echo "🚀 ${{ matrix.service.name }} deployment completed!" \ No newline at end of file diff --git a/Dockerfile.grafana b/Dockerfile.grafana new file mode 100644 index 0000000..971650c --- /dev/null +++ b/Dockerfile.grafana @@ -0,0 +1,145 @@ +name: CD - Deploy to ECS + +on: + push: + branches: + - release + workflow_dispatch: + +env: + AWS_REGION: ap-northeast-2 + ECS_CLUSTER: monew-cluster + # Public ECR 리포지토리 + ECR_REPOSITORY: public.ecr.aws/s7e2q2h9/monew + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + strategy: + matrix: + service: + - name: api + dockerfile: Dockerfile.api + tag: monew-api + task_definition: monew-api-task + ecs_service: monew-api-service + - name: batch + dockerfile: Dockerfile.batch + tag: monew-batch + task_definition: monew-batch-task + ecs_service: monew-batch-service + - name: monitor + dockerfile: Dockerfile.monitor + tag: monew-monitor + task_definition: monew-monitor-task + ecs_service: monew-monitor-service + - name: prometheus + dockerfile: Dockerfile.prom + tag: prometheus + task_definition: monew-prometheus-task + ecs_service: monew-prometheus-service + - name: grafana + dockerfile: Dockerfile.grafana + tag: grafana + task_definition: monew-grafana-task + ecs_service: monew-grafana-service + + steps: + # ======================================== + # 1. 코드 체크아웃 + # ======================================== + - name: Checkout code + uses: actions/checkout@v4 + + # ======================================== + # 2. Public ECR 로그인 (us-east-1) + # ======================================== + - name: Configure AWS credentials for Public ECR + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} + aws-region: us-east-1 # Public ECR은 us-east-1만 사용 + + - name: Login to Public ECR + run: | + aws ecr-public get-login-password --region us-east-1 | \ + docker login --username AWS --password-stdin public.ecr.aws + + # ======================================== + # 3. Docker 이미지 빌드 & 푸시 + # ======================================== + - name: Build and push ${{ matrix.service.name }} image + run: | + # 빌드 + docker build \ + -f ${{ matrix.service.dockerfile }} \ + -t ${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-${{ github.sha }} \ + -t ${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-latest \ + . + + # 푸시 + docker push ${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-${{ github.sha }} + docker push ${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-latest + + # ======================================== + # 4. ECS 배포 (ap-northeast-2) + # ======================================== + - name: Configure AWS credentials for ECS + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Stop ECS service (프리티어 최적화) + run: | + aws ecs update-service \ + --cluster ${{ env.ECS_CLUSTER }} \ + --service ${{ matrix.service.ecs_service }} \ + --desired-count 0 + + echo "Waiting for tasks to stop..." + aws ecs wait services-stable \ + --cluster ${{ env.ECS_CLUSTER }} \ + --services ${{ matrix.service.ecs_service }} + + - name: Update task definition + run: | + # 현재 태스크 정의 가져오기 + TASK_DEFINITION=$(aws ecs describe-task-definition \ + --task-definition ${{ matrix.service.task_definition }} \ + --query 'taskDefinition') + + # 새 이미지로 업데이트 + NEW_TASK_DEFINITION=$(echo $TASK_DEFINITION | jq \ + --arg IMAGE "${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-latest" \ + '.containerDefinitions[0].image = $IMAGE | + del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)') + + # 새 태스크 정의 등록 + NEW_TASK_ARN=$(aws ecs register-task-definition \ + --cli-input-json "$NEW_TASK_DEFINITION" | \ + jq -r '.taskDefinition.taskDefinitionArn') + + echo "NEW_TASK_ARN=$NEW_TASK_ARN" >> $GITHUB_ENV + + - name: Start ECS service + run: | + aws ecs update-service \ + --cluster ${{ env.ECS_CLUSTER }} \ + --service ${{ matrix.service.ecs_service }} \ + --task-definition ${{ env.NEW_TASK_ARN }} \ + --desired-count 1 \ + --force-new-deployment + + echo "✅ ${{ matrix.service.name }} service deployed!" + + - name: Wait for service stability + run: | + echo "Waiting for service to be stable..." + aws ecs wait services-stable \ + --cluster ${{ env.ECS_CLUSTER }} \ + --services ${{ matrix.service.ecs_service }} + + echo "🚀 ${{ matrix.service.name }} deployment completed!" \ No newline at end of file diff --git a/Dockerfile.prom b/Dockerfile.prom new file mode 100644 index 0000000..51c3ac6 --- /dev/null +++ b/Dockerfile.prom @@ -0,0 +1,7 @@ +FROM prom/prometheus:latest + +# Custom prometheus.yml 복사 +COPY monew-monitor/src/main/resources/prometheus.yml /etc/prometheus/prometheus.yml + +# 포트 노출 +EXPOSE 9090 \ No newline at end of file From 988e409defd114bdcdf8b5bdc7a5873ce2de6316 Mon Sep 17 00:00:00 2001 From: userjin2123 Date: Thu, 6 Nov 2025 10:56:46 +0900 Subject: [PATCH 03/16] =?UTF-8?q?fix=20:=20grafana=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20deploy=20=EB=82=B4=EC=9A=A9=EC=9D=B4?= =?UTF-8?q?=20=EB=8D=AE=EC=97=AC=EC=A7=84=20=EB=AC=B8=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile.grafana | 149 ++------------------------------------------- 1 file changed, 6 insertions(+), 143 deletions(-) diff --git a/Dockerfile.grafana b/Dockerfile.grafana index 971650c..0671b26 100644 --- a/Dockerfile.grafana +++ b/Dockerfile.grafana @@ -1,145 +1,8 @@ -name: CD - Deploy to ECS +FROM grafana/grafana:latest -on: - push: - branches: - - release - workflow_dispatch: +# 환경변수 설정 +ENV GF_SECURITY_ADMIN_USER=admin \ + GF_SECURITY_ADMIN_PASSWORD=admin -env: - AWS_REGION: ap-northeast-2 - ECS_CLUSTER: monew-cluster - # Public ECR 리포지토리 - ECR_REPOSITORY: public.ecr.aws/s7e2q2h9/monew - -jobs: - build-and-deploy: - runs-on: ubuntu-latest - strategy: - matrix: - service: - - name: api - dockerfile: Dockerfile.api - tag: monew-api - task_definition: monew-api-task - ecs_service: monew-api-service - - name: batch - dockerfile: Dockerfile.batch - tag: monew-batch - task_definition: monew-batch-task - ecs_service: monew-batch-service - - name: monitor - dockerfile: Dockerfile.monitor - tag: monew-monitor - task_definition: monew-monitor-task - ecs_service: monew-monitor-service - - name: prometheus - dockerfile: Dockerfile.prom - tag: prometheus - task_definition: monew-prometheus-task - ecs_service: monew-prometheus-service - - name: grafana - dockerfile: Dockerfile.grafana - tag: grafana - task_definition: monew-grafana-task - ecs_service: monew-grafana-service - - steps: - # ======================================== - # 1. 코드 체크아웃 - # ======================================== - - name: Checkout code - uses: actions/checkout@v4 - - # ======================================== - # 2. Public ECR 로그인 (us-east-1) - # ======================================== - - name: Configure AWS credentials for Public ECR - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} - aws-region: us-east-1 # Public ECR은 us-east-1만 사용 - - - name: Login to Public ECR - run: | - aws ecr-public get-login-password --region us-east-1 | \ - docker login --username AWS --password-stdin public.ecr.aws - - # ======================================== - # 3. Docker 이미지 빌드 & 푸시 - # ======================================== - - name: Build and push ${{ matrix.service.name }} image - run: | - # 빌드 - docker build \ - -f ${{ matrix.service.dockerfile }} \ - -t ${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-${{ github.sha }} \ - -t ${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-latest \ - . - - # 푸시 - docker push ${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-${{ github.sha }} - docker push ${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-latest - - # ======================================== - # 4. ECS 배포 (ap-northeast-2) - # ======================================== - - name: Configure AWS credentials for ECS - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} - aws-region: ${{ env.AWS_REGION }} - - - name: Stop ECS service (프리티어 최적화) - run: | - aws ecs update-service \ - --cluster ${{ env.ECS_CLUSTER }} \ - --service ${{ matrix.service.ecs_service }} \ - --desired-count 0 - - echo "Waiting for tasks to stop..." - aws ecs wait services-stable \ - --cluster ${{ env.ECS_CLUSTER }} \ - --services ${{ matrix.service.ecs_service }} - - - name: Update task definition - run: | - # 현재 태스크 정의 가져오기 - TASK_DEFINITION=$(aws ecs describe-task-definition \ - --task-definition ${{ matrix.service.task_definition }} \ - --query 'taskDefinition') - - # 새 이미지로 업데이트 - NEW_TASK_DEFINITION=$(echo $TASK_DEFINITION | jq \ - --arg IMAGE "${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-latest" \ - '.containerDefinitions[0].image = $IMAGE | - del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)') - - # 새 태스크 정의 등록 - NEW_TASK_ARN=$(aws ecs register-task-definition \ - --cli-input-json "$NEW_TASK_DEFINITION" | \ - jq -r '.taskDefinition.taskDefinitionArn') - - echo "NEW_TASK_ARN=$NEW_TASK_ARN" >> $GITHUB_ENV - - - name: Start ECS service - run: | - aws ecs update-service \ - --cluster ${{ env.ECS_CLUSTER }} \ - --service ${{ matrix.service.ecs_service }} \ - --task-definition ${{ env.NEW_TASK_ARN }} \ - --desired-count 1 \ - --force-new-deployment - - echo "✅ ${{ matrix.service.name }} service deployed!" - - - name: Wait for service stability - run: | - echo "Waiting for service to be stable..." - aws ecs wait services-stable \ - --cluster ${{ env.ECS_CLUSTER }} \ - --services ${{ matrix.service.ecs_service }} - - echo "🚀 ${{ matrix.service.name }} deployment completed!" \ No newline at end of file +# 포트 노출 +EXPOSE 3000 \ No newline at end of file From ed801c9c33d584db4a0ee5394391eeb95e912dc1 Mon Sep 17 00:00:00 2001 From: userjin2123 Date: Thu, 6 Nov 2025 11:06:14 +0900 Subject: [PATCH 04/16] =?UTF-8?q?fix=20:=20repo=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 188 +++++++---------------------------- 1 file changed, 37 insertions(+), 151 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index df71529..929ad8c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -9,208 +9,100 @@ on: env: AWS_REGION: ap-northeast-2 ECS_CLUSTER: monew-cluster + ECR_REPOSITORY: public.ecr.aws/s7e2q2h9/monew jobs: - build-and-push: + build-and-deploy: runs-on: ubuntu-latest strategy: matrix: service: - name: api dockerfile: Dockerfile.api - ecr_repo: monew-api - ecs_service: monew-api-service + tag: monew-api task_definition: monew-api-task + ecs_service: monew-api-service - name: batch dockerfile: Dockerfile.batch - ecr_repo: monew-batch - ecs_service: monew-batch-service + tag: monew-batch task_definition: monew-batch-task + ecs_service: monew-batch-service - name: monitor dockerfile: Dockerfile.monitor - ecr_repo: monew-monitor - ecs_service: monew-monitor-service + tag: monew-monitor task_definition: monew-monitor-task + ecs_service: monew-monitor-service - name: prometheus dockerfile: Dockerfile.prom - ecr_repo: monew-prometheus - ecs_service: monew-prometheus-service + tag: prometheus task_definition: monew-prometheus-task + ecs_service: monew-prometheus-service - name: grafana dockerfile: Dockerfile.grafana - ecr_repo: monew-grafana - ecs_service: monew-grafana-service + tag: grafana task_definition: monew-grafana-task + ecs_service: monew-grafana-service steps: + # ======================================== + # 1. 코드 체크아웃 + # ======================================== - name: Checkout code uses: actions/checkout@v4 # ======================================== - # ECR 로그인 (Public 또는 Private) + # 2. Public ECR 로그인 (us-east-1) + # ⚠️ 중요: Public ECR은 us-east-1만 사용! # ======================================== - - name: Configure AWS credentials + - name: Configure AWS credentials for Public ECR uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} - aws-region: ${{ env.AWS_REGION }} + aws-region: us-east-1 # ⭐ Public ECR은 us-east-1만 사용 - - name: Login to Amazon ECR - id: login-ecr - uses: aws-actions/amazon-ecr-login@v2 + - name: Login to Public ECR + run: | + aws ecr-public get-login-password --region us-east-1 | \ + docker login --username AWS --password-stdin public.ecr.aws # ======================================== - # Docker 이미지 빌드 & 푸시 + # 3. Docker 이미지 빌드 & 푸시 # ======================================== - - name: Build, tag, and push ${{ matrix.service.name }} - env: - ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} - IMAGE_TAG: ${{ github.sha }} + - name: Build and push ${{ matrix.service.name }} image run: | + # 빌드 docker build \ -f ${{ matrix.service.dockerfile }} \ - -t $ECR_REGISTRY/${{ matrix.service.ecr_repo }}:$IMAGE_TAG \ - -t $ECR_REGISTRY/${{ matrix.service.ecr_repo }}:latest \ + -t ${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-${{ github.sha }} \ + -t ${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-latest \ . - docker push $ECR_REGISTRY/${{ matrix.service.ecr_repo }}:$IMAGE_TAG - docker push $ECR_REGISTRY/${{ matrix.service.ecr_repo }}:latest - - # ======================================== - # ECS 배포 - API & 모니터링 (병렬) - # ======================================== - deploy-main: - runs-on: ubuntu-latest - needs: build-and-push - strategy: - matrix: - service: - - name: api - ecs_service: monew-api-service - task_definition: monew-api-task - ecr_repo: monew-api - - name: monitor - ecs_service: monew-monitor-service - task_definition: monew-monitor-task - ecr_repo: monew-monitor - - name: prometheus - ecs_service: monew-prometheus-service - task_definition: monew-prometheus-task - ecr_repo: monew-prometheus - - name: grafana - ecs_service: monew-grafana-service - task_definition: monew-grafana-task - ecr_repo: monew-grafana - - steps: - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} - aws-region: ${{ env.AWS_REGION }} - - - name: Login to Amazon ECR - id: login-ecr - uses: aws-actions/amazon-ecr-login@v2 - - # ======================================== - # 태스크 정의 업데이트 - # ======================================== - - name: Update task definition - env: - ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} - IMAGE_TAG: ${{ github.sha }} - run: | - # 현재 태스크 정의 가져오기 - TASK_DEFINITION=$(aws ecs describe-task-definition \ - --task-definition ${{ matrix.service.task_definition }} \ - --query 'taskDefinition' \ - --output json) - - # 새 이미지로 업데이트 - NEW_TASK_DEFINITION=$(echo $TASK_DEFINITION | jq \ - --arg IMAGE "$ECR_REGISTRY/${{ matrix.service.ecr_repo }}:$IMAGE_TAG" \ - '.containerDefinitions[0].image = $IMAGE | - del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)') - - # 새 태스크 정의 등록 - NEW_TASK_ARN=$(aws ecs register-task-definition \ - --cli-input-json "$NEW_TASK_DEFINITION" | \ - jq -r '.taskDefinition.taskDefinitionArn') - - echo "NEW_TASK_ARN=$NEW_TASK_ARN" >> $GITHUB_ENV - - # ======================================== - # 서비스 재시작 - # ======================================== - - name: Update ECS service - run: | - aws ecs update-service \ - --cluster ${{ env.ECS_CLUSTER }} \ - --service ${{ matrix.service.ecs_service }} \ - --task-definition ${{ env.NEW_TASK_ARN }} \ - --desired-count 1 \ - --force-new-deployment - - echo "✅ ${{ matrix.service.name }} service deployed!" + # 푸시 + docker push ${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-${{ github.sha }} + docker push ${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-latest # ======================================== - # 배포 상태 확인 (선택사항) + # 4. ECS 배포 (ap-northeast-2) + # ⚠️ ECS는 실제 리전 사용 # ======================================== - - name: Wait for service stability - run: | - echo "Waiting for service to be stable..." - aws ecs wait services-stable \ - --cluster ${{ env.ECS_CLUSTER }} \ - --services ${{ matrix.service.ecs_service }} - - echo "🚀 ${{ matrix.service.name }} deployment completed!" - - # ======================================== - # ECS 배포 - Batch (API 안정화 후) - # ======================================== - deploy-batch: - runs-on: ubuntu-latest - needs: deploy-main # ⭐ API 배포 완료 후 실행 - strategy: - matrix: - service: - - name: batch - ecs_service: monew-batch-service - task_definition: monew-batch-task - ecr_repo: monew-batch - - steps: - - name: Configure AWS credentials + - name: Configure AWS credentials for ECS uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} - aws-region: ${{ env.AWS_REGION }} - - - name: Login to Amazon ECR - id: login-ecr - uses: aws-actions/amazon-ecr-login@v2 + aws-region: ${{ env.AWS_REGION }} # ⭐ ECS는 ap-northeast-2 - # ======================================== - # 태스크 정의 업데이트 - # ======================================== - name: Update task definition - env: - ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} - IMAGE_TAG: ${{ github.sha }} run: | # 현재 태스크 정의 가져오기 TASK_DEFINITION=$(aws ecs describe-task-definition \ --task-definition ${{ matrix.service.task_definition }} \ - --query 'taskDefinition' \ - --output json) + --query 'taskDefinition') # 새 이미지로 업데이트 NEW_TASK_DEFINITION=$(echo $TASK_DEFINITION | jq \ - --arg IMAGE "$ECR_REGISTRY/${{ matrix.service.ecr_repo }}:$IMAGE_TAG" \ + --arg IMAGE "${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-latest" \ '.containerDefinitions[0].image = $IMAGE | del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)') @@ -221,9 +113,6 @@ jobs: echo "NEW_TASK_ARN=$NEW_TASK_ARN" >> $GITHUB_ENV - # ======================================== - # 서비스 재시작 - # ======================================== - name: Update ECS service run: | aws ecs update-service \ @@ -235,9 +124,6 @@ jobs: echo "✅ ${{ matrix.service.name }} service deployed!" - # ======================================== - # 배포 상태 확인 (선택사항) - # ======================================== - name: Wait for service stability run: | echo "Waiting for service to be stable..." From 58550375d90d2119484d719f72d5c2bf402c0f90 Mon Sep 17 00:00:00 2001 From: userjin2123 Date: Thu, 6 Nov 2025 11:14:29 +0900 Subject: [PATCH 05/16] =?UTF-8?q?trigger=20CD=20pipeline,=20IAM=20?= =?UTF-8?q?=EA=B6=8C=ED=95=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From dff4e9f5e69c23e45f0466c2e0381b38a6804086 Mon Sep 17 00:00:00 2001 From: userjin2123 Date: Thu, 6 Nov 2025 11:31:12 +0900 Subject: [PATCH 06/16] =?UTF-8?q?fix=20:=20=EA=B6=8C=ED=95=9C=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=EB=94=94=EB=B2=84=EA=B9=85=20=EC=9A=A9=20run=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80,=20=ED=81=B4=EB=9D=BC=EC=8A=A4=ED=84=B0=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20monew-cluster-1=20=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 929ad8c..6dfee8e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -8,7 +8,7 @@ on: env: AWS_REGION: ap-northeast-2 - ECS_CLUSTER: monew-cluster + ECS_CLUSTER: monew-cluster-1 ECR_REPOSITORY: public.ecr.aws/s7e2q2h9/monew jobs: @@ -61,6 +61,9 @@ jobs: aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} aws-region: us-east-1 # ⭐ Public ECR은 us-east-1만 사용 + - name: Debug AWS caller identity + run: aws sts get-caller-identity --region ${{ env.AWS_REGION }} + - name: Login to Public ECR run: | aws ecr-public get-login-password --region us-east-1 | \ @@ -92,6 +95,15 @@ jobs: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} aws-region: ${{ env.AWS_REGION }} # ⭐ ECS는 ap-northeast-2 + - name: Debug AWS caller identity (ECS creds) + run: aws sts get-caller-identity --region ${{ env.AWS_REGION }} + + - name: Dry-run DescribeTaskDefinition (debug) + run: | + aws ecs describe-task-definition \ + --task-definition ${{ matrix.service.task_definition }} \ + --region ${{ env.AWS_REGION }} \ + --query 'taskDefinition.taskDefinitionArn' --output text - name: Update task definition run: | From 36778544fcd994ecc4185b2f09ce0414dbe01176 Mon Sep 17 00:00:00 2001 From: userjin2123 Date: Thu, 6 Nov 2025 11:39:28 +0900 Subject: [PATCH 07/16] =?UTF-8?q?fix=20:=20=EB=A6=AC=EC=A0=84=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 83 ++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 46 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6dfee8e..d51720f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -2,12 +2,14 @@ name: CD - Deploy to ECS on: push: - branches: - - release + branches: [ release ] workflow_dispatch: env: - AWS_REGION: ap-northeast-2 + # ⭐ 리전 분리 + ECS_REGION: ap-northeast-2 + ECR_PUBLIC_REGION: us-east-1 + ECS_CLUSTER: monew-cluster-1 ECR_REPOSITORY: public.ecr.aws/s7e2q2h9/monew @@ -44,103 +46,92 @@ jobs: ecs_service: monew-grafana-service steps: - # ======================================== - # 1. 코드 체크아웃 - # ======================================== - name: Checkout code uses: actions/checkout@v4 - # ======================================== - # 2. Public ECR 로그인 (us-east-1) - # ⚠️ 중요: Public ECR은 us-east-1만 사용! - # ======================================== + # ===== Public ECR (us-east-1) ===== - name: Configure AWS credentials for Public ECR uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} - aws-region: us-east-1 # ⭐ Public ECR은 us-east-1만 사용 + aws-region: ${{ env.ECR_PUBLIC_REGION }} - - name: Debug AWS caller identity - run: aws sts get-caller-identity --region ${{ env.AWS_REGION }} + - name: Debug AWS caller identity (ECR creds) + run: aws sts get-caller-identity --region ${{ env.ECR_PUBLIC_REGION }} - name: Login to Public ECR run: | - aws ecr-public get-login-password --region us-east-1 | \ + aws ecr-public get-login-password --region ${{ env.ECR_PUBLIC_REGION }} | \ docker login --username AWS --password-stdin public.ecr.aws - # ======================================== - # 3. Docker 이미지 빌드 & 푸시 - # ======================================== - name: Build and push ${{ matrix.service.name }} image run: | - # 빌드 - docker build \ - -f ${{ matrix.service.dockerfile }} \ + set -euo pipefail + docker build -f ${{ matrix.service.dockerfile }} \ -t ${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-${{ github.sha }} \ - -t ${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-latest \ - . - - # 푸시 + -t ${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-latest . docker push ${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-${{ github.sha }} docker push ${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-latest - # ======================================== - # 4. ECS 배포 (ap-northeast-2) - # ⚠️ ECS는 실제 리전 사용 - # ======================================== + # ===== ECS (ap-northeast-2) ===== - name: Configure AWS credentials for ECS uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} - aws-region: ${{ env.AWS_REGION }} # ⭐ ECS는 ap-northeast-2 + aws-region: ${{ env.ECS_REGION }} + - name: Debug AWS caller identity (ECS creds) - run: aws sts get-caller-identity --region ${{ env.AWS_REGION }} + run: aws sts get-caller-identity --region ${{ env.ECS_REGION }} - name: Dry-run DescribeTaskDefinition (debug) run: | + set -euo pipefail + echo "Family: ${{ matrix.service.task_definition }} Region: ${{ env.ECS_REGION }}" aws ecs describe-task-definition \ --task-definition ${{ matrix.service.task_definition }} \ - --region ${{ env.AWS_REGION }} \ - --query 'taskDefinition.taskDefinitionArn' --output text + --region ${{ env.ECS_REGION }} \ + --query 'taskDefinition.taskDefinitionArn' --output text - name: Update task definition run: | - # 현재 태스크 정의 가져오기 + set -euo pipefail TASK_DEFINITION=$(aws ecs describe-task-definition \ --task-definition ${{ matrix.service.task_definition }} \ - --query 'taskDefinition') + --region ${{ env.ECS_REGION }} \ + --query 'taskDefinition' --output json) - # 새 이미지로 업데이트 - NEW_TASK_DEFINITION=$(echo $TASK_DEFINITION | jq \ + NEW_TASK_DEFINITION=$(echo "$TASK_DEFINITION" | jq \ --arg IMAGE "${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-latest" \ - '.containerDefinitions[0].image = $IMAGE | - del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)') + '.containerDefinitions[0].image = $IMAGE + | del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)') - # 새 태스크 정의 등록 NEW_TASK_ARN=$(aws ecs register-task-definition \ - --cli-input-json "$NEW_TASK_DEFINITION" | \ - jq -r '.taskDefinition.taskDefinitionArn') + --cli-input-json "$NEW_TASK_DEFINITION" \ + --region ${{ env.ECS_REGION }} \ + --query 'taskDefinition.taskDefinitionArn' --output text) echo "NEW_TASK_ARN=$NEW_TASK_ARN" >> $GITHUB_ENV - name: Update ECS service run: | + set -euo pipefail aws ecs update-service \ --cluster ${{ env.ECS_CLUSTER }} \ --service ${{ matrix.service.ecs_service }} \ --task-definition ${{ env.NEW_TASK_ARN }} \ --desired-count 1 \ - --force-new-deployment - + --force-new-deployment \ + --region ${{ env.ECS_REGION }} echo "✅ ${{ matrix.service.name }} service deployed!" - name: Wait for service stability run: | + set -euo pipefail echo "Waiting for service to be stable..." aws ecs wait services-stable \ --cluster ${{ env.ECS_CLUSTER }} \ - --services ${{ matrix.service.ecs_service }} - - echo "🚀 ${{ matrix.service.name }} deployment completed!" \ No newline at end of file + --services ${{ matrix.service.ecs_service }} \ + --region ${{ env.ECS_REGION }} + echo "🚀 ${{ matrix.service.name }} deployment completed!" From 4d2518518a5f6959ee046342ddf26f1be84c91f6 Mon Sep 17 00:00:00 2001 From: userjin2123 Date: Thu, 6 Nov 2025 14:33:52 +0900 Subject: [PATCH 08/16] =?UTF-8?q?fix=20:=20EFS=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80,=20deploy=EC=97=90=EC=84=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=EC=9C=BC=EB=A1=9C=20=EC=B6=94=EC=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 7 +++++-- Dockerfile.prom | 17 ++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d51720f..a10f00a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -104,8 +104,11 @@ jobs: NEW_TASK_DEFINITION=$(echo "$TASK_DEFINITION" | jq \ --arg IMAGE "${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-latest" \ - '.containerDefinitions[0].image = $IMAGE - | del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)') + --arg NAME "${{ matrix.service.name }}" ' + .containerDefinitions |= + ( map( if .name == $NAME then (.image = $IMAGE) else . end ) ) + | del(.taskDefinitionArn, .revision, .status, .requiresAttributes, + .compatibilities, .registeredAt, .registeredBy)') NEW_TASK_ARN=$(aws ecs register-task-definition \ --cli-input-json "$NEW_TASK_DEFINITION" \ diff --git a/Dockerfile.prom b/Dockerfile.prom index 51c3ac6..3b501d4 100644 --- a/Dockerfile.prom +++ b/Dockerfile.prom @@ -1,7 +1,18 @@ FROM prom/prometheus:latest -# Custom prometheus.yml 복사 +# 설정 파일 COPY monew-monitor/src/main/resources/prometheus.yml /etc/prometheus/prometheus.yml -# 포트 노출 -EXPOSE 9090 \ No newline at end of file +EXPOSE 9090 + +# 핵심: TSDB 경로를 /prometheus 로! +# (ECS 태스크에서 /prometheus를 EFS로 마운트했으므로 여기에 저장됨) +CMD [ \ + "--config.file=/etc/prometheus/prometheus.yml", \ + "--storage.tsdb.path=/prometheus", \ + "--storage.tsdb.retention.time=30d", \ + "--storage.tsdb.retention.size=10GB", \ + "--web.enable-admin-api", \ + "--web.console.libraries=/usr/share/prometheus/console_libraries", \ + "--web.console.templates=/usr/share/prometheus/consoles" \ +] \ No newline at end of file From 1819fbcaae7f97df8d86191c8494da760efb872a Mon Sep 17 00:00:00 2001 From: userjin2123 Date: Thu, 6 Nov 2025 15:27:22 +0900 Subject: [PATCH 09/16] =?UTF-8?q?fix=20:=20=EB=B0=B0=EC=B9=98=20applicatio?= =?UTF-8?q?n-prod.yml=EC=97=90=EC=84=9C=20monew.api.url=20=EB=88=84?= =?UTF-8?q?=EB=9D=BD=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- monew-batch/src/main/resources/application-prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monew-batch/src/main/resources/application-prod.yml b/monew-batch/src/main/resources/application-prod.yml index f4763f4..7c7b714 100644 --- a/monew-batch/src/main/resources/application-prod.yml +++ b/monew-batch/src/main/resources/application-prod.yml @@ -66,4 +66,4 @@ aws: monew: api: - url: ${http://monew-api.monew.local:8080} \ No newline at end of file + url: ${MONEW_API_URL:http://monew-app-alb-721921608.ap-northeast-2.elb.amazonaws.com/api} \ No newline at end of file From 940af7e3aec7a33279d563530fa05233a5a73740 Mon Sep 17 00:00:00 2001 From: userjin2123 Date: Thu, 6 Nov 2025 15:52:48 +0900 Subject: [PATCH 10/16] =?UTF-8?q?feat=20:=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EB=BF=90=EB=A7=8C=20?= =?UTF-8?q?=EC=95=84=EB=8B=88=EB=9D=BC=20=EC=A4=91=EC=A7=80,=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=EB=90=98=EC=97=88=EC=9D=84=EB=95=8C=20=EC=9E=AC?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 137 +++++++++++++++++++++++++++++------ 1 file changed, 115 insertions(+), 22 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a10f00a..5a84a38 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -6,13 +6,15 @@ on: workflow_dispatch: env: - # ⭐ 리전 분리 ECS_REGION: ap-northeast-2 ECR_PUBLIC_REGION: us-east-1 ECS_CLUSTER: monew-cluster-1 ECR_REPOSITORY: public.ecr.aws/s7e2q2h9/monew + PUBLIC_SUBNETS_CSV: subnet-00530329356c03add,subnet-008505c5a32cd0093,subnet-08601cd2c0aa46873,subnet-03f28f954846ad79d + ECS_SERVICE_SG: sg-0fbe8feeddc4e8195,sg-08a99e539bed73ed3 + jobs: build-and-deploy: runs-on: ubuntu-latest @@ -24,26 +26,41 @@ jobs: tag: monew-api task_definition: monew-api-task ecs_service: monew-api-service + container: monew-api-app + port: 8080 + target_group_arn: arn:aws:elasticloadbalancing:ap-northeast-2:381437600029:targetgroup/monew-api-target/188870153e8974df - name: batch dockerfile: Dockerfile.batch tag: monew-batch task_definition: monew-batch-task ecs_service: monew-batch-service + container: monew-batch-app + port: 8081 + target_group_arn: arn:aws:elasticloadbalancing:ap-northeast-2:381437600029:targetgroup/monew-batch-target/8c8fd5dc5924e8f6 - name: monitor dockerfile: Dockerfile.monitor tag: monew-monitor task_definition: monew-monitor-task ecs_service: monew-monitor-service + container: monew-monitor-app + port: 8082 + target_group_arn: arn:aws:elasticloadbalancing:ap-northeast-2:381437600029:targetgroup/monew-monitor-target/f2041a48b08d9ef9 - name: prometheus dockerfile: Dockerfile.prom tag: prometheus task_definition: monew-prometheus-task ecs_service: monew-prometheus-service + container: monew-prometheus-app + port: 9090 + target_group_arn: arn:aws:elasticloadbalancing:ap-northeast-2:381437600029:targetgroup/monew-prometheus-target/01ce4f2072a65a1f - name: grafana dockerfile: Dockerfile.grafana tag: grafana task_definition: monew-grafana-task ecs_service: monew-grafana-service + container: monew-grafana-app + port: 3000 + target_group_arn: arn:aws:elasticloadbalancing:ap-northeast-2:381437600029:targetgroup/monew-grafana-target/aece83e522ce15b5 steps: - name: Checkout code @@ -94,45 +111,121 @@ jobs: --region ${{ env.ECS_REGION }} \ --query 'taskDefinition.taskDefinitionArn' --output text - - name: Update task definition + - name: Update task definition (swap image) run: | set -euo pipefail - TASK_DEFINITION=$(aws ecs describe-task-definition \ - --task-definition ${{ matrix.service.task_definition }} \ - --region ${{ env.ECS_REGION }} \ + FAMILY="${{ matrix.service.task_definition }}" + REGION="${{ env.ECS_REGION }}" + CNAME="${{ matrix.service.container }}" + IMAGE="${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-latest" + + TD=$(aws ecs describe-task-definition \ + --task-definition "$FAMILY" \ + --region "$REGION" \ --query 'taskDefinition' --output json) - NEW_TASK_DEFINITION=$(echo "$TASK_DEFINITION" | jq \ - --arg IMAGE "${{ env.ECR_REPOSITORY }}:${{ matrix.service.tag }}-latest" \ - --arg NAME "${{ matrix.service.name }}" ' + echo "== Before =="; echo "$TD" | jq '.containerDefinitions[] | {name,image}' + + NEW_TD=$(echo "$TD" | jq \ + --arg IMAGE "$IMAGE" --arg CNAME "$CNAME" ' .containerDefinitions |= - ( map( if .name == $NAME then (.image = $IMAGE) else . end ) ) + ( map( if .name == $CNAME then (.image = $IMAGE) else . end ) ) | del(.taskDefinitionArn, .revision, .status, .requiresAttributes, - .compatibilities, .registeredAt, .registeredBy)') + .compatibilities, .registeredAt, .registeredBy)') + + echo "== After (preview) =="; echo "$NEW_TD" | jq '.containerDefinitions[] | {name,image}' NEW_TASK_ARN=$(aws ecs register-task-definition \ - --cli-input-json "$NEW_TASK_DEFINITION" \ - --region ${{ env.ECS_REGION }} \ + --cli-input-json "$NEW_TD" \ + --region "$REGION" \ --query 'taskDefinition.taskDefinitionArn' --output text) echo "NEW_TASK_ARN=$NEW_TASK_ARN" >> $GITHUB_ENV + echo "Registered: $NEW_TASK_ARN" - - name: Update ECS service + - name: Create or Update ECS service (auto) run: | set -euo pipefail - aws ecs update-service \ - --cluster ${{ env.ECS_CLUSTER }} \ - --service ${{ matrix.service.ecs_service }} \ - --task-definition ${{ env.NEW_TASK_ARN }} \ - --desired-count 1 \ - --force-new-deployment \ - --region ${{ env.ECS_REGION }} - echo "✅ ${{ matrix.service.name }} service deployed!" + CLUSTER="${{ env.ECS_CLUSTER }}" + SERVICE="${{ matrix.service.ecs_service }}" + REGION="${{ env.ECS_REGION }}" + TG_ARN="${{ matrix.service.target_group_arn }}" + CONTAINER="${{ matrix.service.container }}" + PORT="${{ matrix.service.port }}" + TASK_DEF="${{ env.NEW_TASK_ARN }}" + + # Network JSON (public subnets + monew-ecs-sg, public IP) + IFS=',' read -r -a SUBNETS <<< "${{ env.PUBLIC_SUBNETS_CSV }}" + SUBNET_JSON=$(printf '"%s",' "${SUBNETS[@]}"); SUBNET_JSON="[${SUBNET_JSON%,}]" + NET_JSON=$(jq -nc \ + --argjson subnets "$SUBNET_JSON" \ + --arg sg "${{ env.ECS_SERVICE_SG }}" \ + '{awsvpcConfiguration:{subnets: $subnets, securityGroups: [$sg], assignPublicIp: "ENABLED"}}') + + LB_JSON=$(jq -nc \ + --arg tg "$TG_ARN" --arg cn "$CONTAINER" --argjson cp "$PORT" \ + '[{targetGroupArn:$tg, containerName:$cn, containerPort:$cp}]') + + DESC=$(aws ecs describe-services --cluster "$CLUSTER" --services "$SERVICE" --region "$REGION" --output json || true) + FAIL_LEN=$(echo "$DESC" | jq -r '.failures | length // 0') + SVC_LEN=$(echo "$DESC" | jq -r '.services | length // 0') + STATUS=$(echo "$DESC" | jq -r '.services[0].status // empty') + + if [ "$FAIL_LEN" != "0" ] || [ "$SVC_LEN" = "0" ]; then + echo "🟢 Service not found → create-service" + aws ecs create-service \ + --cluster "$CLUSTER" \ + --service-name "$SERVICE" \ + --task-definition "$TASK_DEF" \ + --desired-count 1 \ + --launch-type FARGATE \ + --platform-version LATEST \ + --deployment-configuration "maximumPercent=200,minimumHealthyPercent=100" \ + --deployment-controller "type=ECS" \ + --enable-execute-command \ + --network-configuration "$NET_JSON" \ + --load-balancers "$LB_JSON" \ + --region "$REGION" + else + if [ "$STATUS" != "ACTIVE" ]; then + echo "🟠 Service exists but status=$STATUS → delete & recreate" + aws ecs delete-service --cluster "$CLUSTER" --service "$SERVICE" --force --region "$REGION" || true + for i in {1..30}; do + sleep 10 + D=$(aws ecs describe-services --cluster "$CLUSTER" --services "$SERVICE" --region "$REGION" --output json || true) + FL=$(echo "$D" | jq -r '.failures | length // 0') + SL=$(echo "$D" | jq -r '.services | length // 0') + [ "$FL" != "0" ] || [ "$SL" = "0" ] && break + echo "Waiting deletion..." + done + aws ecs create-service \ + --cluster "$CLUSTER" \ + --service-name "$SERVICE" \ + --task-definition "$TASK_DEF" \ + --desired-count 1 \ + --launch-type FARGATE \ + --platform-version LATEST \ + --deployment-configuration "maximumPercent=200,minimumHealthyPercent=100" \ + --deployment-controller "type=ECS" \ + --enable-execute-command \ + --network-configuration "$NET_JSON" \ + --load-balancers "$LB_JSON" \ + --region "$REGION" + else + echo "🔵 Service ACTIVE → update-service" + aws ecs update-service \ + --cluster "$CLUSTER" \ + --service "$SERVICE" \ + --task-definition "$TASK_DEF" \ + --desired-count 1 \ + --force-new-deployment \ + --region "$REGION" + fi + fi - name: Wait for service stability run: | set -euo pipefail - echo "Waiting for service to be stable..." aws ecs wait services-stable \ --cluster ${{ env.ECS_CLUSTER }} \ --services ${{ matrix.service.ecs_service }} \ From c9b94985fcfe57c5d01a99a230b50a9aec17d819 Mon Sep 17 00:00:00 2001 From: userjin2123 Date: Thu, 6 Nov 2025 16:17:38 +0900 Subject: [PATCH 11/16] =?UTF-8?q?feat=20:=20=EA=B8=B0=EC=82=AC=20=EC=88=98?= =?UTF-8?q?=EC=A7=91=20=EC=8A=A4=EC=BC=80=EC=A5=B4=EB=9F=AC=20cron?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD,=20deploy=20=EB=B3=B4?= =?UTF-8?q?=EC=95=88=EA=B7=B8=EB=A3=B9=20=EB=A7=A4=ED=95=91=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 25 +++++++++++++++---- .../scheduler/AricleBatchScheduler.java | 4 +-- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5a84a38..8a98c72 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -154,13 +154,28 @@ jobs: PORT="${{ matrix.service.port }}" TASK_DEF="${{ env.NEW_TASK_ARN }}" - # Network JSON (public subnets + monew-ecs-sg, public IP) + # Network JSON (public subnets + SGs[], public IP) IFS=',' read -r -a SUBNETS <<< "${{ env.PUBLIC_SUBNETS_CSV }}" - SUBNET_JSON=$(printf '"%s",' "${SUBNETS[@]}"); SUBNET_JSON="[${SUBNET_JSON%,}]" + SUBNET_JSON=$(printf '"%s",' "${SUBNETS[@]}") + SUBNET_JSON="[${SUBNET_JSON%,}]" + + SGS_STR="${{ env.ECS_SERVICE_SG }}" # 예: "sg-aaaa,sg-bbbb" + SG_JSON=$(jq -nc --arg s "$SGS_STR" '$s | split(",")') + NET_JSON=$(jq -nc \ - --argjson subnets "$SUBNET_JSON" \ - --arg sg "${{ env.ECS_SERVICE_SG }}" \ - '{awsvpcConfiguration:{subnets: $subnets, securityGroups: [$sg], assignPublicIp: "ENABLED"}}') + --argjson subnets "$SUBNET_JSON" \ + --argjson sgs "$SG_JSON" \ + '{ + awsvpcConfiguration: { + subnets: $subnets, + securityGroups: $sgs, + assignPublicIp: "ENABLED" + } + }') + + echo "Network: $NET_JSON" + + LB_JSON=$(jq -nc \ --arg tg "$TG_ARN" --arg cn "$CONTAINER" --argjson cp "$PORT" \ diff --git a/monew-batch/src/main/java/com/monew/monew_batch/article/scheduler/AricleBatchScheduler.java b/monew-batch/src/main/java/com/monew/monew_batch/article/scheduler/AricleBatchScheduler.java index 1077cf3..bfc5f3a 100644 --- a/monew-batch/src/main/java/com/monew/monew_batch/article/scheduler/AricleBatchScheduler.java +++ b/monew-batch/src/main/java/com/monew/monew_batch/article/scheduler/AricleBatchScheduler.java @@ -35,8 +35,8 @@ public AricleBatchScheduler( this.yonhapRssJob = yonhapRssJob; } -// @Scheduled(cron = "0 0 * * * *", zone = "Asia/Seoul") - @Scheduled(fixedRate = 600000) // 테스트용 + @Scheduled(cron = "0 0 * * * *", zone = "Asia/Seoul") +// @Scheduled(fixedRate = 600000) // 테스트용 public void runJob() throws Exception { log.info("🕒 [Batch Scheduler] 뉴스 수집 Job 실행"); From 5d811301991c8a98db8a44f104fc012074962f00 Mon Sep 17 00:00:00 2001 From: userjin2123 Date: Thu, 6 Nov 2025 17:05:21 +0900 Subject: [PATCH 12/16] =?UTF-8?q?feat=20:=20=EB=89=B4=EC=8A=A4=20=EB=B0=B1?= =?UTF-8?q?=EC=97=85=20=EC=8A=A4=EC=BC=80=EC=A5=B4=EB=9F=AC=20cron?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../monew_batch/article/scheduler/AricleBackupScheduler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monew-batch/src/main/java/com/monew/monew_batch/article/scheduler/AricleBackupScheduler.java b/monew-batch/src/main/java/com/monew/monew_batch/article/scheduler/AricleBackupScheduler.java index 1dfe588..dcbcd4c 100644 --- a/monew-batch/src/main/java/com/monew/monew_batch/article/scheduler/AricleBackupScheduler.java +++ b/monew-batch/src/main/java/com/monew/monew_batch/article/scheduler/AricleBackupScheduler.java @@ -19,8 +19,8 @@ public class AricleBackupScheduler { private final AricleBackupService aricleBackupService; -// @Scheduled(cron = "0 20 4 * * *", zone = "Asia/Seoul") - @Scheduled(fixedRate = 600000) // 테스트용 + @Scheduled(cron = "0 20 4 * * *", zone = "Asia/Seoul") +// @Scheduled(fixedRate = 600000) // 테스트용 public void backupNews() { log.info("🗄 뉴스 백업 시작"); aricleBackupService.backupAllArticles(); From b8a07ab97e0fdcbec82e506ed98bfde8a4aea1f2 Mon Sep 17 00:00:00 2001 From: userjin2123 Date: Thu, 6 Nov 2025 17:30:47 +0900 Subject: [PATCH 13/16] =?UTF-8?q?feat=20:=20=ED=94=84=EB=A1=9C=EB=A9=94?= =?UTF-8?q?=ED=85=8C=EC=9A=B0=EC=8A=A4=20=EC=99=84=EC=A0=84=20=EC=A2=85?= =?UTF-8?q?=EB=A3=8C=20=EC=9D=B4=ED=9B=84=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EC=8B=9C=EC=9E=91=EC=9C=BC=EB=A1=9C=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B0=94=EA=BF=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존에 락이 걸리던게 헬스체크 0만 가지고는 해결이 안되어서 완전 종료 이후 재시작 로직으로 변경 --- .github/workflows/deploy.yml | 88 +++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 32 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8a98c72..e218d7a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -154,28 +154,15 @@ jobs: PORT="${{ matrix.service.port }}" TASK_DEF="${{ env.NEW_TASK_ARN }}" - # Network JSON (public subnets + SGs[], public IP) - IFS=',' read -r -a SUBNETS <<< "${{ env.PUBLIC_SUBNETS_CSV }}" - SUBNET_JSON=$(printf '"%s",' "${SUBNETS[@]}") - SUBNET_JSON="[${SUBNET_JSON%,}]" + # --- Network (CLI shorthand: 배열 안전 전달) --- + IFS=',' read -r -a SUBNETS_ARR <<< "${{ env.PUBLIC_SUBNETS_CSV }}" + SUBNETS_SH=$(printf '%s,' "${SUBNETS_ARR[@]}"); SUBNETS_SH="[${SUBNETS_SH%,}]" - SGS_STR="${{ env.ECS_SERVICE_SG }}" # 예: "sg-aaaa,sg-bbbb" - SG_JSON=$(jq -nc --arg s "$SGS_STR" '$s | split(",")') + IFS=',' read -r -a SGS_ARR <<< "${{ env.ECS_SERVICE_SG }}" + SGS_SH=$(printf '%s,' "${SGS_ARR[@]}"); SGS_SH="[${SGS_SH%,}]" - NET_JSON=$(jq -nc \ - --argjson subnets "$SUBNET_JSON" \ - --argjson sgs "$SG_JSON" \ - '{ - awsvpcConfiguration: { - subnets: $subnets, - securityGroups: $sgs, - assignPublicIp: "ENABLED" - } - }') - - echo "Network: $NET_JSON" - - + NET_SH="awsvpcConfiguration={subnets=${SUBNETS_SH},securityGroups=${SGS_SH},assignPublicIp=ENABLED}" + echo "Network(shorthand): $NET_SH" LB_JSON=$(jq -nc \ --arg tg "$TG_ARN" --arg cn "$CONTAINER" --argjson cp "$PORT" \ @@ -186,6 +173,13 @@ jobs: SVC_LEN=$(echo "$DESC" | jq -r '.services | length // 0') STATUS=$(echo "$DESC" | jq -r '.services[0].status // empty') + # 배포 정책: Prometheus만 겹침 금지(0/100), 나머지는 기본(100/200) + if [ "$SERVICE" = "monew-prometheus-service" ]; then + DEPLOY_CONF='maximumPercent=100,minimumHealthyPercent=0' + else + DEPLOY_CONF='maximumPercent=200,minimumHealthyPercent=100' + fi + if [ "$FAIL_LEN" != "0" ] || [ "$SVC_LEN" = "0" ]; then echo "🟢 Service not found → create-service" aws ecs create-service \ @@ -195,10 +189,10 @@ jobs: --desired-count 1 \ --launch-type FARGATE \ --platform-version LATEST \ - --deployment-configuration "maximumPercent=200,minimumHealthyPercent=100" \ + --deployment-configuration "$DEPLOY_CONF" \ --deployment-controller "type=ECS" \ --enable-execute-command \ - --network-configuration "$NET_JSON" \ + --network-configuration "$NET_SH" \ --load-balancers "$LB_JSON" \ --region "$REGION" else @@ -220,21 +214,51 @@ jobs: --desired-count 1 \ --launch-type FARGATE \ --platform-version LATEST \ - --deployment-configuration "maximumPercent=200,minimumHealthyPercent=100" \ + --deployment-configuration "$DEPLOY_CONF" \ --deployment-controller "type=ECS" \ --enable-execute-command \ - --network-configuration "$NET_JSON" \ + --network-configuration "$NET_SH" \ --load-balancers "$LB_JSON" \ --region "$REGION" else - echo "🔵 Service ACTIVE → update-service" - aws ecs update-service \ - --cluster "$CLUSTER" \ - --service "$SERVICE" \ - --task-definition "$TASK_DEF" \ - --desired-count 1 \ - --force-new-deployment \ - --region "$REGION" + echo "🔵 Service ACTIVE" + if [ "$SERVICE" = "monew-prometheus-service" ]; then + echo "🧯 Prometheus: scale down to 0 first (avoid TSDB lock)" + aws ecs update-service \ + --cluster "$CLUSTER" \ + --service "$SERVICE" \ + --desired-count 0 \ + --region "$REGION" + + # 모든 태스크 정지 대기 + for i in {1..60}; do + TASKS=$(aws ecs list-tasks --cluster "$CLUSTER" --service-name "$SERVICE" --region "$REGION" --query 'taskArns' --output json) + if [ "$TASKS" = "[]" ]; then + echo "All prometheus tasks stopped." + break + fi + echo "Waiting prometheus tasks to stop..." + sleep 5 + done + + echo "🔁 Update task def & scale up to 1" + aws ecs update-service \ + --cluster "$CLUSTER" \ + --service "$SERVICE" \ + --task-definition "$TASK_DEF" \ + --desired-count 1 \ + --force-new-deployment \ + --region "$REGION" + else + echo "🔁 Regular rolling update" + aws ecs update-service \ + --cluster "$CLUSTER" \ + --service "$SERVICE" \ + --task-definition "$TASK_DEF" \ + --desired-count 1 \ + --force-new-deployment \ + --region "$REGION" + fi fi fi From 2c4ecbf3b43a0617e07ddae016ad3f3b0bb2c26d Mon Sep 17 00:00:00 2001 From: userjin2123 Date: Thu, 6 Nov 2025 23:20:36 +0900 Subject: [PATCH 14/16] =?UTF-8?q?feat=20:=20=EC=9C=A0=EC=A0=80=20=ED=99=9C?= =?UTF-8?q?=EB=8F=99=20=EC=84=B1=EB=8A=A5=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=9A=A9=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UserActivityPerfController.java | 46 +++++++++++++++++++ .../src/main/resources/application-prod.yml | 2 +- 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 monew-api/src/main/java/com/monew/monew_api/useractivity/controller/UserActivityPerfController.java diff --git a/monew-api/src/main/java/com/monew/monew_api/useractivity/controller/UserActivityPerfController.java b/monew-api/src/main/java/com/monew/monew_api/useractivity/controller/UserActivityPerfController.java new file mode 100644 index 0000000..6037860 --- /dev/null +++ b/monew-api/src/main/java/com/monew/monew_api/useractivity/controller/UserActivityPerfController.java @@ -0,0 +1,46 @@ +package com.monew.monew_api.useractivity.controller; + +import com.monew.monew_api.useractivity.dto.UserActivityDto; +import com.monew.monew_api.useractivity.service.UserActivityCacheService; +import com.monew.monew_api.useractivity.service.UserActivityService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@Slf4j +public class UserActivityPerfController { + + private final UserActivityCacheService cacheService; + private final UserActivityService userActivityService; + + @GetMapping("/test/user-activity/{userId}") + public UserActivityDto testUserActivity(@PathVariable String userId, + @RequestParam(defaultValue = "cache") String mode) { + // mode 값: cache | single | multi + long start = System.currentTimeMillis(); + + try { + switch (mode) { + case "cache": + return cacheService.getUserActivityWithCache(userId); + + case "single": + return userActivityService.getUserActivitySingleQuery(userId); + + case "multi": + return userActivityService.getUserActivity(userId); + + default: + throw new IllegalArgumentException("mode 파라미터는 cache | single | multi 중 하나여야 합니다."); + } + } finally { + long elapsed = System.currentTimeMillis() - start; + log.info("[PERF] mode={} took={} ms", mode, elapsed); + } + } +} diff --git a/monew-batch/src/main/resources/application-prod.yml b/monew-batch/src/main/resources/application-prod.yml index 7c7b714..f51b6c1 100644 --- a/monew-batch/src/main/resources/application-prod.yml +++ b/monew-batch/src/main/resources/application-prod.yml @@ -66,4 +66,4 @@ aws: monew: api: - url: ${MONEW_API_URL:http://monew-app-alb-721921608.ap-northeast-2.elb.amazonaws.com/api} \ No newline at end of file + url: ${MONEW_API_URL:http://monew-alb-721921608.ap-northeast-2.elb.amazonaws.com} \ No newline at end of file From 4e6fc5039a23eb54b7841ba305cdedc56d1a743e Mon Sep 17 00:00:00 2001 From: userjin2123 Date: Thu, 6 Nov 2025 23:42:58 +0900 Subject: [PATCH 15/16] =?UTF-8?q?fix=20:=20=EC=9C=A0=EC=A0=80=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20/api=20=EB=88=84=EB=9D=BD=EB=90=9C=EA=B2=83=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../useractivity/controller/UserActivityPerfController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monew-api/src/main/java/com/monew/monew_api/useractivity/controller/UserActivityPerfController.java b/monew-api/src/main/java/com/monew/monew_api/useractivity/controller/UserActivityPerfController.java index 6037860..9912e2f 100644 --- a/monew-api/src/main/java/com/monew/monew_api/useractivity/controller/UserActivityPerfController.java +++ b/monew-api/src/main/java/com/monew/monew_api/useractivity/controller/UserActivityPerfController.java @@ -18,7 +18,7 @@ public class UserActivityPerfController { private final UserActivityCacheService cacheService; private final UserActivityService userActivityService; - @GetMapping("/test/user-activity/{userId}") + @GetMapping("/api/test/user-activity/{userId}") public UserActivityDto testUserActivity(@PathVariable String userId, @RequestParam(defaultValue = "cache") String mode) { // mode 값: cache | single | multi From 4120a542762466614e6f2a3c45746d48a05cad8b Mon Sep 17 00:00:00 2001 From: userjin2123 Date: Fri, 7 Nov 2025 01:08:34 +0900 Subject: [PATCH 16/16] =?UTF-8?q?feat=20:=20add=20=EB=89=B4=EC=8A=A4=20?= =?UTF-8?q?=EA=B8=B0=EC=82=AC=20=EC=A0=9C=EB=AA=A9=EC=97=90=20`"`=20?= =?UTF-8?q?=EA=B0=80=20=EC=9E=88=EC=9C=BC=EB=A9=B4=20"=EB=A1=9C=20html?= =?UTF-8?q?=20=EC=9D=B8=EC=BD=94=EB=94=A9=20=EB=AC=B8=EC=9E=90=EB=A1=9C=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=EB=90=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20Map?= =?UTF-8?q?per=EC=97=90=EC=84=9C=20=ED=8C=8C=EC=8B=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/ArticleViewActivityDto.java | 6 +- .../useractivity/dto/CommentActivityDto.java | 6 +- .../dto/CommentLikeActivityDto.java | 6 +- .../mapper/UserActivityRawMapper.java | 55 ++++++++++++++++--- 4 files changed, 54 insertions(+), 19 deletions(-) diff --git a/monew-api/src/main/java/com/monew/monew_api/useractivity/dto/ArticleViewActivityDto.java b/monew-api/src/main/java/com/monew/monew_api/useractivity/dto/ArticleViewActivityDto.java index 93ee743..d1cc74f 100644 --- a/monew-api/src/main/java/com/monew/monew_api/useractivity/dto/ArticleViewActivityDto.java +++ b/monew-api/src/main/java/com/monew/monew_api/useractivity/dto/ArticleViewActivityDto.java @@ -2,14 +2,12 @@ import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import java.time.LocalDateTime; @Getter +@Setter @Builder @NoArgsConstructor @AllArgsConstructor diff --git a/monew-api/src/main/java/com/monew/monew_api/useractivity/dto/CommentActivityDto.java b/monew-api/src/main/java/com/monew/monew_api/useractivity/dto/CommentActivityDto.java index f713a95..559ec90 100644 --- a/monew-api/src/main/java/com/monew/monew_api/useractivity/dto/CommentActivityDto.java +++ b/monew-api/src/main/java/com/monew/monew_api/useractivity/dto/CommentActivityDto.java @@ -2,14 +2,12 @@ import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import java.time.LocalDateTime; @Getter +@Setter @Builder @NoArgsConstructor @AllArgsConstructor diff --git a/monew-api/src/main/java/com/monew/monew_api/useractivity/dto/CommentLikeActivityDto.java b/monew-api/src/main/java/com/monew/monew_api/useractivity/dto/CommentLikeActivityDto.java index 068cf1c..abf4ee9 100644 --- a/monew-api/src/main/java/com/monew/monew_api/useractivity/dto/CommentLikeActivityDto.java +++ b/monew-api/src/main/java/com/monew/monew_api/useractivity/dto/CommentLikeActivityDto.java @@ -2,14 +2,12 @@ import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import java.time.LocalDateTime; @Getter +@Setter @Builder @NoArgsConstructor @AllArgsConstructor diff --git a/monew-api/src/main/java/com/monew/monew_api/useractivity/mapper/UserActivityRawMapper.java b/monew-api/src/main/java/com/monew/monew_api/useractivity/mapper/UserActivityRawMapper.java index 25626d8..be5d5b5 100644 --- a/monew-api/src/main/java/com/monew/monew_api/useractivity/mapper/UserActivityRawMapper.java +++ b/monew-api/src/main/java/com/monew/monew_api/useractivity/mapper/UserActivityRawMapper.java @@ -8,6 +8,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import org.springframework.web.util.HtmlUtils; import java.util.Collections; import java.util.List; @@ -19,15 +20,12 @@ public class UserActivityRawMapper { private final ObjectMapper objectMapper; - /** - * UserActivityRaw (Record) → UserActivityDto 변환 - */ public UserActivityDto toDto(UserActivityRaw record) { if (record == null) { return null; } - return UserActivityDto.builder() + UserActivityDto dto = UserActivityDto.builder() .id(String.valueOf(record.id())) .email(record.email()) .nickname(record.nickname()) @@ -49,11 +47,13 @@ public UserActivityDto toDto(UserActivityRaw record) { new TypeReference>() {} )) .build(); + + // HTML 엔티티 디코딩 + decodeHtmlEntities(dto); + + return dto; } - /** - * JSON String → List 파싱 - */ private List parseJsonList(String json, TypeReference> typeRef) { if (json == null || json.isBlank() || "[]".equals(json.trim())) { return Collections.emptyList(); @@ -67,4 +67,45 @@ private List parseJsonList(String json, TypeReference> typeRef) { return Collections.emptyList(); } } + + /** + * HTML 엔티티 디코딩 (" → " 등) + */ + private void decodeHtmlEntities(UserActivityDto dto) { + // ArticleViews + if (dto.getArticleViews() != null) { + dto.getArticleViews().forEach(av -> { + if (av.getArticleTitle() != null) { + av.setArticleTitle(HtmlUtils.htmlUnescape(av.getArticleTitle())); + } + if (av.getArticleSummary() != null) { + av.setArticleSummary(HtmlUtils.htmlUnescape(av.getArticleSummary())); + } + }); + } + + // Comments + if (dto.getComments() != null) { + dto.getComments().forEach(c -> { + if (c.getContent() != null) { + c.setContent(HtmlUtils.htmlUnescape(c.getContent())); + } + if (c.getArticleTitle() != null) { + c.setArticleTitle(HtmlUtils.htmlUnescape(c.getArticleTitle())); + } + }); + } + + // CommentLikes + if (dto.getCommentLikes() != null) { + dto.getCommentLikes().forEach(cl -> { + if (cl.getArticleTitle() != null) { + cl.setArticleTitle(HtmlUtils.htmlUnescape(cl.getArticleTitle())); + } + if (cl.getCommentContent() != null) { + cl.setCommentContent(HtmlUtils.htmlUnescape(cl.getCommentContent())); + } + }); + } + } } \ No newline at end of file