Docker로 MySQL 배포하기 완벽 가이드: 데이터 영속화부터 마스터-슬레이브 복제 실전까지

터미널에 눈에 거슬리는 오류 메시지가 떴습니다. 보름 동안 쌓은 테스트 데이터가 모두 사라졌습니다. 오후에 MySQL 컨테이너를 한 번 재시작했을 뿐인데, 평범한 재시작 작업이라고 생각했던 것과 달리 데이터까지 컨테이너와 함께 사라져 버렸습니다.
Docker로 MySQL을 배포하는 일은 간단해 보입니다. docker run -e MYSQL_ROOT_PASSWORD=123456 mysql만 실행하면 바로 구동됩니다. 하지만 컨테이너 재시작 후 데이터가 사라지거나, 설정 파일 마운트가 적용되지 않거나, 로컬 애플리케이션에서 연결할 수 없거나, 프로덕션 환경의 마스터-슬레이브 복제를 설정해야 하는 등 직접 겪어 봐야 심각성을 알게 되는 함정이 있습니다. 이 글에서는 데이터 영속화부터 마스터-슬레이브 복제까지 각 설정을 차근차근 설명합니다.
Docker MySQL 단일 서버 배포 기초
가장 간단한 실행 방법과 권장하지 않는 이유
먼저 가장 흔한 오해부터 짚어 보겠습니다. Docker로 MySQL을 처음 설치하는 많은 사람이 곧바로 다음 명령을 실행합니다.
docker run --name mysql-test -e MYSQL_ROOT_PASSWORD=123456 -d mysql:8.0
컨테이너가 정상적으로 실행되고 데이터베이스 작업도 할 수 있어 모든 것이 완벽해 보입니다. 하지만 이것은 시한폭탄과 같습니다.
왜 그럴까요? 컨테이너는 본질적으로 임시 환경입니다. 컨테이너를 삭제하거나 어느 날 실수로 재시작하면 데이터가 모두 사라질 수 있습니다. MySQL은 기본적으로 컨테이너 내부의 /var/lib/mysql 디렉터리에 데이터를 저장하며, 컨테이너가 삭제되면 이 디렉터리도 함께 사라집니다.
저도 처음 이 문제를 겪었을 때는 MySQL 버그라고 생각했습니다. 나중에야 MySQL 문제가 아니라 데이터 영속화를 전혀 설정하지 않은 제 실수였다는 것을 알았습니다.
데이터 영속화: Volume을 올바르게 마운트하는 방법
간단히 말해 데이터 영속화란 MySQL의 데이터 디렉터리를 호스트에 매핑해, 컨테이너가 중단되더라도 데이터를 보존하는 것입니다.
Docker는 세 가지 마운트 방식을 제공합니다.
- bind mount:
/home/mysql/data처럼 호스트의 특정 디렉터리를 직접 매핑합니다. - named volume: Docker가 관리하는 Volume으로, 실제 저장 위치를 신경 쓸 필요가 없습니다.
- tmpfs: 메모리에 저장하므로 재시작하면 사라지며, 거의 사용하지 않습니다.
2024년 Docker 공식 권장 방식은 named volume이며, 성능도 bind mount와 거의 차이가 없습니다. 저는 상황에 따라 두 방식을 모두 사용합니다. 개발 환경에서는 관리가 편한 named volume을, 프로덕션 환경에서는 백업이 쉬운 bind mount를 사용합니다.
전체 명령은 다음과 같습니다.
docker run --name mysql-persistent \
-e MYSQL_ROOT_PASSWORD=rootpwd123 \
-p 3306:3306 \
-v mysql-data:/var/lib/mysql \
-d mysql:8.0
여기서 핵심은 -v mysql-data:/var/lib/mysql입니다. mysql-data는 Volume 이름이며 Docker가 자동으로 생성합니다. /var/lib/mysql은 MySQL 컨테이너 내부의 데이터 디렉터리입니다.
실제로 데이터가 영속화되는지 확인해 보겠습니다.
# 컨테이너에 들어가 데이터베이스 생성
docker exec -it mysql-persistent mysql -uroot -prootpwd123 -e "CREATE DATABASE testdb;"
# 컨테이너 중지 및 삭제
docker stop mysql-persistent
docker rm mysql-persistent
# 같은 Volume을 사용해 컨테이너 다시 실행
docker run --name mysql-persistent \
-e MYSQL_ROOT_PASSWORD=rootpwd123 \
-p 3306:3306 \
-v mysql-data:/var/lib/mysql \
-d mysql:8.0
# 컨테이너에 들어가 testdb가 남아 있는지 확인
docker exec -it mysql-persistent mysql -uroot -prootpwd123 -e "SHOW DATABASES;"
testdb가 그대로 보이면 데이터가 제대로 보존된 것입니다. 이때 비로소 마음이 놓입니다.
설정 파일 마운트: MySQL 매개변수 사용자 지정
데이터 영속화를 해결했다면 다음 문제는 MySQL 설정을 어떻게 바꾸느냐입니다.
예를 들어 문자 집합을 utf8mb4로 바꾸거나 최대 연결 수를 조정하고 싶을 수 있습니다. 설정 파일을 마운트하지 않으면 컨테이너에 들어가 매번 직접 수정해야 하고, 컨테이너를 재시작할 때마다 같은 작업을 반복해야 하므로 무척 번거롭습니다.
MySQL이 설정 파일을 읽는 경로는 /etc/mysql/conf.d/입니다. 사용자 지정 my.cnf를 이 디렉터리에 마운트하면 됩니다.
먼저 호스트에 설정 파일을 만듭니다.
mkdir -p /home/mysql/conf
cat > /home/mysql/conf/my.cnf << 'EOF'
[mysqld]
# 문자 집합 설정
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
# 연결 수 설정
max_connections=1000
# 인증 플러그인(일부 클라이언트의 연결 문제 해결)
default_authentication_plugin=mysql_native_password
[client]
default-character-set=utf8mb4
EOF
그런 다음 컨테이너를 실행할 때 이 설정 파일을 마운트합니다.
docker run --name mysql-custom \
-e MYSQL_ROOT_PASSWORD=rootpwd123 \
-p 3306:3306 \
-v /home/mysql/conf:/etc/mysql/conf.d \
-v /home/mysql/data:/var/lib/mysql \
-d mysql:8.0
여기서는 설정 파일과 데이터 디렉터리, 두 디렉터리를 함께 마운트했다는 점에 유의하세요.
설정이 적용되었는지 확인합니다.
docker exec -it mysql-custom mysql -uroot -prootpwd123 -e "SHOW VARIABLES LIKE 'character%';"
character_set_server 값이 utf8mb4라면 설정이 제대로 적용된 것입니다.
외부 연결 문제 해결
컨테이너도 실행했고, 데이터도 영속화했으며, 설정도 마운트했습니다. 이제 로컬 애플리케이션에서 이 MySQL에 연결하려고 했더니 다음과 같은 오류가 발생할 수 있습니다.
ERROR 2003 (HY000): Can't connect to MySQL server on 'localhost' (Connection refused)
또는 다음 오류가 나타납니다.
ERROR 1045 (28000): Access denied for user 'root'@'172.17.0.1'
저도 처음에는 두 오류를 모두 겪었고, 원인을 파악하는 데 꽤 오랜 시간이 걸렸습니다.
문제 1: Connection refused
대부분 포트 매핑이 잘못된 경우입니다. 컨테이너를 실행할 때 반드시 -p 3306:3306을 추가해 컨테이너의 3306 포트를 호스트의 3306 포트에 매핑해야 합니다.
호스트의 3306 포트를 이미 사용 중이라면, 예를 들어 로컬에 MySQL이 이미 설치되어 있다면 다른 포트에 매핑할 수 있습니다.
-p 3307:3306 # 호스트에서는 3307로 접속하고 컨테이너 내부에서는 여전히 3306 사용
방화벽이 차단하는 경우도 있습니다. Linux 서버라면 방화벽을 확인하세요.
# CentOS/RHEL
sudo firewall-cmd --zone=public --add-port=3306/tcp --permanent
sudo firewall-cmd --reload
# Ubuntu
sudo ufw allow 3306/tcp
문제 2: Access denied
이 오류는 권한 문제입니다. MySQL이 기본으로 생성한 root 사용자는 localhost에서만 접속할 수 있으므로 외부 IP에서는 연결할 수 없습니다.
해결 방법은 root 사용자의 host를 %로 바꾸어 모든 IP에서 연결할 수 있게 하는 것입니다.
# 컨테이너 접속
docker exec -it mysql-custom mysql -uroot -prootpwd123
# 다음 SQL 실행
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'rootpwd123';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;
여기서는 두 가지에 주의해야 합니다.
mysql_native_password인증 플러그인은 호환성이 더 좋아 일부 구버전 MySQL 클라이언트에서 필요합니다.- 프로덕션 환경에서는 절대로 이렇게 설정하지 마세요. root 사용자가 모든 IP에서 접속하도록 허용하지 말고, 전용 사용자를 생성해 허용 IP를 제한해야 합니다.
설정을 마치면 외부 애플리케이션에서도 연결할 수 있습니다.
첫 번째 부분은 여기까지입니다. 이 문제들을 해결하면 기본적인 Docker MySQL 단일 서버 배포에서 큰 문제는 거의 없습니다.
Docker Compose로 깔끔하게 관리하기
Docker Compose를 권장하는 이유
로컬 개발과 테스트만 한다면 앞에서 소개한 docker run 명령으로도 충분합니다. 하지만 실제 업무에서는 다음과 같은 문제가 생깁니다.
- 명령이 너무 길어서 매번 이전 기록을 찾아야 합니다.
- Volume 경로를 잘못 쓰는 등 매개변수를 실수하기 쉽습니다.
- 팀원마다 실행 명령이 달라 협업 시 설정이 뒤섞일 수 있습니다.
- MySQL, Redis, Nginx처럼 여러 컨테이너를 함께 실행해야 할 때 하나씩 시작하기가 번거롭습니다.
이럴 때 Docker Compose가 유용합니다.
간단히 말해 Docker Compose는 길게 이어진 docker run 명령을 YAML 설정 파일 하나에 작성하는 방식입니다. 이후에는 docker-compose up -d로 실행하고 docker-compose down으로 중지할 수 있으며, 설정 파일을 Git에서 버전 관리할 수도 있습니다.
새로 합류한 팀원이 “MySQL은 어떻게 실행하나요?”라고 물으면 docker-compose.yml을 건네기만 하면 됩니다. 팀원은 git clone 후 한 번의 명령으로 실행할 수 있으므로 추가 설명도 거의 필요하지 않습니다. 정말 편리합니다.
Docker Compose 단일 MySQL 설정
전체 설정부터 제시한 뒤 각 줄을 설명하겠습니다.
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: mysql-standalone
restart: always
environment:
MYSQL_ROOT_PASSWORD: rootpwd123
MYSQL_DATABASE: myapp
MYSQL_USER: appuser
MYSQL_PASSWORD: apppwd123
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
- ./conf/my.cnf:/etc/mysql/conf.d/my.cnf
- ./logs:/var/log/mysql
networks:
- mysql-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-prootpwd123"]
interval: 10s
timeout: 5s
retries: 3
volumes:
mysql-data:
networks:
mysql-network:
driver: bridge
핵심 설정을 항목별로 설명하겠습니다.
environment 부분:
MYSQL_ROOT_PASSWORD: 필수 입력값인 root 비밀번호입니다.MYSQL_DATABASE: 컨테이너 시작 시 자동으로 생성할 데이터베이스입니다.MYSQL_USER와MYSQL_PASSWORD: 자동으로 생성할 일반 사용자로, root보다 안전합니다.
volumes 부분:
mysql-data:/var/lib/mysql: named volume 방식의 데이터 영속화입니다../conf/my.cnf:/etc/mysql/conf.d/my.cnf: 상대 경로로 설정 파일을 마운트합니다../logs:/var/log/mysql: 문제를 쉽게 확인할 수 있도록 로그 디렉터리를 마운트합니다.
restart: always: 컨테이너가 중단되면 자동으로 재시작하고, 서버가 재부팅된 뒤에도 컨테이너를 자동으로 실행합니다.
healthcheck: 주기적으로 MySQL에 ping을 보내 상태를 확인하며, 문제가 생기면 자동으로 재시작합니다.
networks: 여러 컨테이너가 서로 통신해야 한다면 모두 이 사용자 지정 네트워크에 추가하면 됩니다.
사용 절차는 다음과 같습니다.
- 디렉터리 구조를 만듭니다.
mkdir -p mysql-docker/{conf,logs}
cd mysql-docker
conf/my.cnf설정 파일을 만듭니다.
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
max_connections=1000
default_authentication_plugin=mysql_native_password
[client]
default-character-set=utf8mb4
-
위의 전체 설정으로
docker-compose.yml을 만듭니다. -
실행합니다.
docker-compose up -d
- 상태를 확인합니다.
docker-compose ps
다음과 비슷한 출력이 표시됩니다.
Name Command State Ports
------------------------------------------------------------------------------------------------
mysql-standalone docker-entrypoint.sh mysqld Up (healthy) 0.0.0.0:3306->3306/tcp
(healthy) 상태가 표시되면 healthcheck를 통과했다는 뜻입니다.
- 로그를 확인합니다.
docker-compose logs -f mysql
-f 매개변수는 tail -f처럼 로그를 실시간으로 추적합니다. 실행 문제가 있다면 로그에서 대부분 원인을 찾을 수 있습니다.
- 컨테이너를 중지하고 삭제합니다.
docker-compose down
주의: 이 명령은 컨테이너만 삭제하며 Volume은 삭제하지 않으므로 데이터는 사라지지 않습니다. 데이터까지 함께 삭제하려면 다음 명령을 사용합니다.
docker-compose down -v # 주의! 모든 데이터가 삭제됩니다.
솔직히 저는 요즘 로컬 개발에서 거의 항상 Docker Compose를 사용합니다. 설정을 한 번만 작성하면 이후 실행과 중지가 모두 한 줄의 명령으로 끝나므로 편리합니다.
프로덕션급 마스터-슬레이브 복제 설정
MySQL 마스터-슬레이브 복제 원리 간단히 살펴보기
먼저 마스터-슬레이브 복제가 필요한 이유를 살펴보겠습니다.
소규모 프로젝트에서는 단일 MySQL 서버로 충분하지만 트래픽이 늘어나면 감당하기 어려워집니다. 마스터-슬레이브 복제는 주로 두 가지 문제를 해결합니다.
- 읽기/쓰기 분리: 마스터는 쓰기(INSERT, UPDATE, DELETE)를 담당하고 슬레이브는 읽기(SELECT)를 담당합니다. 대부분의 애플리케이션은 쓰기보다 읽기가 많으므로 읽기 요청을 여러 슬레이브에 분산하면 성능이 크게 향상됩니다.
- 데이터 백업과 고가용성: 마스터에 장애가 생겨도 슬레이브가 대신 역할을 수행할 수 있어 최소한 읽기 서비스는 계속 제공할 수 있습니다.
원리는 복잡하지 않습니다.
- 마스터(Master)는 binlog를 활성화해 모든 데이터 변경 작업을 기록합니다.
- 슬레이브(Slave)는 마스터에 연결해 binlog 내용을 읽습니다.
- 슬레이브에서는 두 스레드가 동작합니다. IO 스레드는 마스터에서 binlog를 가져와 relay log에 저장하고, SQL 스레드는 relay log의 SQL 문을 실행합니다.
- 이 과정을 통해 마스터의 데이터 변경 사항이 슬레이브에 동기화됩니다.
Docker 환경에서 마스터-슬레이브 복제를 설정할 때 핵심은 다음과 같습니다.
- 두 컨테이너에 서로 다른 server-id를 설정합니다.
- 마스터에서 binlog를 활성화합니다.
- 슬레이브를 마스터에 연결하고 복제를 시작합니다.
복잡하게 들릴 수 있지만 Docker Compose 설정을 작성하고 실행하면 되므로 생각만큼 어렵지 않습니다.
Master 노드 설정
먼저 마스터를 설정합니다. 마스터에서는 binlog 활성화, server-id 설정, 복제 사용자 생성이라는 세 가지 작업이 필요합니다.
- 마스터 설정 파일
conf/master.cnf를 만듭니다.
[mysqld]
# 고유 서버 ID. 마스터와 슬레이브에서 중복되면 안 됩니다.
server-id=1
# 바이너리 로그 활성화
log-bin=mysql-bin
# binlog 형식. ROW 모드는 각 행의 변경을 기록하므로 더 안전합니다.
binlog-format=ROW
# 문자 집합
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
# 선택 사항: 동기화할 데이터베이스 설정. 지정하지 않으면 모두 동기화합니다.
# binlog-do-db=myapp
# 선택 사항: 동기화하지 않을 데이터베이스 설정
# binlog-ignore-db=mysql
# binlog-ignore-db=information_schema
- 마스터의 docker-compose 설정을 작성합니다.
version: '3.8'
services:
mysql-master:
image: mysql:8.0
container_name: mysql-master
restart: always
environment:
MYSQL_ROOT_PASSWORD: rootpwd123
MYSQL_DATABASE: myapp
ports:
- "3306:3306"
volumes:
- master-data:/var/lib/mysql
- ./conf/master.cnf:/etc/mysql/conf.d/master.cnf
- ./logs/master:/var/log/mysql
networks:
- mysql-replication
volumes:
master-data:
networks:
mysql-replication:
driver: bridge
- 마스터를 실행합니다.
docker-compose up -d mysql-master
- 복제 사용자를 만듭니다.
마스터 컨테이너에 접속해 복제 전용 사용자를 만듭니다.
docker exec -it mysql-master mysql -uroot -prootpwd123
다음 SQL을 실행합니다.
-- 복제 사용자 생성
CREATE USER 'repl'@'%' IDENTIFIED WITH mysql_native_password BY 'replpwd123';
-- 복제 권한 부여
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
-- 권한 새로 고침
FLUSH PRIVILEGES;
-- 마스터 상태 확인 후 File과 Position 기록
SHOW MASTER STATUS;
SHOW MASTER STATUS를 실행하면 다음과 비슷한 결과가 출력됩니다.
+------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000003 | 156 | | |
+------------------+----------+--------------+------------------+
중요: File과 Position 값을 기록해 두세요. 슬레이브를 설정할 때 필요합니다.
Slave 노드 설정
슬레이브 설정은 비교적 간단합니다.
- 슬레이브 설정 파일
conf/slave.cnf를 만듭니다.
[mysqld]
# 고유 서버 ID. 마스터와 달라야 합니다.
server-id=2
# 릴레이 로그
relay-log=relay-bin
# 슬레이브 읽기 전용 설정(실수로 쓰는 작업 방지)
read-only=1
# 문자 집합
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
- docker-compose.yml을 수정해 슬레이브 설정을 추가합니다.
version: '3.8'
services:
mysql-master:
image: mysql:8.0
container_name: mysql-master
restart: always
environment:
MYSQL_ROOT_PASSWORD: rootpwd123
MYSQL_DATABASE: myapp
ports:
- "3306:3306"
volumes:
- master-data:/var/lib/mysql
- ./conf/master.cnf:/etc/mysql/conf.d/master.cnf
- ./logs/master:/var/log/mysql
networks:
- mysql-replication
mysql-slave:
image: mysql:8.0
container_name: mysql-slave
restart: always
environment:
MYSQL_ROOT_PASSWORD: rootpwd123
ports:
- "3307:3306" # 주의: 호스트에서는 3307로 슬레이브에 접속합니다.
volumes:
- slave-data:/var/lib/mysql
- ./conf/slave.cnf:/etc/mysql/conf.d/slave.cnf
- ./logs/slave:/var/log/mysql
networks:
- mysql-replication
depends_on:
- mysql-master
volumes:
master-data:
slave-data:
networks:
mysql-replication:
driver: bridge
- 슬레이브를 실행합니다.
docker-compose up -d mysql-slave
- 슬레이브가 마스터에 연결하도록 설정합니다.
슬레이브 컨테이너에 접속합니다.
docker exec -it mysql-slave mysql -uroot -prootpwd123
다음 SQL을 실행합니다. File과 Position은 앞에서 기록한 값으로 바꾸세요.
-- 마스터 연결 정보 설정
CHANGE MASTER TO
MASTER_HOST='mysql-master', -- 마스터 컨테이너 이름(Docker 네트워크 안에서는 컨테이너 이름을 바로 사용할 수 있음)
MASTER_PORT=3306, -- 마스터 포트
MASTER_USER='repl', -- 복제 사용자
MASTER_PASSWORD='replpwd123', -- 복제 사용자 비밀번호
MASTER_LOG_FILE='mysql-bin.000003', -- 마스터 binlog 파일(SHOW MASTER STATUS에서 확인)
MASTER_LOG_POS=156; -- 마스터 binlog 위치(SHOW MASTER STATUS에서 확인)
-- 슬레이브 복제 시작
START SLAVE;
-- 슬레이브 상태 확인
SHOW SLAVE STATUS\G
마스터-슬레이브 동기화 확인
SHOW SLAVE STATUS\G를 실행하면 많은 정보가 출력됩니다. 다음 필드를 중점적으로 확인하세요.
Slave_IO_Running: Yes # IO 스레드 실행 상태. 반드시 Yes여야 합니다.
Slave_SQL_Running: Yes # SQL 스레드 실행 상태. 반드시 Yes여야 합니다.
Seconds_Behind_Master: 0 # 슬레이브 지연 시간(초). 0은 실시간 동기화를 뜻합니다.
Last_IO_Error: # IO 오류 정보. 비어 있으면 정상입니다.
Last_SQL_Error: # SQL 오류 정보. 비어 있으면 정상입니다.
Slave_IO_Running과 Slave_SQL_Running이 모두 Yes라면 마스터-슬레이브 복제 설정에 성공한 것입니다.
실제로 동기화되는지 테스트해 보겠습니다.
- 마스터에 테스트 데이터를 만듭니다.
docker exec -it mysql-master mysql -uroot -prootpwd123 -e "
USE myapp;
CREATE TABLE test_table (id INT PRIMARY KEY, name VARCHAR(50));
INSERT INTO test_table VALUES (1, 'test data');
"
- 슬레이브에서 조회합니다.
docker exec -it mysql-slave mysql -uroot -prootpwd123 -e "
USE myapp;
SELECT * FROM test_table;
"
방금 삽입한 데이터가 조회되면 마스터-슬레이브 동기화가 정상적으로 동작하는 것입니다.
프로덕션 환경에서 직접 작성한 코드가 제대로 실행되는 모습을 볼 때처럼 짜릿합니다.
자주 발생하는 문제 해결
마스터-슬레이브 복제 자체는 어렵지 않지만 처음 설정할 때는 여러 문제를 만나기 쉽습니다. 자주 발생하는 문제를 정리해 보겠습니다.
문제 1: Slave_IO_Running이 No
가능한 원인은 다음과 같습니다.
- 네트워크 연결 실패: 두 컨테이너가 같은 네트워크에 있는지 확인하고
docker exec -it mysql-slave ping mysql-master로 테스트합니다. - 사용자 권한 오류: 마스터에 repl 사용자가 정상적으로 생성되었고 권한이 올바른지 확인합니다.
- 잘못된 binlog 파일 이름 또는 위치:
SHOW MASTER STATUS를 다시 실행해 확인합니다.
문제 2: Slave_SQL_Running이 No
가능한 원인은 다음과 같습니다.
- SQL 실행 오류:
Last_SQL_Error필드를 확인하고 오류 정보에 따라 해결합니다. - 마스터와 슬레이브의 데이터 불일치: 복제를 설정하기 전에 마스터에 데이터가 있었다면 먼저 마스터 데이터를 내보내 슬레이브로 가져온 뒤 마스터-슬레이브 복제를 설정해야 합니다.
문제 3: Seconds_Behind_Master가 계속 큰 값으로 표시됨
가능한 원인은 다음과 같습니다.
- 슬레이브 성능 부족: 슬레이브의 CPU, 메모리, 디스크 I/O를 확인합니다.
- 마스터의 쓰기 작업이 지나치게 빈번함: 슬레이브 수를 늘려 부하를 분산하는 방안을 고려합니다.
- 네트워크 대역폭 부족: 네트워크 지연을 확인합니다.
문제를 해결하기 어렵다면 슬레이브를 초기화하고 다시 설정할 수 있습니다.
-- 슬레이브에서 실행
STOP SLAVE;
RESET SLAVE;
-- 그런 다음 CHANGE MASTER TO와 START SLAVE를 다시 실행
성능 최적화와 모범 사례
Docker MySQL 성능 최적화 권장 사항
Docker가 MySQL 성능을 저하시킬까 걱정하는 사람이 많습니다. 솔직히 몇 년 전에는 실제로 그런 문제가 있었지만, 2024년 데이터에 따르면 Docker가 MySQL 성능에 미치는 영향은 이미 5% 이내로 줄었고 I/O 집약적인 환경에서는 차이를 거의 느낄 수 없습니다.
그렇더라도 필요한 최적화는 해야 합니다. 몇 가지 실용적인 권장 사항을 소개하겠습니다.
1. Volume 유형 선택
앞에서 named volume과 bind mount를 설명했습니다. 성능 면에서는 큰 차이가 없지만 다음과 같은 세부 차이가 있습니다.
- named volume: Docker가 관리하고 최적의 스토리지 드라이버를 자동으로 선택하므로 개발 환경에 권장합니다.
- bind mount: 호스트 디렉터리를 직접 매핑하므로 백업과 모니터링이 편리해 프로덕션 환경에 권장합니다.
# named volume 방식
volumes:
- mysql-data:/var/lib/mysql
# bind mount 방식
volumes:
- /data/mysql:/var/lib/mysql
2. 네트워크 모드 선택
기본 bridge 네트워크로도 충분하지만 성능을 극대화해야 한다면 host 네트워크 모드를 시도해 볼 수 있습니다.
services:
mysql:
network_mode: "host" # 호스트 네트워크를 직접 사용하므로 성능이 더 좋습니다.
host 모드를 사용하면 ports 매핑이 필요하지 않으며 컨테이너가 호스트의 3306 포트를 직접 수신합니다.
3. 리소스 제한 설정
프로덕션 환경에서는 MySQL 컨테이너가 서버 리소스를 모두 사용하지 않도록 반드시 제한해야 합니다.
services:
mysql:
image: mysql:8.0
deploy:
resources:
limits:
cpus: '2' # CPU 코어는 최대 2개까지 사용
memory: 2G # 메모리는 최대 2GB까지 사용
reservations:
memory: 1G # 최소 1GB 메모리 보장
이 설정을 적용하려면 docker-compose --compatibility up으로 실행하거나 Docker Swarm 모드를 사용해야 합니다.
4. 로그 관리
MySQL 컨테이너의 로그 크기를 제한하지 않으면 시간이 지나 디스크가 가득 찰 수 있습니다. 다음과 같이 로그 제한을 추가하세요.
services:
mysql:
logging:
driver: "json-file"
options:
max-size: "100m" # 로그 파일 하나의 최대 크기는 100MB
max-file: "3" # 로그 파일은 최대 3개 보관
프로덕션 환경 모범 사례 체크리스트
이 부분은 제가 직접 문제를 겪으며 정리한 내용이므로 그대로 적용하기를 강력히 권장합니다.
보안 권장 사항:
- 기본 비밀번호나 단순한 비밀번호를 사용하지 마세요.
# ❌ 이렇게 설정하지 마세요.
MYSQL_ROOT_PASSWORD: 123456
# ✅ 강력한 비밀번호 또는 Docker secrets 사용
MYSQL_ROOT_PASSWORD: "Mx8#kL9$pQ2@vN4!"
- Docker secrets로 민감한 정보를 관리하세요.
services:
mysql:
image: mysql:8.0
secrets:
- mysql_root_password
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root_password
secrets:
mysql_root_password:
file: ./secrets/mysql_root_password.txt
- 컨테이너의 네트워크 접근을 제한하세요.
networks:
mysql-network:
driver: bridge
internal: true # 컨테이너끼리만 통신할 수 있으며 외부 접근은 금지합니다.
외부 접근이 필요하다면 전용 애플리케이션 컨테이너를 프록시로 사용하고 MySQL 컨테이너를 직접 노출하지 마세요.
운영 권장 사항:
- 데이터 디렉터리를 정기적으로 백업하세요.
# 단순하고 직접적인 백업 스크립트
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
docker exec mysql-master mysqldump -uroot -prootpwd123 --all-databases > backup_$DATE.sql
# 또는 데이터 디렉터리를 직접 백업
tar -czf mysql-data-backup_$DATE.tar.gz /data/mysql/
프로덕션 환경에서는 매일 자동으로 백업하고 최소 7일간 백업을 보관하는 것이 좋습니다.
- healthcheck로 컨테이너 상태를 모니터링하세요.
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p$$MYSQL_ROOT_PASSWORD"]
interval: 10s
timeout: 5s
retries: 3
start_period: 30s
컨테이너에 장애가 발생하면 자동으로 재시작하며, 모니터링 알림까지 연동하면 더 좋습니다.
- 로그 Volume을 별도로 마운트하세요.
volumes:
- ./logs:/var/log/mysql
로그를 별도로 마운트하면 문제가 생겼을 때 확인하기 쉽고 데이터 Volume 공간도 차지하지 않습니다.
고가용성 권장 사항:
- 최소한 마스터 1대와 슬레이브 1대를 두고, 가능하면 마스터 1대와 여러 슬레이브를 구성하세요.
읽기가 많고 쓰기가 적은 환경에서는 마스터 하나에 슬레이브 3~5개를 구성해 읽기 요청을 슬레이브로 분산합니다.
- 로드 밸런싱을 함께 사용해 읽기/쓰기 분리를 구현하세요.
MySQL Router, ProxySQL 또는 애플리케이션 계층에서 구현할 수 있습니다.
- 쓰기 작업(INSERT/UPDATE/DELETE) → 마스터
- 읽기 작업(SELECT) → 슬레이브(로드 밸런싱)
- 마스터-슬레이브 전환 절차를 정기적으로 테스트하세요.
마스터에 실제 장애가 생긴 뒤에야 슬레이브가 대신 역할을 수행할 수 없다는 사실을 발견해서는 안 됩니다. 마스터-슬레이브 전환을 정기적으로 연습하세요.
-- 슬레이브에서 실행
STOP SLAVE;
RESET SLAVE ALL;
SET GLOBAL read_only=0; -- 읽기 전용을 해제하고 마스터로 승격
프로덕션 환경에서는 MHA나 Orchestrator 같은 검증된 고가용성 솔루션으로 자동 전환하는 것이 좋습니다.
결론
지금까지 살펴본 내용을 정리해 보겠습니다.
가장 기본적인 Docker MySQL 단일 서버 배포부터 데이터 영속화, 설정 파일 마운트, 외부 연결 문제 해결, Docker Compose를 활용한 관리, 마지막으로 프로덕션급 마스터-슬레이브 복제 설정까지 다뤘습니다. 이 전체 과정은 Docker MySQL 배포에서 흔히 마주치는 거의 모든 상황을 포괄합니다.
핵심 사항을 다시 강조하겠습니다.
- 데이터 영속화는 필수입니다. 더 이상 Volume 없는 컨테이너로 MySQL을 실행하지 마세요. 데이터 손실은 뼈아픈 경험이 됩니다.
- 설정 파일 마운트가 중요합니다. 문자 집합, 연결 수 같은 매개변수는 미리 설정해야 합니다.
- Docker Compose는 효율적인 선택입니다. 설정을 한곳에서 관리할 수 있고 팀 협업에도 유리합니다.
- 마스터-슬레이브 복제는 어렵지 않습니다. 설정 파일과 절차만 정확하면 됩니다.
- 프로덕션 환경에서는 보안과 백업에 주의해야 합니다. 약한 비밀번호를 사용하지 말고 데이터를 정기적으로 백업하세요.
이 글의 모든 설정 코드는 직접 검증해 바로 사용할 수 있는 내용입니다. 프로젝트에 그대로 복사한 뒤 비밀번호와 포트만 바꾸면 실행할 수 있습니다.
Docker로 MySQL을 처음 배포한다면 먼저 단일 서버 배포부터 시작하세요. 데이터 영속화와 설정 마운트를 확실히 이해한 뒤 마스터-슬레이브 복제에 도전하는 것이 좋습니다. 서두르지 말고 한 단계씩 진행하세요.
문제가 생겨도 당황하지 마세요. 대부분의 문제는 로그에서 원인을 찾을 수 있습니다. 해결하기 어렵다면 댓글로 함께 이야기해 주세요. 확인하는 대로 답변하겠습니다.
마지막으로 한마디만 덧붙이겠습니다. Docker로 MySQL을 배포하는 방식은 기존 방식보다 정말 훨씬 편합니다. 한 번 설정하면 어디서든 실행할 수 있습니다. 바로 이 편리함이 Docker의 매력입니다.
Docker로 MySQL을 배포하는 전체 과정
데이터 영속화부터 마스터-슬레이브 복제 설정까지 다루며, 컨테이너 재시작 시 데이터 손실, 설정 파일 마운트, 연결 실패 등 자주 발생하는 문제를 해결합니다.
⏱️ Estimated time: 1 hr
- 1
Step 1: 단일 서버 배포: 데이터 영속화와 설정 마운트
데이터 영속화:
• Volume으로 데이터 디렉터리 마운트:
docker run -d -v mysql-data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0
• 설정 파일 마운트:
docker run -d -v mysql-data:/var/lib/mysql -v ./my.cnf:/etc/mysql/conf.d/my.cnf -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0
• 환경 변수 설정:
MYSQL_ROOT_PASSWORD, MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD
영속화 확인:
• 컨테이너를 삭제한 뒤 다시 생성해도 데이터가 그대로 남아 있는지 확인합니다. - 2
Step 2: 마스터-슬레이브 복제 설정
마스터 설정:
• binlog 활성화: my.cnf에 log-bin=mysql-bin 설정
• server-id 설정: server-id=1
• replication 사용자 생성:
CREATE USER 'repl'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
슬레이브 설정:
• 마스터 주소 지정:
CHANGE MASTER TO
MASTER_HOST='master',
MASTER_USER='repl',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=0;
• 마스터-슬레이브 복제 시작: START SLAVE;
• 복제 상태 확인: SHOW SLAVE STATUS\G; - 3
Step 3: 자주 발생하는 문제 해결과 프로덕션급 배포
자주 발생하는 문제 해결:
• 연결 실패: 포트 매핑 -p 3306:3306, 네트워크 설정, 방화벽 확인
• 데이터 손실: Volume 영속화 설정
• 문자 집합 문제: my.cnf에 character-set-server=utf8mb4 설정
• 권한 문제: Volume 권한 chmod 755 확인
프로덕션급 배포:
• docker-compose로 여러 컨테이너 관리
• 상태 검사(healthcheck) 설정
• 리소스 제한(deploy.resources) 설정
• 백업 전략(정기적인 Volume 백업) 구성
• 관리하기 쉬운 named Volume 사용
FAQ
Docker로 MySQL을 배포할 때 데이터 손실을 방지하려면 어떻게 해야 하나요?
1) Volume으로 데이터 디렉터리 마운트:
docker run -d -v mysql-data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0
2) 설정 파일 마운트:
docker run -d -v mysql-data:/var/lib/mysql -v ./my.cnf:/etc/mysql/conf.d/my.cnf -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0
3) 환경 변수 설정:
• MYSQL_ROOT_PASSWORD
• MYSQL_DATABASE
• MYSQL_USER
• MYSQL_PASSWORD
영속화 확인: 컨테이너를 삭제한 뒤 다시 생성해도 데이터가 그대로 남아 있습니다.
문제의 원인:
Docker 컨테이너는 기본적으로 데이터를 컨테이너 계층에 저장하므로 컨테이너를 삭제하면 데이터도 함께 사라집니다. 따라서 데이터 영속화를 설정해야 합니다.
MySQL 마스터-슬레이브 복제는 어떻게 설정하나요?
1) binlog 활성화(my.cnf에 log-bin=mysql-bin 설정)
2) server-id 설정(server-id=1)
3) replication 사용자 생성:
CREATE USER 'repl'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
슬레이브 설정:
1) 마스터 주소 지정:
CHANGE MASTER TO
MASTER_HOST='master',
MASTER_USER='repl',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=0;
2) 마스터-슬레이브 복제 시작: START SLAVE;
3) 복제 상태 확인: SHOW SLAVE STATUS\G;
Docker MySQL 연결 실패는 어떻게 해결하나요?
• 연결 실패: 포트 매핑 -p 3306:3306, 네트워크 설정, 방화벽 확인
• 데이터 손실: Volume 영속화 설정
• 문자 집합 문제: my.cnf에 character-set-server=utf8mb4 설정
• 권한 문제: Volume 권한 chmod 755 확인
확인 절차:
1) 컨테이너 실행 상태 확인(docker ps)
2) 포트 매핑 확인(docker port container-name)
3) 네트워크 설정 확인(docker network inspect network-name)
4) 컨테이너 로그 확인(docker logs container-name)
5분 읽기 · 게시일: 2025년 12월 18일 · 수정일: 2026년 9월 4일
Docker 실전 가이드
검색으로 들어왔다면 같은 시리즈의 이전 글이나 다음 글로 이동하는 것이 가장 빠릅니다.
이전
Docker로 Redis 배포하기 완벽 가이드: 영속성과 비밀번호 인증으로 데이터 손실 방지
Docker로 Redis를 배포하고 RDB/AOF 영속성과 비밀번호 인증을 설정해 컨테이너 재시작 시 데이터가 사라지는 문제를 방지하는 방법을 단계별로 설명합니다. 운영 환경 배포에 적합한 전체 설정 파일과 코드 예제를 제공합니다.
33편 중 22편
다음
Docker로 Nginx 배포하기 완벽 가이드: 설정 파일 마운트, HTTPS 설정과 리버스 프록시 실전
Docker로 Nginx를 배포할 때 필요한 설정 파일 마운트, HTTPS 인증서 설정, 리버스 프록시를 모두 다룹니다. 설정이 적용되지 않거나 컨테이너가 서로 통신하지 못하는 흔한 문제를 해결하고, Let's Encrypt 자동 갱신과 프로덕션 환경 모범 사례까지 설명합니다.
33편 중 24편



댓글
GitHub로 로그인하여 댓글을 남기세요