본문 바로가기

Programming

[DB] MariaDB 이중화 (Replication + MHA)


※ 하단 블로그들을 참고하여 초보 개발자가 진행한 테스트이므로 틀린 점이 있을 수 있습니다. 유의하며 읽어주시고 틀린 점이 있다면 댓글로 알려주시면 감사하겠습니다!
 
 
 

더보기
Replication이란

 

https://nesoy.github.io/articles/2018-02/Database-Replication

 
Master DB에서 Insert, Update, Delete 작업 수행하고 Slave DB에서 Select 작업 수행
⇒ Master DB 작업으로 변경된 데이터는 Slave DB에 복제됨 (동기화)
 
 

동작 방식

https://danidani-de.tistory.com/28


1. Master 노드에 commit 발생
2. Master 노드의 binlog에 Master 노드에서 발생하는 모든 데이터 변경 사항이 기록됨
3. Slave 노드의 IO 스레드가 binlog를 읽어 Relay Log에 저장
4. Slave 노드의 SQL 스레드가 Relay Log를 읽어 데이터베이스에 적용 
    ( =Slave 노드 DB와 Master 노드 DB 동기화)
   
 
 

MHA란

 

https://www.growin.co.kr/dbms


Manager 노드가 Master 노드를 모니터링하는 중, Master 노드에서 장애 발생 시 failover 실행함 (failover 후 매니저는 가동 중지됨) 

※ failover : 시스템 장애 시 예비 백업 시스템으로 자동 전환하는 기능

 
 

동작 순서

1. [Master] 서버 장애 발생
2. [Manager] Master 서버 VIP down
3. [Manager] 가장 최신 로그의 Slave 서버를 Master 후보로 선정
4. [Manager] Master 후보의 최신 로그를 다른 Slave 서버에 적용 및 동기화
5. [Manager] Master 후보 VIP up, Master로 변경 (서비스 시작)
6. [Manager] Slave 로그 변경분 반영 → 새 Master로 복제
7. [Manager] stop 상태로 변경, 모니터링 중지
 
 
 

VIP 설정

Master 서버에 VIP 할당된 상태에서 장애 발생
→ Manager 서버에서 설정한 VIP 스크립트 실행
→ 구 Master 서버의 VIP는 down되고, 새 Master 서버에 VIP가 새로 up됨 
(WAS-DB는 하나의 VIP로 연결됨)
 
 
 

원복 (Failback)

전 Master 서버 장애 복구
→ 전 Master 서버를 Slave로 수동 설정
→ Master↔Slave 수동 switch
→ 매니저 재시작
 
 
 

테스트 환경

OS : Ubuntu 22.04.3 LTS
DB : MariaDB 10.6.16
MHA : mha manager 0.58, mha node 0.58
IP :
ip1 statm
ip2 stata
ip3 statb
ip4 vip
 
 
 

Replication 설정

1. [Master, Slave] DB 사용자 설정

    [root] service mysql start


    

  • Replication 전용 사용자 생성
        mysql> create user 'rep'@'%' identified by 'rep';
        mysql> grant replication slave on *.* to 'rep'@'%';
        mysql> flush privileges;


    

  • MHA 전용 사용자 생성
        mysql> grant all privileges on *.* to 'mha'@'%' identified by 'mha';
        mysql> flush privileges;

 
 

  •  
    mysql> exit
    
    [root] service mysql stop # 설정 위해 DB 종료

 

2. [All] DB 설정 파일 수정 

  • /etc/mysql/mariadb.conf.d/50-server.cnf      
        bind-address = 0.0.0.0  # 이전: 127.0.0.1


        

  • Master 서버 (/etc/my.cnf)
        [mariadb]
        server_id=1 # master와 slave가 다른 값 가져야 함
        log-bin=mysql-bin 
        expire_logs_days=10

 
        

  • Slave 서버 (/etc/my.cnf)
        [mariadb]
        server_id=2
        log-bin=mysql-bin
        read_only=1
        relay_log_purge=0
        relay_log=mysql-relay-bin
        log_slave_updates=1

        
        
