☁️Cloud/AWS

로컬 MariaDB → AWS DMS 마이그레이션 실습

terranbin 2025. 4. 18. 10:45
728x90
SMALL

🎯 목표

Rocky Linux 8.10 (가상 머신) 에서 최신 MariaDB를 설치하고, 실습용 DB/테이블 구성 뒤 마이그레이션 진행

Step 작업 내용 상태
0 On-Premise MariaDB + 데이터 + binlog 설정 ✅ 완료
1 AWS RDS + VPC + Subnet + SG + Parameter Group ✅ 완료
2 IAM 역할 생성 (dms-vpc-role, dms-cloudwatch-logs-role) ✅ 완료
3 Replication Instance 생성 ✅ 완료
4 Source Endpoint 등록 (→ MariaDB 접속정보) ✅ 완료
5 Target Endpoint 등록 (→ RDS 접속정보) ✅ 완료
6 Replication Task 생성 (Full + CDC) ✅ 완료
7 Task 실행 및 상태 확인   완료
8 RDS 데이터 확인   완료

 

[흐름]

[Insert on On-Prem DB] ---> [Binlog 기록됨] ---> [DMS Task가 읽음] ---> [RDS에 반영됨]

 

[인프라]

+------------------------+
|   On-Premise (집 PC)   |
|------------------------|
| OS: Rocky Linux 8.10   |
| DB: MariaDB 10.11.11   |
| IP: A.B.C.D            |
| 포트: 3306 (open)      |
| bind-address: 0.0.0.0  |
| 사용자: dms / dms123!  |
|------------------------|
| [초기 데이터 구성]     |
| DB 이름: dms_test      |
| 테이블: customer       |
| 내용:                  |
|  - (1, 'southouse', 25)|
|  - (2, 'rita', 21)     |
|  - (3, 'sony', 32)     |
+------------------------+
            |
            | (Public Network)
            v
+-------------------------+
| AWS DMS 구성            |
|-------------------------|
| SubnetGroup: OK         |
| Replication Instance: OK|
| Source Endpoint: 설정 완료  |
| Target Endpoint: 설정 완료  |
| Task: Full Load Only    |
+-------------------------+
            |
            v
+------------------------+
| AWS RDS (MySQL 8.0.35) |
|------------------------|
| DB 이름: dms_test      |
| 사용자: sungbin        |
| 포트: 3306             |
| 접근 허용 IP: ✅ 설정됨 |
+------------------------+

[비교]
- MGN은 서버 내부에서 AWS로 “보내는” 방식이라 공인 IP 없이도 OK
- DMS는 AWS에서 MariaDB로 “들어오는” 방식이라 외부에서 DB 접근 가능해야 함

[마이그레이션 목적 및 기대 결과]
- On-Premise의 `dms_test.customer` 테이블 전체 row를 AWS RDS(MySQL)에 복제
- Full Load 방식이므로, Task 실행 시점 기준으로 존재하는 모든 row가 복제되어야 함
- 복제 후 RDS에서 다음 row가 조회되기를 기대함:

| id | name      | age |
|----|-----------|-----|
| 1  | southouse | 25  |
| 2  | rita      | 21  |
| 3  | sony      | 32  |

[step 0] On-Premise

🛠️ MariaDB 10.11 설치 (공식 리포 자동 등록)

# 공식 리포지토리 등록
curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | sudo bash -s -- --mariadb-server-version=10.11

# 캐시 재생성 후 MariaDB 설치
sudo dnf clean all
sudo dnf makecache
sudo dnf install -y MariaDB-server

# MariaDB 자동 시작 설정 및 실행
sudo systemctl enable --now mariadb

# 버전 확인
mysql --version
 

🔐 root 비밀번호 설정 및 정상 확인

# 비밀번호 설정
sudo mysql -u root

# MariaDB 콘솔에서 암호 설정 및 확인
ALTER USER 'root'@'localhost' IDENTIFIED BY 'asdf';
FLUSH PRIVILEGES;
EXIT;

SELECT User, Host, plugin FROM mysql.user WHERE User='root';
SHOW GRANTS FOR 'root'@'localhost';

🧱 DB 생성 / dms_test 데이터 구성

추후 아래 내용이, AWS 에서 조회되는 것이 목적

sudo mysql -u root -p <<EOF
CREATE DATABASE dms_test;
USE dms_test;
CREATE TABLE customer (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(50),
  age INT
);
INSERT INTO customer (name, age) VALUES
  ('southouse', 25),
  ('rita', 21),
  ('sony', 32);
