[MicroService] 2. MSA
마이크로서비스 아키텍처 (MSA) : 마이크로서비스를 접목한 아키텍처 구조
- 클라우드 인프라와 접목해 아마존, 넷플릭스에 의해 구체화 → 비즈니스 성공 사례
- 각 서비스는 개별 프로세스에서 실행되며,
HTTP API를 통해 통신 - 각 서비스는 비즈니스 기능 단위로 구성되고, 자동화된 배포 방식을 이용해 독립적으로 배포
마이크로서비스 아키텍처 (
MSA)와 서비스 지향 아키텍처 (SOA)의 비교
SOA: 컴포넌트를 모아 비즈니스적으로 의미있고 완결적인 서비스 단위로 모듈화
SOA와MSA의 공통점 : 비즈니스 서비스의 집합으로 시스템을 개발SOA와MSA의 차이점 : 이론적인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) : 저장소에 빌드한 소스 코드의 실행 파일을 실행 환경까지 자동으로 배포
자동 빌드 및 배포 절차
- 매일 자신이 작성한 소스 코드와 이를 테스트할 테스트 코드를 형상관리 시스템에 보낸다. (
Push)- 매일 빌드 도구에서 형상관리 서버의 코드를 가져와 (
Pull) 통합하고, 자동으로 빌드하고 테스트를 수행한다.- 테스트 수행 결과를 리포트에 기록하고, 빌드된 소스 코드를 스테이징 환경에 자동으로 배포한다.
- 테스터가 스테이징 환경에서 테스트를 수행할 때 혹은 리포트 결과에 문제가 있으면, 소스 코드를 수정한다.
- 빌드·배포 파이프라인의 설계 : 빌드·배포 과정동안 수행해야 할 업무 (
task)를 정의한 것- 리포지토리에서 소스 코드를 가져와 빌드해 실행 파일을 만드는 작업
- 이전 작업이 성공하면, 다음 작업이 자동으로 수행히게끔 위의 작업들을 관리하는 작업
- 실행할 어플리케이션을 실행 환경에 배포하는 작업
→
Infrastructure as Code를 통해 빌드·배포 파이프라인의 절차를 완벽하게 자동화할 수 있음 Infrastructure as Code: 인프라 구성을 프로그래밍하는 것처럼 처리해 많은 컨테이너 배포 처리를 함- 형상관리 리포지토리에서 소스 코드를 가져와 빌드하여 실행 파일을 만드는 작업
- 실행 파일을 실행 환경에서 배포하는 작업
- 작업들을 통제하고 연결해서 모든 작업이 성공하면 다음 작업이 자동으로 수행되도록 연계하는 작업
마이크로서비스가 실제 구동되는 환경에서 동작될 수 있도록 운영 관리 요소 → 운영 관리 패턴
- 마이크로서비스의 발전 과정에서 아키텍처의 문제 영역들이 지속적으로 논의되었고, 이에 따른 해결책을 모색해옴
- 1999년
XP방법론, 2001년 에자일 선언을 통해 빠른 실패와 피드백을 기반하는 실용적인 실천법 적용 - 2010년 넷플릭스가
AWS EC2로 인프라를 전환하면서 발생한 문제점들을 해결하기 위한 넷플릭스OSS개발- 여러 마이크로서비스 간의 라우팅을 위한 줄 (
Zuul) - 적절한 부하 분산을 하는 로드밸런싱을 위한 리본 (
Ribbon) - 모니터링을 위한 히스트릭스 (
Hystrix) - 서비스 등록을 위한 유레카 (
Eureka)
- 여러 마이크로서비스 간의 라우팅을 위한 줄 (
- 2013년 마이크로서비스를 쉽게 개발할 수 있는 스프링부트 (
SpringBoot) 프레임워크가 발표 - 2013년 도커, 2014년 쿠버네티스와 같은 컨테이너 기반 기술들이 개발
→ 클라우드 환경, 넷플릭스
OSS, 프레임워크, 컨테이너 기반 기술이 아울러져 마이크로서비스 생태계를 계속 발전시킴 - 1999년
- 스프링 클라우드 : 스프링부트 + 넷플릭스
OSS- 스프링 진영에서 기존 스프링 프레임워크에 넷플릭스
OSS들이 더 잘 돌아갈 수 있도록 통합하여 발표
- 스프링 진영에서 기존 스프링 프레임워크에 넷플릭스
마이크로서비스 서비스와 스프링 클라우드 서비스의 연계 흐름
- 스프링 클라우드를 포함한 모든 마이크로서비스가 인프라에 종속되지 않도록
DB, 파일 등에 저장된 설정 정보를 형상관리 시스템에 연계된Config서비스에서 가져와 주입한 후 클라우드 인프라의 개별 인스턴스로 로딩- 로딩과 동시에 ‘서비스 레지스트리’에 자신의 서비스 이름과 클라우드 인프라로부터 할당받은 물리 주소를 매핑
- 클라이언트가 ‘
API게이트웨이’를 통해 마이크로서비스에 접근하면, …
API게이트웨이는 적절한 라우팅 및 부하 관리를 위한 로드밸런싱을 수행API게이트웨이’는 클라이언트가 마이크로서비스에 접근하기 위한 주소를 알기 위해 ‘서비스 레지스트리’ 검색을 통해 서비스의 위치를 가져옴- ’
API게이트웨이’는 클라이언트가 각 서비스에 접근할 수 있는 권한이 있는지 확인하기 위해 ‘권한 서비스’와 연계해 인증 및 인가 처리를 수행- 모든 마이크로서비스 간의 호출 흐름은 ‘모니터링 서비스’와 ‘추적 서비스’에 의해 모니터링되고 추적됨
다양한 서비스의 등록 및 탐색 → 서비스 레지스트리, 서비스 디스커버리 패턴
- 예시 : 넷플릭스
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)
클라이언트 토큰을 통한 사용자 인증의 흐름
- 브라우저가 사버에 사용자 이름과 패스워드로 인증을 요청한다.
- 서버는 인증 후에 사용자 정보의 인증 및 인가 정보를 포함하여 토큰을 생성하고 브라우저에 전송한다.
- 브라우저는 서버 자원을 요청할 때 토큰을 함께 보내고, 서버는 토큰 정보를 확인한 후 자원 접근을 허가한다.
API게이트웨이를 사용한 클라이언트 토큰API게이트웨이가 외부 요청의 입구로 추가되어 인증 프로세스를 진행- 인증 및 인가를 위한 별도의 전담 서비스가
API게이트웨이와 연동하여 다른 서비스의 인증 및 인가 처리를 위임 → 인증 서비스 (Auth Service)
API게이트웨이와 인증 서비스를 활용한 클라이언트 토큰을 통한 사용자 인증의 흐름
- 클라이언트가 리소스 서비스에 접근을 요청하면,
API게이트웨이는 인증 서비스에 전달한다.- 인증 서비스는 해당 요청이 인증된 사용자가 보낸 것인지 확인하고 (→ 인증),
- 인증 서비스는 해당 요청이 해당 리소스에 대한 접근 권한이 있는지 확인하고 (→ 인가),
- 모두 확인하고 나면 리소스에 접근 가능한 증명서인 액세스 토큰 (
Access Token)을 발급한다.- 클라이언트는 다시 액세스 토큰을 활용해 접근을 요청한다.
- 각 리소스 서비스는 이러한 요청이 액세스 토큰을 포함하고 있는지 판단하여 리소스에 대한 접근을 허용한다.
장애 및 실패 처리 → 서킷 브레이커 패턴
- 여러 서비스로 구성된 시스템에서 한 서비스에 장애가 생기면, 다른 서비스가 영향을 받을텐데…?
- 전체 시스템은 정상적인데, 특정 기능을 누르면 한참 대기하는 경우 → 장애가 다른 서비스로 전이되었다!
→ 장애가 발생한 서비스를 격리해서 유연하게 처리하자!
- 서킷 브레이켜 (
Circuit Breaker) : 시스템 과부하나 특정 서비스에 문제가 생겼을 때, 자연스럽게 다른 정상적인 서비스로 요청 흐름을 변경하는 것 → 장애가 다른 서비스로 전이되지 않게 하자!A → B → A의 서비스 흐름에서B서비스가 장애가 생겼을 때,A가 동기 요청을 보내면? : 계속 기다린다…B서비스 호출에 대한 연속 실패 횟수가 임계값을 초과하면, 이후 서비스 호출 시도를 모두 실패로 처리- 풀백 (
Fallback) 메소드를 지정해 정상 응답을 대신할 대체 응답을 사용자에게 제공
마이크로서비스의 장애 감지 → 모니터링 및 추적 패턴
- 예시 : 스프링 클라우드 히스트릭스 (
Hystrix) + 집킨 (Zipkin)
그래서 서킷 브레이커는 언제, 어떻게 작동하는데…?
→ 각 마이크로서비스의 장애를 실시간으로 감지할 수 있고, 서비스 간의 호출을 파악하자!
- 히스트릭스 대시보드 : 각 요청의 트래픽이 원형으로 표현 → 서비스 성능에 문제가 생기면, 서킷 브레이커 발동 (⦁)
- 집킨 대시보드 : 각 서비스 트랜잭션의 호출을 추적하거나, 지연 구간별 장애 포인트를 확인할 수 있음
- 서비스
API를 선택하면, 각API가 다른API를 어떻게 호출하는지 확인 가능- 전체적인
API간의 호출 빈도를 확인할 수 있는 정적 다이어그램 또한 제공
- 전체적인
- 서비스
마이크로서비스의 로그 관리 → 중앙화된 로그 집계 패턴
- 마이크로서비스가 사용량에 따라 탄력적으로 변화하면, 그 인스턴스가 삭제되면 로컬 로그는 초기화..?
- 로그 (
Logs) 원칙 : 로그는 이벤트 스트림 (Event Streams)로 처리되어야 함
- 로그는 시작과 끝이 고정된 것이 아니라, 서비스가 실행되는 동안 계속 흐르는 흐름
- 서비스는 스트림의 전달 및 저장에 절대 관여하면 안됨!
- 로그를 전달하거나 저장하는 매커니즘 자체가 특정한 기술 및 인프라에 의존하게 됨
- 마이크로서비스가 로그 관련 매커니즘을 직접 구현하면 유연성이 떨어짐
→ 서비스에서 발생한 이벤트 스트림 형태의 로그를 수집하고 분석할 방법을 찾자!
ELK스택 : 엘라스틱서치 & 로그스태시 & 키바나를 기반으로 하는 데이터 분석 환경을 구성- 엘라스틱서치 (
ElasticSearch) : 분산형 검색 및 분석 엔진- 정형, 비정형, 위치 정보, 메트릭 등 원하는 방법으로 검색을 수행 및 결합 가능
- 로그스태시 (
Logstash) : 서버 측의 로그 집합기- 여러 소스에서 동시에 데이터를 수집 및 변한하여 특정 보관소로 데이터를 전송하는 데이터 처리 파이프라인
- 키바나 (
Kibana) : 시각적으로 로그 내역을 보여주는 대시보드- 위치 데이터, 시계열 분석, 그래프 관계 등을 히스토그램, 막대 그래프, 파이차트 등의 형태로 표현
→ 각 서비스의 인스턴스 로그를 집계해서 중앙에서 집중 관리할 수 있고, 특정 로그를 검색 및 분석할 수 있음
- 엘라스틱서치 (
- 마이크로서비스 내의 로그를 중앙 서버의 레디스로 전송한다.
- 중앙 서버의 레디스에서 중앙 로그 저장소에 해당 로그들을 전송한다.
- 중앙 로그 저장소에 엘라스틱서치 엔진이 로그를 인덱싱한다.
- 해당 로그 정보를 키바나 대시보드를 통해 표현한다.
- 중간 지점에 레디스가 존재하는 이유? : 마이크로서비스의 로그스태시에서 보낸 로그 스트림이 중앙 로그 저장소에 몰리면 성능상의 문제가 생길 수 있으므로 임시 저장소 역할의 레디스를 추가
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방식의 요청을 보내면,A서비스가B서비스에HTTP GET방식의 동기 호출을 수행하고,B서비스가 응답을 발생한다. (성공 시200 OK)
- 다양한 클라이언트 채널 연계나 라우팅, 로드밸런싱을 원활하기 위해
API게이트웨이를 둘 수 있음 - 요청을 보내면 바로 응답이 오는 직관적인 방식이므로, 가장 많이 쓰이고 구현하기 쉬움
- 호출을 받은 마이크로서비스에 장애가 생긴다면, 요청을 보낸 서비스는 응답할 때까지 대기하면서 재호출
- 여러 서비스 간의 연계를 통해 업무를 처리하는 마이크로서비스에서는 장애가 연쇄적으로 발생할 수 있음
- 서비스가 다른 서비스를 호출해서 얻은 정보로 기능을 제공한다는 것은, 서비스 간의 의존도가 높다는 의미
- 사용자가
- 메시지 기반 비동기 (
asynchronous) 통신 방식 : 메시지를 보내면, 응답을 기다리지 않고 다음 업무 처리- 메시지 브로커 (
Message Broker) : 동기식처럼 완결성을 보장할 수 없으니, 이를 보장하는 메커니즘을 활용- 아파치 카프카 (
Apache Kafka), 레빗엠큐 (RabbitMQ) 등 - 메시지를 보내는 생산자 (
Producer)와 메시지를 받아 처리하는 소비자 (Consumer)- 생산자와 소비자가 서로 직접 접속하지 않고 메시지 브로커를 통해 연결
- 메시지 브로커에 메시지를 전달하고 자신의 일을 처리하면, 메시지 브로커가 전송을 보장
- 메시지 브로커으로 중계되므로, 서로 통신하는 서비스들이 물리적으로 동일한 시스템에 위치하지 않아도 됨
- 서로 프로세스를 공유할 필요도 없으며, 동일한 시간대에 동시에 동작하지 않아도 됨
- 아파치 카프카 (
- 비동기 방식의 이벤트 기반 아키텍처 (
Event-driven Architecture) : 비동기 통신 방식으로 느슨한 연계 지향- 분산 시스템 간에 발신자가 이벤트를 생성 및 발행 (
Publish)하여, - 해당 이벤트를 필요로 하는 수신자에게 전송하면,
- 이벤트를 구독 (
Subscribe)하고 있던 수신자가 이벤트를 받아 처리한다.
→ 순서에 따라 특정 행동이 발생하는 기존의 방식이 아닌, ‘상태의 변화’를 의미하는 이벤트에 대한 반응으로 동작
- 분산 시스템 간에 발신자가 이벤트를 생성 및 발행 (
- 메시지 브로커 (
마이크로서비스를 독립적으로 수정 및 배포 → 저장소 분리 패턴
- 기존 모노리스 시스템의 저장소인 통합 저장소의 문제점?
- 어플리케이션 모듈은 분리하되 저장 처리는 모듈별로 격리하지 않고 다른 모듈에서의 호출을 허용
- 모든 비즈니스 로직이 데이터베이스의
SQL처리에 몰려있는 경우가 대부분- 데이터 중심 어플리케이션은 특정 관계형 데이터베이스 벤더에 구속되고 복잡해져 유지보수가 어려움
- 성능 문제가 발생하였을 때,
SQL구문 튜닝이나Scale-up에 의존할 수 밖에 없음- 여러 마이크로서비스로 분리해도 요쳥이 증가하면 데이터베이스만 바쁜 상태가 됨
- 자동으로 확장되는 마이크로서비스의
Scale-out이 무의미함→ 여러 개의 마이크로서비스에 적합하게 데이터베이스 벤더를 운영하는 방법을 찾자!
- 각 마이크로서비스는 각자의 비즈니스를 처리하기 위한 데이터를 직접 소유해야 한다!
- 자신이 가진 데이터는 다른 서비스에 직접 노출하지 않고 각자 공개한
API를 통해서 접근 가능 → 정보 은닉 - 저장소가 격리되어 있기 때문에 각 저장소를 자율적으로 선택할 수 있음 → 폴리글랏 저장소
- 해당 제약을 통해 데이터를 통한 변경의 파급 효과를 줄여 서비스를 독립적으로 만듬
여러 분산된 서비스를 하나의 일관된 트랜잭션으로 → 분산 트랜잭션 처리 패턴
- 저장소 분리 패턴의 문제점?
- 여러 분산된 서비스에 걸친 비즈니스 처리를 하면, 비즈니스 정합성과 데이터 일관성은 어떻게 보장하지?
- 2단계 커밋 : 분산 데이터베이스 환경에서 원자성 (
atomicity)을 보장하기 위해,- 분산 트랜잭션에 포함된 모든 노드가 커밋 (
commit) 혹은 롤백 (rollback)되는 메커니즘 - 각 서비스에
lock-in이 걸려 발생하는 성능 문제로 비효율적인 방법 - 각 서비스가 다른 인스턴스로 로딩되므로 이를 통제하기 어려움
- 서비스의 저장소가 각각 다를 경우에 문제가 발생 (
NoSQL저장소 : 2단계 커밋 미지원) - 네트워크 장애 등으로 특정 서비스의 트랜잭션을 처리하지 않는 경우 트랜잭션이 묶인 서비스에 즉시 영향
- 분산 트랜잭션에 포함된 모든 노드가 커밋 (
- 사가 (
Saga) 패턴 : 각 서비스의 로컬 트랜잭션을 순차적으로 처리하는 패턴- 여러 분산된 서비스 내에서 각각의 로컬 트랜잭션과 보상 트랜잭션을 통해 비즈니스 및 데이터의 정합성을 맞춤
- 각 로컬 트랜잭션을 통해 자신의 데이터베이스를 갱신한다.
- 사가 내에 있는 다음 로컬 트랜잭션을 트리거하는 이벤트를 발행하여 데이터의 일관성을 맞춘다.
- 서비스에서 트랜잭션 처리에 실패하면, 앞선 다른 서비스에 처리된 트랜잭션을 롤백할 보상 트랜잭션을 준다.
- 여러 분산된 서비스 내에서 각각의 로컬 트랜잭션과 보상 트랜잭션을 통해 비즈니스 및 데이터의 정합성을 맞춤
- 데이터 일관성에 대한 생각의 전환 → 결과적 일관성
- 이전엔 비즈니스 처리를 위한 규칙을 만족시킬, 데이터 일관성을 반드시 실시간으로 맞춰야 한다고 생각했다.
- 그러나 모든 비즈니스 처리가 반드시 실시간성을 요구하는 것이 아니다. 어떤 비즈니스들은 어느 일정 시점이 되었을 때 일관성을 만족해도 된다. → 결과적 일관성 (
Eventual Consistency) - 이벤트 기반 비동기 통신 및 사가 패턴을 적용한 아키텍처, 메시지 브로커을 활용하여, 결과적 일관성을 반영해 비즈니스 및 시스템의 가용성을 극대화할 수 있다.
읽기와 쓰기의 분리 → CQRS 패턴
- 마이크로서비스의 서비스별 데이터 저장소에서 전통적인
DB트랜잭션을 사용하면?
- 데이터 읽기 및 수정 작업으로 인한 리소스 교착 상태가 발생할 수 있다.
→ 동일한 저장소에 데이터를 넣은 뒤에
CRUD를 처리하는, 기존의 패러다임을 전환하는 것은 어떨까!
- 명령 조회 책임 분리 (
Command Query Responsibility Segregation)- 사용자의 비즈니스 요청은 시스템의 상태를 변경하는 연산과 시스템의 상태를 조회하는 연산으로 나눠짐
- 일반적인 비즈니스 모델에서는 입력, 수정, 삭제보다 조회가 더 많이 사용됨
- 서비스 내에 모든 기능을 넣으면 조회 요청 빈도가 증가함에 따라 다른 명령 기능 또한 확장되니 비효율적
- 하나의 저장소에 쓰기 모델과 읽기 모델을 분리하거나, 쓰기 저장소와 조회 저장소를 따로 두자!
- 쓰기 시스템의 부하를 줄이면서, 조회 대기 시간을 줄일 수 있음
CQRS패턴을 적용한 예시
- 명령 측면의 마이크로서비스
- 입력, 수정, 삭제 처리를 수행
- 쓰기에 최적화된 관계형 데이터베이스를 저장소로 활용
- 업무 규칙을 표한하기 좋은 언어 (
Java)를 프로그래밍 언어로 사용- 조회 측면의 마이크로서비스
- 조회 서비스는 사용량이 많으므로, 스케일 아웃을 통해 인스턴스를 증가시킴
- 조회 성능이 높은
NoSQL데이터베이스를 저장소로 활용- 조회를 간단하게 구현할 수 있는 언어 (
Node.js)를 프로그래밍 언어로 사용- 이벤트 주도 아키텍처
- 명령 서비스를 사용함에 따라 조회 서비스와의 데이터 일관성이 깨지므로 이를 유지하는 목적
- 명령 서비스가 저장소에 데이터를 쓸 때, 저장 내역이 담긴 이벤트를 발생시켜 메시지 브로커에 전달
- 메시지 브로커의 이벤트를 구독하는 조회 서비스는 이벤트 데이터를 가져와 데이터를 최신으로 동기화
- 시간 간격이 있으나 어느 시점에 일치하는 결과적 일관성이 이루어짐
저장소가 격리된 여러 마이크로서비스의 기능들을 연계한 서비스 제공 → API 조합과 CORS
API조합 (API Composition) : 기능을 제공하는 마이크로서비스를 조합할 상위 마이크로서비스로 조합된 기능 제공- 하위 서비스는 각자 독립적인
API를 제공하면서 연계API를 위해 상위 서비스에 정보 제공
→ 상위 서비스가 하위 서비스에 의존되는 결과를 가져옴 : 하위 서비스의 실패가 상위 서비스에 영향을 준다.
- 하위 서비스는 각자 독립적인
CQRS패턴을 활용한 기능 연계 : 상위 마이크로서비스에 통합 이벤트 핸들러와 통합 저장소를 두자- 독자 저장소를 갖는 하위 서비스에서 자신의 서비스 내 정보가 변경될 때 변경 내역을 각 변경 이벤트로 발행
- 상위 서비스는 구독한 이벤트를 가져와서 자신의 서비스 저장소에 기록해 데이터 일관성을 맞춤
쓰기 연산의 최적화 → 이벤트 소싱 패턴
- 사가 패턴과
CQRS패턴에서 비즈니스 불일치를 피하려면,
- 저장소에 저장하는 것과 메시지를 보내는 것은 언제나 완전하게 진행되어야 하지만,
- 객체의 상태를 관계형 데이터베이스에 저장하기 위해
SQL질의어로 변환하는 것은 번거로움- 메시지 발행과 저장 처리의 두 가지 기능을 수행하는 것 또한 빠르지 않음
→ 메시지 발행과 저장 처리를 언제나 완전하게 처리하면서 성능도 최적화시킬 방법을 찾자!
- 비즈니스를 처리할 때 데이터의 처리는 항상 처리 상태의 결괏값을 계산하고 데이터의 최종 상태를 확정해 저장
- 일반적인 관계형 데이터베이스와
Java를 사용할 때,- 품목에 대한 데이터 모델이 정의되어 있다면,
- 품목의 상태가 변경될 때 매번 트랜잭션 결과를 반영해서,
- 품목 데이터 모델의 결과를 계산해야 한다.
→ 객체의 상태 변경에 따라 데이터 모델로 처리하여 최종값을 반영하는 과정은 복잡하고 속도가 느림
- 객체의 상태를 데이터 모델에 맞춰 계산하지 않고, 상태 트랜잭션 자체를 이벤트 저장소에 그대로 저장
- 메시지 브로커와 데이터 저장소를 분리하지 않고 하나로 사용할 수 있음 → 쓰기 속도가 매우 빠름
- 상태의 출발점부터 모든 기록된 상태 변경 트랜잭션을 순차적으로 계산
- 매일 자정 상태를 계산하고 스냅샵으로 저장한 후에, 현재 상태 정보가 필요할 때 스냅샵 이후의 트랜잭션만 처리
- 명령 측면, 조희 측면의 서비스가 이벤트 저장소의 모든
CRUD처리하는 대신, 입력 및 조회만 처리하면 됨- 저장소에서 변경 및 삭제가 발생하지 않으므로, 명령 측면의 서비스를 여러 개로 확장해도 동시 업데이트 및 교착 상태가 발생하지 않음
이벤트 저장소의 데이터 형태 예시
- 이벤트 아이디
- 이벤트 타입 : 어떠한 상태인지?
- 엔티티 타입 : 어떠한 객체의 이벤트인지?
- 엔티티 데이터 : 변경 내용을
JSON형태로 저장 → 상태 객체가 그대로 들어감→ 이벤트 소싱은 모든 트랜잭션의 상태를 즉시 계산하지 않고, 별도의 이벤트 스트림으로 이벤트 스트림 저장소에 저장
- 이벤트 스트림 저장소 : 오로지 추가만 가능하게끔 하여, 계속 이벤트들이 쌓이게끔 함
- 실제 내가 필요한 데이터를 구체화하는 시점에 그때까지 축적된 트랜잭션을 바탕으로 상태를 계산해 구성
- 이벤트 데이터베이스의 역할뿐만이 아니라 메시지 브로커 처럼 작동
- 데이터 저장 처리 메커니즘이나 메시지 큐와 같은 이벤트를 전달하기 위한 메커니즘을 통합
- 저장된 상태를 통해 정확한 검사 로깅을 제공하고, 객체의 예전 상태를 재구성하는 것이 가능
- 외부 어플리케이션에 이벤트를 전달하는 것 또한 저장한 이벤트를 그대로 전송하면 됨
Reference