3. [Master, Slave] 상태 확인 및 설정 

  • Master   
        [root] systemctl restart mariadb        
        [root] mysql -uroot -p
        
        mysql> show master status; # master 상태 확인


       

  • Slave
        [root] systemctl restart mariadb
        
        [root] mysql -uroot -p
        
        # replication 세팅 (위에서 참고한 master 상태 작성)
        mysql> CHANGE MASTER TO
        master_host='ip2', 
        master_user='rep', 
        master_password='rep', 
        master_port=3306, 
        master_log_file='mysql-bin.000001', ##
        master_log_pos=328, ##
        master_connect_retry=10;
        
        mysql> start slave; # slave 구동 시작
        
        mysql> show slave status\G; # slave 상태 확인

 
        
 
 

MHA 설정

1. [All] 서버 OS에 MHA용 계정 생성

    # MHA용 OS 계정 및 디렉토리 생성
    [root] useradd -g mysql -d /home/mha -m -s /bin/bash mha
    [root] passwd mha
    
    [root] chown -R mha:mysql /home/mha

 

2. [All] MHA 노드 설치

    [root] cd /home/mha
    [root] mkdir source
    [root] cd source
    
    [root] wget https://github.com/yoshinorim/mha4mysql-node/releases/download/v0.58/mha4mysql-node-0.58.tar.gz
    [root] tar xf mha4mysql-node-0.58.tar.gz
    
    # 노드 실행에 필요한 명령어, 파일 설치
    
    # perl 
    [root] apt install -y gcc make libdbi-perl libdbd-mysql-perl libio-socket-ssl-perl
    [root] apt install -y libconfig-tiny-perl liblog-dispatch-perl libparallel-forkmanager-perl
    
    # cpan 
    [root] cd /source/mha4mysql-node-0.58
    
    [root] cpan YAML 
    [root] perl -MCPAN -e "install File::Remove"
    [root] perl -MCPAN -e "install Build"
    [root] perl -MCPAN -e "install Module::Install"  
    [root] perl -MCPAN -e "install Net::Telnet"
    [root] perl -MCPAN -e "install Log::Dispatch"
    
    [root] perl Makefile.PL
    [root] make
    [root] make install


    
3. [Manager] MHA Manager 설치

    [root] cd /source
    [root] wget https://github.com/yoshinorim/mha4mysql-manager/releases/download/v0.58/mha4mysql-manager-0.58.tar.gz
    [root] tar xf mha4mysql-manager-0.58.tar.gz
    
    [root] cd /source/mha4mysql-manager-0.58
    [root] perl -MCPAN -e "install inc::Module::Install"
    
    [root] perl Makefile.PL
    [root] make
    [root] make install


    
4. [All] 서버 간 접속 위한 ssh 키 공유 (mha 계정 공개키)

    # ssh 설치
    [root] sudo apt install openssh-server
    
    # 공개키로 접근하기 위한 설정 변경 
    [root] vim /etc/ssh/sshd_config
    
    # /etc/ssh/sshd_config
    Port 22 # 주석 제거
    PermitRootLogin yes # 주석 제거 (prohibit-password)
    
    # ssh 서버 상태 확인
    [root] systemctl status ssh
    
    # 재시작
    [root] service ssh restart
    
    # mha 권한 추가
    [root] visudo
    
    mha ALL = (ALL) NOPASSWD:/sbin/ifconfig # 마지막 줄에 추가
    
    # 서버 간 ssh키 공유
    [root] su - mha
    [mha] ssh-keygen -t rsa # 엔터 3번 -> ssh 키 생성
    [mha] ssh-copy-id -i ~/.ssh/id_rsa.pub mha@연결할서버ip # 타 서버에 해당 키 저장
    
    # 연결 테스트
    [mha] ssh 연결할서버ip
    
    # 공개키 저장된 파일 권한 축소
    [mha] cd .ssh
    [mha] chmod 400 authorized_keys

 