SELECT version();
SELECT * FROM customer;
EOF

🔸 DMS가 접근할 수 있도록 MySQL 설정 변경

sudo tee -a /etc/my.cnf > /dev/null << 'EOF'

[mysqld]
bind-address=0.0.0.0
server-id=1
log_bin=mysql-bin
binlog_format=ROW
binlog_row_image=FULL
expire_logs_days=7

EOF

🔸 재시작

systemctl restart mariadb

🔸 설정 확인

mysql -u root -p -e "SHOW VARIABLES LIKE 'log_bin';"
mysql -u root -p -e "SHOW VARIABLES LIKE 'binlog_format';"

🧑‍💻 DMS용 사용자 생성

# 접속 뒤 사용자 생성
mysql -u root -p <<EOF
CREATE USER 'dms'@'%' IDENTIFIED BY 'dms123!';
GRANT SELECT, RELOAD, REPLICATION CLIENT, REPLICATION SLAVE ON *.* TO 'dms'@'%';
FLUSH PRIVILEGES;
EOF


# 사용자 확인
mysql -u root -p -e "SELECT User, Host FROM mysql.user WHERE User = 'dms';"


[Step 1] AWS

✅ RDS, VPC, Subnet(2개), SubnetGroup, RouteTable, IGW, SecurityGroup, ParameterGroup 전부 포함
✅ 모든 리소스에 Name 태그 포함
✅ Multi-AZ 조건 충족


[0] 변수 정의

RDS_ID="dms-mysql-target"
RDS_USER="sungbin"
RDS_PASS="Asdf1234!"  # ✅ RDS 비밀번호 정책 만족
RDS_ENGINE="mysql"
RDS_VERSION="8.0.35"
RDS_CLASS="db.t3.medium"
RDS_STORAGE=20

VPC_NAME="dms-vpc"
SUBNET_NAME1="dms-subnet-a"
SUBNET_NAME2="dms-subnet-c"
SUBNET_GROUP_NAME="dms-subnet-group"
PARAM_GROUP_NAME="dms-cdc-param-group"
SG_NAME="dms-mysql-sg"

SUBNET_AZ1="ap-northeast-2a"
SUBNET_AZ2="ap-northeast-2c"

MY_IP="$(curl -s https://checkip.amazonaws.com)/32"

 

[1] VPC + Subnet 2개 생성

VPC_ID=$(aws ec2 create-vpc \
  --cidr-block 10.10.0.0/16 \
  --tag-specifications "ResourceType=vpc,Tags=[{Key=Name,Value=$VPC_NAME}]" \
  --query 'Vpc.VpcId' --output text)

aws ec2 modify-vpc-attribute --vpc-id "$VPC_ID" --enable-dns-hostnames "{\"Value\":true}"

SUBNET_ID1=$(aws ec2 create-subnet \
  --vpc-id "$VPC_ID" --cidr-block 10.10.1.0/24 \
  --availability-zone "$SUBNET_AZ1" \
  --tag-specifications "ResourceType=subnet,Tags=[{Key=Name,Value=$SUBNET_NAME1}]" \
  --query 'Subnet.SubnetId' --output text)

SUBNET_ID2=$(aws ec2 create-subnet \
  --vpc-id "$VPC_ID" --cidr-block 10.10.2.0/24 \
  --availability-zone "$SUBNET_AZ2" \
  --tag-specifications "ResourceType=subnet,Tags=[{Key=Name,Value=$SUBNET_NAME2}]" \
  --query 'Subnet.SubnetId' --output text)

[2] Subnet Group 생성

aws rds create-db-subnet-group \
  --db-subnet-group-name "$SUBNET_GROUP_NAME" \
  --subnet-ids "$SUBNET_ID1" "$SUBNET_ID2" \
  --db-subnet-group-description "Multi-AZ subnet group for DMS"

[3] Internet Gateway + Route table 연결

IGW_ID=$(aws ec2 create-internet-gateway \
  --tag-specifications "ResourceType=internet-gateway,Tags=[{Key=Name,Value=$VPC_NAME-igw}]" \
  --query 'InternetGateway.InternetGatewayId' --output text)

aws ec2 attach-internet-gateway --internet-gateway-id "$IGW_ID" --vpc-id "$VPC_ID"

