20 분 소요

마이크로서비스 아키텍처 (MSA) : 마이크로서비스를 접목한 아키텍처 구조

  • 클라우드 인프라와 접목해 아마존, 넷플릭스에 의해 구체화 → 비즈니스 성공 사례
  • 각 서비스는 개별 프로세스에서 실행되며, HTTP API를 통해 통신
  • 각 서비스는 비즈니스 기능 단위로 구성되고, 자동화된 배포 방식을 이용해 독립적으로 배포

마이크로서비스 아키텍처 (MSA)와 서비스 지향 아키텍처 (SOA)의 비교

  • SOA : 컴포넌트를 모아 비즈니스적으로 의미있고 완결적인 서비스 단위로 모듈화
    • SOAMSA의 공통점 : 비즈니스 서비스의 집합으로 시스템을 개발
    • SOAMSA의 차이점 : 이론적인 SOA와 달리, MSA는 클라우드 인프라와 접목해 구체화
  • MSA 내부 아키텍처 : API, 비즈니스 로직, 이벤트 발행, 데이터 처리의 구조화 등 MSA 내부 구조를 정의한 것
  • MSA 외부 아키텍처 : 인프라, 플랫폼, 어플리케이션 영역에 있는 구성 요소 및 그것들의 관계를 정의하는 것

리액티브 선언 (The Reactive Manifesto) : 어플리케이션이 요청에 즉각 응답하고 가동되길 기대

  • 응답성 (Responsive) : 사용자에게 신뢰성 있는 응답을 빠르고 적절히 제공하는 능력
  • 탄력성 (Resilient) : 장애가 발생하더라도 시스템 전체에 영향을 주지 않고 복구하는 능력
  • 유연성 (Elastic) : 사용량에 변화가 있더라도 그에 비례해 자원을 조절해 균일한 응답성을 제공하는 능력
  • 메시지 기반 (Message Driven) : 비동기 메시지로 위치 투명성, 느슨한 결합, 논블로킹 통신을 지향

→ 4가지 요건을 만족하는 시스템을, 급변하는 상황을 적응할 수 있는 리엑티브 시스템 (Reactive System)이라 정의

강결합에서 약결합의 아키텍처로의 변화

  • 소프트웨어 아키텍처 : 소프트웨어를 구성하는 요소와 그 구성 요소 간의 관계를 정의한 것
    • 아키텍처를 정의하는 과정 : 시스템 구축을 위한 여러 비기능 요건들을 만족하는 해결 방법을 찾는 과정
      • 비기능 요건 : 시스템 성능, 시스템 가용성, 보안, 유지보수성, 확장성 등
    • 마이크로서비스 아키텍처는 ‘클라우드’라는 가상화된 인프라를 활용한 것이므로, 이를 고려해 설계해야 함
  • 아키텍처 유연성 (Architecture Flexibility) : 시스템 자체가 변화 및 확장에 언제든지 대응할 수 있는 능력
    • 시스템을 구성하는 구성 요소 간의 관계들이 느슨하게 결합되어 언제든지 대체되거나 확장될 수 있음
    • 리액티브 시스템이 리액티브하기 위해서 반드시 갖춰야 할 특성 중 하나
    • 클라우드 인프라 자체가 유연성과 확장성을 갖추므로, 어플리케이션 아키텍처 또한 아키텍처 유연성이 필요
  • 과거 : 아키텍처 구성 요소들이 특정 벤더의 제품에 전적으로 의존
    • 유명한 제품군을 사용함으로 품질이 보장될 수 있음
    • 특정 기술에 락인 (lock-in)되어 시스템을 쉽게 변경하거나 확장하기 어려움
  • 현재 : 클라우드 환경 아래에서 사용하는 오픈 소스 기반 제품들이 충분한 기능, 품질, 호환성을 제공
    • 아키텍처 설계가 필요한 레이어에서 적절한 솔루션을 선택하고 이를 조합하는 개방적 방식으로 변화
    • 클라우드 기반 어플리케이션의 구축에 필요한 인프라 및 어플리케이션 영역에 다양한 제품들이 등장

MSA 패턴 : MSA의 문제 영역에 대해 여러 사람들에 의해 검증되어 정리된 스타일 혹은 패턴

  • 인프라 구성 요소 : 마이크로서비스를 지탱하는 인프라스트럭처를 구축하는 데에 필요한 구성 요소
  • 플랫폼 패턴 : 인프라 위에서 마이크로서비스의 운영과 관리를 지원하는 플랫폼 차원의 패턴
  • 어플리케이션 패턴 : 마이크로서비스 어플리케이션을 구성하는 데에 필요한 패턴

인프라 구성 요소를 서비스 유형별로 나누어 해당되는 제품 중 하나를 의사결정 → 클라우드 인프라

  • IaaS (Infrastructure as a Service) : 가상 머신, 스트리지, 네트워크 등 인프라 제공
    • 고객이 관리할 수 있는 범위가 가장 넓은 클라우드 컴퓨팅 서비스
    • AWS 등 퍼플릭 클라우드 공급 업체 (CSP)가 준비한 환경을 고객이 선택할 수 있음
    • 가상화된 물리적 자원을 UI 형태의 대시보드 혹은 API 형태로 제공
    • 고객은 서버와 스트리지에 접근할 수 있지만, 클라우드 내 가상 데이터 센터를 통해 리소스를 전달받는 형태
    • 개발자는 운영체제와 어플리케이션을 직접 관리해야 함 : 개발자와 인프라 관리자의 역할이 분담
    • 예시 : AWS EC2, AWS S3
  • CaaS (Container as a Service) : 업로드, 구성, 실행, 확장, 중지할 수 있는 컨테이너 제공
    • 가상 머신이 아닌 컨테이너를 기본 리소스로 활용해 어플리케이션을 개발, 실행, 관리
    • 컨테이너화된 어플리케이션을 빌드하고 배포하는 개발 환경은 퍼플릭 클라우드 공급 업체 (CSP)가 제공
    • 예시 : Kubernetes Service, AWS ECS
  • PaaS (Platform as a Service) : 어플리케이션에 미들웨어, 런타임까지 탑재한 플랫폼을 제공
    • 가상화된 클라우드 위에 원하는 서비스를 개발할 수 있도록 개발 환경을 미리 구축해 서비스 형태로 제공
    • 고객은 개발 환경을 고려할 필요 없이 어플리케이션 자체에 집중할 수 있음
    • 어플리케이션이 플랫폼에 종속되어 개발되므로, 다른 플랫폼으로의 이식이 어려울 수도 있음
    • 예시 : Lambda, AWS Elastic Beanstalk