5. [Manager] 매니저 명령어 단축어

    [mha] vi ~/.profile 
    
    #.profile
    
    set -o vi
    
    alias sshcheck='/usr/local/bin/masterha_check_ssh --conf=/etc/mha.cnf'
    alias replcheck='/usr/local/bin/masterha_check_repl --conf=/etc/mha.cnf'
    alias start='/usr/local/bin/masterha_manager --conf=/etc/mha.cnf &'
    alias stop='/usr/local/bin/masterha_stop --conf=/etc/mha.cnf'
    alias status='/usr/local/bin/masterha_check_status --conf=/etc/mha.cnf'
    alias log='tail -f /home/mha/manager.log'
    alias switch='masterha_master_switch --master_stat=alive --conf=/etc/mha.cnf --orig_master_is_new_slave'
    
    # 스크립트 파일 수정값 적용
    [mha] source .profile


    
6. [Manager] 매니저 config 파일 작성

    [root] cp /home/mha/source/mha4mysql-manager-0.58/samples/conf/app1.cnf /etc/mha.cnf
    
    [root] vim /etc/mha.cnf
    
    # /etc/mha.cnf
    
    [server default]
    user=mha
    password=mha
    ssh_user=mha
    repl_user=rep
    repl_password=rep
    
    manager_workdir=/home/mha
    remote_workdir=/home/mha
    manager_log=/home/mha/manager.log
    master_binlog_dir=/var/lib/mysql
    
    master_ip_online_change_script=/home/mha/scripts/master_ip_online_change
    master_ip_failover_script=/home/mha/scripts/master_ip_failover
    
    [server1]
    hostname=ip2
    candidate_master=1
    
    [server2]
    hostname=ip3
    candidate_master=1


※ 매니저 config 파일
공통 설정 : /etc/masterha_default.cnf
개별 설정 : /etc/masterha/app1.cnf
현 설정 : /etc/mha.cnf
    

7. [Manager] Master ↔ Slave 변경 스크립트

    [mha] mkdir mha/scripts
    [mha] cp ~/source/mha4mysql-manager-0.58/samples/scripts/master_ip_online_change ~/scripts/master_ip_online_change
    
    [mha] vim ~/scripts/master_ip_online_change
    
    # master_ip_online_change 
    # 주석 처리하기
    
        149       ## Drop application user so that nobody can connect. Disabling per-session binlog beforehand
        150       # $orig_master_handler->disable_log_bin_local();
        151       # print current_time_us() . " Drpping app user on the orig master..\n";
        152       # FIXME_xxx_drop_iapp_user($orig_master_handler);
    
        244       ## Creating an app user on the new master
        245       # print current_time_us() . " Creating app user on the new master..\n";
        246       # FIXME_xxx_create_app_user($new_master_handler);
        247       # $new_master_handler->enable_log_bin_local();
        248       # $new_master_handler->disconnect();


    
8. [Manager] Failover 스크립트

    [mha] cp ~/source/mha4mysql-manager-0.58/samples/scripts/master_ip_failover ~/scripts
    [mha] vim ~/scripts/master_ip_failover 
    
    # master_ip_failover 
    # 주석 처리하기
    
         86       ## Creating an apper on the new master
         87       # print "Creating app user on the new master..\n";
         88       # FIXME_xxx_create_user( $new_master_handler->{dbh} );
         89       # $new_master_handler->enable_log_bin_local();
         90       # $new_master_handler->disconnect();
         91
         92       ## Update master ip on the catalog database, etc
         93       # FIXME_xxx;


    
9. [Manager] 설정 확인

    sshcheck
    replcheck

