[GCP] Google Cloud(GCP) 3 tier 아키텍쳐 구축하기 (2)

vinca·2025년 2월 7일

Introduction

이 글부터는 편의를 위해 기존 블로그 글처럼 문어체 평서문으로 쓰겠습니다.

현재까지 구조를 살펴보면 다음과 같다.

앞단에 HHTP 로드밸런서를 부착하고 뒤에 2개의 WEB서버(아파치 HTTP)가 동작중이다.

이제 이 서버의 뒷단이 되는 내부 TCP 로드밸런서 - WAS 서버 2대 - DB(MySQL)을 구성해주면 된다.

(1)번 과정에서 느꼈겠지만, 웹 서버와 LB를 연결하기 위해서는 "웹 서버"를 먼저 만드는 과정이 필요했다. 따라서 뒤에서부터(DB부터 역으로) 만들어 주도록 하자.

시작

역으로 만들게 되므로 순서는 다음과 같다.
본 글에서는 3번 WAS에서 DB로 연결까지 수행한다. (이후는 다음 글)

  1. DB 생성
  2. WAS 생성
  3. WAS에서 DB로 연결 (접속 확인)
  4. WAS와 내부 TCP LB 연결
  5. 최종적으로 WEB과 내부 TCP LB 연결

1. DB 생성

인스턴스에 DB를 설치하여 자체 관리형 DB를 사용한다면, DB의 사용료는 무료겠지만 고가용성이나 DR측면에서 좋지 않다. Failover에 대한 대처도 본인이 직접해야 한다.

따라서 우리는 알아서 재해복구 및 고가용성을 지원하는 GCP의 완전관리형 DB인 Cloud SQL을 사용하도록 한다. (AWS RDS와 동일하다)

1.1 DB 생성

Cloud SQL > MySQL 인스턴스 만들기

Production 환경이 아니므로, 값 싸게 개발 DB를 설정하고 확인을 눌러준다. 리전의 경우도 그대로 놔두면 단일 영역으로 구성된다.

여기서 주의할 점은 WAS 인스턴스가 위치할 리전과 동일하게 구성해 주어야한다. 만약 이 리전이 다르게 되면 PSA(Private Service Access)를 구성할 수 없으므로 외부 인터넷을 타고 DB로 접속해야한다. 테스트 결과 다른 리전이라도 같은 VPC 내에만 있으면 된다.

연결에서 비공개 연결을 체크하여, PSA를 통해서 외부 인터넷을 통해서가 아닌 VPC의 내부 IP를 통해 VM(WAS)에서 DB로 접근할 수 있도록 설정한다.

이후 한 5분정도 기다리면 PSA를 통해서 내부 IP 주소가 할당된 것을 확인할 수 있다.

📍 이 아이피(10.100.0.4)를 잘 기억해두도록 한다.

1.2 DB에 데이터 넣기

우리는 데이터가 실제로 조회되는 것을 최종적으로 확인해야한다.
따라서 텅 빈 DB에 더미 데이터를 간단하게 넣어주도록 하자.

ClOUD SHELL 열기를 통해 클라우드 쉘을 연 뒤, DB를 생성할 때 설정한 패스워드 (mysql1234!)를 입력해 주도록 하자.

이후 아래의 SQL문을 복사하여 붙여넣으면 완료된다.

-- 데이터베이스 생성
CREATE DATABASE mydb;
USE mydb;

-- 테이블 생성
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    age INT NOT NULL
);

-- 데이터 삽입
INSERT INTO users (name, email, age) VALUES 
('홍길동', 'hong@example.com', 30),
('김철수', 'kim@example.com', 25),
('이영희', 'lee@example.com', 28);

2. WAS 생성

WAS를 생성하는 과정은 이전 WEB 인스턴스를 생성하고, 해당 인스턴스를 바탕으로 인스턴스 그룹을 만드는 이전 과정과 거의 동일하다.

2.1 VM 인스턴스 생성

WAS 서버가 될 VM인스턴스를 생성해주도록 하자.
MIGs(관리형 인스턴스 그룹)으로 생성할 것이므로 VM을 하나 만들어 준 뒤, 이를 인스턴스 그룹을 통해 복제하는 형식으로 사용할 것이다.

2.2 WAS 서버 내부 구성

WAS 서버로는 가장 단순한 Flask를 사용하도록 한다.

Flask는 웹 프레임워크로 내부적으로 개발용 서버인 Werkzeug 서버가 동작하며, 실제 서비스 시에는 uWSGI or uWSGI + Nginx를 같이 사용한다.

