주메뉴 바로가기 본문 바로가기

알림

콘솔 이동 시 로그인이 필요합니다.

로그인하시겠습니까?

아니요

닫기

주문 불가 알림

주문권한이 없습니다.

콘솔에 접근할 수 없는 계정입니다.

확인

닫기

알림

신용카드 등록이 필요합니다.

신용카드 등록 페이지로 이동하시겠습니까?

아니요

닫기
C&C Tech
img

AWS Disaster Recovery : Part 1

img Sangho Lee(이상호)
| 2024.06.24
  • DR
  • Ambassador
  • AWS

AWS로 구성된 서비스의 DR에 대한 것을 알아봅니다.(AWS 이중화 서비스 구성)

중요한 서비스일 수록 Disaster Recovery(DR, 재해 복구)는 비즈니스 연속성을 보장하기 위해 필수적입니다. AWS DR 에서는 AWS와 Azure간의 Multi Cloud DR 아키텍처를 구축하는 과정을 공유합니다. 그 첫 번째 단계로 AWS 내부에서의 장애 대응 능력을 검증하여 가용성을 확보하는 방법에 대해 다룹니다.

특히, 이중화된 EC2 인스턴스와 Aurora RDS의 복제 및 장애 조치(Failover)를 테스트하여 시스템이 예상대로 동작하는지 확인합니다. 이를 통해 DR 전략이 왜 중요한지 이해하고, 안정적인 클라우드 아키텍처를 구축하는 방법을 살펴볼 것입니다.

첫 글에서는 DR의 필요성을 설명하며, AWS 내부 구성 요소의 가용성을 테스트하고 결과를 분석하는 과정을 다룹니다. 클라우드 기반의 DR 전략에 관심 있는 독자들에게 실질적인 인사이트를 제공할 것입니다.

Disaster Recovery?

Disaster Recovery(이하 DR)는 예상치 못한 재난 상황(예: 시스템 장애, 자연재해, 사이버 공격 등)에서도 서비스의 연속성을 유지하거나 빠르게 복구할 수 있도록 설계된 전략입니다. 현대의 디지털 환경에서는 데이터 손실과 서비스 중단이 기업에 치명적인 영향을 줄 수 있기 때문에, DR은 기업 운영의 핵심적인 부분으로 자리 잡았습니다.

DR의 궁극적인 목적은 데이터 무결성 보장, 서비스 연속성 유지, 그리고 최소한의 다운타임으로 복구를 실현하는 것입니다. 이를 위해 다양한 DR 전략과 기술이 사용됩니다.

DR 전략

DR 전략은 애플리케이션 및 시스템의 중요도, 복구 목표(RTO/RPO), 예산 등에 따라 다음과 같은 유형으로 나뉩니다.
 

전략

특징

장점

단점

백업 및 복구(Backup and Restore)

가장 기본적인 DR 전략입니다. 데이터를 주기적으로 백업하여 필요 시 복구하는 방식입니다.

비용이 적게 들고, 구현이 간단함 복구 시간이 오래 걸릴 수 있으며, RTO(복구 시간 목표)가 높은 워크로드에는 적합하지 않음
Pilot Light 필수적인 시스템만 최소한의 리소스로 준비해 두는 전략입니다. 실제로 장애가 발생하면 이 필수 리소스를 확장하여 서비스를 복구합니다. 유지 비용이 적음 복구 준비 및 확장에 시간이 걸릴 수 있음
Warm Standby 주요 애플리케이션의 중요한 부분을 활성화한 상태로 대기시켜 두는 전략입니다. 장애가 발생하면 필요에 따라 시스템을 확장하여 전체 서비스를 복구합니다. 빠른 복구 가능 일정 수준의 유지 비용이 발생
Active-Standby 하나의 사이트가 활성 상태로 모든 작업을 처리하고, 다른 사이트는 대기 상태로 준비됩니다. 활성 사이트에 장애가 발생하면 대기 사이트가 즉시 운영을 이어받습니다. 복구 시간이 매우 짧음(RTO가 낮음) 대기 사이트를 유지하는 비용이 높음
Active-Active 두 개 이상의 사이트가 동시에 활성 상태로 작동하며, 워크로드를 분산 처리합니다. 한쪽 사이트에 장애가 발생해도 다른 사이트가 즉시 모든 트래픽을 처리합니다. 완전한 이중화로 서비스 다운타임이 거의 없음 동일한 구성의 유지로 인한 높은 비용