매니저 구동 전 단축 명령어로 ssh, replication 상태 확인
    

  • ⚠️ replcheck 오류 ⇒ SlaveUtil.pm, NodeUtil.pm 파일 수정
    • Redundant argument in sprintf at /usr/local/share/perl/5.34.0/MHA/NodeUtil.pm line 195
        # Manager 서버
        # /usr/local/share/perl/5.34.0/MHA/NodeUtil.pm 
        
        sub parse_mysql_version($) {
          my $str = shift;
         **($str) =  $str =~ m/^[^-]*/g; #### 추가**
          my $result = sprintf( '%03d%03d%03d', $str =~ m/(\d+)/g );
          return $result;
        }
        
        sub parse_mysql_major_version($) {
          my $str = shift;
          **$str =~ /(\d+)\.(\d+)/; #### 추가
          my $strmajor = "$1.$2"; ####  추가
          my $result = sprintf( '%03d%03d', $strmajor =~ m/(\d+)/g ); #### 변경**
          return $result;
        }

 

  • Checking if super_read_only is defined and turned on..DBD::mysql::st execute failed: Unknown system variable 'super_read_only' at /usr/local/share/perl5/MHA/SlaveUtil.pm line 245. 
        # 모든 서버
        # /usr/local/share/perl/5.34.0/MHA/SlaveUtil.pm
        
        # 244행
        # my $sth = $dbh->prepare("SELECT \@\@global.super_read_only as Value"); 기존 
        **my $sth = $dbh->prepare("SELECT 0 as Value"); #### 변경**
        
        # 262행
        # **$dbh = do("SET GLOBAL super_read_only=off;"); #### 주석**
        
        # 275행
        # $dbh = do("SET GLOBAL super_read_only=on;"); 기존
        **$dbh = do("SET GLOBAL read_only=on;"); #### 변경**

 
 
10. [Manager] 스크립트 테스트

    # ~/.profile 참고
    
    alias sshcheck='/usr/local/bin/masterha_check_ssh --conf=/etc/mha.cnf'
    alias replcheck='/usr/local/bin/masterha_check_repl --conf=/etc/mha.cnf'
    alias start='/usr/local/bin/masterha_manager --conf=/etc/mha.cnf &'
    alias stop='/usr/local/bin/masterha_stop --conf=/etc/mha.cnf'
    alias status='/usr/local/bin/masterha_check_status --conf=/etc/mha.cnf'
    alias log='tail -f /mha/manager.log'
    alias switch='masterha_master_switch --master_stat=alive --conf=/etc/mha.cnf --orig_master_is_new_slave'

 
 
sshcheck : SSH 접속 확인
replcheck : replication 상태 확인
start : 매니저 실행(모니터링 시작) 
stop : 매니저 중지
status : 매니저 현재 상태 확인
log : 매니저 로그 (가동 중에만 로그 생성)
switch : Take Over(relocate) 수행 (Master↔Slave 변경)
    