(Apach 톰캣 서버를 사용해도 상관없다.)

# flask 설치
pip install flask mysql-connector-python
# 파일 생성
nano app.py

아래 내용을 app.py에 써준다.

이 때 db_config 부분의 host에는 본인이 PSA를 통해서 발급받은 내부 IP를 써주면 된다.

이후의 소스코드는 db에 접근하여 전체 데이터를 조회하여 JSON 형식으로 반환하는 소스코드이다.

from flask import Flask, jsonify
import mysql.connector

app = Flask(__name__)

# DB 연결 설정
db_config = {
    "host": "10.100.0.4",
    "user": "root",
    "password": "mysql1234!",
    "database": "mydb"
}

@app.route('/api/data', methods=['GET'])
def get_data():
    conn = mysql.connector.connect(**db_config)
    cursor = conn.cursor(dictionary=True)

    cursor.execute("SELECT * FROM sample_table")
    data = cursor.fetchall()

    cursor.close()
    conn.close()

    return jsonify(data)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

2.3 서버 동작

Flask 서버를 동작시켜 주도록 하자.

# 플라스크 서버 실행
python3 app.py
# 백그라운드 동작 시 실행
nohup python3 app.py > flask.log 2>&1 &

웹 페이지에서 WAS 외부 IP:8080/api/users로 접속해보면 DB를 조회한 결과를 JSON형태로 반환되는 것을 확인할 수 있다.

2.4 인스턴스 그룹 생성

이제 이 WAS 서버가 정상적으로 동작하는 것을 확인하였으니, 인스턴스 그룹으로 만들어 주도록 한다.(오른쪽 위의 버튼 클릭)

⚙️인스턴스 템플릿 맞춤설정 으로 들어가서 자동화 - 시작 스크립트 부분에 아래와 같이 써주도록 한다.

이를 통해서 VM 인스턴스가 생성되면서 자동으로 flask 서버가 켜지게 된다.

# 실행 시 flask 서버가 자동으로 백그라운드 동작하도록
nohup python3 app.py > flask.log 2>&1 &

이후 ⚙️인스턴스 그룹 구성 부분에서 아래와 같이 포트를 매핑해주도록 하자.
8080 포트를 사용할 것이므로, 이 포트에 대한 이름을 지정해주는 작업이다. 이 이름을 통해서 추후 인스턴스 그룹과 LB의 백엔드 서비스가 연결된다.

⚙️인스턴스 그룹 구성 의 포트 매핑
L7 로드밸런서 or L4 프록시 부하분산기를 사용한다면 포트 매핑이 필수이다. 하지만 L4 패스스루 부하분산기를 사용하는 경우 포트 매핑이 필요없다. (백엔드 서비스를 거치지 않고, 직접 전달되기 때문)

2.5 생성결과 확인

생성된 인스턴스 그룹은 위와 같다. 만약 현재 VM 인스턴스가 1개라면 EDIT 버튼을 눌러서 인스턴스의 수량을 2개로 수정해주면 된다.

여기서 어느 외부 IP로 /api/users 요청을 하더라도 2.3 서버 동작에서 본것과 같이 DB의 정보가 조회가능하다.
(기존 VM을 기반으로 복제한 동일한 인스턴스이기 때문)

3. 내부 4계층 LB 생성

이제 이 WAS 서버에 들어오는 부하를 분산시켜주는 내부 4계층 LB(TCP 로드밸런서)를 생성해보도록 하자.

3.1 내부 부하 분산기 선택

그림과 같이 선택해주도록 한다.

3.2 백엔드 서버 구성

인스턴스 그룹을 생성할 때 지정했던, 포트 매핑(포트 이름 매핑)을 여기서 사용하게 된다. WAS 인스턴스가 동작중인 포트 번호와 이름을 써준다.

3.3 백엔드 헬스 체크 구성

HTTP 8080 포트로 동작하는 Flask WAS 서버에 대한 헬스 체크이므로, 해당 서비스의 URL이 정상적으로 동작해야만 한다.

현재 열려있는 HTTP API는 /api/users만 있으므로 해당 URL을 통해서 검증하도록 한다.