시스템의 기반이 되는 인프라 레이어의 구축 → 베어메탈 장비 혹은 가상 인프라 환경을 통한 구축

  • 가상 인프라 환경 : 하이퍼바이저 (Hypervisor)의 사용 여부 및 게스트 OS 유무에 따라 나뉨
    • 가상 머신 (Virtual Machine) : 하이퍼바이저를 통해 하나의 시스템에서 여러 운영체제를 사용
      • 운영체제 패치 및 관련 라이브러리 설치로 인한 오버헤드가 지속적으로 발생
    • 컨테이너 (Container) : 컨테이너 엔진을 사용해 가상의 격리된 공간을 생성
      • 도커 (Docker) : 필요 라이브러리나 실행 파일을 여러 레이어 이미지로 제어
        • 이식성 : 도커만 실행할 수 있으면 호스트 커널에 상관없이 동일하게 사용
        • 신속성 : 크기가 작고 가벼워 빠른 배포가 가능 + 문제 발생 시 다시 가동하면 됨
        • 재사용성 : 동일한 환경을 재사용해 쉽게 설정 가능 → 서버 환경 구축이 쉬워짐
    • 컨테이너 오케스트레이션 (Container Orchestration) : 컨테이너 관리 기술
      • 컨테이너 배치 및 복제, 확장 및 축소, 장애 복구 컨테이너 간 통신, 로드밸런싱 등
      • 쿠버네티스 (Kubernetes) : Pod, Deployment, Replica Sets 정보 확인 가능
        • 각 컨테이너가 요구하는 자원을 쿠버네티스에 요청하면 노드에 맞춰 자동 배치
        • 컨테이너 이상을 점검해, 실패하면 컨테이너를 자동으로 교체하고 리스케줄링
        • 일정량의 CPU 및 메모리 사용량을 초과하면 자동으로 수평 확장

마이크로서비스의 운영과 관리를 지원할 클라우드 플랫폼 (미들웨어)의 구축 → 플랫폼 패턴

  • 데비옵스 (DevOps) : 개발과 운영이 분리되지 않은 개발 및 운영을 병행할 수 있는 조직 또는 문화
    • 소프트웨어를 빠르게 개발하게끔 지원하는 빌드, 테스트, 배포를 위한 자동화 환경
    • 지속적 제공 (CI) : 빌드된 소스 코드의 실행 파일을 실행 환경에 반영하기 전에 진행
    • 지속적 배포 (CD) : 저장소에 빌드한 소스 코드의 실행 파일을 실행 환경까지 자동으로 배포

자동 빌드 및 배포 절차

  1. 매일 자신이 작성한 소스 코드와 이를 테스트할 테스트 코드를 형상관리 시스템에 보낸다. (Push)
  2. 매일 빌드 도구에서 형상관리 서버의 코드를 가져와 (Pull) 통합하고, 자동으로 빌드하고 테스트를 수행한다.
  3. 테스트 수행 결과를 리포트에 기록하고, 빌드된 소스 코드를 스테이징 환경에 자동으로 배포한다.
  4. 테스터가 스테이징 환경에서 테스트를 수행할 때 혹은 리포트 결과에 문제가 있으면, 소스 코드를 수정한다.
  • 빌드·배포 파이프라인의 설계 : 빌드·배포 과정동안 수행해야 할 업무 (task)를 정의한 것
    • 리포지토리에서 소스 코드를 가져와 빌드해 실행 파일을 만드는 작업
    • 이전 작업이 성공하면, 다음 작업이 자동으로 수행히게끔 위의 작업들을 관리하는 작업
    • 실행할 어플리케이션을 실행 환경에 배포하는 작업

    Infrastructure as Code를 통해 빌드·배포 파이프라인의 절차를 완벽하게 자동화할 수 있음

  • Infrastructure as Code : 인프라 구성을 프로그래밍하는 것처럼 처리해 많은 컨테이너 배포 처리를 함
    • 형상관리 리포지토리에서 소스 코드를 가져와 빌드하여 실행 파일을 만드는 작업
    • 실행 파일을 실행 환경에서 배포하는 작업
    • 작업들을 통제하고 연결해서 모든 작업이 성공하면 다음 작업이 자동으로 수행되도록 연계하는 작업

