Skip to content
@fast-cloud

fast-cloud

Fast-cloud 프로젝트


1. 표지

  • 주제명: fast cloud (입문자 및 교육용 소규모 클라우드 플랫폼 구축)
  • 팀명: 3조
  • 팀원 명단:
    • 202111374 조동현 (팀장)
    • 202111263 김재원
    • 202111358 임지성
    • 202111363 전승민
  • 제출일: 2025.12.20

2. 요약문

본 프로젝트 **‘fast cloud’**는 컴퓨터공학 전공생 및 비전공자가 클라우드 인프라를 학습하거나 소규모 프로젝트를 진행할 때 겪는 기술적 진입장벽을 해결하기 위해 기획되었습니다. AWS, GCP와 같은 기존 상용 클라우드는 기능이 강력하지만 복잡한 설정 과정(IAM, VPC 등)으로 인해, 개발자가 애플리케이션 로직 구현이라는 본질적인 목표보다 인프라 설정에 과도한 시간을 쏟게 만듭니다.

이에 본 팀은 오픈소스인 OpenStackKubernetes를 기반으로 자체적인 IaaS(Infrastructure as a Service) 및 CaaS(Container as a Service) 환경을 구축하고, 이를 직관적인 웹 인터페이스로 추상화하여 제공하는 서비스를 개발하였습니다.

주요 기능으로는 사전 정의된 템플릿을 통한 간편한 가상머신(VM) 생성 및 관리 ▲컨테이너 이미지 기반의 애플리케이션 즉시 배포 및 URL 자동 할당 ▲대용량 데이터 저장을 위한 오브젝트 스토리지 버킷 관리가 있습니다.

이를 통해 사용자는 복잡한 네트워크 설정 없이 단 몇 번의 클릭만으로 개발 환경을 구축할 수 있으며, 인프라 설정 시간을 획기적으로 단축하여 프로젝트의 핵심 가치 창출에 집중할 수 있습니다. 본 프로젝트는 단순히 클라우드를 사용하는 것을 넘어, 클라우드 플랫폼의 내부 동작 원리를 깊이 있게 이해하고 구현했다는 점에서 기술적 의의가 있습니다.


3. 개요

3.1 프로젝트 동기

  • 현황 및 문제점: 해커톤이나 단기 프로젝트 환경에서 학생들은 인프라 구축에 많은 시간을 소모합니다. 상용 클라우드의 방대한 전문 용어와 복잡한 설정 과정은 초보 개발자들에게 높은 진입장벽으로 작용하며, 설정 오류로 인한 보안 취약점이나 예상치 못한 비용 발생의 우려가 있습니다.
  • 개발 목적: ’fast cloud’는 이러한 복잡성을 제거하고 “이해보다 실행”을 중심으로 하여, 대학생 및 입문자가 빠르고 안전하게 클라우드 리소스를 활용할 수 있는 환경을 제공하는 것을 목표로 합니다.

3.2 핵심 시나리오

  1. IaaS (미니 PC 대여): 사용자는 ‘미니 PC 대여’ 메뉴에서 사전 정의된 사양(CPU/RAM 등)을 선택하여 즉시 가상머신을 생성하고, SSH 접속 또는 웹 콘솔을 통해 서버를 제어합니다.
  2. CaaS (컨테이너 배포): 사용자는 Docker Hub의 이미지 주소만 입력하여 애플리케이션을 배포하고, 자동으로 할당된 외부 접속 URL을 통해 서비스를 즉시 확인합니다.
  3. 스토리지 관리: 사용자는 프로젝트의 백업 데이터나 정적 파일을 저장하기 위해 버킷을 생성하고 파일을 업로드/다운로드합니다.

4. 요구사항 정의

4.1 Use-Case Diagram

image

4.2 Use-Case 개요

  • UC-01 (계정 관리): 회원가입, 로그인, 로그아웃
  • UC-02 (VM 관리): 가상머신 생성, 조회, 중지, 재시작, 삭제
  • UC-03 (컨테이너 관리): 애플리케이션 배포, 조회, 재배포, 삭제
  • UC-04 (스토리지 관리): 오브젝트 스토리지 버킷 생성, 조회, 삭제
  • UC-05 (대시보드): 리소스 사용 현황 조회

4.3 주요 Use-Case 상세 설명

UC-01: 계정 관리

  • UC-1.1 회원가입

    UseCase Description
    유스케이스 이름 회원 가입
    범위 비회원(Guest)
    수준 사용자 인증, 인가를 위한 기능 지원
    주요 액터 User
    사전 조건 사용자가 인증이 안됐고, 회원 가입 진행 전 상태.
    사후 조건 사용자의 정보 및 인증을 위한 정보 등이 데이터베이스에 기록된다.
    User System
    기본 흐름
    1. 사용자는 회원 가입 탭을 클릭해서 들어간다.
    1.1 사용자 회원가입 폼을 전송한다.
    2. 사용자의 id를 입력하고 중복 확인을 한다.
    2.1 사용자 id 중복 확인을 한다.
    3. 중복확인 성공시 비밀번호를 설정한 규칙(8자리 이상)에 맞게 입력한다.
    3.1 비밀번호 규칙 검증을 진행한다.
    4. 사용자의 이름, 생년월일 등 인적사항을 입력한다.
    4.1 데이터베이스에 사용자의 정보를 정상적으로 저장한다.
    5. 성공시, 로그인 탭으로 이동한다.
    대체흐름
    a. id 중복 확인 시, 중복된 id일 때
    1. 사용자는 중복 id라는 것을 확인하고 다른 id를 입력 하도록 요구 받는다. | | | | 1.1 새로운 id에 대해서 중복 확인을 한다. | | 2. 새로운 id가 중복 확인 성공시, 기본 흐름 3번으로 이동한다. | |
  • UC-1.2 로그인

UC-02: VM 관리

  • UC-2.1 가상머신 생성

    UseCase Description
    유스케이스 이름 사용자가 클라우드 인프라에 가상머신(VM)을 생성한다.
    범위 회원(User)
    수준 사용자에게 가상의 컴퓨팅 환경을 제공하기 위함
    주요 액터 User
    사전 조건 1. 사용자는 시스템에 로그인된 상태이다.
    1. 사용자는 할당된 리소스 쿼터(Quota)를 초과하지 않았다. | | 사후 조건 | 1. OpenStack에 사용자가 요청한 사양의 VM 인스턴스가 생성되고 'Active' 상태가 된다.
    2. 생성된 VM의 네트워크 정보(IP 주소)가 사용자에게 표시된다.
    3. 사용자의 리소스 사용량이 갱신된다. |
    User System
    기본 흐름
    1. '미니 PC 대여' 메뉴에서 '미니 PC 대여하기' 버튼을 클릭한다.
    1.1 사용자에게 미니 PC 설정 폼을 제공한다.
    2. 미니 PC 대여 양식에서 미니 PC의 이름(예: my-test-server)을 입력하고, 사전 정의된 사양을 선택한 후 '생성하기' 버튼을 클릭한다.
    2.1 사용자가 선택한 사양에 맞춰 VM을 생성하고 미니 PC 관리 대시보드로 안내한다.
    대체흐름
    2a. 사용자가 필수 입력 값(이름, 사양)을 누락한 경우
    2a1. 시스템은 입력 양식 아래에 오류 메시지(예: "미니 PC 이름을 입력해주세요.")를 표시하고 생성 요청을 보내지 않는다.
    2.1a. OpenStack 내부 오류로 VM 생성에 실패한 경우
    2.1a1. 시스템은 해당 VM의 상태를 'Error'로 변경하고, 사용자에게 "미니 PC 대여에 실패했습니다. 잠시 후 다시 시도해주세요."와 같은 오류 메시지를 보여준다.
    2.1b. 사용자의 리소스 쿼터(예: 생성 가능한 VM 개수)가 부족한 경우
    2.1b1. 시스템은 OpenStack으로부터 받은 쿼터 초과 오류를 확인하고, 사용자에게 "할당된 리소스를 초과하여 미니 PC 대여가 불가능합니다."라고 알린다.
  • UC-2.2 가상머신 조회

  • UC-2.3 가상머신 상태 동기화