DR 전략을 선택할 때 고려해야 할 요소

  1. 복구 시간 목표(RTO, Recovery Time Objective) :

    • 서비스가 중단된 후 복구되기까지 허용 가능한 최대 시간
  2. 복구 지점 목표(RPO, Recovery Point Objective) :

    • 데이터 손실 허용 한도, 즉 복구 시점 기준으로 데이터 손실을 얼마나 감당할 수 있는지.
  3. 비용 :

    • DR 전략은 RTO/RPO 요구사항과 예산 간의 균형을 고려해야 합니다. 예를 들어, Active-Active 전략은 비용이 많이 드는 반면, Backup and Restore는 상대적으로 저렴합니다.
  4. 서비스 중요도 :

    • DR 전략은 서비스의 중요도에 따라 달라져야 합니다. 핵심 비즈니스 애플리케이션에는 빠르고 강력한 복구가 필요하지만, 비핵심 시스템에는 기본적인 백업만으로 충분할 수 있습니다.
  5. 운영 복잡성 :

    • DR 전략이 복잡할수록 유지 관리가 어려워지므로, 팀의 기술 역량에 맞는 적절한 수준의 전략을 선택해야 합니다.


지금까지 DR에 대한 간단하게 알아보았으며, 아래에서부터는 앞으로 다루게 될 내용에 대해 알아보도록 하겠습니다.

AWS DR?



일반적으로 AWS 내부에서 제공하는 고가용성을 활용하고 온프레미스와의 DR을 구성하는 경우는 많이 있습니다.
비용과 도입에 대한 어려움이 있기 때문에 Multi Cloud로 구성하지 못하는 것이 있어 통상적으로 다루는 내용만 있기에 직접 실습을 통해 가능한 여부와 어떤 동작방식으로 이뤄지는지 확인해보도록 하겠습니다.

파트 별 설명

  • Part 1 : AWS로 이중화하여 서비스를 구성하고 그에 대한 고가용성을 측정
  • Part 2 : Azure로 동일한 환경을 구성하고 VPN을 통해 Private하게 데이터를 동기화
  • Part 3 : AWS의 장애상황시에 Azure로의 Failover

AWS DR Part 1 구축

본 글에서는 자원을 생성하는 상세한 캡쳐에 대한 것은 제공하지 않으며, 구성된 것에 대한 결과와 특이사항을 제공합니다.

AWS 부분에 대한 구성 순서는 아래와 같습니다.
  1. VPC 생성
  2. Bastion(EC2) 생성
  3. WEB/WAS(EC2) 1개 생성
  4. Aurora(Mysql) 생성
  5. WEB/WAS(EC2) 설정 및 복제
    • Tomcat 구성
    • Aurora 연결 확인
    • EC2 복제(이미지)
  6. ALB 생성
  7. 도메인 및 Route 53 구성
  

VPC 생성

VPC는 아래와 같이 생성하였으며, 아키텍처와 같이 IP를 설정하였고, 큰 특이사항은 없이 기본적인 구성입니다.
  • 본 글에서는 기본적으로 Private한 환경의 서비스를 구성하는 목적이 있으므로, Public/Private Subnet을 구분하여 구성합니다.

Bastion 생성

  • Bastion EC2는 Windows Server로 구성
  • Public Subnet에 위치하며, 보안그룹은 RDP(3389) Inbound 오픈
  • 작업의 편의성을 위해 t2.xlarge 인스턴스 선택

WEB/WAS(EC2) 1개 생성

  • WEB/WAS는 Ubunut 22.04로 구성
  • Private Subnet에 위치하며 Public으로는 접근이 불가능하도록 구성
  • 보안그룹은 웹서비스 제공을 위한 8080과 DB에서의 접근을 위한 3306(Aurora-Mysql)을 인바운드 오픈

Aurora(Mysql) 생성

  • 고가용성을 위해서 Replication이 되는 Read/Write 클러스터로 구성
  • 테스트 비용을 줄이기위해 서버리스로 구성

