Cloud/SpringCloud로 개발하는 MSA

MS 들 dockerize + window local mysql data -> wsl2 docker mysql + kafka listener

Tony Lim 2022. 11. 9. 15:40

docker network 에서 bridge 모드로 직접 gateway , subnet을 명시하여 하나 만들어준다.

ga,subnet을 직접 명시하지 않으면 ip 를 직접 지정해서 container를 띄울떄 오류가 발생할 수 있다.

기본 모드인 bridge모드면 자체 DNS서비스를 제공함으로 container 로 만들시 --name에 부여한 값으로 ping 해보고 통신이 가능하다.

 

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

rabbit mq와 각종 plugin들(admin page등) 을 port fowarding을 해주면서 container로 띄운다.

 

Config Service

docker run -d -p 8888:8888 --network ecommerce-network \
 -e "spring.rabbitmq.host=rabbitmq" \
 -e "spring.profiles.active=default" \
 --name config-service kuuku123/config-service:1.0

application.yml에 rabbit mq 에 접속하려는 ip가 localhost로 되어있는데 이 상태로 기동을 하면 container 자신을 가리키게 된다. 현재 container안에 rabbit mq가 있는 것이 아니므로

환경 변수를 통해 전달해주면 docker 는 같은 user-defined bridge network에 있으면 DNS 서비스를 지원해줌으로 잘 연결 할 수 있다.

 

Discovery Service

docker run -d -p 8761:8761 --network ecommerce-network \
 -e "spring.cloud.config.uri=http://config-service:8888" \
 --name discovery-service kuuku123/discovery-service:1.0

Apigateway

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 apigateway-service \
 kuuku123/apigateway-service:1.0

Eureka 에 나오는 정보가 ip대신 containerId가 나오는것을 확인 할 수 있다.

 

MySQL

1. C:\ProgramData\MySQL\MySQL Server 8.0\Data 를 Dockerfile 위치로 cp -r 로 옮긴다.

2. Data 안의 모든 파일들을 chmod 777 로 변경 

3. Dockerfile

FROM mysql:8.0
ENV MYSQL_ROOT_PASSWORD 1234
ENV MYSQL_DATABASE test
COPY ./Data /var/lib/mysql
EXPOSE 3306
CMD ["--lower_case_table_names=1"]
ENTRYPOINT ["mysqld"]

lower_case 저 옵션이 OS마다 기본이 다른가봄 같게 만들어줘야 window host에서 쓰던 mysql table 의 data들이 잘 적용되서 켜짐

3.1 docker-compose.yml

version: '3'
services:
  mysql:
    container_name: mysqldb
    image: mysql:8.0
    volumes:
      - ./Data:/var/lib/mysql
    environment:
        MYSQL_ROOT_PASSWORD: 1234
    command: --lower_case_table_names=1
    ports:
      - "3316:3306"
networks:
  default:
    name: ecommerce-network
    external: true

volume 설정을 Data로 해놓았기에 docker-compose down으로 끄고 다시켜도 data가 잘 보존되는것을 확인 할 수 있다.

 

Kafka

version: '2'
services:
  zookeeper:
    image: wurstmeister/zookeeper
    ports:
      - "2181:2181"
    networks:
      my-network:
        ipv4_address: 172.18.0.100
  kafka:
    #build: .
    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

mysql에서는 ecommerce-network에서 어떤 ip를 받을지 명시하지 않은것이고 여기는 자체적으로 서비스안에 명시한 것이다.
이때 주어진 subnet에 맞게 정해줘야함

 

Zipkin

docker run -d -p 9411:9411 \
 --network ecommerce-network \
 --name zipkin \
 openzipkin/zipkin

 

Prometheus , Grafana

version: '3'
services:
  prometheus:
    container_name: prometheus
    image: prom/prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus-2.39.1.linux-amd64/prometheus.yml:/etc/prometheus.yml

  grafana:
    container_name: grafana
    image: grafana/grafana
    ports:
      - "3000:3000"
networks:
  default:
    name: ecommerce-network
    external: true
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-service/actuator/prometheus"
    static_configs:
      - targets: ["apigateway-service:8000"]

  - job_name: "apigateway-service"
    scrape_interval: 15s
    metrics_path: "/actuator/prometheus"
    static_configs:
      - targets: ["apigateway-service:8000"]

  - job_name: "order-service"
    scrape_interval: 15s
    metrics_path: "/order-service/actuator/prometheus"
    static_configs:
      - targets: ["apigateway-service:8000"]

프로메테우스는 로컬에서 썼던 설정파일을 그대로 쓰기 위해 volume을 지정해주었다.