마이크로서비스가 실제 구동되는 환경에서 동작될 수 있도록 운영 관리 요소 → 운영 관리 패턴

  • 마이크로서비스의 발전 과정에서 아키텍처의 문제 영역들이 지속적으로 논의되었고, 이에 따른 해결책을 모색해옴
    • 1999년 XP 방법론, 2001년 에자일 선언을 통해 빠른 실패와 피드백을 기반하는 실용적인 실천법 적용
    • 2010년 넷플릭스가 AWS EC2로 인프라를 전환하면서 발생한 문제점들을 해결하기 위한 넷플릭스 OSS 개발
      • 여러 마이크로서비스 간의 라우팅을 위한 줄 (Zuul)
      • 적절한 부하 분산을 하는 로드밸런싱을 위한 리본 (Ribbon)
      • 모니터링을 위한 히스트릭스 (Hystrix)
      • 서비스 등록을 위한 유레카 (Eureka)
    • 2013년 마이크로서비스를 쉽게 개발할 수 있는 스프링부트 (SpringBoot) 프레임워크가 발표
    • 2013년 도커, 2014년 쿠버네티스와 같은 컨테이너 기반 기술들이 개발

    → 클라우드 환경, 넷플릭스 OSS, 프레임워크, 컨테이너 기반 기술이 아울러져 마이크로서비스 생태계를 계속 발전시킴

  • 스프링 클라우드 : 스프링부트 + 넷플릭스 OSS
    • 스프링 진영에서 기존 스프링 프레임워크에 넷플릭스 OSS들이 더 잘 돌아갈 수 있도록 통합하여 발표

마이크로서비스 서비스와 스프링 클라우드 서비스의 연계 흐름

  1. 스프링 클라우드를 포함한 모든 마이크로서비스가 인프라에 종속되지 않도록 DB, 파일 등에 저장된 설정 정보를 형상관리 시스템에 연계된 Config 서비스에서 가져와 주입한 후 클라우드 인프라의 개별 인스턴스로 로딩
  2. 로딩과 동시에 ‘서비스 레지스트리’에 자신의 서비스 이름과 클라우드 인프라로부터 할당받은 물리 주소를 매핑
  3. 클라이언트가 ‘API 게이트웨이’를 통해 마이크로서비스에 접근하면, …
    • API 게이트웨이는 적절한 라우팅 및 부하 관리를 위한 로드밸런싱을 수행
    • API 게이트웨이’는 클라이언트가 마이크로서비스에 접근하기 위한 주소를 알기 위해 ‘서비스 레지스트리’ 검색을 통해 서비스의 위치를 가져옴
    • API 게이트웨이’는 클라이언트가 각 서비스에 접근할 수 있는 권한이 있는지 확인하기 위해 ‘권한 서비스’와 연계해 인증 및 인가 처리를 수행
  4. 모든 마이크로서비스 간의 호출 흐름은 ‘모니터링 서비스’와 ‘추적 서비스’에 의해 모니터링되고 추적됨

다양한 서비스의 등록 및 탐색 → 서비스 레지스트리, 서비스 디스커버리 패턴

  • 예시 : 넷플릭스 OSS 유레카 (Eureka), 스프링 유레카 (Spring Eureka), 쿠버네티스 DNS 및 서비스
  • 프런트엔드 클라이언트가 여러 백엔드 마이크로서비스를 어떻게 호출해야 할까?
  • 스케일 아웃을 통해 인스턴스가 여러 개로 복제되었을 때 어떻게 부하를 적절히 분산할 수 있을까?

    → 최적 경로를 찾아주는 라우팅 기능과 적절한 부하 분산을 위한 로드밸런싱 기능이 제공되어야 한다!

  • 라우터가 최적 경로를 탐색하려면 서비스 이름에 해당하는 IP 주소를 알아야 함
    • 그런데 이러한 라우팅 정보를 클라이언트가 가진다면, 클라우드 환경에서 동적으로 변경되는 백엔드의 유동 IP 정보를 매번 전송받아 변경해야 함 → 제3의 공간이 필요

    → 마이크로서비스의 이름과 유동적인 백엔드의 유동 IP 정보를 보관할 저장소를 제공하자!

  • 각 서비스 인스턴스가 로딩될 때 자신의 서비스 이름과 할당된 IP 주소를 레지스트리에 등록
  • 클라이언트가 해당 서비스 이름을 호출할 때, 라우터가 레지스트리 서비스를 검색해 매핑된 IP 주소를 호출
  • 즉, 레지스트리는 모든 마이크로서비스의 인스턴스의 주소를 알고 있는 서비스 매핑 저장소
  • 모든 마이크로서비스가 처음 가동될 때 자신의 위치 정보를 레지스트리에 저장하고, 서비스가 종료될 때 삭제
  • 레지스트리는 Config, 모니터링, 추적과 같이 관리와 운영을 위한 기반 서비스의 주소 또한 함께 보관

서비스 단일 진입 → API 게이트웨이 패턴

  • 예시 : 스프링 클라우드 Spring API Gateway, 쿠버네티스 인그레스 리소스 (Ingress Resources) 및 서비스
  • 여러 클라이언트가 여러 개의 서버 서비스를 각각 호출하면 호출 관계가 매우 복잡해짐

    → 이러한 복잡한 호출 관계를 통제할 수 있는 방법이 필요하다!

  • API 게이트웨이 (Gateway) : 다양한 클라이언트가 다양한 서비스에 접근하기 위한 단일 진입점
    • L4 하드웨어 장비나 소프트웨어로 구현된 서비스 흐름 제어를 위한 서비스 라우팅 기능을 수행
      • 다른 유형의 클라이언트에게 서로 다른 API 조합을 제공할 수 있음
      • 각 서비스에 접근할 때 필요한 인증 및 인가 기능을 한번에 처리할 수 있음
      • 서비스에 문제가 발생해 요청에 대한 응답 지연이 발생할 때 다른 서비스로 요청 경로를 변경할 수 있음
    • 레지스트리 서비스와 연계하여 여러 인스턴스로 부하를 분산하는 동적 라우팅이나 로드밸런싱이 가능
    • 권한 서비스와 연계하여 인증 및 인가 처리를 수행할 수 있음
    • 로그 집계 서비스와 연계하여 요청 및 응답 데이터 등에 대한 로깅이 가능
    • 에러율, 평균/최고 지연 시간, 호출 빈도와 같이 시간에 따른 환경 변화를 추적할 수 있는 메트릭 (Metric) 데이터를 시계열 형태로 저장할 수 있음
    • 트래킹 ID 추적과 같이 트레이싱 서비스와 연계하여 서비스 추적이 가능
    • 모니터링 서비스와 연계해 장애 격리가 가능 → 서킷 브레이커 패턴