RT_ID=$(aws ec2 create-route-table \
  --vpc-id "$VPC_ID" \
  --tag-specifications "ResourceType=route-table,Tags=[{Key=Name,Value=$VPC_NAME-rt}]" \
  --query 'RouteTable.RouteTableId' --output text)

aws ec2 create-route --route-table-id "$RT_ID" --destination-cidr-block 0.0.0.0/0 --gateway-id "$IGW_ID"
aws ec2 associate-route-table --route-table-id "$RT_ID" --subnet-id "$SUBNET_ID1"
aws ec2 associate-route-table --route-table-id "$RT_ID" --subnet-id "$SUBNET_ID2"

[4] 보안 그룹 생성 (MySQL 접속 허용)

SG_ID=$(aws ec2 create-security-group \
  --group-name "$SG_NAME" \
  --description "Allow MySQL from on-prem" \
  --vpc-id "$VPC_ID" \
  --tag-specifications "ResourceType=security-group,Tags=[{Key=Name,Value=$SG_NAME}]" \
  --query 'GroupId' --output text)

aws ec2 authorize-security-group-ingress \
  --group-id "$SG_ID" \
  --protocol tcp --port 3306 --cidr "$MY_IP"

[5] 파라미터 그룹 생성 및 CDC 세팅

aws rds create-db-parameter-group \
  --db-parameter-group-name "$PARAM_GROUP_NAME" \
  --db-parameter-group-family "mysql8.0" \
  --description "CDC for DMS"

aws rds modify-db-parameter-group \
  --db-parameter-group-name "$PARAM_GROUP_NAME" \
  --parameters \
    "ParameterName=binlog_format,ParameterValue=ROW,ApplyMethod=immediate" \
    "ParameterName=binlog_row_image,ParameterValue=FULL,ApplyMethod=immediate" \
    "ParameterName=binlog_checksum,ParameterValue=NONE,ApplyMethod=immediate"

[6] RDS 인스턴스 생성

aws rds create-db-instance \
  --db-instance-identifier "$RDS_ID" \
  --db-instance-class "$RDS_CLASS" \
  --engine "$RDS_ENGINE" \
  --engine-version "$RDS_VERSION" \
  --allocated-storage "$RDS_STORAGE" \
  --master-username "$RDS_USER" \
  --master-user-password "$RDS_PASS" \
  --vpc-security-group-ids "$SG_ID" \
  --db-subnet-group-name "$SUBNET_GROUP_NAME" \
  --db-parameter-group-name "$PARAM_GROUP_NAME" \
  --publicly-accessible \
  --backup-retention-period 0 \
  --no-multi-az \
  --storage-type gp2 \
  --port 3306

[Step 2] IAM Role 생성 및 추가

  •  dms-vpc-role 생성
aws iam create-role \
  --role-name dms-vpc-role \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": {
        "Service": "dms.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }]
  }'

AmazonDMSVPCManagementRole / 정책 연결

aws iam attach-role-policy \
  --role-name dms-vpc-role \
  --policy-arn arn:aws:iam::aws:policy/service-role/AmazonDMSVPCManagementRole

 

  • dms-cloudwatch-logs-role 생성
aws iam create-role \
  --role-name dms-cloudwatch-logs-role \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": {
        "Service": "dms.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }]
  }'

AmazonDMSCloudWatchLogsRole / 정책 연결

aws iam attach-role-policy \
  --role-name dms-cloudwatch-logs-role \
  --policy-arn arn:aws:iam::aws:policy/service-role/AmazonDMSCloudWatchLogsRole

 

[etc] 역할 등록 확인

aws iam list-attached-role-policies --role-name dms-vpc-role

aws iam list-attached-role-policies --role-name dms-cloudwatch-logs-role

[1~2] 분 뒤 재시도


🧠 DMS 구성 핵심 개념 정리

- Replication Instance: AWS에서 MariaDB 데이터를 복제하는 DMS 인스턴스(VM)
- Source Endpoint: 외부 Rocky 서버의 MariaDB 정보 (IP, 포트, 유저 등)
- Target Endpoint: AWS RDS(MySQL) 정보
- Replication Task: 어떤 데이터베이스/테이블을 어떻게 마이그레이션할지 정의 (예: 전체 + CDC)
- CDC (Change Data Capture): 변경된 데이터만 추적하여 복제하는 방식


[Step 3] Replication Instance 생성

[1] Replication Subnet Group 생성

