Post

MSA 구현하기(4) - DB 구성 (feat. Docker)

Skala 과정에서 마이크로 서비스 아키텍처 구조에 대해 새롭게 알게 되었습니다.
배운 것을 실제로 구현해보기 위해 이 여정을 시작하기로 했습니다.
처음 글부터 보러가기

Docker를 이용하여 MariaDB 서버를 3개 만들어보겠습니다!

MariaDB 서버 띄우기

docker-compose.yml 구성

여러 개의 MariaDB 서버를 띄워야 하기 때문에 docker-compose.yml 파일을 이용해서 한꺼번에 띄워보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
version: "3.8"

services:
  order-db:
    image: mariadb:latest
    container_name: order-db
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${ROOT_PASSWORD}
      MYSQL_DATABASE: order
      MYSQL_USER: ${USER}
      MYSQL_PASSWORD: ${PASSWORD}
    ports:
      - "3307:3306"
    volumes:
      - order_data:/var/lib/mysql
      - ./order-init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - mariadb_network
    healthcheck:
      test: ["CMD", "mariadb", "-uroot", "-p${ROOT_PASSWORD}", "-e", "SELECT 1"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s

  item-db:
    image: mariadb:latest
    container_name: item-db
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${ROOT_PASSWORD}
      MYSQL_DATABASE: item
      MYSQL_USER: ${USER}
      MYSQL_PASSWORD: ${PASSWORD}
    ports:
      - "3309:3306"
    volumes:
      - item_data:/var/lib/mysql
      - ./item-init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - mariadb_network
    healthcheck:
      test: ["CMD", "mariadb", "-uroot", "-p${ROOT_PASSWORD}", "-e", "SELECT 1"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s

  user-db:
    image: mariadb:latest
    container_name: user-db
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${ROOT_PASSWORD}
      MYSQL_DATABASE: user
      MYSQL_USER: ${USER}
      MYSQL_PASSWORD: ${PASSWORD}
    ports:
      - "3308:3306"
    volumes:
      - user_data:/var/lib/mysql
      - ./user-init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - mariadb_network
    depends_on:
      order-db:
        condition: service_healthy
      item-db:
        condition: service_healthy

volumes:
  user_data:
  order_data:
  item_data:

networks:
  mariadb_network:
    driver: bridge

docker-compose.yml 파일을 Git에 올릴 예정이어서 따로 .env 파일을 만들어서 변수들의 보안을 관리해주었습니다.
.env 파일은 docker-compose.yml과 같은 경로에 저장하면 됩니다.

각 서버의 포트번호도 겹치지 않게 설정합니다.
도커 컨테이너로 올리지 않은 로컬 mysql과 겹치지 않게 3306 포트는 피해서 설정해주었습니다

사실 로컬에 MySQL 과 MariaDB를 같이 설치하니 충돌이 일어나서 Docker를 사용하게 되었습니다..

user-dborder-dbitem-db 가 생성된 이후에 federated 엔진을 통해 테이블을 생성해야 하기 때문에 healthcheck 를 수행하여 두 컨테이너가 정상적으로 생성된 이후에 생성되도록 설정하였습니다


.env 파일

1
2
3
4
5
6
7
8
9
ROOT_PASSWORD={비밀번호}
USER={유저 이름}
PASSWORD={비밀번호}
USER_DATABASE=user
ITEM_DATABASE=item
ORDER_DATABASE=orders
ORDER_TABLE=orders
ITEM_DATABASE=item
ITEM_TABLE=item

데이터베이스 생성 및 유저 생성/권한 부여 자동화

매번 docker-compose up -d 을 실행할 때마다 데이터베이스를 만들고, 유저를 생성하는 일은 굉장히 귀찮습니다.
원래 모놀리식 구조로 동작할 때 유저와 데이터베이스를 자동으로 만들어주는 파일을 만들 때에는 init.sql 파일 하나면 되지만 저는 MSA 구조로 동작하기 때문에 만들어야 할 파일이 많습니다.
그래서 서버 별로 데이터베이스가 자동으로 만들어질 수 있게 .sh 파일을 먼저 만들어보겠습니다.


entrypoint.sh

1
2
3
4
5
#!/bin/sh
export $(grep -v '^#' .env | xargs)
for file in user-init-template.sql item-init-template.sql order-init-template.sql; do
  envsubst < "$file" > "${file/-template/}"
done

이렇게하면 user-init-templete.sql, item-init-template.sql, order-init-template.sql을 한번에 각각의 init.sql 파일로 만들 수 있습니다.

export ~ 구문을 실행하는 이유는 .env 파일에 저장되어 있는 환경 변수로 안전하게 데이터베이스와 유저를 생성하기 위함입니다.
또한, 한꺼번에 변수들을 관리할 수 있어 유지보수도 편리합니다.


init.sql 파일 (Ex: user-init-template.sql)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
CREATE DATABASE IF NOT EXISTS `${USER_DATABASE}`;
USE `${USER_DATABASE}`;

CREATE TABLE IF NOT EXISTS `${USER_TABLE}` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `email` varchar(255) DEFAULT NULL,
    `password` varchar(255) DEFAULT NULL,
    `phone` varchar(255) DEFAULT NULL,
    `username` varchar(255) DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;

CREATE TABLE IF NOT EXISTS federated_item (
    id BIGINT(20) NOT NULL AUTO_INCREMENT,
    quantity INT(11) NOT NULL,
    seller_id BIGINT(20) DEFAULT NULL,
    description VARCHAR(255) DEFAULT NULL,
    name VARCHAR(255) DEFAULT NULL,
    PRIMARY KEY (id)
) ENGINE=FEDERATED
CONNECTION='mysql://${USER}:${PASSWORD}@host.docker.internal:3309/item/item';

CREATE TABLE IF NOT EXISTS federated_orders (
    id BIGINT(20) NOT NULL AUTO_INCREMENT,
    address VARCHAR(255) DEFAULT NULL,
    customer_id BIGINT(20) DEFAULT NULL,
    description VARCHAR(255) DEFAULT NULL,
    item_id BIGINT(20) DEFAULT NULL,
    order_date DATETIME(6) DEFAULT NULL,
    quantity INT(11) NOT NULL,
    PRIMARY KEY (id)
) ENGINE=FEDERATED
CONNECTION='mysql://${USER}:${PASSWORD}@host.docker.internal:3307/orders/orders';

CREATE USER IF NOT EXISTS '${USER}'@'%' IDENTIFIED BY '${PASSWORD}';
GRANT ALL PRIVILEGES ON `${USER_DATABASE}`.* TO '${USER}'@'%';
FLUSH PRIVILEGES;

이런 식으로 모든 파일을 작성해주면 docker-compose up -d 만 실행해주면 MariaDB 서버 3개가 동시에 생기게 됩니다.

위 sql문의 단점이 한 가지 있는데,
스프링에서 엔티티의 구조가 바뀔 때마다 DDL을 변경해주어야 한다는 점입니다,,,

###### 더 좋은 방법이 있다면 알려주세욥,,
다른 좋은 방법이 생기면 수정해보겠습니다!

2025.03.14
찾았습니다! 요기로 오세욧
Federated 관련 설정을 아예 없애도 좋습니다 ^^
(즉, init.sql의 테이블 선언 코드 부분이 없어도 된다는 의미입니다 하하)

그래도 데이터베이스를 생성할 때마다 계속해서 데이터베이스를 생성하고, 유저를 만들어서 권한을 부여하는 일련의 과정들이 생략되었습니다!


FEDERATED 엔진

MariaDB 에 있는 FEDERATED 엔진을 사용하면 Oracle의 SYNONYM을 이용한 DB Link 같이 실시간으로 외부 데이터베이스에 있는 테이블을 읽어올 수 있습니다.

차이점은
Oracle 은 접속하고나면 권한이 허용된 범위내에서 단순 SELECT 뿐만 아니라, 다양한 작업을 자유롭게 할 수 있지만(원격 테이블 수정 가능!),
MariaDB 는 각자 다른 데이터베이스 간 테이블 동기화에 더 가깝습니다. (원격 테이블 수정 불가!)

이를 통해 CUD만 가능했던 Kafka의 단점을 보완할 수 있었습니다!

무한 감사,,

MariaDB에도 Synonym과 비슷한게 있을거라고 힌트 주신 Skala 정윤석 교수님 감사합니다,,


결과

Docker-MariaDB Docker-Ps MariaDB-Result

아주 잘 생성된 모습을 볼 수 있습니다!


마치며

아직 많이 부족하지만..열심히 달려보겠습니다…!!
다음은 Federated 기술을 사용하지 않고, GraphQL 을 통해 API Server와 통신하고, API Server가 각 서버로 요청을 전달하는 형태로 동작하는 API 기능 구현에 대해 작성해보겠습니다 🤯



참고 자료

하나, , ,

This post is licensed under CC BY 4.0 by the author.