설정파일에서도 localhost 말고 container이름을 지정해줘야 DNS가 동작하고 제대로 소통가능해진다.


ms들 dockerize

user-service

version: '3'
services:
  user-service:
    container_name: user-service
    image: kuuku123/user-service:1.0
    environment:
      - spring.cloud.config.uri=http://config-service:8888
      - spring.rabbitmq.host=rabbitmq
      - spring.zipkin.base-url=http://zipkin:9411
      - eureka.client.serviceUrl.defaultZone=http://discovery-service:8761/eureka/
      - logging.file=/api-logs/user-ws.log

networks:
  default:
    name: ecommerce-network
    external: true

config server에서 설정 정보를 읽어오니 uri를 변경해줘야 하는데 이때 user-service.yml 을 읽어오는데 

gateway:
  #ip: localhost
  ip: apigateway-service

마찬가지로 변경을 먼저 해줘야한다. config server 그리고 재기동 하거나 actuator 를 호출하면 된다.

http.authorizeRequests().mvcMatchers("/**").hasIpAddress("172.18.0.0/16")

securty filterChain 메소드에서 같은 네트워크에 속해있는 녀석들만 403 안뜨게 해주고 싶을 때 hasIpAddress에 현재 속해있는 user defined network의 subnet을 넘겨 줄 수 있다.

.hasIpAddress(env.getProperty("gateway.ip"))

아니면 config-server가 git에서 읽어오는 user-service.yml 에 존재하는 gateway.ip 를 만 허용해줘도 결국 apigateway를 통해서 요청이 오기 때문에 403이 뜨지 않는다.


order-service

@EnableKafka
@Configuration
public class KafkaProducerConfig {

    @Bean
    public ProducerFactory<String, String> producerFactory() {
        Map<String, Object> properties = new HashMap<>();
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"kafka:9092");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);

        return new DefaultKafkaProducerFactory<>(properties);
    }

    @Bean
    public KafkaTemplate<String,String> kafkaTemplate() {
        return new KafkaTemplate<>(producerFactory());
    }
}

현재 user defined docker network 에 카프카의 container_name은 kafka 임으로 bootstrapserver를 저렇게 변경해준다.

version: '2'
services:
  zookeeper:
    container_name: zookeeper
    image: wurstmeister/zookeeper
    ports:
      - "2181:2181"
    networks:
      my-network:
        ipv4_address: 172.18.0.100

  kafka:
    container_name: kafka
    #build: .
    image: wurstmeister/kafka
    ports:
      - "9092:9092"
      - "29092:29092"
    environment:
      KAFKA_ADVERTISED_HOST_NAME: 172.18.0.101
      KAFKA_CREATE_TOPICS: "test:1:1"
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_LISTENERS: LISTENER_BOB://kafka:29092,LISTENER_FRED://kafka:9092
      KAFKA_ADVERTISED_LISTENERS: LISTENER_BOB://localhost:29092,LISTENER_FRED://kafka:9092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LISTENER_BOB:PLAINTEXT,LISTENER_FRED:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: LISTENER_FRED
    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

위에서 배포했던 카프카, 주키퍼의 docker-compose.yml에 Listener를 추가하였다. 

docker network 내에서는 Listener fred로 소통하고 docekr network 외부의 localhost하고는 listener bob 이랑 소통을 하게 된다.

새로운 터미널에서 docker의 kafka를 접속하기 위해서는 29092로 bootstrap server를 설정해주면 메타데이터를 잘받아오고 원하는 동작을 할 수 있는 것을 확인

https://rmoff.net/2018/08/02/kafka-listeners-explained/

 

Kafka Listeners - Explained

How to connect clients to Kafka hosted in separate networks, such as Docker, AWS EC2, GCP, Azure, etc

rmoff.net

여기에 자세히 listener에 관한것이 나와있다.

 

catalog-service

@EnableKafka
@Configuration
public class KafkaProducerConfig {

    @Bean
    public ProducerFactory<String, String> producerFactory() {
        Map<String, Object> properties = new HashMap<>();
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"kafka:9092");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);

        return new DefaultKafkaProducerFactory<>(properties);
    }

    @Bean
    public KafkaTemplate<String,String> kafkaTemplate() {
        return new KafkaTemplate<>(producerFactory());
    }
}

client가 apigateway -> orderservice로 userid 로 order create를 신청하면 신청한 quantity 만큼 catalog service db에서 감소시켜줘야한다.

현재 order service에서 jpa를 통해 mysql 에 저장한후에 kafka에 topic에 저장까지 하게 된다. 이때 catalog service에서 consumer를 통해 topic정보를 읽어서 자신의 h2 db에 qty를 감소시킨다.