aws dms create-replication-subnet-group \
  --replication-subnet-group-identifier dms-subnet-group \
  --replication-subnet-group-description "Subnet group for DMS" \
  --subnet-ids "$SUBNET_ID1" "$SUBNET_ID2"

[2] Replication Instance 생성

REPL_INSTANCE_ID="dms-repl-instance"

aws dms create-replication-instance \
  --replication-instance-identifier "$REPL_INSTANCE_ID" \
  --replication-instance-class dms.t3.medium \
  --allocated-storage 50 \
  --vpc-security-group-ids "$SG_ID" \
  --replication-subnet-group-identifier "$SUBNET_GROUP_NAME" \
  --publicly-accessible \
  --tags Key=Name,Value="$REPL_INSTANCE_ID"
  • 상태 확인
aws dms describe-replication-instances \
>   --query "ReplicationInstances[?ReplicationInstanceIdentifier=='dms-repl-instance'].{ID:ReplicationInstanceIdentifier, Status:ReplicationInstanceStatus, Endpoint:ReplicationInstancePrivateIpAddress}" \
>   --output table
  • 시간이 매우 오래 걸림 (10분)


[Step 4] Source Endpoint (MariaDB)

aws dms create-endpoint \
  --endpoint-identifier mariadb-source-endpoint \
  --endpoint-type source \
  --engine-name mariadb \
  --username dms \
  --password dms123! \
  --server-name "${MY_IP%%/*}" \
  --port 3306 \
  --database-name dms_test \
  --tags Key=Name,Value=mariadb-source-endpoint

DMS > 엔드포인트


[Step 5] Target Endpoint (RDS MySQL)

RDS_HOST=$(aws rds describe-db-instances \
  --db-instance-identifier "$RDS_ID" \
  --query "DBInstances[?DBInstanceIdentifier=='$RDS_ID'].Endpoint.Address | [0]" \
  --output text)

aws dms create-endpoint \
  --endpoint-identifier rds-target-endpoint \
  --endpoint-type target \
  --engine-name mysql \
  --username "$RDS_USER" \
  --password "$RDS_PASS" \
  --server-name "$RDS_HOST" \
  --port 3306 \
  --database-name dms_test \
  --tags Key=Name,Value=rds-target-endpoint

aws dms describe-endpoints \
  --query "Endpoints[].{ID:EndpointIdentifier, Engine:EngineName, Type:EndpointType, Server:ServerName}" \
  --output table


[Step 6] Replication Task 생성

mapping.json (테이블 전체 복제):

cat <<EOF > mapping.json
{
  "rules": [
    {
      "rule-type": "selection",
      "rule-id": "1",
      "rule-name": "1",
      "object-locator": {
        "schema-name": "%",
        "table-name": "%"
      },
      "rule-action": "include"
    }
  ]
}
EOF

settings.json (기본 설정):

cat <<EOF > settings.json
{
  "TargetMetadata": {
    "TargetSchema": "",
    "SupportLobs": true
  },
  "FullLoadSettings": {
    "TargetTablePrepMode": "DROP_AND_CREATE",
    "StopTaskCachedChangesApplied": false,
    "StopTaskCachedChangesNotApplied": false
  }
}
EOF

Replication Task 생성 명령:

SOURCE_ARN=$(aws dms describe-endpoints \
  --query "Endpoints[?EndpointIdentifier=='mariadb-source-endpoint-v2'].EndpointArn | [0]" \
  --output text)

TARGET_ARN=$(aws dms describe-endpoints \
  --query "Endpoints[?EndpointIdentifier=='rds-target-endpoint'].EndpointArn | [0]" \
  --output text)

REPL_ARN=$(aws dms describe-replication-instances \
  --query "ReplicationInstances[?ReplicationInstanceIdentifier=='dms-repl-instance'].ReplicationInstanceArn | [0]" \
  --output text)
  
  echo "SOURCE_ARN=$SOURCE_ARN"
echo "TARGET_ARN=$TARGET_ARN"
echo "REPL_ARN=$REPL_ARN"


MAPPINGS_PATH="$(pwd)/mapping.json"
SETTINGS_PATH="$(pwd)/settings.json"

aws dms create-replication-task \
  --replication-task-identifier dms-full-cdc-task \
  --source-endpoint-arn "$SOURCE_ARN" \
  --target-endpoint-arn "$TARGET_ARN" \
  --replication-instance-arn "$REPL_ARN" \
  --migration-type cdc \
  --table-mappings file://"$MAPPINGS_PATH" \
  --replication-task-settings file://"$SETTINGS_PATH" \
  --tags Key=Name,Value=dms-full-cdc-task

 