11. VIP(가상 아이피) 설정
    

  • [Master, Slave] VIP 할당
        [root] apt install net-tools
        
        [root] ip address add ip4 dev ens192 # ip 생성
        
        [mha] sudo ifconfig -a # 서버 ip 확인
        
        # Master
        [mha] sudo ifconfig ens192:0 ip4 up # vip up **(★Master만)**
        
        [mha] sudo ifconfig ens192:0 # 설정한 vip 확인

 
 

  • [Manager] 스크립트 변경
    • master_ip_online_change     
        # ~/scripts/master_ip_online_change
        
        #248 라인 밑에 아래 스크립트 추가 후 저장
        
        #1번 방법
        system("/bin/bash ~/scripts/mha_change_vip.sh $new_master_ip"); 
        
        #2번 방법
        if ( $new_master_ip eq "ip2" ) {
        system("/bin/sh /var/log/masterha/app1/custom_scripts/master_vip_up.sh");
        }
        elsif ( $new_master_ip eq "ip3" ) {
        system("/bin/sh /var/log/masterha/app1/custom_scripts/slave_vip_up.sh");
        }
        else {}
        
        $exit_code = 0;
        };

 

  • master_ip_failover
        # ~/scripts/master_ip_failover
        
        #93 라인 밑에 아래 스크립트 추가 후 저장
        
        #1번 방법
        system("/bin/bash ~/scripts/mha_change_vip.sh $new_master_ip"); 
        
        #2번 방법
        if ( $new_master_ip eq "ip2" ) {
        system("/bin/sh /var/log/masterha/app1/custom_scripts/master_vip_up.sh");
        }
        elsif ( $new_master_ip eq "ip3" ) {
        system("/bin/sh /var/log/masterha/app1/custom_scripts/slave_vip_up.sh");
        }
        else {}
        
        $exit_code = 0;



  • [Manager] mha_change_vip.sh 생성
    • 1번 방법
        [mha] vim ~/scripts/mha_change_vip.sh
        
        #!/bin/bash
        ## Fail-Over VIP Change
        
        V_NEW_MASTER=`cat /etc/hosts | grep $1 | awk '{print $2}'`
        V_EXIST_VIP_CHK=`ping -c 1 -W 1 vip | grep "packet loss" | awk '{print $6}'`
        V_VIP_IP=`cat /etc/hosts | grep vip | awk '{print $1}'`
        
        if [ $V_EXIST_VIP_CHK = "0%" ]
        then
            echo "VIP IS Alive, VIP Relocate $V_NEW_MASTER " 
            /bin/ssh -o StrictHostKeyChecking=no vip /bin/sudo /sbin/ifconfig ens192:0 down &
            ssh -o StrictHostKeyChecking=no $V_NEW_MASTER /bin/sudo /sbin/ifconfig ens192:0 $V_VIP_IP netmask 255.255.255.0
            ssh -o StrictHostKeyChecking=no $V_NEW_MASTER /sbin/arping -c 5 -D -I ens192 -s $V_VIP_IP $V_VIP_IP
            VIP_NOHUP_PS=`ps -ef| grep "ifconfig ens192:0" | grep ssh | grep -v grep | awk '{print $2}'` && kill -9 $VIP_NOHUP_PS
        
        elif [ $V_EXIST_VIP_CHK = "100%" ]
        then
            echo "VIP IS dead, VIP Relocate $V_NEW_MASTER "
            /bin/ssh -o StrictHostKeyChecking=no $V_NEW_MASTER /bin/sudo /sbin/ifconfig ens192:0 $V_VIP_IP netmask 255.255.255.0
            /bin/ssh -o StrictHostKeyChecking=no $V_NEW_MASTER /sbin/arping -c 5 -D -I ens192 -s $V_VIP_IP $V_VIP_IP
        
        fi
        
        # 권한 확대
        [mha] chmod 755 mha_change_vip.sh

 참고

  • 2번 방법
        # master_vip_up.sh
        ssh root@ip3 sudo /sbin/ifconfig ens192:0 ip4 down 
        ssh root@ip2 sudo /sbin/ifconfig ens192:0 ip4 up
        
        # slave_vip_up.sh
        ssh root@ip2 sudo /sbin/ifconfig ens192:0 ip4 down 
        ssh root@ip3 sudo /sbin/ifconfig ens192:0 ip4 up


     
12. 장애 상황 테스트
    1) [Manager] 매니저 시작
    2) [구 Master] mysql 강제 종료 (=장애 상황 가정)
    3) [새 Master] VIP, slave status, read_only 확인
    4) [Manager] 로그, 매니저 상태 확인
    5) DB CRUD 테스트
    

    # Manager
    [mha] sshcheck # ssh 확인
    [mha] replcheck # replication 확인
    
    [mha] start # 매니저 구동 시작
    
    # [구 Master] MariaDB 종료
    [root] service mysql stop
    
    # [구 Master] VIP down 확인
    [root] ifconfig -a
    
    # [Manager] 로그, 매니저 상태 확인
    [mha] log # [Manager] 로그 확인 (장애 확인, Master 서버 변경 확인)
    [mha] status # [Manager] 매니저 현재 상태 확인 (stopped)
    
    # [새 Master] 확인
    mysql> show slave status\G; # Empty set
    mysql> show variables like 'read_only'; # OFF
    
    # VIP 확인
    [mha] sudo ifconfig -a

 

  • ⚠️에러
    • 상황 : manager.log 확인 시 failover이 phase 3.4에서 멈춤
    • 해결 :  스크립트 설정 내 경로 변경 (mha/scripts/master_ip_online_change)