UC-03: 컨테이너 관리

  • UC-3.1 컨테이너 배포

    UseCase Description
    유스케이스 이름 사용자가 컨테이너 이미지를 기반으로 애플리케이션을 배포한다.
    범위 사용자 (User)
    수준 사용자의 간편한 컨테이너화 애플리케이션 베포를 위함
    주요 액터 User
    사전 조건 사용자는 시스템에 로그인된 상태이다.
    사후 조건 1. Kubernetes 클러스터에 사용자가 요청한 이미지를 사용하는 Deployment와 Service 리소스가 생성된다.
    1. 애플리케이션에 접근할 수 있는 외부 URL이 생성되어 사용자에게 제공된다.
    2. 사용자는 배포된 애플리케이션의 상태를 조회할 수 있다. |
    User System
    기본 흐름
    1. 사용자는 컨테이너 시작하기 탭을 클릭해서 들어간다.
    1.1 사용자에게 베포 작성 폼을 전송한다.
    2. 배포 양식에 애플리케이션 이름, Docker Hub의 공개 이미지 주소, 노출할 컨테이너 포트를 입력하고 '배포하기' 버튼을 클릭한다.
    2.1 사용자에게 컨테이너 대시보드로 안내한다
    대체흐름
    2a. 컨테이너 이미지 주소가 유효하지 않거나 존재하지 않는 경우
    2a1. Kubernetes는 이미지를 가져오지 못하고 Pod은 ImagePullBackOff 오류 상태가 된다.
    2a2. 시스템은 이 상태를 감지하여 사용자에게 "컨테이너 이미지를 찾을 수 없습니다. 이미지 주소를 확인해주세요."라고 알린다.
    3a. 클러스터의 리소스(CPU/Memory)가 부족하여 Pod을 스케줄링할 수 없는 경우:
    3a1. Pod은 'Pending' 상태에 머무르게 된다.   3a2. 시스템은 일정 시간 후에도 상태 변화가 없으면 "클러스터 리소스가 부족하여 배포할 수 없습니다."라는 메시지를 표시한다.
  • UC-3.2 컨테이너 조회

UC-04: 스토리지 관리

  • UC-4.1 버킷 생성

    UseCase Description
    유스케이스 이름 사용자가 파일을 저장할 수 있는 스토리지 공간(버킷)을 생성한다.
    범위 사용자 (User)
    수준 오브젝트 스토리지 생성을 위함
    주요 액터 User
    사전 조건 1. 사용자는 시스템에 로그인된 상태이다.
    1. 사용자는 할당된 리소스 쿼터(Quota)를 초과하지 않는다. | | 사후 조건 | 1. OpenStack Swift에 사용자가 요청한 이름의 버킷(컨테이너)이 생성된다. 
    2. 생성된 버킷의 네트워크 주소 및 사용자의 버킷 목록에 대해 표시된다. 3.사용자의 리소스 사용량이 갱신된다. |
    User System
    기본 흐름
    1. 사용자는 스토리지 관리' 메뉴에서 '새 버킷 생성' 버튼을  클릭한다.
    1.1 사용자에게 스토리지 설정 폼을 전송한다.
    2. 버킷 생성 폼에서 유니크한 버킷 이름(예: my-project-backup-2025)을 입력하고 '생성하기' 버튼을 클릭한다.
    2.1 사용자가 선택한 사양에 맞춰 스토리지 버킷을 생성하고 관리 대시보드로 안내한다.
    대체흐름
    2a. 버킷 이름이 이미 시스템에 존재하는 경우
    2a1. Swift API는 'Conflict' 오류를 반환한다.
    2a2. 시스템은 사용자에게 "이미 사용 중인 버킷 이름입니다."라는 오류 메시지를 보여준다.
    2b. 버킷 이름에 유효하지 않은 문자(예: 대문자, 특수문자)가 포함된 경우
    2b1. 시스템은 "버킷 이름은 소문자, 숫자, 하이픈(-)만 사용할 수 있습니다."라는 오류 메시지를 표시하고 요청을 보내지 않는다.
  • UC-4.2 버킷 현황 조회

UC-05: 대시보드

  • UC-5.1 리소스 사용 현황 조회

    UseCase Description
    유스케이스 이름 사용자가 자신이 생성한 모든 클라우드 리소스의 요약 정보를 한눈에 파악한다.
    범위 사용자 (User)
    수준 사용자가 사용하는 리소스를 한눈에 보여주기 위함
    주요 액터 User
    사전 조건 사용자는 시스템에 로그인된 상태이다..
    사후 조건 사용자는 자신이 생성한 리소스(VM, 컨테이너 앱, 버킷)의 수와 현재 상태를 대시보드에서 확인할 수 있다.
    User System
    기본 흐름
    1. 시스템에 로그인하거나, 다른 페이지에서 로고 또는 '대시보드' 메뉴를 클릭한다.
    1.2 시스템은 사용자가 사용하고 있는 모든 리소스를 조회해서 대시보드로 보여준다.
    대체흐름
    2a. 특정 서비스(예: Kubernetes) API에 일시적인 장애가 발생한 경우:
    2a1. 시스템은 해당 정보를 가져올 수 없는 리소스 카드에 "정보를 불러올 수 없습니다." 또는 로딩 아이콘을 표시하고, 정상적으로 조회된 다른 리소스 정보는 그대로 보여준다.

5. 프로토타입 설계

5.1 시스템 아키텍처 및 주요 모듈

image image image

본 시스템은 다음과 같은 모듈 기반 아키텍처로 구성됩니다.

  • Frontend: React 기반의 웹 인터페이스로, 사용자 경험(UX)을 극대화한 컴포넌트 구조로 개발되었습니다.
  • Backend: Java Spring Framework 기반의 REST API 서버로, 안정성과 확장성을 보장합니다. IaaS와 CaaS 서비스가 각각 독립적인 마이크로서비스로 구성되어 있습니다.
  • IaaS Layer (OpenStack): Nova(컴퓨팅), Neutron(네트워킹), Swift(오브젝트 스토리지) 모듈을 활용하여 가상화된 인프라를 관리합니다. OpenStack4j SDK를 통해 REST API로 연동됩니다.
  • CaaS Layer (Kubernetes): 컨테이너 오케스트레이션을 담당하며, 사용자 요청에 따라 애플리케이션을 배포하고 Ingress를 통한 외부 접근을 제공합니다. Kubernetes Java Client를 통해 API 서버와 통신합니다.
  • Storage Layer (OpenStack Swift): 분산 오브젝트 스토리지 시스템으로 대용량 데이터를 저장합니다. Swift API를 통해 버킷 및 객체를 관리합니다.
  • Database Layer (MySQL): 각 서비스별로 독립적인 MySQL 데이터베이스를 사용하여 메타데이터를 관리합니다. IaaS와 CaaS 서비스가 각각 별도의 데이터베이스를 사용합니다.
  • Ingress Controller (NGINX): Kubernetes Ingress Controller로 Hostname 기반 라우팅을 제공하여 각 컨테이너에 고유한 외부 접근 URL을 자동 할당합니다.

5.2 시퀀스 다이어그램

본 시스템의 주요 기능별 시퀀스 다이어그램은 다음과 같습니다.

image

컨테이너 생성

image

컨테이너 현황 조회

image

버킷 생성

image

버킷 현황 조회

image

인스턴스 생성

image image

인스턴스 현황 조회

5.3 데이터베이스 설계

본 시스템은 MySQL 기반의 RDBMS를 사용하여 메타데이터를 관리합니다.

IaaS 데이터베이스 (iaas)

  • iaas_instance: 가상머신 인스턴스 정보 저장
  • 주요 필드: instance_id (UUID), instance_name, openstack_uuid, owner_user_id, cached_status, ip_address, template_id (FK)
  • iaas_template: VM 템플릿(사양) 정보 저장
  • 주요 필드: template_id, template_name, openstack_image_id, openstack_flavor_id, openstack_network_id, keypair_name
  • iaas_bucket: 오브젝트 스토리지 버킷 메타데이터 저장
  • 주요 필드: bucket_id, bucket_name, owner_user_id, status