다양한 클라이언트에 대한 특화된 처리 → BFF 패턴

  • 진입점을 하나로 두는 API 게이트웨이와 달리, 프런트엔드의 유형에 따라 각각의 진입점을 둠
  • 프런트엔드를 위한 백엔드라는 의미에서 Backend for Frontend
  • 각 프런트엔드에 대한 처리만 수행하는 BFF 이후에 통합 API 게이트웨이를 두어, 공통 처리를 통제할 수도 있음

마이크로서비스 어플리케이션 구성 정보의 관리 → 외부 구성 저장소 패턴

  • 예시 : 스프링 클라우드 Spring Cloud Config, 쿠버네티스 컨피그맵 (ConfigMap)
  • 클라우드 인프라를 사용할 때, 어플리케이션이 마이크로서비스가 사용하는 자원의 설정 정보를 포함하면?
    • 자원의 설정 정보이 변경될 때 어플리케이션이 반드시 재배포해야 하므로 서비스가 중단되어야 함
    • 여러 마이크로서비스가 동일한 구성 정보를 사용한다면 이를 일일이 변경하기가 어려움
    • 여러 마이크로서비스를 변경하는 시점에 일부 마이크로서비스의 구성 정보가 불일치할 수도 있음

    → 마이크로서비스가 사용하는 자원의 설정 정보를 쉽고 일관되게 변경 가능하도록 관리해야 한다!

  • 외부 저장소 : DB 연결 정보, 파일 스토리지 정보와 같이 각 마이크로서비스의 외부 환경 설정 정보를 공동으로 관리
  • Config 원칙 : Staging, Dev, Test처럼 어플리케이션 배포 환경은 매번 달라지니, 코드와 설정 정보는 분리돼야
    • 클라우드에서 운영되는 어플리케이션은 특정한 배포 환경에 종속된 정보를 코드에 두면 안됨
      • 배포 시 변경될 호스트명, 백엔드 서비스의 연결을 위한 리소스 정보, 서버의 IP 주소나 포트 정보 등

사용자의 신원을 증명하고, 접근 권한을 부여 → 인증 및 인가 패턴

  • 여러 마이크로서비스에 대한 인증 및 인가에 대한 접근 제어는 어떻게 구현해야 할까?

    → 각 서비스가 모두 인증 및 인가를 중복으로 구현하는 것은… 당연히 비효율적!

  • 중앙 집중식 세션 관리
    • 마이크로서비스는 사용량에 따라 수시로 수평 확장되어 로드밸런싱이 되기 때문에 세션 데이터가 손상될 수 있음
    • 각자의 서비스에 세션을 저장하지 않고 공유 저장소에 세션을 저장해 모든 서비스가 같은 사용자 데이터를 얻게 함
    • 레디스 (Redis) 등을 세션 저장소로 사용
  • 클라이언트 토큰
    • 중앙 서버에 저장되는 세션과 달리, 토큰은 사용자의 브라우저에 저장
    • 사용자의 신원 정보를 가진 토큰은 서버로 요청을 보낼 때 전송되기 때문에 서버에서 인가 처리를 할 수 있음
    • JWT : 토큰 형식을 정의하고 암호화되는 공개 표준 (RFC 7519)

클라이언트 토큰을 통한 사용자 인증의 흐름

  1. 브라우저가 사버에 사용자 이름과 패스워드로 인증을 요청한다.
  2. 서버는 인증 후에 사용자 정보의 인증 및 인가 정보를 포함하여 토큰을 생성하고 브라우저에 전송한다.
  3. 브라우저는 서버 자원을 요청할 때 토큰을 함께 보내고, 서버는 토큰 정보를 확인한 후 자원 접근을 허가한다.
  • API 게이트웨이를 사용한 클라이언트 토큰
    • API 게이트웨이가 외부 요청의 입구로 추가되어 인증 프로세스를 진행
    • 인증 및 인가를 위한 별도의 전담 서비스가 API 게이트웨이와 연동하여 다른 서비스의 인증 및 인가 처리를 위임 → 인증 서비스 (Auth Service)

API 게이트웨이와 인증 서비스를 활용한 클라이언트 토큰을 통한 사용자 인증의 흐름

  1. 클라이언트가 리소스 서비스에 접근을 요청하면, API 게이트웨이는 인증 서비스에 전달한다.
  2. 인증 서비스는 해당 요청이 인증된 사용자가 보낸 것인지 확인하고 (→ 인증),
  3. 인증 서비스는 해당 요청이 해당 리소스에 대한 접근 권한이 있는지 확인하고 (→ 인가),
  4. 모두 확인하고 나면 리소스에 접근 가능한 증명서인 액세스 토큰 (Access Token)을 발급한다.
  5. 클라이언트는 다시 액세스 토큰을 활용해 접근을 요청한다.
  6. 각 리소스 서비스는 이러한 요청이 액세스 토큰을 포함하고 있는지 판단하여 리소스에 대한 접근을 허용한다.