WEB/WAS(EC2) 설정 및 복제

  • Bastion을 통해 EC2 01접속
  • Tomcat 을 설치하고, Bastion 안에서 EC2 01번의 Private IP : 8080 으로 Tomcat이 정상적인지 확인
  • Aurora 연결을 위해 Tomcat안에 "select.jsp"를 생성하며 아래 jsp코드 참고
    • 생성한 Aurora(Mysql)의 Endpoint, DB ID, DB Password를 수정
    • select.jsp는 Ubuntu 기준 "/var/lib/tomcat9/webapps/ROOT/"하위에 생성
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.sql.*" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>WEB01 - 10.20.128.250</title>
</head>
<body>
    <h2>WEB02 - 10.20.128.250</h2>
    <table border="1">
        <tr>
            <th>ID</th>
            <th>Event Time</th>
            <th>Test Data</th>
        </tr>
        <%
            // Database connection parameters
            String url = "jdbc:mysql://[DB Endpoint]:3306/mydata";
            String username = "admin";
            String password = "[DB Password]";

            Connection conn = null;
            Statement stmt = null;
            ResultSet rs = null;

            try {
                // Load the JDBC driver
                Class.forName("com.mysql.cj.jdbc.Driver");

                // Establish the connection
                conn = DriverManager.getConnection(url, username, password);

                // Create the statement
                stmt = conn.createStatement();

                // Execute the query to get data from dr_test table
                rs = stmt.executeQuery("SELECT * FROM dr_test");

                // Process the result set
                while (rs.next()) {
                    int id = rs.getInt("id");
                    Timestamp eventTime = rs.getTimestamp("event_time");
                    String testData = rs.getString("test_data");
        %>
                    <tr>
                        <td><%= id %></td>
                        <td><%= eventTime %></td>
                        <td><%= testData %></td>
                    </tr>
        <%
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // Close the resources
                if (rs != null) try { rs.close(); } catch (SQLException e) { e.printStackTrace(); }
                if (stmt != null) try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); }
                if (conn != null) try { conn.close(); } catch (SQLException e) { e.printStackTrace(); }
            }
        %>
    </table>
</body>
</html>
  • 동일하게 Bastion에서 EC2 01번 Private IP:8080/select.jsp를 조회하면 아래와 같이 확인 가능
    • DB에는 사전에 데이터를 삽입해두었음

EC2 복제(이미지)

  • WEB 01 EC2를 기준으로 이미지를 아래와 같이 생성하고, 해당 이미지로 02번을 생성
  • 01번을 기준으로 동일하게 복제되었기 때문에 추후 웹 서비스를 호출했을때 어떤 EC2로 접속하고 있는지 확인이 안되기 때문에 Select.jsp 코드에서 IP부분을 WEB 02번으로 변경

ALB 생성

  • ALB 생성전에 "대상 그룹"은 아래와 같이 위에서 생성한 EC2 2개를 등록
  • Sticky Session을 활성화하도록 함(Sticky Session : Client의 Session을 특정 기간 동안 동일한 서버에 요청)
    • 해당 설정을 하지 않을 경우 ALB로 웹서비스 호출시에 생성된 EC2 2개가 번갈아가면서 랜덤하게 표시되는 현상이 발생함.
  • 로드밸런서 타입은 Application Load Balancer로 하며, Public 접근이 가능한 Internet-facing(인터넷 경계)로 생성
  • ALB에서 80으로 받아 뒤에 WEB EC2로 8080으로 전달될 수 있도록 설정

도메인 및 Route 53 구성

  • 외부 도메인은 CAFE24를 통해 구입하였으며, 유료(550원)에 하였음
  • Route 53에는 아래와 같이 호스팅 영역의 도메인 이름은 위에서 구매한 도메인으로 설정
  • 레코드에 구입한 도메인으로 A레코드로 구성하며 대상은 위에서 생성한 ALB의 DNS 이름으로 설정



여기까지 구성이 잘되었다면, 아래와 같이 외부(모바일, 로컬 등)에서 구입한 도메인명 또는 도메인명/select.jsp로 호출시에 아래와 같이 정상적인 접근이 가능함을 확인할 수 있습니다.

  • HTTPS를 구현하지 않았기 떄문에 아래와 같이 표시됩니다.


AWS DR Part 1 가용성 테스트

가용성에 대한 테스트는 아래와 같이 크게 2가지를 통해 서비스가 정상적으로 동작하는지, 데이터가 유실되는 것은 없는지를 확인해보겠습니다.
  • 이중화된 EC2 중 1개를 중지하고 정상적인 서비스가 호출되는지 확인
  • Replicated된 Aurora(Mysql) 장애조치를 눌러 유실되는 데이터가 있는지 확인
테스트를 진행하기 전에 데이터가 유실되는지 정상적인 서비스가 되는지를 위해 아래와 같은 사전작업을 진행합니다.