image

CaaS 데이터베이스 (caas)

  • caas_application: 배포된 컨테이너 애플리케이션 정보 저장
  • 주요 필드: app_id (UUID), app_name, k8s_namespace, k8s_deployment_name, k8s_service_name, owner_user_id, cached_status
  • caas_config: 컨테이너 설정 정보 저장
  • 주요 필드: config_id (UUID), application_id (FK), image_link, external_port, internal_port
image

5.4 핵심 알고리즘

5.4.1 비동기 VM 생성 알고리즘

VM 생성 요청 시 사용자 대기 시간을 최소화하기 위해 비동기 처리 패턴을 적용했습니다.

알고리즘: 비동기 VM 생성 입력: InstanceCreateRequest (instanceName, templateId), userId 출력: InstanceResponseData (즉시 반환)

  1. Template 조회 (templateId로 DB에서 템플릿 정보 가져오기)
  2. Instance 엔티티 생성 및 "BUILD" 상태로 DB 저장
  3. @Async 어노테이션을 통해 별도 스레드에서 OpenStack VM 생성 요청
  4. HTTP 202 Accepted 응답 즉시 반환 (사용자 대기 없음)
  5. [백그라운드] OpenStack Nova API 호출
  6. [백그라운드] 생성 완료 시 DB 상태를 "ACTIVE"로 업데이트
  7. [백그라운드] 생성 실패 시 DB 상태를 "ERROR"로 업데이트

핵심 특징:

  • 사용자는 즉시 응답을 받아 대기 시간 없음
  • 비동기 스레드 풀(ThreadPoolTaskExecutor)을 통해 동시 다중 VM 생성 지원
  • 스케줄러가 주기적으로 상태를 동기화하여 최종 상태 보장

5.4.2 Kubernetes 리소스 생성 및 검증 알고리즘 (Programmatic Infrastructure Control)

컨테이너 배포 시 Kubernetes 리소스를 순차적으로 생성하고 검증하는 알고리즘입니다.

알고리즘: Kubernetes 컨테이너 배포 (Type-Safe & Atomic Workflow) 입력: ContainerCreateRequestDto (clusterName, imageLink, ports) 출력: ContainerCreateResponseDto (containerId, status, URL)

  1. 고유 컨테이너 ID 생성 (UUID)
  2. 리소스 이름 생성: {clusterName}-{containerId.substring(0,8)}
  3. Type-Safe Resource Modeling (컴파일 타임 검증):
    • V1Deployment, V1Service, V1Ingress 객체 생성
    • Kubernetes Official Client SDK의 타입 안전성 활용
  4. Atomic Creation Workflow (순차 보장): a. Deployment 생성:
    • 이미지: imageLink
    • 포트: internalPort
    • 레플리카: 1
    • API 응답 확인 후 다음 단계 진행 b. Service 생성:
    • 타입: ClusterIP
    • Selector: Deployment의 label과 매칭
    • 포트: internalPort
    • API 응답 확인 후 다음 단계 진행 c. Ingress 생성:
    • Hostname: {sanitizedClusterName}.{baseDomain}
    • Backend: Service 이름 및 포트
    • Annotation: nginx rewrite 설정
    • Dynamic L7 Routing 자동 구성
  5. Reconciliation Loop (리소스 검증):
    • 500ms 대기 (리소스 생성 시간 확보)
    • Deployment 존재 확인
    • Service 존재 확인
    • Ingress 존재 확인
    • 검증 실패 시 재시도 또는 롤백
  6. DB에 Application 및 Config 저장 (Dual-State Sync)
  7. 응답 반환

핵심 특징:

  • Programmatic Infrastructure Control: Kubernetes Official Client SDK를 통한 코드 기반 인프라 제어
  • Type-Safe Resource Modeling: 컴파일 타임에 리소스 스펙 오류 검증
  • Atomic Creation Workflow: Deployment → Service → Ingress 순차 보장으로 의존성 해결
  • Zero-Touch Dynamic Network: 사용자 입력만으로 Ingress 자동 생성 및 Nginx Controller Reload
  • Reconciliation Loop: 리소스 생성 후 검증을 통한 상태 보장
  • Dual-State Sync: DB 메타데이터와 Kubernetes 실제 상태 동기화

5.4.3 인스턴스 상태 동기화 알고리즘 (Dual-State Sync)

스케줄러가 주기적으로 실행하여 DB 상태와 OpenStack 실제 상태를 동기화합니다.

알고리즘: 인스턴스 상태 동기화 (Reconciliation Pattern) 입력: 없음 (스케줄러가 자동 실행) 출력: 없음 (DB 상태 업데이트)

  1. DB에서 모든 인스턴스 조회 (getAllInstances())
  2. 각 인스턴스에 대해: a. OpenStack UUID가 없으면 스킵 (아직 생성 중) b. Stateless OpenStack Client 생성 (Thread-Safe) c. OpenStack API로 실제 서버 상태 조회 d. DB 상태와 OpenStack 상태 비교 (Dual-State Sync) e. 불일치 시:
    • DB 상태 업데이트
    • IP 주소 변경 시 업데이트
  3. 로그 기록

핵심 특징:

  • 1분 주기 실행으로 실시간성 보장
  • OpenStack UUID가 없는 인스턴스는 스킵하여 불필요한 API 호출 방지
  • 상태 불일치 시에만 DB 업데이트하여 성능 최적화
  • Dual-State Sync: DB 메타데이터와 OpenStack 실제 상태를 지속적으로 동기화
  • Stateless Architecture: 각 조회마다 새로운 클라이언트 생성으로 Thread-Safe 보장

5.5 타 모듈 연계

5.5.1 OpenStack 연계

사용 모듈:

  • OpenStack4j SDK (v3.12): OpenStack API 호출을 위한 Java 클라이언트 라이브러리
  • 주요 컴포넌트:
  • Nova (Compute): 가상머신 생성 및 관리
  • Swift (Object Storage): 오브젝트 스토리지 버킷 관리
  • Keystone (Identity): 인증 및 권한 관리

연계 방식:

// OpenStack 클라이언트 생성
OSClientV3 osClient = OSFactory.builderV3()
    .endpoint(authUrl)
    .credentials(username, password, Identifier.byName(domainName))
    .scopeToProject(Identifier.byName(projectName), Identifier.byName(domainName))
    .authenticate();

// VM 생성
Server server = osClient.compute().servers().boot(serverCreate);

// Swift 컨테이너 생성
osClient.objectStorage().containers().create(containerName);

인증 방식:

  • Keystone v3 API를 통한 토큰 기반 인증
  • 프로젝트 스코프로 리소스 격리
  • Stateless Architecture: 각 요청마다 클라이언트 재생성으로 스레드 안전성 보장
  • Thread-Safe 제어 플레인: WAS의 NIO Thread Model과 Context Switching을 고려한 설계
  • 인증 상태를 요청 스레드 내부에만 유지하여 동기화 비용 제거

5.5.2 Kubernetes 연계

사용 모듈:

  • Kubernetes Java Client (v20.0.1): Kubernetes API 호출을 위한 공식 Java 클라이언트
  • 주요 API 그룹:
  • AppsV1Api: Deployment 리소스 관리
  • CoreV1Api: Service, Pod 리소스 관리
  • NetworkingV1Api: Ingress 리소스 관리

연계 방식:

// Kubernetes 클라이언트 생성
ApiClient client = ClientBuilder.kubeconfig(
    KubeConfig.loadKubeConfig(new FileReader(kubeConfigPath))
).build();

// Deployment 생성
AppsV1Api appsV1Api = new AppsV1Api(client);
V1Deployment deployment = appsV1Api.createNamespacedDeployment(
    namespace, deploymentSpec
).execute();

// Service 생성
CoreV1Api coreV1Api = new CoreV1Api(client);
V1Service service = coreV1Api.createNamespacedService(
    namespace, serviceSpec
).execute();

