2025. 4. 10. 18:22ㆍ기술 창고/Docker
이전에 근무했던 회사에서 만들었던 프로젝트를 회고해보면서 많은 문제점과 아쉬운 부분들이 많다고 느껴졌습니다.
아무래도 독박으로 모든 백엔드와 서버에 대한 작업들을 다 혼자서 감당해야 했기에 부족한 부분이 많았기 때문입니다.
때문에 이전에 부족했던 부분들을 전체적으로 마이그레이션 하면서 보완할 부분들을 개선해나가고자 했습니다.
그 중에서 오늘은 데이터베이스와 관련된 작업들에 대한 부하를 낮춰주고 고가용성으로 데이터베이스들을 활용하기 위해 레플리케이션(Replication)을 도입해보기로 하였습니다.
들어가기에 앞서서...
일단 레플리케이션이 도입되기 전인 현 상태를 파악해보았습니다.
- 운영 서버 : Linux
- 사용 데이터베이스 : MySQL
- 하나의 MySQL 데이터베이스만을 Linux 서버에 올려 데이터에 대한 write/update/delete/select 모든 데이터 작업을 수행
즉, 하나의 데이터베이스로 모든 데이터 작업을 처리하고 조회하기 때문에 데이터베이스에 대한 부하가 많이 걸리게 될 것이고, 또한, 데이터베이스에 문제가 발생된다면 별도로 운영하고 있는 서브 데이터베이스가 존재하지 않기 때문에 이는 매우 크리티컬한 문제가 될 가능성이 높습니다.
레플리케이션을 어떻게 구현할까에 대한 고민
그렇다면 레플리케이션을 반영해야 하는 것은 필수라고 보이는데, 어떻게 구현하면 좋을지 생각해보았습니다.
일단 운영하고있는 서버는 Linux 1개이고, 추가로 서버를 구매하는 것은 제 현 상황을 보고 판단했을 때 리스크가 매우 컸습니다. (생활비 이슈 등등...)
보통 일반적으로 레플리케이션을 도입하게 되면 여러 서버를 구매 / 운영하여 해당 서버마다 데이터베이스를 설치하여 레플리케이션을 구현하는 것이 일반적이라고 알고있습니다.
하지만 저처럼 혼자만의 힘으로 이 다수의 서버를 구매하고 레플리케이션을 구현하는 것은 매우 비용이 많이 지출되고 힘이 드는 작업이였기 때문에 여러 서버를 구매하여 일반적으로 레플리케이션을 구현하는 방법은 제외되었습니다.
이 때 Docker를 활용해서 레플리케이션을 구현할 수 있지않을까 하는 생각이 들었습니다.
Docker는 가상화 컨테이너를 여러개 둠으로서 다수의 운영체제 서버를 운영할 수 있게끔 할 수 있었기 때문에 물리적으로 서버를 구매할 필요도 없고 비용도 많이 들지 않아 적합하다고 생각되었습니다.
즉, MySQL 컨테이너를 여러개 실행시킴으로서 그 안에서 레플리케이션을 적용할 수 있을 것으로 생각되었습니다.
이제 본격적으로 Docker 를 활용하여 데이터베이스 레플리케이션을 적용해보도록 하겠습니다.
(1) 운영 Linux 서버에 Docker, Docker Compose 설치
- Docker 설치 : https://jindevelopetravel0919.tistory.com/439#google_vignette
- Docker Compose 설치 : https://jindevelopetravel0919.tistory.com/393#google_vignette
(2) Docker Compose, 레플리케이션 DB 설정 파일 경로 설정
실행할 Docker Compose 파일과 경로, Docker Compose 파일에 참조될 레플리케이션 DB 설정 파일 경로들을 만들어줍니다.
/usr/local/bin
/docker-service # Docker Compose 관련 파일들이 저장될 경로
docker-compose-replication-database.yml # 실행할 Docker Compose 파일
/mysql # 레플리케이션할 DB 관련 설정 파일 경로
/master # master DB 관련 경로
Dockerfile # master DB Docker 파일
master.cnf # master DB 용 my.cnf 설정 파일
/scripts # master DB 용 entrypoint 실행 스크립트 경로
docker-entrypoint-master.sh # master DB 용 entrypoint 실행 스크립트 파일
/slave # slave DB 관련 경로
Dockerfile # slave DB Docker 파일
slave.cnf # slave DB 용 my.cnf 설정 파일
/scripts # slave DB 용 entrypoint 실행 스크립트 경로
docker-entrypoint-slave.sh # slave DB 용 entrypoint 실행 스크립트 파일
/usr/local/bin 경로 내부에 자체적으로 제가 docker-service 경로를 만들고 해당 경로 내부 경로들도 직접 만들어주고, Dockerfile, master.cnf, slave.cnf, docker-entrypoint 셸 파일들도 직접 만들어주어야 하고 이후에 정리할 것입니다.
(3) Docker Compose 파일 생성
위에서 직접 만든 docker-service 경로 내에 최종적으로 실행할 Docker Compose 파일을 만들어줍니다.
docker-compose-replication-database.yml
#docker-compose-replication-database.yml
version: "3"
services:
db-master:
container_name: mysql-master
build:
context: ./mysql/master/
dockerfile: Dockerfile
restart: always
environment:
MYSQL_ROOT_PASSWORD: {MYSQL ROOT 계정 패스워드}
MYSQL_USER_PASSWORD: {MYSQL USER 계정 패스워드}
MYSQL_DB: {생성 / 실행할 MYSQL DATABASE 명}
ports:
- "13306:3306"
volumes:
- master_vol:/var/lib/mysql
- ./mysql/master/scripts:/docker-entrypoint-initdb.d
networks:
net-mysql:
ipv4_address: 172.28.0.2
db-slave:
container_name: mysql-slave
build:
context: ./mysql/slave
dockerfile: Dockerfile
restart: always
environment:
MYSQL_ROOT_PASSWORD: {MYSQL ROOT 계정 패스워드}
MYSQL_USER_PASSWORD: {MYSQL USER 계정 패스워드}
MYSQL_DB: {생성 / 실행할 MYSQL DATABASE 명}
ports:
- "13307:3306"
volumes:
- slave_vol:/var/lib/mysql
- ./mysql/slave/scripts:/docker-entrypoint-initdb.d
networks:
net-mysql:
ipv4_address: 172.28.0.3
depends_on:
- db-master
volumes:
master_vol:
slave_vol:
networks:
net-mysql:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/16
(4) 레플리케이션용 mysql 경로, 레플리케이션 master, slave 경로 생성
mkdir mysql
mkdir 명령어로 mysql 경로를 생성합니다.
cd mysql
mkdir master
mkdir slave
mysql 경로로 들어가 레플리케이션 master, slave 경로를 추가적으로 만들어줍니다.
(5) 레플리케이션 master 관련 파일 설정 및 생성
cd master
레플리케이션 master 경로에 들어갑니다.
Dockerfile
FROM mysql:8.0.32-debian
USER root
COPY ./master.cnf /etc/mysql/my.cnf
RUN mkdir /var/log/mysql && touch /var/log/mysql/error.log && chmod -R 777 /var/log/mysql/
master.cnf
[mysqld]
server-id=1
log-bin=mysql-bin
expire_logs_days=10
binlog_cache_size=2M
max_binlog_size=100M
lower_case_table_names=1
log_error=/var/log/mysql/error.log
mater 경로에서 Dockerfile, master.cnf 파일을 만들어줍니다.
mkdir scripts
cd scripts
scripts 경로를 만들어주고 진입합니다.
docker-entrypoint-master.sh
#!/bin/bash
# (1)
set -e
# (2)
until mysqladmin -u root -p"${MYSQL_ROOT_PASSWORD}" ping; do
echo "# waiting for mysql - $(date)"
sleep 3
done
# (3)
mysql -u root -p"${MYSQL_ROOT_PASSWORD}" -e "CREATE USER 'replUser'@'172.28.0.%' IDENTIFIED BY '${MYSQL_USER_PASSWORD}'"
mysql -u root -p"${MYSQL_ROOT_PASSWORD}" -e "GRANT ALL PRIVILEGES ON *.* TO 'replUser'@'172.28.0.%' WITH GRANT OPTION"
mysql -u root -p"${MYSQL_ROOT_PASSWORD}" -e "GRANT REPLICATION SLAVE ON *.* TO 'replUser'@'172.28.0.%'"
mysql -u root -p"${MYSQL_ROOT_PASSWORD}" -e "FLUSH PRIVILEGES"
scripts 경로에서 docker-entrypoint-master.sh 파일을 만들어줍니다.
(6) 레플리케이션 slave 관련 파일 설정 및 생성
cd slave
mysql 경로로 돌아가서 slave 경로로 들어갑니다.
Dockerfile
FROM mysql:8.0.32-debian
USER root
COPY ./slave.cnf /etc/mysql/my.cnf
RUN mkdir /var/log/mysql && touch /var/log/mysql/error.log && chmod -R 777 /var/log/mysql/
slave.cnf
[mysqld]
server-id=2
log-bin=mysql-bin
relay_log=/var/lib/mysql/mysql-relay-bin
#log_replica_updates=ON
expire_logs_days=10
binlog_cache_size=2M
max_binlog_size=100M
read_only=ON
lower_case_table_names=1
log_error=/var/log/mysql/error.log
slave 경로에서 Dockerfile, slave.cnf 파일을 만들어줍니다.
mkdir scripts
cd scripts
scripts 경로를 만들어주고 진입합니다.
docker-entrypoint-slave.sh
#!/bin/bash
set -e
# (1)
until mysqladmin -u root -p"${MYSQL_ROOT_PASSWORD}" -h 172.28.0.2 ping; do
echo "# waiting for master - $(date)"
sleep 3
done
# (2)
mysql -u root -p"${MYSQL_ROOT_PASSWORD}" -e "CREATE USER 'replUser'@'172.28.0.%' IDENTIFIED BY '${MYSQL_USER_PASSWORD}'"
mysql -u root -p"${MYSQL_ROOT_PASSWORD}" -e "GRANT ALL PRIVILEGES ON *.* TO 'replUser'@'172.28.0.%' WITH GRANT OPTION"
mysql -u root -p"${MYSQL_ROOT_PASSWORD}" -e "FLUSH PRIVILEGES"
# (3)
source_log_file=$(mysql -u root -p"${MYSQL_ROOT_PASSWORD}" -h 172.28.0.2 -e "SHOW MASTER STATUS\G" | grep mysql-bin)
re="[a-z]*-bin.[0-9]*"
if [[ $source_log_file =~ $re ]];then
source_log_file=${BASH_REMATCH[0]}
fi
# (4)
source_log_pos=$(mysql -u root -p"${MYSQL_ROOT_PASSWORD}" -h 172.28.0.2 -e "SHOW MASTER STATUS\G" | grep Position)
re="[0-9]+"
if [[ $source_log_pos =~ $re ]];then
source_log_pos=${BASH_REMATCH[0]}
fi
# (5)
sql="CHANGE REPLICATION SOURCE TO SOURCE_HOST='172.28.0.2', SOURCE_USER='replUser', SOURCE_PASSWORD='${MYSQL_USER_PASSWORD}', SOURCE_LOG_FILE='${source_log_file}', SOURCE_LOG_POS=${source_log_pos}, GET_SOURCE_PUBLIC_KEY=1"
mysql -u root -p"${MYSQL_ROOT_PASSWORD}" -e "${sql}"
mysql -u root -p"${MYSQL_ROOT_PASSWORD}" -e "START REPLICA"
# (6)
mysql -u root -p"${MYSQL_ROOT_PASSWORD}" -h 172.28.0.2 -e "CREATE DATABASE ${MYSQL_DB}"
scripts 경로에서 docker-entrypoint-slave.sh 파일을 만들어줍니다.
(7) Docker Compose 실행 후 레플리케이션 정상 동작 확인
처음에 만든 Docker Compose 파일인 docker-compose-replication-database.yml 파일이 있는 docker-service 경로로 돌아가서 Docker Compose를 실행시켜줍니다.
Docker Compose 실행 명령어
docker-compose -f docker-compose-replication-database.yml up -d
실행 명령어 입력 후 master, slave DB 컨테이너가 done 처리가 될 때까지 기다려줍니다.
실행중인 Docker 컨테이너 확인 명령어
docker ps
docker ps 명령어를 입력하여 방금 실행한 master, slave DB 컨테이너가 정상적으로 up 되어 실행된 상태인지 확인해줍니다.
위의 이미지처럼 up 상태로 실행되어있으면 정상적으로 실행된 것입니다.
(8) master, slave 컨테이너에 직접 들어가 레플리케이션 동작 확인
docker exec -it mysql-master /bin/sh // master 컨테이너 접속
docker exec -it mysql-slave /bin/sh // slave 컨테이너 접속
위의 명령어를 입력하여 실행하고 있는 master, slave 컨테이너에 직접 접속할 수 있습니다.
우선 master 쪽을 확인해보겠습니다.
mysql -u root -p
> DB root 계정 비밀번호 입력
커맨드로 mysql에 root 계정으로 진입합니다.
show databases 명령어로 현재 존재하고 있는 DB 들을 확인해줍니다.
아까 Docker Compose 파일에 설정값으로 넣었던 MYSQL_DB: {생성 / 실행할 데이터베이스 명}에 맞는 DB가 새롭게 추가된 것을 확인할 수 있습니다.
show master status 명령어로 설정한대로 현재 DB가 master DB 로 설정되었는지 확인합니다.
위의 이미지처럼 bin 파일이 생성된 목록이 나온다면 정상적으로 설정된 것입니다.
이제 slave 쪽을 확인해보겠습니다.
mysql -u root -p
> DB root 계정 비밀번호 입력
slave DB 컨테이너에 접속해서 마찬가지로 mysql에 root 계정으로 진입합니다.
show databases 명령어로 현재 존재하고 있는 DB 들을 확인해줍니다.
아까 Docker Compose 파일에 설정값으로 넣었던 MYSQL_DB: {생성 / 실행할 데이터베이스 명}에 맞는 DB가 새롭게 추가된 것을 확인할 수 있습니다.
show slave status 명령어로 Slave IO Running, Slave SQL Running 설정 모두 Yes로 되었는지 확인해줍니다.
위의 이미지처럼 둘 다 Yes로 나온다면 축하합니다. 정상적으로 설정이 된 것입니다.
여기서 IO Running 설정은, master DB에서 발생된 데이터나 SQL 구문같은 정보들이 담긴 바이너리 로그를 slave DB 와 비교하여 다른 점이 존재할 경우 이것을 바이너리 로그에서 가져올 수 있도록 입출력을 하는 것을 말합니다.
SQL Running 설정은, 바이너리 로그에서 가져온 데이터나 SQL구문들을 실제 slave DB에 SQL 형식으로 날려 반영될 수 있도록 하는 설정입니다.
이제 master, slave DB 모두 정상적으로 설정된 것을 확인했으니 이번에는 실제로 데이터베이스가 동기화를 이루는지 마지막으로 확인해보겠습니다.
master DB에서 테스트 DB를 생성하고 slave DB에서 생성된 데이터베이스를 조회해보니 아까 전까진 없던 testdb가 정상적으로 동일하게 생성된 것을 확인할 수 있었습니다.
+
이렇게 해서 Docker 를 활용하여 데이터베이스 레플리케이션을 진행해보았습니다.
이제 이 레플리케이션 DB들을 활용하여 데이터에 대한 write, update, delete와 같은 작업들은 master DB에 일임하고, select 조회 작업들은 slave DB에 일임하여 프로젝트를 마이그레이션하면 될 것입니다.
# 참고 글
https://given-dev.tistory.com/113
MySQL Replication 구성하기 - 1 with Docker
과거에 진행했던 코인 모의투자 프로젝트에서 겪었던 문제와 Replication을 구성한 내용을 작성해보려고 한다. 1. 배경 맡았던 도메인은 사용자의 매수, 매도 주문이었고 기능 중 하나는 주문이 체
given-dev.tistory.com
'기술 창고 > Docker' 카테고리의 다른 글
[Docker] Docker 설치 (0) | 2025.04.10 |
---|---|
[Docker] Docker-Compose 설치 (0) | 2024.04.15 |
[Docker] 명령어 실행 / 반영 (1) | 2023.06.16 |
[Docker] Docker 네트워크 운영 방법 (0) | 2023.06.16 |
[Docker] Docker 네트워크 (0) | 2023.06.16 |