🧐 TCP 프로토콜로 검증하면 안되나요?
✅ 해도된다.
TCP 프로토콜로 검증해도 상관없다. TCP 프로토콜 + 포트 8080으로 설정한 경우, TCP 즉 네트워크 계층까지만 검증 한다.
즉, 실제로 애플리케이션의 특정 엔드포인트(/api/users)까지 확인하는 것이 아니라 포트(8080)에서 TCP 연결이 성공하는지만 검사한다.
하지만, TCP를 선택 후 요청 및 응답에 ASCII값을 넣는 순간 TCP 응답을 해주는 로직을 WAS 서버에서 따로 구현해 줘야한다.

3.4 프론트엔드 구성

프론트엔드 구성으로는 주요하게 봐야하는 부분은, Subnetwork-region을 선택한다는 점인데, 프록시 부하 분산기를 위한 전용 서브넷을 만들어 줘야한다.

포트의 경우 다른 포트로 설정해도 상관없지만 GCP에서 내부 프록시 부하 분산기를 만들면 25번 포트가 사용됨을 알아두자.

  • 최종 검토

최종적으로 검토를 마친 뒤 생성을 눌러주도록 하자.


4. 정리

내부 TCP 로드밸런서까지 생성을 완료했다.
자 이제 여기까지 온 과정을 정리하고 다음장으로 넘어가도록 하자.

이제 우리가 3티어 아키텍쳐 구축에 남은 것은 빨간 색 부분(WEB 서버와 내부 LB의 연결)이다.

거의 다 했다. ☘️

4. LB 테스트

끝으로 생성했던 LB가 잘 되는지 테스트 해보고 마지막 장(WEB 서버와 내부 LB의 연결)으로 넘어가도록 하자.

LB가 WAS 서버에 대해서 잘 동작하는지, 즉 WAS 서버 2대의 부하를 잘 분산하는 지(접속되는 지) 확인한다.

이를 확인하기 위해서는 4계층 "내부" 부하 분산기 이므로 현재 내부 IP만 존재하는 것을 확인할 수 있다.

따라서 VPC 내부에 접속하여 내부 로드밸런서 IP:25/api/users를 확인 하는 작업이 필요하다.

4.1 VPC 내부 접속 및 테스트 (WEB 서버 접속)

VPC 내부로 접속하기 위해서는 VPC 내부에 생성해둔 웹 서버에 SSH로 접속하면 된다.

접속 후 http://10.128.0.59:25/api/users 에 요청을 보내면?

위 그림과 같이 4계층 LB → WAS 서버 접속 → DB 쿼리(조회)를 통해서 사용자에게 DB 조회의 결과가 반환되는 것을 확인할 수 있다.

이것으로 4계층 LB까지의 생성을 모두 완료하였다.


🏝️ (쉬어가기) 왜 TCP LB인데 HTTP 요청을 처리 가능한걸까?

📌 1. 클라이언트 → LB로 요청 (HTTP 요청)

클라이언트가 http://10.128.0.59:25/api/users 요청을 보낼 때 아래와 같은 HTTP 패킷이 LB로 전송된다.

# 패킷
GET /api/users HTTP/1.1
Host: 10.128.0.59:25
User-Agent: curl/7.64.1
Accept: */*

📌 2. TCP LB 동작 (패킷을 그대로 백엔드 VM으로 전달)

TCP LB는 이 패킷을 4계층(전송 계층 TCP/UDP)까지만 처리할 수 있음.
즉, TCP LB 입장에서 이 패킷은 4계층 패킷일 뿐.

따라서 URL(/api/user)이나 HTTP 메서드(GET, POST 등)는 해석하지 않고 원본 그대로 VM에게 전달.

# 패킷
GET /api/users HTTP/1.1
Host: 10.128.0.59:25
User-Agent: curl/7.64.1
Accept: */*

📌 3. 백엔드 서버에서 HTTP 요청을 해석

백엔드 서버에서는 HTTP 요청을 처리하는 Flask 프레임워크가 동작하므로 HTTP 패킷을 뜯어볼 수 있음.
따라서 이 단계에서 해당 패킷을 뜯어 /api/user 엔드포인트를 처리하고 HTTP 응답을 생성하여 TCP LB에게 반환

# 응답 패킷
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 42

{"user": "Alice", "age": 30}

📌 4. TCP LB 통과 후, 최종 사용자에게 결과 출력

TCP LB는 응답 HTTP 패킷도 건드리지 않고(HTTP 패킷을 뜯어 볼 수 없음) 그대로 다시 클라이언트로 전달.

클라이언트에서 결과 확인.

profile
붉은 배 오색 딱다구리 개발자 🦃Cloud & DevOps

0개의 댓글