/mha/scripts -> /home/mha/mha/scripts

         
            

13. 원복 (Failback)
    1) [Manager] mha.failover.complete 파일 삭제
    2) [구 Master] 장애 복구
    3) [구 Master] slave로 설정
    4) [Manager] Master ↔ Slave switch (기존 Master, Slave로 원복)
    5) [Manager] 매니저 재시작
 

    # mha.failover.complete 파일 삭제
    [root] rm /home/mha/mha.failover.complete 
    
    # 새 Master 서버
    mysql> show master status; # 마스터 정보 확인
    
    # 구 Master 서버 - 장애 복구 후 slave로 구동
    [root] service mysql restart # mysql 재시작 (장애 복구)
    
    [root] mysql -uroot -p
    
    mysql> CHANGE MASTER TO
    master_host='ip2', 
    master_user='rep', 
    master_password='rep', 
    master_port=3306, 
    master_log_file='', ##### 새 Master 정보
    master_log_pos=, ###### 새 Master 정보
    master_connect_retry=10;
    
    mysql> start slave;
    
    # Manager 서버
    [mha] switch # Master<->Slave 원복
    
    [mha] start # 매니저 재시작


   

  • ⚠️에러
    • 원복 후 매니저 start 안 될 때
ps kill -9 0000


perl 프로세스 중지 
            

  • could not find first log file name in binary log index file
    • 원인 : Slave 설정 시 Master이 아닌 Slave 서버의 master 정보를 가져옴
    • 해결
# Master에서 정보 조회
mysql> show master status;

# Slave에서 Slave 설정
mysql> CHANGE MASTER TO
master_host='Master ip', 
master_user='rep', 
master_password='rep', 
master_port=3306, 
master_log_file='', ##### 새 Master 정보
master_log_pos=, ###### 새 Master 정보
master_connect_retry=10;

 
 
 

후기

이번에 사내에서 가상화 플랫폼(VMware vSphere)의 서버를 구축하고 DB 이중화 테스트를 진행해보게 되었다.
 
예전에 프로젝트를 AWS 리소스 기반으로 진행하고 로드 밸런싱, 오토 스케일링 설정을 간편히 하며 클라우드의 편리함을 실감했었다. 이번에는 온프레미스 환경에서 이중화를 진행하고 MHA 관련하여 세부 설정하고 테스트하면서 새삼 클라우드가 개발자에게 많은 자유를 안겨줬구나 하고 다시금 깨달았다. 각각의 서버들에 replication, mha를 하나씩 설정하면서 작고 큰 오류가 자주 발생했고 이에 따라 테스트 완료까지 시간도 오래 걸렸다. 그렇지만 이번 기회를 통해 VIP, SSH, 프로세스 등의 개념을 실무로 경험하고 리눅스 명령어에 익숙해질 수 있어 좋았다. (특히 디렉토리 권한을 잘못 건드려서 안전모드 들어가고 몇십번 폴더들 왔다갔다하며 권한 바꿨을 때..😇)
 
MHA는 yoshinori라는 개발자가 개발한 오픈 소스인데 관련 오류가 발생할 때마다 이 분의 깃허브에서 해결법을 참고하기도 했다. 나도 앞으로 오픈소스를 배포할 정도의 실력자가 되고 싶다... CS 공부, 알고리즘 공부, 프로젝트 경험을 어떻게 시간 분배해야 기초를 닦으면서 빠르게 성장할 수 있을 것인가가 요즘 내 고민이다.