[WEB 서비스가 정상적으로 200OK가 접근 되는지 확인 및 로그 생성]

  • 쉘 스크립트는 아래와 같으며 어디서 실행해도 무관합니다.(도메인(URL)만 수정이 필요합니다.)
#!/bin/bash

# URL 설정
URL="http://yamistudy.store/select.jsp"
LOG_FILE="log.txt"

# 로그 파일 초기화
echo "Logging status for $URL" > $LOG_FILE
echo "Timestamp (ms)       Status" >> $LOG_FILE
echo "--------------------------------" >> $LOG_FILE

# 무한 루프 시작
while true; do
  # 현재 시간 밀리초 단위로 가져오기
  TIMESTAMP=$(date +%Y-%m-%d\ %H:%M:%S.%3N)

  # curl을 사용하여 HTTP 상태 코드 가져오기
  STATUS=$(curl -o /dev/null -s -w "%{http_code}" "$URL")

  # 상태와 시간 로그에 기록
  echo "$TIMESTAMP       $STATUS" >> $LOG_FILE

  # 0.1초 대기
  sleep 0.1
done
 

[Bastion -> RDS로 0.1초 단위로 데이터 삽입]

  • 쉘 스크립트를 아래와 같이 작성하였으며, [수정필요]는 각 자원에 맞게 수정하여 사용할 수 있습니다.
#!/bin/bash

# MySQL 연결 정보
SERVER=[수정필요]
DATABASE=[수정필요]
USER=[수정필요]
PASSWORD=[수정필요]
TABLE=[수정필요]
INTERVAL=0.1                                                    # 데이터 삽입 간격 (초 단위)

# 랜덤 문자열 생성 함수
generate_random_value() {
  # 알파벳 3자리 + 숫자 3자리 랜덤 생성
  ALPHABETS=$(cat /dev/urandom | tr -dc 'A-Z' | head -c 3)
  NUMBERS=$(cat /dev/urandom | tr -dc '0-9' | head -c 3)
  echo "$ALPHABETS$NUMBERS"
}

# 종료 시 처리 함수
cleanup() {
  echo "Script terminated by user."
  exit 0
}

# 종료 시 처리 함수 연결
trap cleanup SIGINT

# 실행 루프
while true; do
  # 랜덤 데이터 생성
  RANDOM_VALUE=$(generate_random_value)

  # INSERT 쿼리 실행
  QUERY="INSERT INTO $TABLE (test_data) VALUES ('$RANDOM_VALUE');"
  OUTPUT=$(mysql --default-character-set=utf8mb4 -h $SERVER -u $USER -p$PASSWORD -D $DATABASE -e "$QUERY" 2>&1)

  # 에러 처리
  if [ $? -ne 0 ]; then
    echo "Error while inserting data: $QUERY"
    echo "$OUTPUT"  # 에러 메시지 출력
    exit 1
  else
    echo "Data inserted successfully: $RANDOM_VALUE"
  fi

  # 삽입 간격 대기
  sleep $INTERVAL
done

테스트1 : EC2(WEB)서버 한대 종료시 서비스 상황

  • 도메인/select.jsp 호출시 현재 WEB화면이며, DB는 데이터가 없는 상태의 서비스를 확인
    • WEB01번에 연결되어 세션을 물고 있는 상태
  1. Data 삽입, 서비스 정상여부 Shell Script 모두 실행(서버 시간 기준 : 대략 4:38:26)

  2. EC2 01번 중지(Script 실행과 유사한 시간대)
  3. ​ALB의 대상 그룹 중 한개만 정상으로 표시됨.
  4. 서비스 02번을 호출됨으로 정상적인 접근 확인
  5. 200이 아닌 502(Bad Gateway), 504(Gateway Timeout)가 있음을 확인 할 수 있음.(0.1초 단위로 확인했을 때의 기준)

결론

  • 더 상세한 설정을 하지 않는 기준에서의 AWS 자원의 이중화 시에 고가용성은 완벽한 무중단, 무손실을 보장할수는 없을 것 같음
    • 즉, 금융, 통신 등 특정 도메인에서의 요구사항이 있을 경우 많은 테스트와 설정을 잘 해야함.
  • 가용성 테스트로 서버가 다운되는 것만 했지만, 부하테스트와 같은 성능에 관련된것도 하는 것도 다뤄볼 예정
img
Sangho Lee(이상호) | Cloud Architect Team

Cloud Architect로 컨설팅, 설계, 구축을 지원하고 있습니다.