장애 및 실패 처리 → 서킷 브레이커 패턴

  • 여러 서비스로 구성된 시스템에서 한 서비스에 장애가 생기면, 다른 서비스가 영향을 받을텐데…?
    • 전체 시스템은 정상적인데, 특정 기능을 누르면 한참 대기하는 경우 → 장애가 다른 서비스로 전이되었다!

    → 장애가 발생한 서비스를 격리해서 유연하게 처리하자!

  • 서킷 브레이켜 (Circuit Breaker) : 시스템 과부하나 특정 서비스에 문제가 생겼을 때, 자연스럽게 다른 정상적인 서비스로 요청 흐름을 변경하는 것 → 장애가 다른 서비스로 전이되지 않게 하자!
    • A → B → A의 서비스 흐름에서 B 서비스가 장애가 생겼을 때, A가 동기 요청을 보내면? : 계속 기다린다…
      • B 서비스 호출에 대한 연속 실패 횟수가 임계값을 초과하면, 이후 서비스 호출 시도를 모두 실패로 처리
      • 풀백 (Fallback) 메소드를 지정해 정상 응답을 대신할 대체 응답을 사용자에게 제공

마이크로서비스의 장애 감지 → 모니터링 및 추적 패턴

  • 예시 : 스프링 클라우드 히스트릭스 (Hystrix) + 집킨 (Zipkin)
  • 그래서 서킷 브레이커는 언제, 어떻게 작동하는데…?

    → 각 마이크로서비스의 장애를 실시간으로 감지할 수 있고, 서비스 간의 호출을 파악하자!

  • 히스트릭스 대시보드 : 각 요청의 트래픽이 원형으로 표현 → 서비스 성능에 문제가 생기면, 서킷 브레이커 발동 ()
  • 집킨 대시보드 : 각 서비스 트랜잭션의 호출을 추적하거나, 지연 구간별 장애 포인트를 확인할 수 있음
    • 서비스 API를 선택하면, 각 API가 다른 API를 어떻게 호출하는지 확인 가능
      • 전체적인 API 간의 호출 빈도를 확인할 수 있는 정적 다이어그램 또한 제공

마이크로서비스의 로그 관리 → 중앙화된 로그 집계 패턴

  • 마이크로서비스가 사용량에 따라 탄력적으로 변화하면, 그 인스턴스가 삭제되면 로컬 로그는 초기화..?
    • 로그 (Logs) 원칙 : 로그는 이벤트 스트림 (Event Streams)로 처리되어야 함
      • 로그는 시작과 끝이 고정된 것이 아니라, 서비스가 실행되는 동안 계속 흐르는 흐름
      • 서비스는 스트림의 전달 및 저장에 절대 관여하면 안됨!
        • 로그를 전달하거나 저장하는 매커니즘 자체가 특정한 기술 및 인프라에 의존하게 됨
        • 마이크로서비스가 로그 관련 매커니즘을 직접 구현하면 유연성이 떨어짐

    → 서비스에서 발생한 이벤트 스트림 형태의 로그를 수집하고 분석할 방법을 찾자!

  • ELK 스택 : 엘라스틱서치 & 로그스태시 & 키바나를 기반으로 하는 데이터 분석 환경을 구성
    • 엘라스틱서치 (ElasticSearch) : 분산형 검색 및 분석 엔진
      • 정형, 비정형, 위치 정보, 메트릭 등 원하는 방법으로 검색을 수행 및 결합 가능
    • 로그스태시 (Logstash) : 서버 측의 로그 집합기
      • 여러 소스에서 동시에 데이터를 수집 및 변한하여 특정 보관소로 데이터를 전송하는 데이터 처리 파이프라인
    • 키바나 (Kibana) : 시각적으로 로그 내역을 보여주는 대시보드
      • 위치 데이터, 시계열 분석, 그래프 관계 등을 히스토그램, 막대 그래프, 파이차트 등의 형태로 표현

    → 각 서비스의 인스턴스 로그를 집계해서 중앙에서 집중 관리할 수 있고, 특정 로그를 검색 및 분석할 수 있음

  1. 마이크로서비스 내의 로그를 중앙 서버의 레디스로 전송한다.
  2. 중앙 서버의 레디스에서 중앙 로그 저장소에 해당 로그들을 전송한다.
  3. 중앙 로그 저장소에 엘라스틱서치 엔진이 로그를 인덱싱한다.
  4. 해당 로그 정보를 키바나 대시보드를 통해 표현한다.
  • 중간 지점에 레디스가 존재하는 이유? : 마이크로서비스의 로그스태시에서 보낸 로그 스트림이 중앙 로그 저장소에 몰리면 성능상의 문제가 생길 수 있으므로 임시 저장소 역할의 레디스를 추가

MSA 운영 관리 패턴의 변화 1 : 쿠버네티스 (Kubernetes)

  • 기존 : 문제마다 상이한 기술들로 접근하여 해결
    • 넷플릭스 OSS나 스프링 클라우드를 이용해 각각의 서비스를 별도로 둠
    • 유연성처럼 수평 확장이 필요한 요소들은 AWS IaaS 서비스를 통해 해결
  • 현재 : 쿠버네티스 하나로 모든 문제들을 해결
    • 인프라 차원의 AWS IaaS → 컨테이너 레플리카 기술로 탐색 및 호출을 통합한 소프트웨어 차원의 쿠버네티스