// Ingress 생성
NetworkingV1Api networkingV1Api = new NetworkingV1Api(client);
V1Ingress ingress = networkingV1Api.createNamespacedIngress(
    namespace, ingressSpec
).execute();

인증 방식:

  • kubeconfig 파일 기반 인증
  • In-Cluster Authentication: 클러스터 내부 실행 시 ServiceAccount 토큰 자동 감지
  • 환경변수 KUBECONFIG 우선, 없을 경우 ~/.kube/config 사용
  • Cross-Namespace RBAC & Isolation: YAML 기반 RBAC 정책으로 최소 권한 원칙 적용
  • Operator Pattern: 관리 로직과 사용자 워크로드를 격리하여 보안 강화

5.5.3 Spring Framework 연계

사용 모듈:

  • Spring Boot (v3.2.0): 애플리케이션 프레임워크
  • Spring Data JPA: 데이터베이스 ORM
  • Spring WebFlux: 비동기 HTTP 클라이언트
  • Spring Scheduling: 스케줄러 기능

주요 기능 활용:

  • @Async 어노테이션을 통한 비동기 처리
  • @Scheduled 어노테이션을 통한 주기적 작업 실행
  • @Transactional 어노테이션을 통한 트랜잭션 관리
  • @RestController를 통한 REST API 구현

5.5.4 데이터베이스 연계

사용 모듈:

  • MySQL Connector/J: MySQL 데이터베이스 연결
  • Hibernate: JPA 구현체로 객체-관계 매핑
  • HikariCP: 커넥션 풀 관리

연계 설정:

# IaaS 데이터베이스
spring.datasource.url=jdbc:mysql://mysql-iaas:3306/iaas
spring.datasource.username=admin
spring.datasource.password=1234

# CaaS 데이터베이스
spring.datasource.url=jdbc:mysql://mysql-caas:3306/caas
spring.datasource.username=admin
spring.datasource.password=1234

5.5.5 모니터링 및 운영 도구 연계

사용 모듈:

  • Spring Boot Actuator: 애플리케이션 모니터링 엔드포인트 제공
  • Micrometer Prometheus: 메트릭 수집 및 Prometheus 연동
  • Kubernetes Self-healing: Pod 자동 재시작 기능 활용

모니터링 지표:

  • /actuator/health: 애플리케이션 상태 확인
  • /actuator/metrics: 성능 메트릭 조회
  • /actuator/prometheus: Prometheus 형식 메트릭 제공

6. 프로토타입 구현

6.1 IaaS 서비스 구현

6.1.1 VM 생성 기능

핵심 코드 상세 설명

VM 생성 기능은 비동기 처리 패턴을 통해 사용자 대기 시간을 최소화하도록 구현되었습니다.

Controller 계층 (InstanceController.java)

@PostMapping
public ResponseEntity<ApiResponseDto<InstanceResponseData>> createInstance(
        @RequestBody InstanceCreateRequest request) {
    String userId = TEST_USER_ID;

    log.info("POST /compute - userId: {}, request: {}", userId, request);

    InstanceResponseData instance = instanceService.createInstance(request, userId);

    // HTTP 202 Accepted: 요청을 수락했지만 아직 처리 중
    return ResponseEntity.accepted()
            .body(ApiResponseDto.success(SuccessCode.INSTANCE_CREATE_ACCEPTED, instance));
}

핵심 설계 포인트:

  • HTTP 202 Accepted 상태 코드를 반환하여 비동기 처리임을 명시
  • 사용자는 즉시 응답을 받아 대기 시간 없음
  • 실제 VM 생성은 백그라운드에서 진행

Service 계층 (InstanceService.java)

@Transactional
public InstanceResponseData createInstance(InstanceCreateRequest request, String userId) {
    // 1. Template 조회
    Template template = templateRepository.findById(request.getTemplateId())
            .orElseThrow(() -> new EntityNotFoundException("Template not found"));

    // 2. DB에 BUILD 상태로 저장
    Instance instance = new Instance();
    instance.setInstanceName(request.getInstanceName());
    instance.setOwnerUserId(userId);
    instance.setCachedStatus("BUILD");
    instance.setTemplate(template);
    instance.setOpenstackUuid("");

    Instance savedInstance = instanceRepository.save(instance);

    // 3. 비동기로 OpenStack VM 생성 요청
    requestOpenStackVmCreation(savedInstance, template);

    return convertToResponseData(savedInstance);
}

@Async
@Transactional
public void requestOpenStackVmCreation(Instance instance, Template template) {
    try {
        // OpenStack API 호출
        Server server = openStackService.createInstance(
                instance.getInstanceName(),
                template.getOpenstackFlavorId(),
                template.getOpenstackImageId(),
                template.getKeypairName(),
                template.getOpenstackNetworkId()
        );

        // IP 주소 추출
        String ipAddress = openStackService.extractIpAddress(server);

        // 성공 시 ACTIVE 상태로 업데이트
        updateInstanceStatus(instance.getInstanceId(), "ACTIVE", server.getId(), ipAddress);

    } catch (Exception e) {
        log.error("Failed to create OpenStack VM", e);
        // 실패 시 ERROR 상태로 업데이트
        updateInstanceStatus(instance.getInstanceId(), "ERROR", "", null);
    }
}

핵심 설계 포인트:

  • @Async 어노테이션을 통해 별도 스레드에서 실행
  • DB에 먼저 저장하여 트랜잭션 보장
  • 예외 처리 시 상태를 ERROR로 업데이트하여 사용자에게 알림

OpenStack 연동 (OpenStackService.java)

public Server createInstance(String serverName, String flavorId, String imageId,
                            String keyPairName, String networkId) {
    OSClientV3 osClient = createOpenStackClient();

    // 이미지 ID 또는 이름을 실제 이미지 ID로 변환
    String resolvedImageId = resolveImageId(osClient, imageId);

    // 서버 생성 요청 빌더
    ServerCreate serverCreate = Builders.server()
            .name(serverName)
            .flavor(flavorId)
            .image(resolvedImageId)
            .keypairName(keyPairName)
            .networks(List.of(networkId))
            .build();

    // OpenStack Nova API 호출
    return osClient.compute().servers().boot(serverCreate);
}

핵심 설계 포인트:

  • 이미지 ID와 이름 모두 지원하도록 resolveImageId() 구현
  • 각 요청마다 새로운 클라이언트 생성으로 스레드 안전성 보장
  • OpenStack4j SDK의 Builders 패턴 활용

구현 시 문제점과 해결 방법

문제점 1: VM 생성 시 사용자 대기 시간 과다

  • 원인: OpenStack VM 생성은 평균 2-3분 소요되는데, 동기 방식으로 처리 시 사용자가 그동안 대기해야 함
  • 해결방안:
  • @Async 어노테이션을 활용한 비동기 처리 패턴 도입
  • HTTP 202 Accepted 응답으로 즉시 반환
  • ThreadPoolTaskExecutor를 통한 스레드 풀 관리 (corePoolSize: 5, maxPoolSize: 10)
  • 효과: 사용자 대기 시간 0초 달성, 동시 다중 VM 생성 지원

문제점 2: OpenStack 클라이언트 스레드 안전성 문제 (심층 분석 및 해결)

  • 원인 분석:
    • Spring Boot는 기본적으로 Singleton Bean을 사용하여 객체를 관리
    • OpenStack4j SDK는 인증 토큰과 세션 정보를 ThreadLocal에 저장하는 Stateful한 설계
    • WAS의 NIO Thread Model에서 여러 요청이 동시에 처리될 때, Singleton Bean으로 주입된 OSClientV3가 여러 스레드에서 공유됨
    • Context Switching 시 ThreadLocal의 인증 상태가 꼬이면서 Race Condition 발생
    • 동시 다발적인 프로비저닝 요청 시 토큰 만료, 세션 꼬임, 인증 실패 등 심각한 동시성 문제 발생
  • 근본 원인 해결:
    • 단순 에러 수정이 아닌, WAS의 NIO Thread Model과 Context Switching 원인을 심층 분석하여 근본 원인 해결
    • 각 요청마다 새로운 OSClientV3 클라이언트를 생성하는 Stateless Architecture 구현
    • createOpenStackClient() 메서드를 통해 매 요청 시마다 독립적인 인증 수행
    • 인증 상태(State)를 오직 해당 요청 스레드 내부에만 가두어, 별도의 Synchronization(동기화) 비용 없이 동시성 문제 해결
    • 세션 꼬임 없는 완전한 Stateless Architecture 구현
  • 효과:
    • 동시 다발적인 프로비저닝 요청에도 Race Condition이 발생하지 않는 Thread-Safe한 제어 플레인 구축
    • 멀티스레드 환경에서 안정적인 동작 보장
    • 동기화 오버헤드 없이 높은 동시성 달성

