“이제와서 시작하는 Docker 마스터하기” 데이터베이스는 애플리케이션의 핵심입니다. Docker로 데이터베이스를 운영하면 개발과 테스트가 쉬워지지만, 프로덕션 환경에서는 추가적인 고려사항이 필요합니다. 이번 편에서는 주요 데이터베이스를 Docker로 안전하게 운영하는 방법을 알아보겠습니다.
PostgreSQL 운영
1. 기본 PostgreSQL 설정
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
| # docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:15-alpine
container_name: postgres_db
environment:
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password}
POSTGRES_DB: ${POSTGRES_DB:-myapp}
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --locale=C"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init-scripts:/docker-entrypoint-initdb.d
- ./postgresql.conf:/etc/postgresql/postgresql.conf
ports:
- "5432:5432"
command: postgres -c config_file=/etc/postgresql/postgresql.conf
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- db-network
volumes:
postgres_data:
driver: local
networks:
db-network:
driver: bridge
|
2. PostgreSQL 커스텀 설정
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
| # postgresql.conf
# 연결 설정
listen_addresses = '*'
max_connections = 200
# 메모리 설정
shared_buffers = 256MB
effective_cache_size = 1GB
maintenance_work_mem = 64MB
work_mem = 4MB
# 체크포인트
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
# 로깅
log_destination = 'stderr'
logging_collector = on
log_directory = 'pg_log'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_statement = 'all'
log_duration = on
# 성능 튜닝
random_page_cost = 1.1
effective_io_concurrency = 200
|
3. 초기화 스크립트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| -- init-scripts/01-create-extensions.sql
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- init-scripts/02-create-user.sql
CREATE USER app_user WITH PASSWORD 'app_password';
GRANT CONNECT ON DATABASE myapp TO app_user;
GRANT USAGE ON SCHEMA public TO app_user;
GRANT CREATE ON SCHEMA public TO app_user;
-- init-scripts/03-create-tables.sql
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_users_email ON users(email);
|
MySQL 운영
1. MySQL 설정
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
| # docker-compose.yml
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: mysql_db
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword}
MYSQL_DATABASE: ${MYSQL_DATABASE:-myapp}
MYSQL_USER: ${MYSQL_USER:-appuser}
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-apppassword}
volumes:
- mysql_data:/var/lib/mysql
- ./my.cnf:/etc/mysql/conf.d/my.cnf
- ./init-mysql:/docker-entrypoint-initdb.d
ports:
- "3306:3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s
retries: 10
restart: unless-stopped
networks:
- db-network
volumes:
mysql_data:
networks:
db-network:
|
2. MySQL 커스텀 설정
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
| # my.cnf
[mysqld]
# 기본 설정
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
default-authentication-plugin = mysql_native_password
# InnoDB 설정
innodb_buffer_pool_size = 1G
innodb_log_file_size = 256M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
# 쿼리 캐시
query_cache_type = 1
query_cache_size = 128M
# 연결 설정
max_connections = 500
connect_timeout = 10
# 로깅
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
log_queries_not_using_indexes = 1
# 성능 스키마
performance_schema = ON
|
MongoDB 운영
1. MongoDB Replica Set
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
| # docker-compose.yml
version: '3.8'
services:
mongo1:
image: mongo:6
container_name: mongo1
command: mongod --replSet rs0 --bind_ip_all
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: password
volumes:
- mongo1_data:/data/db
- mongo1_config:/data/configdb
ports:
- "27017:27017"
networks:
- mongo-network
mongo2:
image: mongo:6
container_name: mongo2
command: mongod --replSet rs0 --bind_ip_all
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: password
volumes:
- mongo2_data:/data/db
- mongo2_config:/data/configdb
ports:
- "27018:27017"
networks:
- mongo-network
mongo3:
image: mongo:6
container_name: mongo3
command: mongod --replSet rs0 --bind_ip_all
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: password
volumes:
- mongo3_data:/data/db
- mongo3_config:/data/configdb
ports:
- "27019:27017"
networks:
- mongo-network
mongo-init:
image: mongo:6
depends_on:
- mongo1
- mongo2
- mongo3
volumes:
- ./scripts:/scripts
command: bash /scripts/mongo-init.sh
networks:
- mongo-network
volumes:
mongo1_data:
mongo1_config:
mongo2_data:
mongo2_config:
mongo3_data:
mongo3_config:
networks:
mongo-network:
|
2. MongoDB 초기화 스크립트
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
| #!/bin/bash
# scripts/mongo-init.sh
echo "Waiting for MongoDB nodes to start..."
sleep 10
echo "Initializing replica set..."
mongosh --host mongo1:27017 -u admin -p password --authenticationDatabase admin <<EOF
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "mongo1:27017" },
{ _id: 1, host: "mongo2:27017" },
{ _id: 2, host: "mongo3:27017" }
]
});
EOF
echo "Waiting for replica set to be ready..."
sleep 5
echo "Creating application user..."
mongosh --host mongo1:27017 -u admin -p password --authenticationDatabase admin <<EOF
use myapp;
db.createUser({
user: "appuser",
pwd: "apppassword",
roles: [
{ role: "readWrite", db: "myapp" }
]
});
EOF
echo "MongoDB initialization complete!"
|
Redis 운영
1. Redis Cluster
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
| # docker-compose.yml
version: '3.8'
services:
redis-master:
image: redis:7-alpine
container_name: redis-master
command: redis-server /usr/local/etc/redis/redis.conf
volumes:
- ./redis-master.conf:/usr/local/etc/redis/redis.conf
- redis_master_data:/data
ports:
- "6379:6379"
networks:
- redis-network
redis-slave:
image: redis:7-alpine
container_name: redis-slave
command: redis-server /usr/local/etc/redis/redis.conf
volumes:
- ./redis-slave.conf:/usr/local/etc/redis/redis.conf
- redis_slave_data:/data
ports:
- "6380:6379"
depends_on:
- redis-master
networks:
- redis-network
redis-sentinel:
image: redis:7-alpine
container_name: redis-sentinel
command: redis-sentinel /usr/local/etc/redis/sentinel.conf
volumes:
- ./sentinel.conf:/usr/local/etc/redis/sentinel.conf
ports:
- "26379:26379"
depends_on:
- redis-master
- redis-slave
networks:
- redis-network
volumes:
redis_master_data:
redis_slave_data:
networks:
redis-network:
|
2. Redis 설정 파일
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # redis-master.conf
bind 0.0.0.0
port 6379
requirepass yourpassword
masterauth yourpassword
# 영속성
save 900 1
save 300 10
save 60 10000
appendonly yes
appendfsync everysec
# 메모리 정책
maxmemory 2gb
maxmemory-policy allkeys-lru
# 로깅
loglevel notice
logfile /data/redis.log
|
1
2
3
4
5
6
7
8
9
| # redis-slave.conf
bind 0.0.0.0
port 6379
requirepass yourpassword
masterauth yourpassword
replicaof redis-master 6379
# 읽기 전용
replica-read-only yes
|
백업과 복원
1. PostgreSQL 백업
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| #!/bin/bash
# backup-postgres.sh
BACKUP_DIR="/backup/postgres"
DB_NAME="myapp"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# 디렉토리 생성
mkdir -p $BACKUP_DIR
# 백업 실행
docker exec postgres_db pg_dump -U postgres -d $DB_NAME | gzip > $BACKUP_DIR/backup_$TIMESTAMP.sql.gz
# 오래된 백업 삭제 (7일 이상)
find $BACKUP_DIR -name "backup_*.sql.gz" -mtime +7 -delete
echo "Backup completed: backup_$TIMESTAMP.sql.gz"
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| #!/bin/bash
# restore-postgres.sh
BACKUP_FILE=$1
DB_NAME="myapp"
if [ -z "$BACKUP_FILE" ]; then
echo "Usage: $0 <backup_file>"
exit 1
fi
# 기존 연결 종료
docker exec postgres_db psql -U postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='$DB_NAME' AND pid <> pg_backend_pid();"
# 데이터베이스 재생성
docker exec postgres_db psql -U postgres -c "DROP DATABASE IF EXISTS $DB_NAME;"
docker exec postgres_db psql -U postgres -c "CREATE DATABASE $DB_NAME;"
# 복원
gunzip -c $BACKUP_FILE | docker exec -i postgres_db psql -U postgres -d $DB_NAME
echo "Restore completed from: $BACKUP_FILE"
|
2. MySQL 백업
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # docker-compose.yml에 추가
mysql-backup:
image: databack/mysql-backup
container_name: mysql-backup
environment:
- DB_SERVER=mysql
- DB_USER=root
- DB_PASS=${MYSQL_ROOT_PASSWORD:-rootpassword}
- DB_DUMP_FREQ=1440 # 매일
- DB_DUMP_BEGIN=0200 # 새벽 2시
- DB_DUMP_TARGET=/backup
- COMPRESSION=gzip
volumes:
- ./backup:/backup
depends_on:
- mysql
networks:
- db-network
|
3. MongoDB 백업
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| #!/bin/bash
# backup-mongo.sh
BACKUP_DIR="/backup/mongo"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# 백업 실행
docker exec mongo1 mongodump \
--host rs0/mongo1:27017,mongo2:27017,mongo3:27017 \
-u admin -p password \
--authenticationDatabase admin \
--out /backup/dump_$TIMESTAMP
# 압축
tar -czf $BACKUP_DIR/mongodb_backup_$TIMESTAMP.tar.gz -C /backup dump_$TIMESTAMP
# 임시 파일 삭제
rm -rf /backup/dump_$TIMESTAMP
|
모니터링
1. 데이터베이스 모니터링 스택
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
| # monitoring-compose.yml
version: '3.8'
services:
postgres-exporter:
image: prometheuscommunity/postgres-exporter
environment:
DATA_SOURCE_NAME: "postgresql://postgres:password@postgres:5432/myapp?sslmode=disable"
ports:
- "9187:9187"
networks:
- db-network
mysql-exporter:
image: prom/mysqld-exporter
environment:
DATA_SOURCE_NAME: "root:rootpassword@(mysql:3306)/"
ports:
- "9104:9104"
networks:
- db-network
mongodb-exporter:
image: percona/mongodb_exporter:0.35
command:
- "--mongodb.uri=mongodb://admin:password@mongo1:27017"
- "--collect-all"
ports:
- "9216:9216"
networks:
- mongo-network
redis-exporter:
image: oliver006/redis_exporter
environment:
REDIS_ADDR: "redis-master:6379"
REDIS_PASSWORD: "yourpassword"
ports:
- "9121:9121"
networks:
- redis-network
|
2. Grafana 대시보드
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
| // PostgreSQL 대시보드 설정
{
"dashboard": {
"title": "PostgreSQL Monitoring",
"panels": [
{
"title": "Active Connections",
"targets": [{
"expr": "pg_stat_activity_count"
}]
},
{
"title": "Database Size",
"targets": [{
"expr": "pg_database_size_bytes"
}]
},
{
"title": "Transaction Rate",
"targets": [{
"expr": "rate(pg_stat_database_xact_commit[5m])"
}]
}
]
}
}
|
성능 최적화
1. 볼륨 최적화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| services:
postgres:
volumes:
# 성능 향상을 위한 tmpfs 사용
- postgres_data:/var/lib/postgresql/data
- type: tmpfs
target: /tmp
tmpfs:
size: 1G
volumes:
postgres_data:
driver: local
driver_opts:
type: none
o: bind
device: /ssd/postgres # SSD 사용
|
2. 리소스 제한
1
2
3
4
5
6
7
8
9
10
| services:
postgres:
deploy:
resources:
limits:
cpus: '4'
memory: 8G
reservations:
cpus: '2'
memory: 4G
|
고가용성 구성
PostgreSQL HA
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
| # postgres-ha-compose.yml
version: '3.8'
services:
postgres-master:
image: bitnami/postgresql:15
environment:
- POSTGRESQL_REPLICATION_MODE=master
- POSTGRESQL_REPLICATION_USER=replicator
- POSTGRESQL_REPLICATION_PASSWORD=replicatorpassword
- POSTGRESQL_USERNAME=postgres
- POSTGRESQL_PASSWORD=password
- POSTGRESQL_DATABASE=myapp
volumes:
- postgres_master_data:/bitnami/postgresql
postgres-slave:
image: bitnami/postgresql:15
depends_on:
- postgres-master
environment:
- POSTGRESQL_REPLICATION_MODE=slave
- POSTGRESQL_REPLICATION_USER=replicator
- POSTGRESQL_REPLICATION_PASSWORD=replicatorpassword
- POSTGRESQL_MASTER_HOST=postgres-master
- POSTGRESQL_PASSWORD=password
- POSTGRESQL_MASTER_PORT_NUMBER=5432
volumes:
- postgres_slave_data:/bitnami/postgresql
pgpool:
image: bitnami/pgpool:4
depends_on:
- postgres-master
- postgres-slave
environment:
- PGPOOL_BACKEND_NODES=0:postgres-master:5432,1:postgres-slave:5432
- PGPOOL_SR_CHECK_USER=replicator
- PGPOOL_SR_CHECK_PASSWORD=replicatorpassword
- PGPOOL_ENABLE_LDAP=no
- PGPOOL_POSTGRES_USERNAME=postgres
- PGPOOL_POSTGRES_PASSWORD=password
- PGPOOL_ADMIN_USERNAME=admin
- PGPOOL_ADMIN_PASSWORD=adminpassword
ports:
- "5432:5432"
volumes:
postgres_master_data:
postgres_slave_data:
|
보안 고려사항
1. 네트워크 격리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| networks:
frontend:
external: false
backend:
internal: true # 외부 접근 차단
services:
app:
networks:
- frontend
- backend
postgres:
networks:
- backend # 내부 네트워크만
|
2. 시크릿 관리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # docker-compose.yml
version: '3.8'
secrets:
postgres_password:
file: ./secrets/postgres_password.txt
services:
postgres:
image: postgres:15
secrets:
- postgres_password
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
|
문제 해결
일반적인 문제들
1
2
3
4
5
6
7
8
9
10
11
| # 연결 문제 디버깅
docker exec postgres_db psql -U postgres -c "SELECT * FROM pg_stat_activity;"
# 느린 쿼리 확인
docker exec postgres_db psql -U postgres -c "SELECT * FROM pg_stat_statements ORDER BY total_time DESC LIMIT 10;"
# 로그 확인
docker logs postgres_db --tail 100 -f
# 디스크 사용량 확인
docker exec postgres_db df -h /var/lib/postgresql/data
|
마무리
Docker로 데이터베이스를 운영할 때는 데이터 영속성, 백업, 성능, 보안을 모두 고려해야 합니다. 개발 환경에서는 간단하게 시작할 수 있지만, 프로덕션에서는 신중한 계획이 필요합니다. 다음 편에서는 Docker 이미지 최적화 기법을 자세히 알아보겠습니다.
다음 편 예고
- 이미지 크기 줄이기
- 빌드 시간 단축
- 레이어 캐싱 전략
- 멀티 스테이지 빌드 고급 기법
효율적인 이미지로 더 빠른 배포를! 🚀
📚 Docker 마스터하기 시리즈
🐳 기초편 (입문자용 - 5편)
- Docker란 무엇인가?
- Docker 설치 및 환경 설정
- 첫 번째 컨테이너 실행하기
- Docker 이미지 이해하기
- Dockerfile 작성하기
💼 실전편 (중급자용 - 6편)
- Docker 네트워크 기초
- Docker 볼륨과 데이터 관리
- Docker Compose 입문
- 멀티 컨테이너 애플리케이션
- Docker Hub 활용하기
- Docker 보안 베스트 프랙티스
🚀 고급편 (전문가용 - 9편)
- Docker 로그와 모니터링
- Docker로 Node.js 애플리케이션 배포
- Docker로 Python 애플리케이션 배포
- Docker로 데이터베이스 운영 ← 현재 글
- Docker 이미지 최적화
- Docker와 CI/CD
- Docker Swarm 기초
- 문제 해결과 트러블슈팅
- Docker 생태계와 미래