MSA 운영 관리 패턴의 변화 2 : 서비스 메시 (Service Mash)

  • 기존 : API 게이트웨이, 서비스 레지스트리, Config 서비스와 같이 운영 관리를 위한 여러 기반 서비스를 별도로 둠
    • 업무 처리 마이크로서비스에 스프링 클라우드 서비스를 사용하기 위한 라이브러리를 비즈니스 로직과 함께 탑재
    • 스프링 클라우드는 Java 기반이므로, 다른 언어로 폴리글랏하게 구현할 수 없음
  • 현재 : 이스티오과 같은 서비스 메시 패턴을 적용
    • MSA 문제 영역 해걸을 위한 기능을 비즈니스 로직과 분리해 네트워크 인프라 계층에서 수행
    • 인프라 레이어로서 서비스 간의 통신을 처리하여 문제 해결 패턴을 포괄해 처리
  • 이스티오 (Istio) : 서비스 메시 패턴의 대표적인 구현체
    • 사이드카 (Sidecar) 패턴 : 어플리케이션이 배포되는 컨테이너에 완전히 격리되어 별도의 컨테이너로 배포
      • 마이크로서비스마다 함께 배포되는 사이드카 프락시에 운영 관리를 위한 기능이 별도로 존재
      • 운영 관리 기능과 별개로 존재하는 마이크로서비스는 순수한 비즈니스 로직으로 제공될 수 있음
      • 컨트롤 플레인 (Control Plain)으로 중앙에서 통제되며, 사이드카끼리 통신하여 관련 운영 관리 기능 제공

      → 쿠버네티스의 파드 (Pod)에 서비스 컨테이너와 사이드카 구현체인 엔보이 (Envoy) 컨테이너가 함께 배포

넷플릭스 OSS & 스프링 클라우드와 이스티오의 차이점

  • 이스티오는 사이드카로 격리되어 YAML과 같은 설정 파일르 정의되어 어플리케이션 코드 변경이 거의 없음
  • Java만이 아닌, 폴리글랏 어플리케이션도 지원 가능
  • 쿠버네티스와 완벽하게 통합된 환경을 지원

마이크로서비스 어플리케이션 구현을 위한 마이크로서비스 구성 및 설계 → 어플리케이션 패턴

  • 어플리케이션 영역에서도 유연성, 확장성, 독립성을 염두에 두어 설계되어야 함 → 백엔드, 프론트엔드 영역 모두!

마이크로 프론트엔드 → UI 컴포넌트 패턴

  • 마이크로서비스의 장점인 서비스의 독립적인 변경 및 배포를, 기존 모노리스 프론트엔드로는 힘들텐데…?
    • 모노리스 프론트엔드 : 백엔드의 여러 API를 호출하고 조합하여 화면을 구성해 표현

→ 프론트엔드도 마이크로서비스처럼 기능별로 분리하고 각 프런트엔드를 조합하여 동작하게 하자!

  • 프레임 (Frame) 형태의 부모 창에 각 마이크로 프론트엔드를 조합해 동작
    • 실제 각 기능의 표현은 마이크로 프런트엔드 조각이 구현 → 여러 백엔드 마이크로서비스 API 호출

프론트엔드 ↔ 백엔드, 백엔드 ↔ 백엔드 간의 통신 → 마이크로서비스 통신 패턴

  • 동기 호출 방식 : 클라이언트에서 서버 측에 존재하는 마이크로서비스 REST API를 호출할 때 사용
    • 사용자가 A 서비스에 B 서비스가 필요한 HTTP GET 방식의 요청을 보내면,
      1. A 서비스가 B 서비스에 HTTP GET 방식의 동기 호출을 수행하고,
      2. B 서비스가 응답을 발생한다. (성공 시 200 OK)
    • 다양한 클라이언트 채널 연계나 라우팅, 로드밸런싱을 원활하기 위해 API 게이트웨이를 둘 수 있음
    • 요청을 보내면 바로 응답이 오는 직관적인 방식이므로, 가장 많이 쓰이고 구현하기 쉬움
    • 호출을 받은 마이크로서비스에 장애가 생긴다면, 요청을 보낸 서비스는 응답할 때까지 대기하면서 재호출
      • 여러 서비스 간의 연계를 통해 업무를 처리하는 마이크로서비스에서는 장애가 연쇄적으로 발생할 수 있음
      • 서비스가 다른 서비스를 호출해서 얻은 정보로 기능을 제공한다는 것은, 서비스 간의 의존도가 높다는 의미
  • 메시지 기반 비동기 (asynchronous) 통신 방식 : 메시지를 보내면, 응답을 기다리지 않고 다음 업무 처리
    • 메시지 브로커 (Message Broker) : 동기식처럼 완결성을 보장할 수 없으니, 이를 보장하는 메커니즘을 활용
      • 아파치 카프카 (Apache Kafka), 레빗엠큐 (RabbitMQ) 등
      • 메시지를 보내는 생산자 (Producer)와 메시지를 받아 처리하는 소비자 (Consumer)
        • 생산자와 소비자가 서로 직접 접속하지 않고 메시지 브로커를 통해 연결
        • 메시지 브로커에 메시지를 전달하고 자신의 일을 처리하면, 메시지 브로커가 전송을 보장
      • 메시지 브로커으로 중계되므로, 서로 통신하는 서비스들이 물리적으로 동일한 시스템에 위치하지 않아도 됨
      • 서로 프로세스를 공유할 필요도 없으며, 동일한 시간대에 동시에 동작하지 않아도 됨
    • 비동기 방식의 이벤트 기반 아키텍처 (Event-driven Architecture) : 비동기 통신 방식으로 느슨한 연계 지향
      1. 분산 시스템 간에 발신자가 이벤트를 생성 및 발행 (Publish)하여,
      2. 해당 이벤트를 필요로 하는 수신자에게 전송하면,
      3. 이벤트를 구독 (Subscribe)하고 있던 수신자가 이벤트를 받아 처리한다.

      → 순서에 따라 특정 행동이 발생하는 기존의 방식이 아닌, ‘상태의 변화’를 의미하는 이벤트에 대한 반응으로 동작