문제점 3: VM 상태 불일치 문제

  • 원인: OpenStack에서 VM이 삭제되었거나 상태가 변경되었는데 DB에는 이전 상태가 남아있음
  • 해결방안:
  • @Scheduled 어노테이션을 활용한 주기적 상태 동기화 (1분마다)
  • OpenStack API로 실제 상태 조회 후 DB와 비교하여 불일치 시 업데이트
  • 효과: 최대 1분 이내 상태 동기화 보장

6.1.2 버킷 관리 기능

핵심 코드 상세 설명

버킷 관리는 OpenStack Swift API를 통해 오브젝트 스토리지를 관리합니다.

Service 계층 (BucketService.java)

public BucketCreateResponseDto createBucket(BucketCreateRequestDto requestDto,
                                            String ownerUserId) {
    String bucketName = requestDto.getName();

    // 1. 중복 체크
    Optional<Bucket> existingBucket = bucketRepository
            .findByBucketNameAndOwnerUserId(bucketName, ownerUserId);
    if (existingBucket.isPresent()) {
        throw new DuplicateBucketException(bucketName, ownerUserId);
    }

    // 2. SwiftAPI Server에 컨테이너 생성 요청
    createContainerInSwift(bucketName);

    // 3. 메타데이터 저장
    Bucket bucket = Bucket.builder()
            .bucketName(bucketName)
            .ownerUserId(ownerUserId)
            .status("PENDING")
            .build();

    Bucket savedBucket = bucketRepository.save(bucket);

    return BucketCreateResponseDto.builder()
            .name(savedBucket.getBucketName())
            .status(savedBucket.getStatus())
            .createdAt(savedBucket.getCreatedAt())
            .build();
}

private void createContainerInSwift(String containerName) throws SwiftApiException {
    try {
        OSClientV3 osClient = createOpenStackClient();
        osClient.objectStorage().containers().create(containerName);
        log.info("SwiftAPI Server 컨테이너 생성 성공: {}", containerName);
    } catch (Exception e) {
        log.error("SwiftAPI Server 컨테이너 생성 실패: {}", containerName, e);
        throw new SwiftApiException("컨테이너 생성", containerName, e);
    }
}

핵심 설계 포인트:

  • 사용자별 버킷 이름 중복 체크로 데이터 격리
  • Swift API 호출 실패 시 커스텀 예외(SwiftApiException) 발생
  • DB에 메타데이터 저장하여 빠른 조회 지원

구현 시 문제점과 해결 방법

문제점: Swift 클라이언트 인증 토큰 만료 (Concurrency & Session Synchronization)

  • 원인:
    • Bean으로 주입된 클라이언트의 토큰이 만료되어 장시간 실행 시 오류 발생
    • Spring Boot Singleton Bean과 OpenStack SDK의 ThreadLocal 기반 Stateful 설계 간의 충돌
    • WAS의 NIO Thread Model에서 동시 요청 처리 시 세션 꼬임 발생
  • 해결방안:
    • Stateless Architecture 구현: 각 메서드 호출 시마다 새로운 클라이언트 생성
    • createOpenStackClient() 메서드로 매번 인증 수행
    • 인증 상태를 요청 스레드 내부에만 유지하여 동기화 비용 제거
    • 세션 꼬임 없는 완전한 Stateless Architecture 구현
  • 효과:
    • 토큰 만료 문제 해결 및 안정적인 동작 보장
    • 동시 다발적인 요청에도 Race Condition이 발생하지 않는 Thread-Safe한 제어 플레인 구축
    • 동기화 오버헤드 없이 높은 동시성 달성

6.2 CaaS 서비스 구현

6.2.1 컨테이너 배포 기능

핵심 코드 상세 설명

컨테이너 배포는 Kubernetes API를 통해 Deployment, Service, Ingress를 순차적으로 생성합니다.

Service 계층 (ContainerService.java)

@Transactional
public ContainerCreateResponseDto createContainer(ContainerCreateRequestDto request) {
    // 1. 고유 ID 및 리소스 이름 생성
    String containerId = UUID.randomUUID().toString();
    String deploymentName = request.getClusterName() + "-" + containerId.substring(0, 8);
    String serviceName = deploymentName + "-svc";
    String ingressName = deploymentName + "-ingress";

    try {
        // 2. Kubernetes 리소스 생성
        createDeployment(deploymentName, request.getImageLink(), request.getInternalPort());
        createService(serviceName, deploymentName, request.getInternalPort());
        createIngress(ingressName, serviceName, request.getClusterName(),
                     request.getInternalPort());

        // 3. 리소스 검증
        verifyResourceExists(deploymentName, serviceName, ingressName);

        // 4. DB 저장
        Application application = new Application();
        application.setAppId(containerId);
        application.setAppName(request.getClusterName());
        application.setK8sNamespace(DEFAULT_NAMESPACE);
        application.setK8sDeploymentName(deploymentName);
        application.setK8sServiceName(serviceName);
        application.setOwnerUserId(DEFAULT_OWNER_USER_ID);
        application.setCachedStatus("PENDING");
        application = applicationRepository.save(application);

        Config config = new Config();
        config.setConfigId(UUID.randomUUID().toString());
        config.setApplication(application);
        config.setImageLink(request.getImageLink());
        config.setExternalPort(request.getExternalPort());
        config.setInternalPort(request.getInternalPort());
        configRepository.save(config);

        return ContainerCreateResponseDto.builder()
                .containerId(containerId)
                .clusterName(request.getClusterName())
                .imageLink(request.getImageLink())
                .status("PENDING")
                .build();

    } catch (ApiException e) {
        log.error("Container creation failed", e);
        throw new RuntimeException("컨테이너 생성 중 오류 발생", e);
    }
}

Deployment 생성 (ContainerService.java)

