[MSA] e-commerce API; 프로젝트 흐름
[MSA] e-commerce API; 구현1 - 설정(config-server, eureka-server, gateway)
[MSA] e-commerce API; 구현2 - customer, product, payment, order, notification
먼저, 최상위 디렉토리를 생성합니다.
관리할 항목들에 대한 디렉토리를 생성하고, docker-compose.yml을 작성합니다.
0. docker-compose.yml
주요 항목은 4 가지 입니다.
services, network, volumes, secrets
services는 postgres, mongodb, kafka 등의 서비스를 담당합니다.
networks는 각 컨테이너의 네트워크 설정을 담당합니다.
volumes는 컨테이너 저장공간을,
sercrets는 컨테이너 설정 시 비밀번호와 같은 보안 파일을 담당합니다.
secrets에서 선언하고 services의 environment에서 사용합니다.
secrets는 _FILE변수에 사용합니다.
(구체적인 예시는 아래를 참조합니다)
각 컨테이너는 네트워크가 격리되어 있습니다.
따라서 networks 설정을 통해 하나의 네트워크로 연결하여 상호작용하도록 합니다.
services:
postgres:
container_name: ms_pg_sql
image: postgres
ports:
- "5432:5432"
secrets:
- postgres_user
- postgres_password
environment:
- POSTGRES_USER_FILE=/run/secrets/postgres_user
- POSTGRES_PASSWORD_FILE=/run/secrets/postgres_password
- PGDATA=/var/lib/postgresql/data
volumes:
- postgres:/var/lib/postgresql/data
networks:
- microservices-net
restart: unless-stopped
mongodb:
container_name: ms_mongo_db
image: mongo
ports:
- "27017:27017"
secrets:
- mongo_username
- mongo_password
environment:
- MONGO_INITDB_ROOT_USERNAME_FILE=/run/secrets/mongo_username
- MONGO_INITDB_ROOT_PASSWORD_FILE=/run/secrets/mongo_password
volumes:
- mongo:/data
mongo-express:
container_name: ms_mongo_express
image: mongo-express
ports:
- "8081:8081"
secrets:
- mongo_admin_username
- mongo_admin_password
environment:
- ME_CONFIG_MONGODB_ENABLE_ADMIN=true
- ME_CONFIG_MONGODB_ADMINUSERNAME_FILE=/run/secrets/mongo_admin_username
- ME_CONFIG_MONGODB_ADMINPASSWORD_FILE=/run/secrets/mongo_admin_password
- ME_CONFIG_MONGODB_SERVER=mongodb
restart: unless-stopped
zookeeper:
image: confluentinc/cp-zookeeper:latest
container_name: zookeeper
ports:
- "2181:2181"
environment:
- ZOOKEEPER_SERVER_ID=1
- ZOOKEEPER_CLIENT_PORT=2181
- ZOOKEEPER_TICK_TIME=2000
networks:
- microservices-net
kafka:
image: confluentinc/cp-kafka:latest
container_name: ms_kafka
depends_on:
- zookeeper
ports:
- "9092:9092"
environment:
- KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1
- KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=1
- KAFKA_TRANSACTION_STATE_LOG_MIN_ISR=1
- KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181
- KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
- KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092
networks:
- microservices-net
mail-dev:
container_name: ms_mail_dev
image: maildev/maildev
ports:
- "1080:1080"
- "1025:1025"
zipkin:
container_name: ms_zipkin
image: openzipkin/zipkin
ports:
- "9411:9411"
networks:
- microservices-net
networks:
microservices-net:
driver: bridge
volumes:
postgres:
mongo:
secrets:
postgres_user:
file: ./secrets/postgres_user.txt
postgres_password:
file: ./secrets/postgres_password.txt
mongo_username:
file: ./secrets/mongo_username.txt
mongo_password:
file: ./secrets/mongo_password.txt
mongo_admin_username:
file: ./secrets/mongo_admin_username.txt
mongo_admin_password:
file: ./secrets/mongo_admin_password.txt
(이 외 부분의 최적화는 최적화 게시글에서 다루겠습니다)
1. Config Server
Config Server에서는 Spring Cloud 프로젝트의 구성관리를 담당합니다.
Eureka에 등록할 서비스의 yml 파일을 관리합니다.
필요한 의존성은 아래와 같습니다.
implementation 'org.springframework.cloud:spring-cloud-config-server'
ConfigServer의 스프링 컨테이너가 가동될 main클래스에,
@EnableConfigServer 어노테이션을 선언합니다.
package com.ride.configserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
resources에 configurations 디렉토리를 생성하여,
공통 구성파일(application.yml), 각 서비스별 구성 파일을 작성하고 관리합니다.
config-server의 application.yml은 아래와 같습니다.
server:
port: 8888
spring:
profiles:
active: native
application:
name: config-server
cloud:
config:
server:
native:
search-locations: classpath:/configurations
server.port 을 통해 config-server가 수신할 포트를 지정합니다.
spring.profiles.active 를 native로 설정하여,
로컬 파일 시스템에서 다른 설정 파일(각 클라이언트 별 yml, properties 파일)을 읽어올 수 있도록 합니다.
spring.cloud.config.server.native.search-locations 설정을 통해,
config-server가 configurations디렉토리에서 설정 파일을 읽어오도록 합니다.
configurations의 공통 구성관리 파일 application.yml은 아래와 같습니다.
eureka:
instance:
hostname: localhost
client:
service-url:
defaultZone: http://localhost:8761/eureka
spring:
cloud:
config:
override-system-properties: false
management:
tracing:
sampling:
probability: 1.0
eureka.instance.hostname 을 통해 Eureka 서버에 등록하고 관리할 hostname을 설정합니다.
Eureka 클라이언트(각 서비스)는 이렇게 설정된 hostname를 통해 Eureka 서버에 접근합니다.
eureka.client.service-url.defaultZone 설정을 통해,
Eureka 클라이언트(각 서비스)가 Eureka 서버에 접근하기 위해 Eureka Server 의 기본 URL을 설정하고,
클라이언트(각 서비스)가 Eureka 서버에 등록됩니다.
(Eureka Client 의존성이 있는 서비스만 등록할 수 있습니다)
등록된 서비스들은 http://localhost:8761/eureka 에서 관리됩니다.
spring.cloud.config.override-system-properties 설정을 통해,
Spring Cloud Config가 시스템 프로퍼티를 덮어쓰지 않도록 합니다.
management.tracing.sampling.probability 는 분산 추적에 대한 설정입니다.
0.0에서 1.0까지 설정할 수 있으며, 요청 중 몇 퍼센트를 추적할지 설정합니다.
개발 환경에서는 샘플링 확률을 높게 설정하고,
운영 환경에서는 낮게 설정하여 성능에 미치는 영향을 최소화할 수 있습니다.
저는 개발 환경이기 때문에 1.0, 모든 요청을 샘플링하도록 설정했습니다.
추후에 다른 서비스를 구현하면,
그 서비스의 구성관리 파일을 config-server의 configurations 디렉토리에 생성합니다.
2. Eureka(Discovery) Server
Eureka Server는 Config Server의 설정 정보를 바탕으로 Eureka Client를 관리합니다.
주요 의존성은 아래와 같습니다.
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.cloud:spring-cloud-starter-config'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
Config Server와 마찬가지로 main 클래스에 어노테이션을 추가하여 Eureka Server 임을 알림니다.
package com.ride.discovery;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class DiscoveryApplication {
public static void main(String[] args) {
SpringApplication.run(DiscoveryApplication.class, args);
}
}
Eureka Server (다른 방식으로 표현하면), discovery service 의 application.yml을 작성합니다.
spring:
application:
name: discovery-service
config:
import: optional:configserver:http://localhost:8888
spring.config.import 설정을 통해 외부 서버(config-server)로부터 설정을 가져옵니다.
optional: 을 통해 configserver에 문제가 있어도 애플리케이션이 동작되도록 합니다.
configserver 를 통해 Spring Cloud Config 서버에서 설정을 가져도록 합니다.
8888은 config-server의 port입니다.
이는 discovery의 설정파일을 config-server로부터 가져오겠다는 것을 의미합니다.
spring.applciation.name을 discovery-service로 설정하여 다른 서비스에서 식별할 수 있도록 합니다.
그리하여 config-server의 configurations에 생성하는 yml 파일의 이름을 discovery-service로 합니다.
아래는 config-server에 생성한 discovery-service.yml입니다.
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}/${server.port}/eureka/
server.port 를 통해 Eureka 애플리케이션의 port를 8761로 설정합니다.
eureka.instance.hostname 설정을 통해 로컬 호스트에서 실행되도록 합니다.
eureka.client.registerWithEureka 를 통해 Eureka에 등록할지 설정합니다.
eureka.client.fetchRegistry 를 통해, 이 애플리케이션이 Eureka 서버로부터 레지스트리를 가져올지 설정합니다.
Eureka 서버 자체의 설정에서 주로 위 두 옵션은 false로 합니다.
eureka.client.serviceUrl.defaultZone 을 통해 Eureka 클라이언트가 Eureka 서버에 접근하기 위한 URL을 설정합니다.
이제 다른 클라이언트 애플리케이션이 http://localhost:8761/eureka/ URL을 통해 Eureka 서버에 접근할 수 있습니다.
3. Gateway
gateway가 요청을 받으면, 각 서비스에 알맞게 리디렉션합니다.
각 서비스의 부하를 고려하여 로드밸런싱을 적용할 수 있습니다.
아래는 gateway의 의존성입니다.
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-config'
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-tracing-bridge-brave'
implementation 'io.zipkin.reporter2:zipkin-reporter-brave'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.projectreactor:reactor-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
위부터 차례로 gateway-service가 config client, gateway, eureka client 의존성을 사용할 것임을 알 수 있습니다.
Actuator, brave 의존성은 ZIPIN(분산 추적)에 대한 것입니다.
oauth2 resource server 의존성은 KeyCloak을 사용하기 위한 의존성입니다.
나중에 따로 다루도록 하겠습니다.
gateway의 구성 관리파일은 아래와 같습니다.
spring:
application:
name: gateway-service
config:
import: optional:configserver:http://localhost:8888
security:
oauth2:
resourceserver:
jwt:
issuer-uri: "http://localhost:9098/realms/micro-service"
서비스 식별자를 지정하고, configserver로 부터 설정을 가져옵니다.
security 설정은 나중에 KeyCloak과 함께 다루겠습니다.
config-server에 gateway의 설정파일을 gateway-service.yml이라는 이름으로 생성합니다.
port를 8222로 설정했습니다.
이제 네트워크 내부의 다른 서비스 또한 gateway의 8222포트로 접근할 수 있습니다.
server:
port: 8222
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: customer-service
uri: lb:http://CUSTOMER-SERVICE # eureka service
predicates:
- Path=/api/v1/customers/**
- id: order-service
uri: lb:http://ORDER-SERVICE # eureka service
predicates:
- Path=/api/v1/orders/**
- id: order-lines-service
uri: lb:http://ORDER-SERVICE # eureka service
predicates:
- Path=/api/v1/order-lines/**
- id: product-service
uri: lb:http://PRODUCT-SERVICE # eureka service
predicates:
- Path=/api/v1/products/**
- id: payment-service
uri: lb:http://PAYMENT-SERVICE # eureka service
predicates:
- Path=/api/v1/payments/**
spring.cloud.gateway.discovery.locator.enabled 설정을 통해,
gateway가 Eureka 서비스 레지스트리에서 경로를 동적으로 찾도록 합니다.
만약 false로 한다면, 아래에 routes에서 선언할 uri를 명시적으로 설정합니다.
spring.cloud.gateway.routes 에서 개별 서비스에 대한 라우팅 규칙을 정의합니다.
id 는 각 route의 식별자입니다.
uri 는 요청을 전달할 대상 서비스의 URI입니다.
lb를 통해 로드 밸런싱을 가능하게 합니다.
predicates 특정 조건에 따라 route를 적용할지를 결정하는 조건자(Path, Method, Header 등)를 정의합니다.
lb를 통해 로드 밸런싱을 가능하게 했습니다.
각 프로젝트별로 다른 포트(8070, 8071, 8072)를 사용하되
spring.application.name을 동일하게 한다면(order-service),
gateway는 라운드 로빈 형식으로 요청을 로드 밸런싱(부하 분산)합니다.
(트래픽을 균등하게 배포합니다)
(최적화 게시글에서 따로 다루겠습니다)
'Java' 카테고리의 다른 글
[MSA] e-commerce API; 구현2.2 - product (0) | 2024.06.19 |
---|---|
[MSA] e-commerce API; 구현2.1 - customer (0) | 2024.06.18 |
[MSA] e-commerce API; 프로젝트 흐름 (0) | 2024.06.18 |
[HTTP Client] 예제; HttpURLConnection, Apache HttpClient, RestTemplate, OpenFeign, WebClient, RestClient (0) | 2024.06.14 |
[HTTP Client] 개요; HttpURLConnection, Apache HttpClient, RestTemplate, OpenFeign, WebClient, RestClient (0) | 2024.06.14 |