마이크로서비스를 독립적으로 수정 및 배포 → 저장소 분리 패턴

  • 기존 모노리스 시스템의 저장소인 통합 저장소의 문제점?
    • 어플리케이션 모듈은 분리하되 저장 처리는 모듈별로 격리하지 않고 다른 모듈에서의 호출을 허용
    • 모든 비즈니스 로직이 데이터베이스의 SQL 처리에 몰려있는 경우가 대부분
    • 데이터 중심 어플리케이션은 특정 관계형 데이터베이스 벤더에 구속되고 복잡해져 유지보수가 어려움
    • 성능 문제가 발생하였을 때, SQL 구문 튜닝이나 Scale-up에 의존할 수 밖에 없음
    • 여러 마이크로서비스로 분리해도 요쳥이 증가하면 데이터베이스만 바쁜 상태가 됨
      • 자동으로 확장되는 마이크로서비스의 Scale-out이 무의미함

    → 여러 개의 마이크로서비스에 적합하게 데이터베이스 벤더를 운영하는 방법을 찾자!

  • 각 마이크로서비스는 각자의 비즈니스를 처리하기 위한 데이터를 직접 소유해야 한다!
  • 자신이 가진 데이터는 다른 서비스에 직접 노출하지 않고 각자 공개한 API를 통해서 접근 가능 → 정보 은닉
  • 저장소가 격리되어 있기 때문에 각 저장소를 자율적으로 선택할 수 있음 → 폴리글랏 저장소
  • 해당 제약을 통해 데이터를 통한 변경의 파급 효과를 줄여 서비스를 독립적으로 만듬

여러 분산된 서비스를 하나의 일관된 트랜잭션으로 → 분산 트랜잭션 처리 패턴

  • 저장소 분리 패턴의 문제점?
    • 여러 분산된 서비스에 걸친 비즈니스 처리를 하면, 비즈니스 정합성과 데이터 일관성은 어떻게 보장하지?
  • 2단계 커밋 : 분산 데이터베이스 환경에서 원자성 (atomicity)을 보장하기 위해,
    • 분산 트랜잭션에 포함된 모든 노드가 커밋 (commit) 혹은 롤백 (rollback)되는 메커니즘
    • 각 서비스에 lock-in이 걸려 발생하는 성능 문제로 비효율적인 방법
    • 각 서비스가 다른 인스턴스로 로딩되므로 이를 통제하기 어려움
    • 서비스의 저장소가 각각 다를 경우에 문제가 발생 (NoSQL 저장소 : 2단계 커밋 미지원)
    • 네트워크 장애 등으로 특정 서비스의 트랜잭션을 처리하지 않는 경우 트랜잭션이 묶인 서비스에 즉시 영향
  • 사가 (Saga) 패턴 : 각 서비스의 로컬 트랜잭션을 순차적으로 처리하는 패턴
    • 여러 분산된 서비스 내에서 각각의 로컬 트랜잭션과 보상 트랜잭션을 통해 비즈니스 및 데이터의 정합성을 맞춤
      1. 각 로컬 트랜잭션을 통해 자신의 데이터베이스를 갱신한다.
      2. 사가 내에 있는 다음 로컬 트랜잭션을 트리거하는 이벤트를 발행하여 데이터의 일관성을 맞춘다.
      3. 서비스에서 트랜잭션 처리에 실패하면, 앞선 다른 서비스에 처리된 트랜잭션을 롤백할 보상 트랜잭션을 준다.
  • 데이터 일관성에 대한 생각의 전환 → 결과적 일관성
    • 이전엔 비즈니스 처리를 위한 규칙을 만족시킬, 데이터 일관성을 반드시 실시간으로 맞춰야 한다고 생각했다.
    • 그러나 모든 비즈니스 처리가 반드시 실시간성을 요구하는 것이 아니다. 어떤 비즈니스들은 어느 일정 시점이 되었을 때 일관성을 만족해도 된다. → 결과적 일관성 (Eventual Consistency)
    • 이벤트 기반 비동기 통신 및 사가 패턴을 적용한 아키텍처, 메시지 브로커을 활용하여, 결과적 일관성을 반영해 비즈니스 및 시스템의 가용성을 극대화할 수 있다.

읽기와 쓰기의 분리 → CQRS 패턴

  • 마이크로서비스의 서비스별 데이터 저장소에서 전통적인 DB 트랜잭션을 사용하면?
    • 데이터 읽기 및 수정 작업으로 인한 리소스 교착 상태가 발생할 수 있다.

    → 동일한 저장소에 데이터를 넣은 뒤에 CRUD를 처리하는, 기존의 패러다임을 전환하는 것은 어떨까!

  • 명령 조회 책임 분리 (Command Query Responsibility Segregation)
    • 사용자의 비즈니스 요청은 시스템의 상태를 변경하는 연산과 시스템의 상태를 조회하는 연산으로 나눠짐
    • 일반적인 비즈니스 모델에서는 입력, 수정, 삭제보다 조회가 더 많이 사용됨
      • 서비스 내에 모든 기능을 넣으면 조회 요청 빈도가 증가함에 따라 다른 명령 기능 또한 확장되니 비효율적
    • 하나의 저장소에 쓰기 모델과 읽기 모델을 분리하거나, 쓰기 저장소와 조회 저장소를 따로 두자!
      • 쓰기 시스템의 부하를 줄이면서, 조회 대기 시간을 줄일 수 있음