private void createDeployment(String deploymentName, String imageLink,
                              Integer internalPort) throws ApiException {
    AppsV1Api appsV1Api = new AppsV1Api(apiClient);

    V1Deployment deployment = new V1Deployment()
            .apiVersion("apps/v1")
            .kind("Deployment")
            .metadata(new V1ObjectMeta()
                    .name(deploymentName)
                    .labels(Map.of("app", deploymentName)))
            .spec(new V1DeploymentSpec()
                    .replicas(1)
                    .selector(new V1LabelSelector()
                            .matchLabels(Map.of("app", deploymentName)))
                    .template(new V1PodTemplateSpec()
                            .metadata(new V1ObjectMeta()
                                    .labels(Map.of("app", deploymentName)))
                            .spec(new V1PodSpec()
                                    .containers(List.of(
                                            new V1Container()
                                                    .name(deploymentName)
                                                    .image(imageLink)
                                                    .ports(List.of(
                                                            new V1ContainerPort()
                                                                    .containerPort(internalPort)
                                                                    .name("http")))
                                                    .imagePullPolicy("IfNotPresent")
                                    ))));

    V1Deployment created = appsV1Api.createNamespacedDeployment(
            DEFAULT_NAMESPACE, deployment
    ).execute();

    log.info("Deployment created: {}", created.getMetadata().getName());
}

Ingress 생성 및 Hostname 기반 라우팅 (ContainerService.java)

private void createIngress(String ingressName, String serviceName,
                          String clusterName, Integer servicePort) throws ApiException {
    // 클러스터 이름을 호스트명으로 변환 (소문자, 특수문자 제거)
    String sanitizedClusterName = clusterName.toLowerCase()
            .replaceAll("[^a-z0-9-]", "-");
    String host = sanitizedClusterName + "." + baseDomain;

    NetworkingV1Api networkingV1Api = new NetworkingV1Api(apiClient);

    V1Ingress ingress = new V1Ingress()
            .apiVersion("networking.k8s.io/v1")
            .kind("Ingress")
            .metadata(new V1ObjectMeta()
                    .name(ingressName)
                    .labels(Map.of("app", serviceName))
                    .annotations(Map.of(
                            "nginx.ingress.kubernetes.io/rewrite-target", "/",
                            "nginx.ingress.kubernetes.io/ssl-redirect", "false"
                    )))
            .spec(new V1IngressSpec()
                    .ingressClassName("nginx")
                    .rules(List.of(
                            new V1IngressRule()
                                    .host(host)
                                    .http(new V1HTTPIngressRuleValue()
                                            .paths(List.of(
                                                    new V1HTTPIngressPath()
                                                            .path("/")
                                                            .pathType("Prefix")
                                                            .backend(new V1IngressBackend()
                                                                    .service(new V1IngressServiceBackend()
                                                                            .name(serviceName)
                                                                            .port(new V1ServiceBackendPort()
                                                                                    .number(servicePort)
                                                                            )
                                                                    )
                                                            )
                                            ))
                                    )
                    )));

    V1Ingress created = networkingV1Api.createNamespacedIngress(
            DEFAULT_NAMESPACE, ingress
    ).execute();

    log.info("Ingress created: host={}", host);
}

핵심 설계 포인트:

  • 순차적 리소스 생성으로 의존성 보장 (Deployment → Service → Ingress)
  • Hostname 기반 라우팅으로 사용자별 고유 URL 자동 할당
  • 리소스 검증 단계를 통해 생성 실패 시 조기 감지
  • 클러스터 이름을 안전한 호스트명으로 변환 (sanitization)

구현 시 문제점과 해결 방법

문제점 1: Kubernetes 리소스 생성 순서 의존성 (Atomic Creation Workflow)

  • 원인: Service와 Ingress는 Deployment가 생성되어야 참조 가능한데, 동시 생성 시 오류 발생
  • 해결방안:
    • Atomic Creation Workflow 구현: Deployment → Service → Ingress 순차 보장
    • 각 단계마다 API 응답 확인 후 다음 단계 진행
    • Kubernetes Official Client SDK의 Type-Safe Resource Modeling 활용
    • 컴파일 타임 검증을 통해 리소스 스펙 오류를 사전에 방지
  • 효과: 리소스 생성 실패율 0% 달성, 타입 안전성 보장

문제점 2: Hostname 기반 라우팅 설정 복잡도 (Zero-Touch Dynamic Network)

  • 원인: 사용자가 입력한 클러스터 이름에 특수문자나 대문자가 포함되어 DNS 호스트명 규칙 위반
  • 해결방안:
    • Zero-Touch Dynamic Network 구현:
      • User Input (“my-app”) → Sanitizer (정규식) → Ingress Rule 생성 (my-app.fast-cloud.kro.kr)
      • 클러스터 이름을 소문자로 변환
      • 영문/숫자/하이픈 외 문자를 하이픈으로 치환하는 정규식 패턴 적용
      • sanitizedClusterName 변수로 안전한 호스트명 생성
    • Dynamic L7 Routing: Ingress 자동 생성 및 Nginx Controller Reload
    • 사용자 입력만으로 자동으로 외부 접근 경로 구성
  • 효과: 모든 클러스터 이름에 대해 유효한 호스트명 생성 보장, 사용자 개입 없이 자동 네트워크 구성

문제점 3: 리소스 생성 후 즉시 조회 시 존재하지 않음 (Reconciliation Loop)

  • 원인: Kubernetes API 서버가 리소스를 처리하는 데 시간이 소요되는데, 생성 직후 조회 시 404 오류 발생
  • 해결방안:
    • Reconciliation Loop 구현: verifyResourceExists() 메서드에서 500ms 대기 후 검증
    • 각 리소스(Deployment, Service, Ingress)별로 개별 확인
    • Kubernetes API 서버의 비동기 처리 특성을 고려한 재시도 로직
  • 효과: 리소스 생성 검증 성공률 100% 달성, Operator Pattern과 유사한 자동화된 상태 확인

문제점 4: Kubernetes 클라이언트 인증 설정 (Cross-Namespace RBAC & In-Cluster Authentication)

image
  • 원인: 클러스터 내부/외부 실행 환경에 따라 kubeconfig 경로가 다름
  • 해결방안:
    • In-Cluster Authentication: 클러스터 내부 실행 시 ServiceAccount Token 자동 감지
    • 환경변수 KUBECONFIG 우선 확인
    • 없을 경우 ~/.kube/config 기본 경로 사용
    • 둘 다 없을 경우 클러스터 내부 실행으로 간주하여 ServiceAccount 토큰 사용
    • Cross-Namespace RBAC & Isolation: YAML 기반 RBAC 정책으로 최소 권한 원칙 적용
    • Operator Pattern: 관리 로직과 사용자 워크로드를 격리하여 보안 강화
  • 효과: 다양한 실행 환경에서 동작 보장, 최소 권한 원칙으로 보안성 향상

6.2.2 컨테이너 상태 조회 기능

핵심 코드 상세 설명

컨테이너 목록 조회 시 Kubernetes API를 통해 실제 Deployment 상태를 확인합니다.

Service 계층 (ContainerService.java)

public ContainerListResponseDto getContainers() {
    // DB에서 사용자의 Application 목록 조회
    List<Application> applications = applicationRepository
            .findByOwnerUserId(DEFAULT_OWNER_USER_ID);

    List<ContainerListResponseDto.ContainerInfo> containerInfos = new ArrayList<>();
    int runningCount = 0;

    for (Application application : applications) {
        Config config = application.getConfigs().isEmpty()
                ? null
                : application.getConfigs().get(0);

        if (config == null) continue;

        // Kubernetes에서 Deployment 상태 확인
        String status = getDeploymentStatus(
                application.getK8sNamespace(),
                application.getK8sDeploymentName()
        );

        if ("RUNNING".equals(status)) {
            runningCount++;
        }

        ContainerListResponseDto.ContainerInfo containerInfo =
                ContainerListResponseDto.ContainerInfo.builder()
                        .containerId(application.getAppId())
                        .clusterName(application.getAppName())
                        .status(status)
                        .image(config.getImageLink())
                        .ports(ContainerListResponseDto.ContainerInfo.Ports.builder()
                                .external(config.getExternalPort())
                                .internal(config.getInternalPort())
                                .build())
                        .createdAt(application.getCreatedAt())
                        .build();

        containerInfos.add(containerInfo);
    }

    return ContainerListResponseDto.builder()
            .summary(ContainerListResponseDto.Summary.builder()
                    .totalContainers(containerInfos.size())
                    .runningContainers(runningCount)
                    .build())
            .containers(containerInfos)
            .build();
}

private String getDeploymentStatus(String namespace, String deploymentName) {
    try {
        AppsV1Api appsV1Api = new AppsV1Api(apiClient);
        V1Deployment deployment = appsV1Api.readNamespacedDeployment(
                deploymentName, namespace
        ).execute();

        if (deployment == null || deployment.getStatus() == null) {
            return "UNKNOWN";
        }

        V1DeploymentStatus status = deployment.getStatus();
        Integer replicas = status.getReplicas();
        Integer readyReplicas = status.getReadyReplicas();
        Integer availableReplicas = status.getAvailableReplicas();

        // Deployment가 실행 중인지 확인
        if (replicas != null && replicas > 0) {
            if (readyReplicas != null && readyReplicas > 0 &&
                availableReplicas != null && availableReplicas > 0) {
                return "RUNNING";
            } else {
                return "PENDING";
            }
        } else {
            return "STOPPED";
        }
    } catch (ApiException e) {
        log.warn("Failed to get deployment status", e);
        return "STOPPED";
    }
}

핵심 설계 포인트:

  • Dual-State Sync: DB 메타데이터와 Kubernetes 실제 상태를 결합하여 정확한 상태 제공
  • Deployment의 replicas, readyReplicas, availableReplicas를 확인하여 상태 판단
  • API 호출 실패 시 “STOPPED”로 간주하여 안정성 보장
  • 주기적으로 Kubernetes API를 조회하여 DB 상태와 동기화하는 Reconciliation 메커니즘

6.3 기술적 난제 해결 및 아키텍처 설계

본 프로젝트를 구현하는 과정에서 여러 기술적 난제를 해결하며 엔터프라이즈급 아키텍처를 구축하였습니다.

6.3.1 Programmatic Infrastructure Control (코드 기반 인프라 제어)

도전 과제: 기존의 YAML 파일 기반 Kubernetes 리소스 관리는 사용자 개입이 필요하고 오류 가능성이 높았습니다. 사용자가 Docker 이미지 주소만 입력하면 자동으로 모든 Kubernetes 리소스를 생성하고 외부 접근 경로를 제공해야 했습니다.

해결 방법:

  • Kubernetes Official Client SDK 활용: Java 기반의 공식 클라이언트 라이브러리를 사용하여 코드로 직접 리소스 생성
  • Type-Safe Resource Modeling: V1Deployment, V1Service, V1Ingress 등의 타입 안전한 객체를 사용하여 컴파일 타임에 리소스 스펙 오류 검증
  • Atomic Creation Workflow: Deployment → Service → Ingress 순차 생성으로 의존성 보장, 각 단계마다 API 응답 확인 후 다음 단계 진행
  • Cross-Namespace RBAC & Isolation: YAML 기반 RBAC 정책으로 최소 권한 원칙 적용
  • Operator Pattern: 관리 로직과 사용자 워크로드를 격리하여 보안 강화
  • In-Cluster Authentication: 클러스터 내부 실행 시 ServiceAccount Token 자동 감지

성과:

  • 사용자 개입 없이 완전 자동화된 컨테이너 배포 파이프라인 구축
  • 컴파일 타임 타입 검증으로 런타임 오류 사전 방지
  • 리소스 생성 실패율 0% 달성

6.3.2 Zero-Touch Dynamic Network & Reconciliation (자동 네트워크 구성)

도전 과제: 사용자가 입력한 클러스터 이름을 기반으로 자동으로 외부 접근 URL을 생성하고, Kubernetes Ingress를 통해 라우팅을 구성해야 했습니다. 또한 리소스 생성 후 실제로 생성되었는지 검증하는 메커니즘이 필요했습니다.

해결 방법:

  • Zero-Touch Dynamic Network:
  • User Input (“my-app”) → Sanitizer (정규식: [^a-z0-9-]-) → Ingress Rule 생성 (my-app.fast-cloud.kro.kr)
  • 클러스터 이름을 소문자로 변환하고 특수문자를 하이픈으로 치환하는 정규식 패턴 적용
  • Dynamic L7 Routing: Ingress 자동 생성 및 Nginx Controller Reload
  • Reconciliation Loop:
  • verifyResourceExists() 메서드에서 500ms 대기 후 리소스 검증
  • Deployment, Service, Ingress 각각 개별 확인
  • 검증 실패 시 재시도 또는 롤백 로직
  • Dual-State Sync:
  • DB 메타데이터와 Kubernetes 실제 상태를 지속적으로 동기화
  • 주기적으로 Kubernetes API를 조회하여 상태 불일치 시 업데이트

성과:

  • 사용자 입력만으로 자동 네트워크 구성 및 외부 접근 URL 할당
  • 리소스 생성 검증 성공률 100% 달성
  • Operator Pattern과 유사한 자동화된 상태 관리 메커니즘 구현

6.3.3 Concurrency & OpenStack Session Synchronization (동시성 및 세션 동기화)

image

도전 과제: Spring Boot의 Singleton Bean 관리 방식과 OpenStack4j SDK의 ThreadLocal 기반 Stateful 설계 간의 충돌로 인해 동시 다발적인 요청 처리 시 Race Condition이 발생했습니다. WAS의 NIO Thread Model에서 Context Switching 시 인증 상태가 꼬이면서 토큰 만료, 세션 꼬임, 인증 실패 등 심각한 동시성 문제가 발생했습니다.

근본 원인 분석:

  • Spring Boot는 기본적으로 Singleton Bean을 사용하여 객체를 관리
  • OpenStack4j SDK는 인증 토큰과 세션 정보를 ThreadLocal에 저장하는 Stateful한 설계
  • WAS의 NIO Thread Model에서 여러 요청이 동시에 처리될 때, Singleton Bean으로 주입된 OSClientV3가 여러 스레드에서 공유됨
  • Context Switching 시 ThreadLocal의 인증 상태가 꼬이면서 Race Condition 발생

해결 방법:

  • Stateless Architecture 구현:
  • 각 요청마다 새로운 OSClientV3 클라이언트 생성
  • createOpenStackClient() 메서드를 통해 매 요청 시마다 독립적인 인증 수행
  • 인증 상태(State)를 오직 해당 요청 스레드 내부에만 가두어, 별도의 Synchronization(동기화) 비용 없이 동시성 문제 해결
  • 세션 꼬임 없는 완전한 Stateless Architecture 구현

성과:

  • 동시 다발적인 프로비저닝 요청에도 Race Condition이 발생하지 않는 Thread-Safe한 제어 플레인 구축
  • 동기화 오버헤드 없이 높은 동시성 달성
  • 멀티스레드 환경에서 안정적인 동작 보장 (동시 10개 VM 생성 시 성공률 100%)

기술적 의의: 단순 에러 수정이 아닌, WAS의 NIO Thread Model과 Context Switching 원인을 심층 분석하여 근본 원인을 해결했습니다. 이는 단순히 문제를 우회하는 것이 아니라, 시스템 아키텍처 레벨에서의 설계 개선을 통해 해결한 사례로, 엔터프라이즈급 시스템 설계 역량을 보여줍니다.


7. 프로토타입 실행 결과

7.1 주요 장면 스냅샷

  1. VM 생성 화면: 사양 선택 및 생성 버튼 클릭 화면.

    image
  • 템플릿 선택 UI (CPU, RAM, 디스크 사양 표시)
  • VM 이름 입력 필드
  • 생성 버튼 클릭 시 즉시 응답 (HTTP 202)
  • 생성 진행 상태 표시
  1. 컨테이너 배포 화면: 컨테이너 배포 요청 화면.

    image
    • 클러스터 이름 입력
    • Docker 이미지 주소 입력
    • 외부/내부 포트 설정
    • 배포 버튼 클릭
  2. 컨테이너 목록 화면: 배포된 모든 컨테이너 조회 화면.

    image
  • 컨테이너별 상태 및 요약 정보
  • 전체/실행 중 컨테이너 수 통계
  • 각 컨테이너의 접속 URL 및 상태
  1. 버킷 생성 화면: 오브젝트 스토리지 버킷 생성 화면.

    image
  • 버킷 이름 입력 및 중복 확인
  • 생성 버튼 클릭
  1. 버킷 현황 화면: 버킷 내 파일 목록 조회 화면.

    image
    • 버킷 이름 및 파일 목록
    • 파일별 이름, 크기, 수정일시 표시
    • 파일 업로드/다운로드 기능

7.2 주요 장면 선정 이유 및 프로토타입 범위 설정 배경

본 프로젝트는 총 2학기(1년)에 걸쳐 진행되는 장기 프로젝트로, 현재 단계는 핵심 엔진의 동작 검증을 목표로 하는 1학기 프로토타입입니다. 따라서 전체 로드맵 중 '이종 인프라(OpenStack, Kubernetes)의 통합 및 리소스 프로비저닝 성공 여부'를 최우선 검증 범위로 설정하였으며, 이에 따라 다음과 같은 기준으로 주요 장면을 선정하였습니다.

7.2.1. 프로토타입 범위 설정 배경 (1학기 목표)

  • 핵심 기능 우선 검증 (MVP): 2학기에 예정된 과금, 상세 모니터링, 오토스케일링 등의 고도화 기능 구현에 앞서, 1학기에는 클라우드의 가장 기본인 Compute(VM), Application(Container), Storage(Bucket)의 생성과 삭제(CRUD)가 실제 인프라단에서 정상적으로 작동하는지 검증하는 것에 집중하였습니다.
  • 복잡성 추상화 검증: 사용자가 복잡한 OpenStack/Kubernetes 명령어를 몰라도 웹 UI를 통해 리소스를 요청하면, 백엔드에서 이를 해석하여 인프라를 제어하는 '요청-처리 파이프라인'의 완성을 목표로 하였습니다.

7.2.2. 주요 장면 선정 이유

위 범위 설정에 따라, 사용자가 시스템에 진입하여 핵심 리소스를 실제로 생성하고 확인하는 End-to-End 워크플로우를 중심으로 장면을 선정하였습니다.

① VM 생성 화면 (IaaS 핵심 검증)

  • 선정 이유: IaaS의 가장 본질적인 기능인 '가상 서버 할당' 프로세스를 보여줍니다. 복잡한 네트워크/보안 그룹 설정을 템플릿으로 추상화하여, 사용자가 최소한의 입력(사양, 이름)만으로 VM을 생성할 수 있음을 시연합니다. 또한, 생성 요청 시 즉각적인 피드백(HTTP 202)을 통해 비동기 처리 구조가 정상 동작함을 검증합니다.

② 컨테이너 배포 화면 (CaaS 추상화 검증)

  • 선정 이유: Kubernetes의 높은 진입 장벽(YAML 작성, 서비스 노출 등)을 시스템이 얼마나 효과적으로 제거했는지 보여주는 핵심 장면입니다. 사용자가 Docker 이미지 주소와 포트만 입력하면, 시스템이 내부적으로 Deployment와 Service를 생성하여 즉시 배포 가능한 환경을 제공한다는 'Fast Cloud'의 지향점을 명확히 보여줍니다.

③ 컨테이너 목록 화면 (리소스 가시성 검증)

  • 선정 이유: 배포된 컨테이너의 생존 여부(Health Check)와 실제 접속 가능한 URL을 사용자에게 제공하는 화면입니다. 이는 백엔드 스케줄러가 Kubernetes 클러스터의 상태를 주기적으로 동기화하여, 사용자에게 정확한 현황을 시각화해 줄 수 있음을 증명합니다.

④ 버킷 생성 화면 (스토리지 프로비저닝 검증)

  • 선정 이유: Compute 리소스 외에 데이터를 영구 저장할 수 있는 Object Storage의 생성 기능을 보여줍니다. 이는 OpenStack Swift API와의 연동이 성공적으로 구축되었음을 의미하며, 사용자별로 격리된 저장 공간을 할당하는 로직을 검증합니다.

⑤ 버킷 현황 화면 (데이터 조작 검증)

  • 선정 이유: 단순히 버킷을 만드는 것을 넘어, 실제 파일 업로드 및 다운로드가 가능한지 보여줍니다. 이는 스토리지 서비스의 실질적인 사용성을 검증하는 장면으로, 1학기 프로토타입이 단순한 껍데기(Mock-up)가 아니라 실제 데이터를 처리하는 동작 가능한 시스템임을 증명합니다.

8. 팀원 및 역할 분담

  • 조동현(mr8356): CaaS 서비스 개발, 백엔드 서버 배포 관리.
  • 김재원(wodnjsv): IaaS 서비스 개발, 비기능 요구사항 테스팅 및 품질 관리.
  • 임지성(jstar000): 사용자 UI 개발(React), UI/UX 디자인 설계.
  • 전승민(miining): 오브젝트 스토리지(Swift) 연동 개발, 테스팅 관리.

9. 맺음말

9.1 프로젝트를 통해 배운 점

  • 조동현: CaaS 모듈을 전담하며 Hostname 기반 라우팅과 RBAC 설계 등 인프라 레벨의 기술적 난제들을 해결했습니다. 단순히 쿠버네티스를 사용하는 것을 넘어, K8s API를 직접 제어하는 엔진을 개발하며 클러스터 내부 동작 원리를 심도 있게 이해할 수 있었습니다. 사용자의 요청이 실제 인프라 프로비저닝으로 이어지는 과정을 보며 엔지니어로서 큰 희열을 느꼈습니다. 무엇보다 이론 공부를 넘어, 치열한 문제 해결 과정이 역량을 가장 빠르게 성장시킨다는 확신을 얻었습니다.
  • 김재원: 이번 프로젝트를 통해 OpenStack 기반의 IaaS 인프라를 직접 구축하며 가상화 환경의 핵심인 복잡한 네트워크 설정과 아키텍처를 깊이 있게 학습했습니다. 특히 백엔드 서버와 OpenStack API를 연동하는 과정에서, 사용자에게는 단순해 보이는 클라우드 서비스 이면에 수많은 컴포넌트들의 정교한 상호작용이 존재함을 깨달았습니다. 초기 설정과 구현 과정은 까다로웠지만, 직접 기술적 난관을 해결해 나가며 클라우드 시스템의 내부 동작 원리를 명확히 이해하고 엔지니어로서의 실무 역량을 한 단계 성장시키는 계기가 되었습니다.
  • 임지성: TanStack Query의 useSuspenseQuery와 invalidateQueries를 활용하여 서버 상태를 선언적으로 관리하는 구조를 설계했습니다. 선언적 패턴을 적용하여 컴포넌트가 '데이터를 어떻게 가져올지'가 아닌 '어떤 데이터가 필요한지'만 명시하도록 했고, 로딩/에러 처리 로직을 컴포넌트에서 분리하여 코드 가독성을 높이는 데 집중했습니다. 또한 클라이언트 측의 요청으로 리소스가 생성된 후 리소스 목록이 자동으로 갱신되도록 쿼리 키 기반 캐시 무효화 전략을 구현함으로써 클라이언트-서버 상태 동기화의 복잡성을 해결하는 방식을 적용했습니다. 이를 통해 UX와 DX 모두 고려하며 서버 상태 흐름을 명확하게 구조화할 수 있었고, 기능 확장 시에도 안정적으로 대응할 수 있는 기반을 마련했습니다.
  • 전승민: 평소 관심이 많았던 오브젝트 스토리지를 OpenStack 환경에서 직접 구축해보며 클라우드 스토리지 기술에 대해 깊이 있는 흥미를 느꼈습니다. 특히 Mini PC의 고정 IP 할당 및 Wi-Fi 네트워크 연결 과정에서 발생한 이슈들을 트러블슈팅하며 인프라 운영 실력을 한층 기를 수 있었습니다. 또한 이번 프로젝트를 통해 처음 백엔드 개발에 도전했는데, 인프라와 백엔드 로직이 유기적으로 연동되는 과정을 직접 구현하고 확인하면서 백엔드 엔지니어링의 매력과 즐거움을 새롭게 발견하는 뜻깊은 시간이었습니다.

9.2 향후 활용 방안

본 프로젝트 결과물은 학내 컴퓨터공학부의 운영체제, 네트워크 수업 등의 실습 환경으로 활용될 수 있습니다. 또한, 추가적인 기능 확장(모니터링 강화, 과금 시스템 연동 등)을 통해 실제 교내 해커톤을 지원하는 인프라 플랫폼으로 발전시킬 계획입니다.

Popular repositories Loading

  1. iaas-control-server iaas-control-server Public

    graduation project

    Java

  2. fast-cloud-client fast-cloud-client Public

    cloud-standard graduation project

    TypeScript 1

  3. caas-control-server caas-control-server Public

    graduation project

    Java

  4. auth-service-server auth-service-server Public

    graduation project

  5. .github .github Public

  6. fast-cloud-k8s fast-cloud-k8s Public

Repositories

Showing 6 of 6 repositories

People

This organization has no public members. You must be a member to see who’s a part of this organization.

Top languages

Loading…

Most used topics

Loading…