diff --git a/.github/workflows/cd-dev.yaml b/.github/workflows/cd-dev.yaml new file mode 100644 index 0000000..5668f09 --- /dev/null +++ b/.github/workflows/cd-dev.yaml @@ -0,0 +1,29 @@ +name: gateway-service dev CD 파이프라인 + +on: + workflow_run: + workflows: ["gateway-service CI pipeline"] + types: + - completed + +jobs: + deploy: + if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + environment: dev + permissions: + contents: read + + steps: + - name: Docker 이미지 dev 서버 배포 + uses: appleboy/ssh-action@master + with: + host: ${{secrets.DEV_HOST}} + username: ${{secrets.DEV_USERNAME}} + key: ${{secrets.DEV_KEY}} + script: | + cd /home/ubuntu + docker rm -f gateway-service-dev || true + docker compose pull gateway-service-dev + docker compose up -d --no-deps --force-recreate --pull always gateway-service-dev + docker image prune -f diff --git a/.github/workflows/cd-prod.yaml b/.github/workflows/cd-prod.yaml new file mode 100644 index 0000000..d1b345f --- /dev/null +++ b/.github/workflows/cd-prod.yaml @@ -0,0 +1,29 @@ +name: gateway-service prod CD 파이프라인 + +on: + workflow_run: + workflows: ["gateway-service CI pipeline"] + types: + - completed + +jobs: + deploy: + if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + environment: prod + permissions: + contents: read + + steps: + - name: Docker 이미지 dev 서버 배포 + uses: appleboy/ssh-action@master + with: + host: ${{secrets.PROD_HOST}} + username: ${{secrets.PROD_USERNAME}} + key: ${{secrets.PROD_KEY}} + script: | + cd /home/ubuntu + docker rm -f gateway-service-prod || true + docker compose pull gateway-service-prod + docker compose up -d --no-deps --force-recreate --pull always gateway-service-prod + docker image prune -f diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..fc2b4c0 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,46 @@ +name: gateway-service CI pipeline + +on: + push: + branches: + - dev + pull_request: + branches: + - dev + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + environment: production + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: jdk 설정 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + cache: 'gradle' + + - name: Gradle Wrapper 권한 부여 + run: chmod +x gradlew + + - name: gradle 빌드 + run: ./gradlew clean build + + - name: 도커 로그인 + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} + + - name: 이미지 빌드 및 푸시 + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + tags: ${{ secrets.DOCKER_USERNAME }}/unionmate-gateway-service:latest + push: true diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6532d51 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM openjdk:21-jdk + +COPY build/libs/*SNAPSHOT.jar app.jar + +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -Dspring.profiles.active=${PROFILE} -jar /app.jar"] diff --git a/build.gradle b/build.gradle index 9cd88d5..6e011c3 100644 --- a/build.gradle +++ b/build.gradle @@ -23,11 +23,14 @@ ext { } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.cloud:spring-cloud-starter-gateway-server-webmvc' implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' + implementation 'org.springframework.boot:spring-boot-starter-webflux' + implementation 'org.springframework.cloud:spring-cloud-starter-gateway' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + //swagger + implementation 'org.springdoc:springdoc-openapi-starter-webflux-ui:2.8.8' } dependencyManagement { diff --git a/src/main/java/com/unionmate/gateway_service/GatewayServiceApplication.java b/src/main/java/com/unionmate/gateway_service/GatewayServiceApplication.java index 599d998..4180897 100644 --- a/src/main/java/com/unionmate/gateway_service/GatewayServiceApplication.java +++ b/src/main/java/com/unionmate/gateway_service/GatewayServiceApplication.java @@ -2,6 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.context.annotation.Bean; +import org.springframework.web.client.RestClient; +import org.springframework.web.reactive.function.client.WebClient; @SpringBootApplication public class GatewayServiceApplication { @@ -10,4 +14,15 @@ public static void main(String[] args) { SpringApplication.run(GatewayServiceApplication.class, args); } + @Bean + @LoadBalanced + public WebClient.Builder loadBalancedWebClientBuilder() { + final WebClient.Builder builder = WebClient.builder(); + return builder; + } + + @Bean + public RestClient.Builder restClientBuilder() { + return RestClient.builder(); + } } diff --git a/src/main/java/com/unionmate/gateway_service/global/SecurityConfig.java b/src/main/java/com/unionmate/gateway_service/global/SecurityConfig.java new file mode 100644 index 0000000..8ac80b5 --- /dev/null +++ b/src/main/java/com/unionmate/gateway_service/global/SecurityConfig.java @@ -0,0 +1,28 @@ +package com.unionmate.gateway_service.global; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.reactive.CorsWebFilter; +import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; + +@Configuration +public class SecurityConfig { + @Bean + public CorsWebFilter corsWebFilter() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowedOrigins(List.of("http://localhost:3000")); + config.setAllowedMethods(Arrays.asList("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); + config.setAllowCredentials(true); + config.setAllowedHeaders(List.of("*")); + config.setExposedHeaders(List.of("Authorization", "Authorization-refresh")); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + + return new CorsWebFilter(source); + } +} diff --git a/src/main/java/com/unionmate/gateway_service/global/gateway/GatewayConfiguration.java b/src/main/java/com/unionmate/gateway_service/global/gateway/GatewayConfiguration.java new file mode 100644 index 0000000..2115612 --- /dev/null +++ b/src/main/java/com/unionmate/gateway_service/global/gateway/GatewayConfiguration.java @@ -0,0 +1,33 @@ +package com.unionmate.gateway_service.global.gateway; + +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.http.HttpHeaders; + +@Configuration +@Profile({"local","dev","prod"}) +public class GatewayConfiguration { + + @Bean + public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { + return builder.routes() + // 인증 필요 없는 라우트 + // path 경로는 추후 개발에 진행되며 수정될 예정 + .route("backend_route", r -> r.path("/back") + .filters(f -> f + .removeRequestHeader(HttpHeaders.COOKIE) + ) + .uri("lb://backend-service")) + + //스웨거를 위한 라우트 설정(각 서비스마다 등록해줘야 합니다.) + .route("backend-service_api_docs", r -> r.path("/api-docs/backend/**") + .filters(f -> f + .rewritePath("/api-docs/backend/(?.*)", "/${rem}") + ) + .uri("lb://backend-service")) + .build(); + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..e072e90 --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,24 @@ +server: + port: 8000 + +spring: + main: + web-application-type: reactive + application: + name: gateway-service + +eureka: + client: + fetch-registry: true + register-with-eureka: true + service-url: + defaultZone: http://3.34.87.16:8761/eureka + +springdoc: + api-docs: + enabled: true + swagger-ui: + path: /swagger-ui.html + urls: + - name: backend-service + url: /api-docs/backend/v3/api-docs \ No newline at end of file diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml new file mode 100644 index 0000000..3a35d3e --- /dev/null +++ b/src/main/resources/application-local.yml @@ -0,0 +1,24 @@ +server: + port: 8000 + +spring: + main: + web-application-type: reactive + application: + name: gateway-service + +eureka: + client: + fetch-registry: true + register-with-eureka: true + service-url: + defaultZone: http://localhost:8761/eureka + +springdoc: + api-docs: + enabled: true + swagger-ui: + path: /swagger-ui.html + urls: + - name: backend-service + url: /api-docs/backend/v3/api-docs diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000..a76ab54 --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,25 @@ +server: + port: 8000 + +spring: + main: + web-application-type: reactive + application: + name: gateway-service + +eureka: + client: + fetch-registry: true + register-with-eureka: true + service-url: + defaultZone: http://discovery-service-prod:8761/eureka + +springdoc: + api-docs: + enabled: true + swagger-ui: + path: /swagger-ui.html + urls: + - name: backend-service + url: /api-docs/backend/v3/api-docs + diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index 7ad05e5..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=gateway_service