CQRS 패턴을 적용한 예시

  1. 명령 측면의 마이크로서비스
    • 입력, 수정, 삭제 처리를 수행
    • 쓰기에 최적화된 관계형 데이터베이스를 저장소로 활용
    • 업무 규칙을 표한하기 좋은 언어 (Java)를 프로그래밍 언어로 사용
  2. 조회 측면의 마이크로서비스
    • 조회 서비스는 사용량이 많으므로, 스케일 아웃을 통해 인스턴스를 증가시킴
    • 조회 성능이 높은 NoSQL 데이터베이스를 저장소로 활용
    • 조회를 간단하게 구현할 수 있는 언어 (Node.js)를 프로그래밍 언어로 사용
  3. 이벤트 주도 아키텍처
    • 명령 서비스를 사용함에 따라 조회 서비스와의 데이터 일관성이 깨지므로 이를 유지하는 목적
    1. 명령 서비스가 저장소에 데이터를 쓸 때, 저장 내역이 담긴 이벤트를 발생시켜 메시지 브로커에 전달
    2. 메시지 브로커의 이벤트를 구독하는 조회 서비스는 이벤트 데이터를 가져와 데이터를 최신으로 동기화
      • 시간 간격이 있으나 어느 시점에 일치하는 결과적 일관성이 이루어짐

저장소가 격리된 여러 마이크로서비스의 기능들을 연계한 서비스 제공 → API 조합과 CORS

  • API 조합 (API Composition) : 기능을 제공하는 마이크로서비스를 조합할 상위 마이크로서비스로 조합된 기능 제공
    • 하위 서비스는 각자 독립적인 API를 제공하면서 연계 API를 위해 상위 서비스에 정보 제공

    → 상위 서비스가 하위 서비스에 의존되는 결과를 가져옴 : 하위 서비스의 실패가 상위 서비스에 영향을 준다.

  • CQRS 패턴을 활용한 기능 연계 : 상위 마이크로서비스에 통합 이벤트 핸들러와 통합 저장소를 두자
    1. 독자 저장소를 갖는 하위 서비스에서 자신의 서비스 내 정보가 변경될 때 변경 내역을 각 변경 이벤트로 발행
    2. 상위 서비스는 구독한 이벤트를 가져와서 자신의 서비스 저장소에 기록해 데이터 일관성을 맞춤

쓰기 연산의 최적화 → 이벤트 소싱 패턴

  • 사가 패턴과 CQRS 패턴에서 비즈니스 불일치를 피하려면,
    • 저장소에 저장하는 것과 메시지를 보내는 것은 언제나 완전하게 진행되어야 하지만,
      • 객체의 상태를 관계형 데이터베이스에 저장하기 위해 SQL 질의어로 변환하는 것은 번거로움
      • 메시지 발행과 저장 처리의 두 가지 기능을 수행하는 것 또한 빠르지 않음

    → 메시지 발행과 저장 처리를 언제나 완전하게 처리하면서 성능도 최적화시킬 방법을 찾자!

  • 비즈니스를 처리할 때 데이터의 처리는 항상 처리 상태의 결괏값을 계산하고 데이터의 최종 상태를 확정해 저장
    1. 일반적인 관계형 데이터베이스와 Java를 사용할 때,
    2. 품목에 대한 데이터 모델이 정의되어 있다면,
    3. 품목의 상태가 변경될 때 매번 트랜잭션 결과를 반영해서,
    4. 품목 데이터 모델의 결과를 계산해야 한다.

    → 객체의 상태 변경에 따라 데이터 모델로 처리하여 최종값을 반영하는 과정은 복잡하고 속도가 느림

  • 객체의 상태를 데이터 모델에 맞춰 계산하지 않고, 상태 트랜잭션 자체를 이벤트 저장소에 그대로 저장
    • 메시지 브로커와 데이터 저장소를 분리하지 않고 하나로 사용할 수 있음 → 쓰기 속도가 매우 빠름
    • 상태의 출발점부터 모든 기록된 상태 변경 트랜잭션을 순차적으로 계산
    • 매일 자정 상태를 계산하고 스냅샵으로 저장한 후에, 현재 상태 정보가 필요할 때 스냅샵 이후의 트랜잭션만 처리
    • 명령 측면, 조희 측면의 서비스가 이벤트 저장소의 모든 CRUD 처리하는 대신, 입력 및 조회만 처리하면 됨
      • 저장소에서 변경 및 삭제가 발생하지 않으므로, 명령 측면의 서비스를 여러 개로 확장해도 동시 업데이트 및 교착 상태가 발생하지 않음

이벤트 저장소의 데이터 형태 예시

  • 이벤트 아이디
  • 이벤트 타입 : 어떠한 상태인지?
  • 엔티티 타입 : 어떠한 객체의 이벤트인지?
  • 엔티티 데이터 : 변경 내용을 JSON 형태로 저장 → 상태 객체가 그대로 들어감

→ 이벤트 소싱은 모든 트랜잭션의 상태를 즉시 계산하지 않고, 별도의 이벤트 스트림으로 이벤트 스트림 저장소에 저장

  • 이벤트 스트림 저장소 : 오로지 추가만 가능하게끔 하여, 계속 이벤트들이 쌓이게끔 함
    • 실제 내가 필요한 데이터를 구체화하는 시점에 그때까지 축적된 트랜잭션을 바탕으로 상태를 계산해 구성
    • 이벤트 데이터베이스의 역할뿐만이 아니라 메시지 브로커 처럼 작동
    • 데이터 저장 처리 메커니즘이나 메시지 큐와 같은 이벤트를 전달하기 위한 메커니즘을 통합
    • 저장된 상태를 통해 정확한 검사 로깅을 제공하고, 객체의 예전 상태를 재구성하는 것이 가능
    • 외부 어플리케이션에 이벤트를 전달하는 것 또한 저장한 이벤트를 그대로 전송하면 됨

Reference