컨테이너 가상화
배포
일반적으로 서버 애플리케이션을 배포하는 방법은 온프레미스 방식과 클라우드 환경에 배포하는 방식으로 두 가지로 나뉜다.
온프로미스 배포 방법은
Intellij와 같은 IDEA로 직접 실행하여 Local 환경에서 실행하는 방법, jar 파일을 통해 Local 환경에서 실행시키는 방법, Docker를 통해 Local 환경에서 실행시키는 방법이 있다.
반면 클라우드 환경 배포 방법에는
일반적으로 Docker와 EC2 환경에 배포를 수행하는데, Docker Swarm이나 Kubernetes 같은 오케스트레이션 툴을 이용하기도 한다.
이와 같이 12개의 서버를 Docker를 통해 컨테이너로 가상화하여 어떤 환경에서든 가동시킬 수 있도록 구성할 예정이다.
호스트 환경은 Docker Network의 브릿지 이용하여 구성한다.
docker network create --gateway 172.18.0.1 --subnet 172.18.0.0/16 ecommerce-network
Shell
복사
Backing Services
RabbitMQ
docker run -d --name rabbitmq --network ecommerce-network \
-p 15672:15672 -p 5672:5672 -p 15671:15671 -p 5671:5671 -p 4369:4369 \
-e RABBITMQ_DEFAULT_USER=guest \
-e RABBITMQ_DEFAULT_PASS=guest rabbitmq:management
Shell
복사
위 명령어를 통해 RabbitMQ 서버를 Docker 환경으로 실행시킨다.
Configuration Service
spring:
rabbitmq:
# host: 127.0.0.1
port: 5672
username: guest
password: guest
YAML
복사
컨테이너 내부에서 localhost를 사용할 수 없으니 주석 처리하고,
encrypt:
# key: abcdefhijklmnopqrstuvwxyz1234567890
key-store:
# location: file://${user.home}/MSA/keystore/apiEncryptionKey.jks
location: file:/apiEncryptionKey.jks
password: 1234test
alias: apiEncryptionKey
YAML
복사
bootstrap.yml 파일에서 encryption Key의 파일 경로를 수정한다.
수정한 경로에 맞춰서 해당 폴더 내에 Key를 넣어주고, Docker 파일을 생성한다.
FROM openjdk:17-ea-11-slim
VOLUME /tmp
COPY apiEncryptionKey.jks apiEncryption.jks
COPY build/libs/config-service-0.0.1-SNAPSHOT.jar ConfigServer.jar
ENTRYPOINT ["java", "-jar", "ConfigServer.jar"]
Docker
복사
이와 같이 Dockerfile을 작성 후
docker build -t config-service:1.0 .
Shell
복사
빌드하여 이미지를 생성한다.
여기서도 마찬가지로 기존의 로컬 호스팅(127.0.0.1) 관련 설정 주석 처리 후,
docker run -d -p 8888:8888 --network ecommerce-network \
-e "spring.rabbitmq.host=rabbitmq" \
-e "spring.profiles.active=default" \
--name config-service config-service:1.0
Shell
복사
컨테이너 이름으로 rabbitmq host 연결하도록 Docker 실행 옵션에 추가한다.
Discovery Service
FROM openjdk:17-ea-11-jdk-slim
VOLUME /tmp
COPY target/discoveryservice-0.0.1-SNAPSHOT.jar DiscoveryService.jar
ENTRYPOINT ["java", "-jar", "DiscoveryService.jar"]
Docker
복사
Discovery Service도 마찬가지로 이와 같이 Dockerfile을 작성 후
docker build -t discovery-service:1.0 .
Shell
복사
빌드하여 이미지를 생성한다.
docker run -d -p 8761:8761 --network ecommerce-network \
-e "spring.cloud.config.uri=http://config-service:8888" \
--name discovery-service discovery-service:1.0
Shell
복사
그 후 이와 같이 설정을 추가하여 이미지를 실행시키면
컨테이너가 잘 올라가게 된다.
API Gateway Service
FROM openjdk:17-ea-11-jdk-slim
VOLUME /tmp
COPY target/api-gateway-service-0.0.1-SNAPSHOT.jar ApiGatewayService.jar
ENTRYPOINT ["java", "-jar", "ApiGatewayService.jar"]
Docker
복사
docker build -t api-gateway-service:1.0 .
Shell
복사
마찬가지로 이미지 빌드 후,
docker run -d -p 8000:8000 --network ecommerce-network \
-e "spring.cloud.config.uri=http://config-service:8888" \
-e "spring.rabbitmq.host=rabbitmq" \
-e "eureka.client.serviceUrl.defaultZone=http://discovery-service:8761/eureka/" \
--name api-gateway-service \
api-gateway-service:1.0
Shell
복사
컨테이너를 올린다.
MariaDB
FROM mariadb
ENV MYSQL_ROOT_PASSWORD test1357
ENV MYSQL_DATABASE mydb
EXPOSE 3306
Docker
복사
docker build -t my-mariadb:1.0 .
Shell
복사
docker run -d -p 3306:3306 --network ecommerce-network --name mariadb my-mariadb:1.0
Shell
복사
MariaDB도 동일하다.
grant all privileges on *.* to 'root'@'%' identified by 'test1357';
flush privileges;
SQL
복사
만약 호스팅 접속이 안된다면, MariaDB에서 위 명령어를 입력하여 root 계정으로 접속하는 모든 호스팅에 대해 접속을 허가해주면 된다.
Kafka
Kafka의 경우 wurstmeister라는 사람이 올려둔 docker 이미지와 docker-compose 파일을 살짝 변형해서 사용할 것이다.
version: "2"
services:
zookeeper:
image: wurstmeister/zookeeper
ports:
- "2181:2181"
networks:
my-network:
ipv4_address: 172.18.0.100
kafka:
image: wurstmeister/kafka
ports:
- "9092:9092"
environment:
KAFKA_ADVERTISED_HOST_NAME: 172.18.0.101
KAFKA_CREATE_TOPICS: "test:1:1"
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
volumes:
- /var/run/docker.sock:/var/run/docker.sock
depends_on:
- zookeeper
networks:
my-network:
ipv4_address: 172.18.0.101
networks:
my-network:
name: ecommerce-network
external: true
YAML
복사
이와 같이 docker-compose.yml 파일을 구성하고 실행 시키면,
docker compose -f docker-compose-single-broker.yml up -d
Shell
복사
이처럼 컨테이너도 잘 올라가고 네트워크 연결도 잘 되어있는 것을 확인할 수 있다.
Zipkin
docker run -d -p 9411:9411 \
--network ecommerce-network \
--name zipkin \
openzipkin/zipkin
Shell
복사
Docker hub에 올려져있는 Zipkin을 사용하여 서버를 실행시킨다.
Prometheus
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
- job_name: "prometheus"
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
static_configs:
- targets: ["prometheus:9090"]
- job_name: 'user-service'
scrape_interval: 15s
metrics_path: '/user-servcie/actuator/prometheus'
static_configs:
- targets: ['api-gateway-service:8000']
- job_name: 'order-service'
scrape_interval: 15s
metrics_path: '/order-servcie/actuator/prometheus'
static_configs:
- targets: ['api-gateway-service:8000']
- job_name: 'apigateway-service'
scrape_interval: 15s
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['api-gateway-service:8000']
YAML
복사
프로메테우스의 경우 prometheus.yml 파일에 수집하는 정보에 대한 job을 설정해두는데, 위와 같이 localhost로 되어있던 부분을 전부 각 서비스 이름(Docker 컨테이너 이름)으로 바꾸어 저장한다.
docker run -d -p 9090:9090 \
--network ecommerce-network \
--name prometheus \
-v /Users/jiwon/MSA/monitoring/prometheus-2.45.3.darwin-amd64/prometheus.yml:/etc/prometheus/prometheus.yml \
prom/prometheus
Shell
복사
Grafana
docker run -d -p 3000:3000 \
--network ecommerce-network \
--name grafana \
grafana/grafana
Shell
복사
Microservices
위 Backing Service들과는 달리 마이크로 서비스들은 Service Discovery에 의해 주소가 저장되기 때문에 IP 주소와 Port 번호는 중요하지 않다.
User Microservice
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password: "{cipher}AQCCczzrFJhv8DCQBA7GGwD11XYQSK4XIttKeE+UbEp4VkmHz5QnfeX8mIUb93jrgFO6ke4mXPyP9zZonQWYd8FjiUfqS2zIiyx0p6JYdzJoeDiIdS1PZvbDOtu4RbRxGzF1dhB9TYazeE7MT+mWG+f12kDmW9JY7/PdJh3HQll5K8kJ1YmbgPZmvCF2uyMWC72vg7fTKqFlm/HofmU8c7+sTO/+wjapminaBaHZOE728sn15CJEXgY57qpkXpJ0JW65Z1U8ZNM3kqBvI6c2kyh+hCzEKIhh4tpoeWYZKra+IHpsx06RLBOes1raoFs0e1YAQsox7n5yuOtGKLLV61uyEjaQyfGGU8IVtwKUOEZCbixgDVI0ce+CAYDPikaQzRc="
gateway:
ip: 172.18.0.5
order_service:
url: http://ORDER-SERVICE/order-service/%s/orders
exception:
order_is_empty: User's order is empty
YAML
복사
configuration service의 user-service.yml 파일에서 gateway.ip 주소 변경
spring:
# rabbitmq:
# host: 127.0.0.1
# port: 5672
# username: guest
# password: guest
eureka:
instance:
instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}
client:
register-with-eureka: true
fetch-registry: true
# service-url:
# defaultZone: http://localhost:8761/eureka
management:
# zipkin:
# tracing:
# endpoint: http://localhost:9411/api/v2/spans
YAML
복사
user-service에서 application.yml의 localhost를 사용하는 부분 전부 주석 처리하고, 이후 docker 실행 시 옵션으로 추가로 처리할 것이다.
먼저 애플리케이션을 빌드 후
FROM openjdk:17-ea-11-jdk-slim
VOLUME /tmp
COPY build/libs/user-service-0.0.1-SNAPSHOT.jar UserService.jar
ENTRYPOINT ["java", "-jar", "UserService.jar"]
Docker
복사
빌드한 jar파일 경로를 통해 실행할 수 있도록 위와 같이 Dockerfile을 작성한다.
docker build -t user-service:1.0 .
Shell
복사
docker run -d --network ecommerce-network \
--name user-service \
-e "spring.cloud.config.uri=http://config-service:8888" \
-e "spring.rabbitmq.host=rabbitmq" \
-e "spring.zipkin.tracing.endpoint=http://zipkin:9411/api/v2/spans" \
-e "eureka.client.serviceUrl.defaultZone=http://discovery-service:8761/eureka/" \
-e "logging.file=/api-logs/users-ws.log" \
user-service:1.0
Shell
복사
Order Microservice
spring:
# datasource:
# url: jdbc:h2:mem:testdb
# driver-class-name: org.h2.Driver
# url: jdbc:mariadb://localhost:3306/mydb
# driver-class-name: org.mariadb.jdbc.Driver
# username: root
# password: test1357
eureka:
instance:
instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}
client:
register-with-eureka: true
fetch-registry: true
# service-url:
# defaultZone: http://localhost:8761/eureka
management:
# zipkin:
# tracing:
# endpoint: http://localhost:9411/api/v2/spans
YAML
복사
order-service도 마찬가지로 application.yml의 localhost를 사용하는 부분과 mariadb 설정 관련 부분을 전부 주석 처리한다. 이 또한 이후 docker 옵션으로 추가하여 실행시킬 것이다.
@Bean
public ProducerFactory<String, String> producerFactory() {
Map<String, Object> properties = new HashMap<>();
// properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9092");
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "172.18.0.101:9092");
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
return new DefaultKafkaProducerFactory<>(properties);
}
Java
복사
추가적으로 java 코드에서 이와 같이 Kafka 연결 부분도 Config service의 IP 주소로 수정한다.
FROM openjdk:17-ea-11-jdk-slim
VOLUME /tmp
COPY build/libs/order-service-0.0.1-SNAPSHOT.jar OrderService.jar
ENTRYPOINT ["java", "-jar", "OrderService.jar"]
Docker
복사
docker build -t order-service:1.0 .
Shell
복사
docker run -d --network ecommerce-network \
--name order-service \
-e "spring.zipkin.tracing.endpoint=http://zipkin:9411/api/v2/spans" \
-e "eureka.client.serviceUrl.defaultZone=http://discovery-service:8761/eureka/" \
-e "spring.datasource.url=jdbc:mariadb://mariadb:3306/mydb" \
-e "logging.file=/api-logs/orders-ws.log" \
order-service:1.0
Shell
복사
Catalog Microservice
eureka:
instance:
instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}
client:
register-with-eureka: true
fetch-registry: true
# service-url:
# defaultZone: http://localhost:8761/eureka
Shell
복사
@Bean
public ConsumerFactory<String, String> consumerFactory() {
Map<String, Object> properties = new HashMap<>();
// properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9092");
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "172.18.0.101:9092");
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "consumerGroupId");
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
return new DefaultKafkaConsumerFactory<>(properties);
}
Java
복사
마찬가지로 Kafka 코드에서 Config service 주소를 변경한다.
FROM openjdk:17-ea-11-slim
VOLUME /tmp
COPY build/libs/catalog-service-0.0.1-SNAPSHOT.jar CatalogService.jar
ENTRYPOINT ["java", "-jar", "CatalogService.jar"]
Docker
복사
docker build -t catalog-service:1.0 .
Shell
복사
docker run -d --network ecommerce-network \
--name catalog-service \
-e "eureka.client.serviceUrl.defaultZone=http://discovery-service:8761/eureka/" \
-e "logging.file=/api-logs/catalogs-ws.log" \
catalog-service:1.0
Shell
복사