Replication Task 조회 명령어

aws dms describe-replication-tasks \
  --query "ReplicationTasks[].{ID:ReplicationTaskIdentifier, Status:Status}" \
  --output table


[Step 7] Task 실행

1) [RDS 대상 DB 사전 생성]

DMS Target Endpoint 연결 테스트 시, 지정된 DB(`dms_test`)가 존재하지 않으면 연결 실패가 발생함.
따라서 연결 테스트 전에 RDS(MySQL)에 직접 접속하여 `dms_test` 데이터베이스를 미리 생성함.

# [사전 작업] RDS에 대상 DB 생성
# 이유: DMS Endpoint 연결 테스트 시 지정된 DB가 존재해야 성공함

mysql -h dms-mysql-target.xxxxxx.ap-northeast-2.rds.amazonaws.com \
      -P 3306 \
      -u sungbin \
      -p  # 비밀번호: Asdf1234!

# 접속 후 쿼리
CREATE DATABASE dms_test;

 

2) 연결 테스트 수행 (source /target endpoint)

# 연결 테스트 실행
aws dms test-connection \
  --replication-instance-arn "$REPL_ARN" \
  --endpoint-arn "$SOURCE_ARN"

aws dms test-connection \
  --replication-instance-arn "$REPL_ARN" \
  --endpoint-arn "$TARGET_ARN"


# 테스트 결과 확인
# source endpoijt 결과 확인
aws dms describe-connections \
  --filters "Name=endpoint-arn,Values=$SOURCE_ARN" \
  --query "Connections[].{EndpointID:EndpointIdentifier, Status:Status, LastFailureMessage:LastFailureMessage}" \
  --output table

# 타겟 엔드포인트 결과 확인
aws dms describe-connections \
  --filters "Name=endpoint-arn,Values=$TARGET_ARN" \
  --query "Connections[].{EndpointID:EndpointIdentifier, Status:Status, LastFailureMessage:LastFailureMessage}" \
  --output table

 

 

3) start-replication-test

# Task 시작 방법
aws dms start-replication-task \
  --replication-task-arn "$TASK_ARN" \
  --start-replication-task-type resume-processing

4) TASK_ARN 변수 저장

 

aws dms describe-replication-tasks \
  --query "ReplicationTasks[].{ID:ReplicationTaskIdentifier, ARN:ReplicationTaskArn}" \
  --output table

TASK_ARN="arn:aws:dms:ap-northeast-2:544264220406:task:WEKSSBEXRNHV5N3YNUDGWRKTPI"
echo $TASK_ARN

 


[Step 8] RDS에서 결과 확인

mysql -h <RDS 엔드포인트 주소> -P 3306 -u sungbin -p
USE dms_test;

SELECT * FROM customer ORDER BY id ASC;
# SELECT * FROM dms_test.customer

 


[Step 9] CDC 반영 테스트 

테스팅을 위해 On-premise 에서 새로운 데이터 삽입

  • 현재 RDS / 기본 정보: Task ID, 타입, 상태 확인 조회 명령어
aws dms describe-replication-tasks \
  --query "ReplicationTasks[?ReplicationTaskIdentifier=='dms-full-cdc-task'].{ID:ReplicationTaskIdentifier, Type:MigrationType, Status:Status}" \
  --output table

 

On-Premise 로 이동 뒤 데이터 삽입해라.

CDC 의 경우, 반영이 1분까지 걸릴 수도 있다.
반영을 즉각적으로 안된다면,CloudWatch log 조회 혹은 Task 재시작

# Task 종료 방법
aws dms stop-replication-task --replication-task-arn "$TASK_ARN"

# sleep
sleep 5

# Task 시작 방법
aws dms start-replication-task \
  --replication-task-arn "$TASK_ARN" \
  --start-replication-task-type resume-processing

 

(참고)
binlog 설정 누락 / 사용자 권한 부족 / Transaction 미커밋 / 대상 테이블 누락 등이 있을 경우, 변경점이 반영이 되지 않는다

+ RDS의 binlog 설정은 수동 변경 불가하며,
+ Parameter Group을 통해 binlog_format, row_image 등을 설정해야 CDC가 작동합니다.

LIST