마이크로서비스를 성공적으로 설계하고 구축하려면 범죄 현장의 목격자들을 인터뷰하는 방식처럼 접근해야 한다. 비록 모든 목격자가 같은 사건을 목격했더라도, 그들의 배경, 중요하게 여기는 것(예시 동기 부여), 목격 당시의 환경적 요인에 따라 사건에 대한 해석은 달라질 수 있다. 각 목격자는 자신이 중요하다고 생각하는 요소에 대해 서로 다른 시각이나 편견을 가질 수 있다.
성공적으로 진실을 추적하는 형사처럼, 성공적인 마이크로서비스 아키텍처를 구축하는 과정은 소프트웨어 개발 조직 내 다양한 개인들의 관점을 통합하는 것과 밀접하게 관련된다. 전체 애플리케이션을 제공하려면 기술 인력 외에도 다양한 인력이 필요하므로, 마이크로서비스 개발 기반을 성공적으로 마련하려면 다음의 세 가지 중요한 역할에서부터 시작할 수 있다.
- 아키텍트 (architect): 큰 그림을 염두에 두고 애플리케이션을 개별 마이크로서비스로 분해한 후, 각 마이크로서비스가 전체 솔루션을 제공하기 위해 어떻게 상호 작용하는지를 이해하는 것이 중요하다.
- 소프트웨어 개발자 (software developer): 코드를 작성하고 적절한 언어와 개발 프레임워크를 활용하여 마이크로서비스를 구현하는 방법을 이해하는 것이 중요하다.
- 데브옵스 (devops engineer): 운영 환경과 비운영 환경에 서비스를 배포하고 관리하는 방법을 결정할 때, 데브옵스의 핵심 원칙인 일관성(consistency)과 반복성(repeatability)을 중시해야 한다. 이는 모든 환경에서 동일한 방식으로 서비스를 배포하고 관리할 수 있도록 하는 데 중요한 역할을 한다.
3.1 아키텍트 이야기: 마이크로서비스 아키텍처 설계
프로젝트에서 아키텍트의 역할은 해결해야 할 문제에 대한 작동 모델을 제공하는 것이다. 아키텍트는 애플리케이션의 각 구성 요소가 잘 통합될 수 있도록 개발자가 작성할 코드의 기본 틀을 제공한다. 마이크로서비스를 구축할 때, 아키텍트는 세 가지 핵심 업무에 집중한다.
- 비즈니스 문제 분해
- 서비스 세분화 확정
- 서비스 인터페이스 정의
3.1.1 비즈니스 문제 분해
문제가 복잡해지면 대부분의 사람들은 이를 관리 가능한 단위로 나누려 한다. 이렇게 하면 문제의 모든 세부 사항을 기억할 필요 없이 핵심 부분에 집중할 수 있기 때문이다. 그런 후, 각 부분을 나누고 이들 간의 관계를 파악하려고 한다.
마이크로서비스 아키텍처에서도 이 과정은 비슷하다. 아키텍트는 비즈니스 문제를 여러 활동 영역을 대표하는 부분으로 나누고, 각 부분 안에 해당 비즈니스 규칙과 데이터 로직을 숨긴다. 예를 들어, 아키텍트는 비즈니스 흐름을 분석하면서 고객 정보와 제품 정보가 모두 필요하다는 것을 인식할 수 있다.
비즈니스 영역을 분해하는 과정은 이분법적 과학보다는 예술적인 측면이 강하다. 비즈니스 문제를 식별하고 이를 마이크로서비스로 분해하는 데 도움이 될 수 있는 몇 가지 지침은 다음과 같다.
- 비즈니스 문제를 기술하는데 사용된 명사 (nouns)에 주목하라: 문제를 설명할 때 동일한 명사가 반복적으로 등장한다면, 이는 핵심 비즈니스 영역을 정의하거나 마이크로서비스로 분리할 수 있는 좋은 신호일 수 있다. 예를 들어, 계약 (contract), 라이선스 (license), 자산 (assets)과 같은 명사는 마이크로서비스 설계의 중요한 기준이 될 수 있다.
- 동사 (verb)에 주목하라: 동사는 행동을 강조하며, 종종 문제 영역의 구조를 명확하게 드러낸다. 예를 들어, 조회하다 (looks up)와 업데이트하다 (updates)와 같은 동사는 마이크로서비스의 주요 작업을 정의하는 데 중요한 역할을 한다. 이러한 동사는 각 마이크로서비스가 수행해야 할 구체적인 기능이나 작업을 나타내는 데 유용하다.
- 데이터 응집성 (cohesion)을 찾아라: 비즈니스 문제를 나눌 때, 서로 밀접하게 연관된 데이터 부분을 식별하는 것이 중요하다. 만약 특정 서비스가 논의 중인 것과 전혀 다른 데이터를 읽거나 업데이트한다면, 이는 새로운 서비스로 분리될 수 있는 가능성을 나타낸다. 마이크로서비스는 독립적으로 자신의 데이터를 관리해야 하므로, 데이터의 일관성이나 결합도를 고려하여 각 서비스가 어떤 데이터를 다룰지를 명확히 구분하는 것이 필요하다.
3.1.2 서비스 세분화 확정
데이터 모델을 간소화한 후, 애플리케이션에서 필요한 마이크로서비스를 정의하는 단계로 나아갈 수 있다. 그 다음으로 고려할 수 있는 네 가지 잠재적 마이크로서비스가 있다.
- 자산 (assets)
- 라이선스 (liencese)
- 계약 (contract)
- 조직 (organization)
우리의 목표는 주요 기능을 독립적이고 빌드 및 배포 가능한 완전한 자립형 유닛으로 분리하는 것이다. 이러한 유닛들은 데이터베이스를 공유할 수도 있고 개별적으로 가질 수도 있다. 그러나 서비스 추출은 단순히 코드를 개별 프로젝트로 나누거나 다시 패키징하는 것 이상의 작업이 필요하다. 또한, 이 과정에는 서비스가 액세스하는 실제 데이터베이스 테이블을 이해하고 특정 문제 영역에 관련된 테이블만을 접근하도록 제한하는 작업도 포함된다.
문제 영역을 세분화한 후, 각 서비스의 적절한 크기를 결정하는 데 어려움을 겪을 수 있다. 너무 작거나 너무 큰 마이크로서비스는 몇 가지 특징을 보일 수 있다.
마이크로서비스를 구축할 때 세분화 수준을 결정하는 것은 중요한 질문이다. 이를 위해 올바른 세분화 수준을 찾기 위해 다음 개념들을 설명할 수 있다.
- 마이크로서비스는 광범위하게 시작하고 더 작은 서비스로 리팩터링 하는 것이 좋다: 마이크로서비스 아키텍처를 도입하는 초기에 모든 것을 작은 서비스로 나누고 싶은 유혹을 받을 수 있다. 하지만 문제 영역을 너무 세분화하면, 마이크로서비스가 단지 작은 데이터 서비스로 전락하게 되어 초기에는 불필요한 복잡성을 초래할 수 있다.
- 서비스간 교류하는 방식에 중점을 둔다: 이것은 문제 도메인(영역)에 대해 큰 단위의 인터페이스를 설계하는 데 도움이 된다. 크기가 클수록 작은 단위로 리팩터링하는 것이 더 쉬워진다.
- 문제 도메인에 이해가 깊어지면서 서비스 책임도 계속 변한다: 새로운 애플리케이션 기능이 요구될 때, 대개 마이크로서비스가 그 책임을 맡게 된다. 마이크로서비스는 처음에는 단일 서비스로 시작하지만 점차 여러 서비스로 분화되며 성장한다. 이 과정에서 원래의 서비스는 새로운 서비스들을 오케스트레이션하고, 애플리케이션의 다른 기능들을 캡슐화하는 역할을 하게 된다.
너무 큰 마이크로서비스의 징후
- 책임이 너무 많은 서비스: 해당 서비스에서 비즈니스 로직의 흐름이 복잡하고, 지나치게 다양한 종류의 비즈니스 규칙을 적용하는 것처럼 보인다.
- 다수 테이블에 걸처 데이터를 관리하는 서비스: 마이크로서비스는 자신이 관리하는 데이터에 대한 책임이 있다. 여러 테이블에 데이터를 저장하거나 서비스의 데이터베이스 외부에 있는 테이블에 접근하는 경우, 이는 서비스가 지나치게 커졌다는 신호일 수 있다. 일반적으로 약 3~5개의 테이블을 소유하는 것이 적당한 지침이며, 그 이상이라면 서비스가 너무 많은 책임을 지고 있을 가능성이 크다.
- 테스트가 너무 많은 서비스: 서비스는 시간이 흐름에 따라 규모와 책임이 확장될 수 있다. 서비스가 적은 수의 테스트 케이스로 시작하여, 나중에는 수백 개의 유닛 테스트와 통합 테스트를 필요로 한다면, 이는 리팩터링이 필요한 신호일 수 있다.
너무 작은 마이크로서비스의 징후
- 문제 도메인의 한 부분에 속한 마이크로서비스가 토끼처럼 번식한다: 모든 것이 마이크로서비스로 분할되면, 서비스 간의 비즈니스 로직을 구성하는 것이 복잡하고 어려워진다. 이는 작업을 완료하는 데 필요한 서비스 수가 급격히 증가하기 때문이다. 흔히 나타나는 징후는 애플리케이션에 수십 개의 마이크로서비스가 존재하며, 각 서비스가 하나의 데이터베이스 테이블과 상호작용하는 경우이다.
- 마이크로서비스가 지나치게 상호 의존적이다: 문제 영역의 한 부분에 속한 마이크로서비스들이 하나의 사용자 요청을 처리하기 위해 서로 계속 호출하는 상황이 발견될 수 있다.
- 마이크로서비스가 단순한 CRUD (Create, Read, Update, Delete) 서비스 집합이 된다: 마이크로서비스는 비즈니스 로직을 표현하는 데이터 소스의 추상화 계층이 아니다. 만약 마이크로서비스가 CRUD 관련 로직만 수행한다면, 그것은 너무 세분화된 설계일 수 있다.
마이크로서비스 아키텍처는 처음부터 완벽한 설계를 얻기 어려우므로, 진화적인 사고 방식으로 개발되어야 한다. 따라서 첫 서비스들을 지나치게 세분화하기보다는, 큰 단위로 나누어 시작하는 것이 더 바람직하다.
독단적인 설계를 피하는 것도 중요하다. 서비스에는 물리적인 제약이 있을 수 있기 때문에, 예를 들어 두 개의 서비스 간에 너무 많은 호출이 발생하거나 서비스의 도메인 경계가 불명확하다면, 데이터를 조인하는 집계 서비스를 만들어야 한다. 마지막으로, 완벽한 설계를 추구하다 시간을 낭비하고 결과적으로 아무것도 보여주지 못하는 것보다는, 실용적인 접근 방식을 통해 설계를 단계적으로 전달하는 것이 더 바람직하다.
3.1.3 서비스 인터페이스 설계
아키텍터에 대한 마지막 조언은 애플리케이션 내 마이크로서비스들 간의 상호 통신 방식을 정의하는 것이다. 마이크로서비스 기반으로 비즈니스 로직을 구축할 때, 서비스의 인터페이스는 직관적이어야 하며, 개발자는 애플리케이션 내 일부 서비스만 충분히 이해해도 모든 서비스가 어떻게 동작하는지에 대한 규칙을 습득할 수 있어야 한다.
- REST 철학을 수용하라: 이것은 리처드슨의 성숙도 모델과 관련된 모범 사례 중 하나로, 서비스에 대한 REST 접근 방식을 강조한다. 여기서 중요한 점은 표준 HTTP 동사(GET, PUT, POST, DELETE)를 사용하고, 서비스 호출 프로토콜로 HTTP를 채택하는 것이다. 이를 통해 HTTP 동사를 기반으로 기본적인 행동 양식을 모델링하는 것이 핵심이다.
- URI를 사용하여 의도 (intent)를 전달하라: 서비스의 엔드포인트에서 사용되는 URI는 문제 영역에 존재하는 다양한 자원을 명확하게 기술하고, 자원 간 관계를 표현하는 기본적인 메커니즘을 제공해야 한다.
- 요청 (request)과 응답 (response)에 JSON을 사용하라: JSON은 총 경량 데이터 직렬화 프로토콜이면 XML보다 훨씬 사용하기 쉽다.
- HTTP 상태 코드로 결과를 전달하라: HTTP 프로토콜에는 서비스 호출의 성공과 실패를 명확히 나타내는 표준 응답 코드가 존재한다. 이러한 상태 코드를 잘 이해하고, 모든 서비스에서 일관되게 사용하는 것이 매우 중요하다.
기본 지침의 주요 목표는 서비스 인터페이스가 쉽게 이해되고 사용될 수 있도록 만드는 것이다. 개발자가 서비스를 살펴보고 즉시 활용할 수 있도록 해야 한다. 만약 마이크로서비스가 쉽게 소비되지 않는다면, 개발자는 문제를 해결하려 시도하거나 아키텍처의 본래 목적을 방해할 수 있다.
3.2 마이크로서비스를 사용하지 말아야 할때
마이크로서비스를 사용하지 말아야 할때가 있다. 이 금기 사항은 아래와 같다.
- 분산 시스템 구축의 복잡성
- 가상 서버나 컨테이너 스프롤 (sprawl)
- 애플리케이션 타입
- 데이터 트랜잭션과 일관성
3.2.1 분산 시스템 구축의 복잡성
마이크로서비스는 분산되고 세분화된 구조로 인해 모놀리식 애플리케이션에서 발생하지 않았던 복잡성을 야기한다. 또한, 마이크로서비스 아키텍처는 높은 수준의 운영 성숙도를 요구한다. 따라서 조직이 고도로 분산된 애플리케이션을 성공적으로 운영하려면, 자동화와 운영 작업(모니터링, 확장 등)에 충분히 투자할 준비가 되어 있어야 한다. 그렇지 않다면 마이크로서비스 도입을 고려하지 않는 것이 바람직하다.
3.2.2 서버 또는 컨테이너 스프롤
마이크로서비스의 가장 일반적인 배포 모델은 각 컨테이너에 하나의 마이크로서비스 인스턴스를 배포하는 것이다. 대규모 마이크로서비스 기반 애플리케이션에서는 운영 환경에서 구축 및 관리해야 하는 컨테이너가 50개에서 100개 정도일 수 있다. 클라우드에서 이들 서비스를 실행하는 데는 비용이 상대적으로 저렴할 수 있지만, 서버를 관리하고 모니터링하는 운영 작업은 매우 복잡할 수 있다.
3.2.3 애플리케이션 타입
마이크로서비스는 높은 재사용성, 회복성, 확장성을 요구하는 대규모 애플리케이션 구축에 유용하다. 이로 인해 많은 클라우드 기반 기업들이 마이크로서비스 아키텍처를 채택하는 이유 중 하나이다. 그러나 부서 수준의 소규모 애플리케이션이나 적은 사용자 기반을 가진 애플리케이션의 경우, 마이크로서비스와 같은 분산 아키텍처를 도입하면 그 복잡성이 증가해 기대되는 가치보다 더 많은 비용이 발생할 수 있다.
3.2.4 데이터 트랜잭션 일관성
마이크로서비스를 설계할 때는 서비스와 서비스 소비자가 사용하는 데이터 패턴을 충분히 고려해야 한다. 마이크로서비스는 적은 수의 테이블을 다루며, 단순한 쿼리 작업이나 데이터를 추가하고 실행하는 데 적합하다. 하지만 애플리케이션이 여러 데이터 소스에서 복잡한 데이터를 집계하거나 변환해야 하는 경우, 마이크로서비스의 분산 특성으로 인해 이러한 작업이 복잡해지고 성능 문제를 초래할 수 있다. 결국 마이크로서비스는 과도한 책임을 지게 되어 성능 저하를 겪을 위험이 있다.
3.3 개발자 이야기: 스프링 부트와 자바
예졔 및 예제 설명 생략
3.2.1 마이크로서비스의 출입구 만들기: 스프링 부트 컨트롤러
예졔 및 예제 설명 생략
@RestController는 특정 자바 클래스가 REST 기반 서비스를 제공하도록 스프링 컨테이너에 등록될 것을 지정하는 클래스 레벨의 자바 어노테이션이다. 이 어노테이션은 JSON 또는 XML 형식으로 데이터를 자동으로 직렬화하며, 기본적으로 JSON으로 직렬화된 데이터를 반환한다. 기존의 @Controller 어노테이션과 달리, @RestController를 사용하면 메서드에서 ResponseBody를 명시적으로 반환할 필요가 없다. 이는 @RestController가 @ResponseBody 어노테이션을 내포하고 있기 때문이다.
3.3.2 라인선싱 서비스에 국제화 추가하기
국제화는 애플리케이션을 다양한 언어와 문화에 맞게 적응시키는 중요한 요구 사항이다. 주요 목표는 애플리케이션의 콘텐츠와 기능이 여러 언어와 지역에 맞게 제공될 수 있도록 만드는 것이다. 이를 통해 글로벌 사용자에게 원활한 경험을 제공할 수 있다.
예졔 및 예제 설명 생략
3.3.3 관련 링크를 표시하는 스프링 HATEOAS 구현
스프링 HATEOAS는 HATEOAS (Hypermedia as the Engine of Application State) 원칙을 준수하는 API를 생성하기 위한 작은 프로젝트로, 이 원칙에 따르면 API는 각 서비스 응답과 함께 가능한 다음 단계에 대한 정보를 제공해야 한다. 이를 통해 클라이언트는 API를 이용할 때, 어떤 행동을 취할 수 있는지 명확히 알 수 있게 된다. HATEOAS는 필수 기능은 아니지만, 리소스에 대한 완전한 가이드를 제공하고자 한다면 매우 유용한 도구로 활용될 수 있다.
스프링 HATEOAS를 사용하면 리소스 표현 모델에 링크를 추가하는 모델 클래스를 손쉽게 생성할 수 있다. 이 라이브러리는 리소스 객체에 대한 하이퍼미디어 링크를 자동으로 처리하며, 이를 통해 클라이언트가 리소스 간의 관계를 쉽게 탐색할 수 있게 된다. 또한, 스프링 MVC 컨트롤러 메서드에 대해 특정 링크를 생성하는 링크 빌더 API도 제공하여, 서비스 간의 연결을 명확하게 정의하고, API 응답에서 리소스 간의 관계를 쉽게 정의할 수 있다. 이를 통해 HATEOAS 원칙을 준수하는 API를 더욱 효율적으로 구현할 수 있다.
add()는 RepresentationModel 클래스의 메서드이며, linkTo() 메서드는 LicenseController 클래스를 검사해서 루트 매핑을 얻고, methodOn() 메서드는 대상 메서드에 더미 호출을 수행하여 메서드 매핑을 가져온다. 두 메서드 모두 org.springframework.hateoas.sever.mvc.WebMVCLinkBuilder의 정적 메서드다. WebMVCLinkBuilder는 컨트롤러 클래스에 대한 링크를 생성하는 유틸리티 클래스다.
예졔 및 예제 설명 생략
3.4 데드옵스 이야기: 혹독한 런타임 구축
데브옵스는 급격히 성장하는 IT 분야이지만, 데브옵스 엔지니어에게 마이크로서비스 설계는 실제 운영 환경에서의 서비스 관리와 관련이 있다. 코드 작성은 상대적으로 쉬운 일이지만, 서비스가 지속적으로 작동하도록 만드는 것이 더 어려운 일이다. 다음은 마이크로서비스 개발을 시작할 때 적용할 네 가지 원칙이다.
- 마이크로서비스는 일체형 (self-contained)이어야 한다: 하나의 소프트웨어 산출물은 여러 인스턴스를 독립적으로 배포할 수 있어야 하며, 각각의 인스턴스는 시작과 종료가 가능한 서비스로 운영되어야 한다.
- 마이크로서비스는 구성 가능 (configurable)해야 한다: 서비스 인스턴스가 시작될 때 필요한 구성 정보는 한 곳에서 자동으로 읽어오거나 환경 변수로 전달받아야 하며, 서비스 구성 정보를 설정하는 과정에서 사람의 개입은 없어야 한다.
- 마이크로서비스 인스턴스는 클라이언트에 투명해야 (transparent)한다: 클라이언트는 서비스의 정확한 위치를 알 필요가 없다. 대신, 애플리케이션이 마이크로서비스 인스턴스의 물리적 위치를 알지 못하더라도, 마이크로서비스 클라이언트는 서비스 디스커버리 에이전트와 통신하여 인스턴스의 위치를 찾아야 한다.
- 마이크로서비스는 자기 상태 (health)를 전달해야 한다:이것은 클라우드 아키텍처에서 중요한 요소이다. 마이크로서비스 인스턴스는 고장날 수 있으며, 서비스 디스커버리 에이전트는 고장난 인스턴스를 우회하여 정상적인 인스턴스로 트래픽을 라우팅해야 한다. 예시로 스프링 액추에이터 (actuator)를 사용해서 각 마이크로서비스의 상태를 표시한다.
이 네 가지 원칙은 마이크로서비스 개발에서 발생할 수 있는 역설을 보여준다. 마이크로서비스는 크기와 범위가 작지만 분산되어 있으며, 각 서비스는 독립적인 컨테이너에서 실행된다. 이로 인해 애플리케이션의 부품이 더 많아지며, 그에 따라 애플리케이션은 더욱 정교한 조정 기술을 필요로 하고, 장애 발생 가능성도 더 커지게 된다.
데브옵스 관점에서, 마이크로서비스와 관련된 운영 요구 사항을 사전에 해결하고, 이 네 가지 원칙을 마이크로서비스를 빌드하고 배포할 때마다 발생하는 표준 수명 주기 이벤트로 변환하는 것이 중요하다. 이러한 원칙들은 운영 수명 주기와 매핑되어, 마이크로서비스의 효율적인 관리와 원활한 운영을 보장할 수 있다.
- 서비스 조립 (service assembly): 이는 서비스가 동일한 코드와 런타임을 사용하여 반복적이고 일관된 방식으로 배포될 수 있도록 보장하는 패키징 및 배포 방법이다. 이렇게 함으로써 서비스의 배포 과정에서 발생할 수 있는 변수를 최소화하고, 안정적인 환경에서 일관된 실행을 유지할 수 있다.
- 서비스 부트스트래핑 (service bootstrapping): 이는 마이크로서비스가 사람의 개입 없이 모든 환경에서 빠르게 시작되고 배포될 수 있도록, 런타임 코드에서 애플리케이션 코드와 환경별 구성 코드를 분리하는 방법이다. 이를 통해 환경에 따라 적절한 구성을 자동으로 적용하고, 서비스가 다양한 환경에서 일관되게 동작하도록 할 수 있다.
- 서비스 등록 및 디스커버리 (service registration / discovery): 이는 새 마이크로서비스 인스턴스가 배포될 때, 애플리케이션 클라이언트가 새 서비스 인스턴스를 발견할 수 있도록 하는 방법이다. 이 과정은 일반적으로 서비스 디스커버리 메커니즘을 통해 이루어지며, 클라이언트는 서비스 디스커버리 에이전트와 통신하여 새로운 인스턴스를 자동으로 인식하고 연결한다.
- 서비스 모니터링 (service monitoring): 마이크로서비스 환경에서는 높은 가용성이 필수적이므로, 일반적으로 하나의 서비스에 여러 인스턴스를 실행한다. 데브옵스 관점에서, 각 마이크로서비스 인스턴스를 모니터링하고 장애가 발생한 인스턴스를 우회하여 라우팅하는 것이 중요하다. 또한, 서비스 인스턴스가 종료되었는지 확인하고, 필요한 경우 새로운 인스턴스를 시작하거나 적절한 대체 인스턴스를 배포하여 서비스의 연속성을 보장해야 한다.
3.4.1 서비스 조립: 마이크로서비스의 패키징과 배포
마이크로서비스 아키텍처의 핵심 개념 중 하나는 애플리케이션 환경의 변화에 신속하게 대응할 수 있는 능력이다. 예를 들어 갑작스러운 사용자 요청 증가나 인프라 문제와 같은 상황에 대비하여, 마이크로서비스는 여러 인스턴스를 빠르게 배포할 수 있어야 한다. 이를 위해 각 마이크로서비스는 모든 의존성을 포함한 단일 산출물로 패키징되어 배포 가능해야 하며, 이러한 의존성에는 마이크로서비스를 호스팅하는 런타임 엔진(예: HTTP 서버나 애플리케이션 서버)도 포함된다.
운영팀에게 런타임 환경을 JAR 파일에 내장하는 개념은 애플리케이션 배포 방식에서 큰 변화를 의미할 수 있다. 전통적인 자바 웹 애플리케이션에서는 애플리케이션이 애플리케이션 서버에 배포되며, 이 서버는 애플리케이션과 독립적으로 서버 구성을 관리하는 시스템 관리팀에 의해 감독된다. 그러나 마이크로서비스에서는 이러한 구성이 단일 JAR 파일로 패키징되어, 별도의 애플리케이션 서버 없이 자체 실행 가능한 독립적인 유닛으로 운영될 수 있다. 이로 인해 시스템 관리팀의 역할은 달라지며, 배포와 관리가 더 효율적이고 자동화된 방식으로 이루어질 수 있다.
애플리케이션 서버의 구성을 애플리케이션과 분리할 경우 배포 과정에서 문제가 발생할 수 있다. 많은 조직에서는 애플리케이션 서버의 구성 정보를 소스 제어 저장소에서 관리하지 않고, 사용자 인터페이스나 자체 관리 스크립트를 통해 관리하기 때문이다. 이로 인해 구성 불일치(configuration drift)가 쉽게 발생하게 되며, 표면적으로는 예기치 않은 장애가 갑자기 발생할 수 있다. 이 문제는 애플리케이션 배포를 자동화하고, 환경 구성을 코드로 관리하는 방식으로 해결할 수 있으며, 이는 마이크로서비스 환경에서 일관된 구성을 유지하는 데 필수적인 요소이다.
3.4.2 서비스 부트스트래핑: 마이크로서비스의 구성 관리
모든 애플리케이션 개발자는 애플리케이션의 실행 중 동작을 구성할 필요가 있을 때가 있다는 것을 잘 알고 있다. 보통 이 과정은 애플리케이션과 함께 배포된 프로퍼티 파일에서 설정 정보를 읽거나, 관계형 데이터베이스와 같은 데이터 저장소에서 정보를 가져오는 방식으로 이루어진다.
마이크로서비스는 일반적으로 비슷한 유형의 구성을 요구하지만, 클라우드에서 실행되는 마이크로서비스 애플리케이션에서는 수백 또는 수천 개의 마이크로서비스가 동시에 운영될 수 있다. 서비스가 글로벌하게 확장되면 이러한 문제는 더욱 복잡해지며, 지리적으로 분산된 서비스가 많아지기 때문에 새로운 구성 데이터를 가져오기 위해 서비스를 재배포하는 것이 어려워진다. 이 문제는 서비스 외부의 데이터 저장소에 구성 데이터를 저장함으로써 해결할 수 있지만, 클라우드 환경에서 마이크로서비스를 운영할 때는 다음과 같은 고유한 문제들이 발생할 수 있다
- 구성 데이터는 보통 간단한 구조를 가지고 있어 자주 읽고 가끔 쓰는 방식으로 처리된다. 관계형 데이터베이스는 더 복잡한 데이터 모델을 관리하는 용도로 설계되었기 때문에, 이러한 구성 데이터에는 과한 선택일 수 있다. 대신 NoSQL 데이터베이스나 키-값 저장소가 더 효율적인 선택이 될 수 있다.
- 데이터는 빈번히 접근되지만 수정이 거의 이루어지지 않아, 읽기 지연 시간이 짧아야 한다.
- 데이터 저장소는 높은 가용성을 유지해야 하며, 데이터 읽기 서비스를 위해 근접한 위치에 있어야 한다. 구성 데이터 저장소는 애플리케이션에서 단일 장애 지점으로 작용하기 때문에 절대 완전히 중단되어서는 안 된다.
3.4.3 서비스 등록과 디스커버리: 클라이언트가 마이크로서비스와 통신하는 방법
마이크로서비스는 소비자 입장에서 위치에 의존하지 않아야 한다. 이는 클라우드 환경에서 서버가 일시적 (ephemeral)으로 운영되기 때문이다. 서버의 일시적 특성은 클라우드 서버의 수명이 일반적인 기업 데이터 센터 내 서버보다 짧다는 것을 뜻한다. 클라우드 서비스는 새로운 IP 주소를 할당받아 빠르게 시작되거나 종료될 수 있다.
마이크로서비스 아키텍처는 서비스를 수명이 짧고 쉽게 폐기 가능한 객체로 간주하는 접근 방식 덕분에, 다수의 서비스 인스턴스를 실행하며 높은 확장성과 가용성을 확보할 수 있다. 또한, 상황이 허락하는 한 빠르게 서비스 요구를 충족하고 회복탄력성을 유지할 수 있다. 모든 서비스는 고유하며 비영구적인 IP 주소를 할당받는다. 그러나 이러한 일시적 서비스의 단점은 서비스가 빈번히 시작되고 종료되는 상황에서, 대규모 서비스를 수동으로 관리하거나 직접 조정하면 장애를 유발할 가능성이 있다는 점이다.
마이크로서비스 인스턴스는 외부 에이전트에 자산을 등록해야 하며, 이 과정을 서비스 디스커버리(service discovery)라고 한다. 마이크로서비스 인스턴스가 서비스 디스커버리 에이전트에 등록되면, 두 가지 정보를 에이전트에 전달한다.
- 물리적 IP 주소 (또는 서비스 인스턴스의 도메인 주소)
- 애플리케이션이 서비스를 찾을 때 사용되는 논리적 이름
일부 서비스 디스커버리 에이전트는 상태 확인(health check)을 수행하기 위해 호출 가능한 URL을 서비스에 등록하도록 요구한다. 이후, 서비스 클라이언트는 디스커버리 에이전트와 통신하여 서비스의 위치를 파악한다.
3.4.4 마이크로서비스의 상태 전달
서비스 디스커버리 에이전트는 단순히 클라이언트를 서비스 위치로 안내하는 교통 경찰 역할에 그치지 않는다. 클라우드 기반 마이크로서비스 환경에서는 많은 서비스 인스턴스가 실행되고 있으며, 그중 하나가 고장 날 가능성도 존재한다. 서비스 디스커버리 에이전트는 등록된 서비스 인스턴스의 상태를 지속적으로 모니터링하고, 고장 난 인스턴스를 라우팅 테이블에서 제거함으로써 클라이언트가 실패한 인스턴스와 통신하지 않도록 방지한다.
마이크로서비스가 시작된 후, 서비스 디스커버리 에이전트는 해당 서비스가 정상적으로 동작하는지 확인하기 위해 지속적으로 상태 확인 인터페이스를 핑(ping)하며 모니터링을 수행한다.
일관된 상태 확인 인터페이스를 구축하면 클라우드 기반 모니터링 도구를 활용해 문제를 탐지하고 효과적으로 대응할 수 있다. 서비스 디스커버리 에이전트는 장애가 있는 서비스 인스턴스를 감지하면 이를 종료하거나 새로운 인스턴스를 추가하는 등의 조치를 통해 서비스의 정상 상태를 유지할 수 있다.
REST를 사용하는 마이크로서비스 환경에서 상태 확인 인터페이스를 구현하는 가장 간단한 방법은 HTTP 엔드포인트를 통해 JSON 페이로드와 HTTP 상태 코드를 반환하도록 하는 것이다. 스프링 부트를 사용하지 않는 마이크로서비스의 경우, 서비스 상태를 반환하는 이러한 엔드포인트를 개발자가 직접 작성해야 하는 경우가 흔하다.
스프링 부트를 사용하면 엔드포인트를 노출하는 작업이 간단하다. 메이븐 빌드 파일에 스프링 액추에이터(Spring Actuator) 모듈을 추가하기만 하면 된다. 스프링 액추에이터는 서비스 상태를 모니터링하고 관리할 수 있는 기능을 제공하며, 별도의 설정 없이도 바로 사용할 수 있는 운영 가능한 상태 확인 엔드포인트를 포함하고 있다.
3.5 모든 관점 통합하기
클라우드 기반 마이크로서비스는 겉보기에는 간단해 보일 수 있지만, 성공적으로 구현하려면 아키텍트, 개발자, 데브옵스 엔지니어의 관점을 통합하여 전체적인 비전을 달성해야 한다. 각 역할의 관점에서 중요한 요소는 다음과 같다.
- 아키텍트: 비즈니스 문제의 본질적인 구조를 파악하는데 주력해야 한다. 문제 영역을 상세히 정의하고 이야기를 들어보면 잠재적인 마이크로서비스 후보가 자연스럽게 나타날 것이다. 처음부터 지나치게 세분화된 많은 서비스로 시작하기보다는, 더 큰 단위의 마이크로서비스로 시작하여 점차 작은 서비스로 리팩터링하는 접근법이 더 효과적임을 기억하자. 마이크로서비스 아키텍처도 다른 훌륭한 아키텍처와 마찬가지로, 철저히 사전에 계획되기보다는 점진적으로 진화하며 형성된다.
- 소프트웨어 엔지니어: 서비스의 크기가 작다고 해서 좋은 설계 원칙을 무시해서는 안 된다. 각 계층의 역할과 책임을 명확히 분리하여 계층화된 서비스를 설계하는 데 주력해야 한다. 코드에서 프레임워크를 만들려는 유혹을 경계하고, 완전히 독립적인 마이크로서비스를 목표로 삼아야 한다. 서두른 프레임워크 설계와 채택은 장기적으로 애플리케이션 수명 주기 후반에 과도한 유지보수 비용을 발생시킬 위험이 있다.
- 데브옵스 엔지니어: 서비스는 외부와 독립적으로 존재하지 않는다. 서비스의 수명 주기를 초기부터 체계적으로 수립하는 것이 중요하다. 데브옵스 관점에서, 서비스의 빌드와 배포를 자동화하는 방안뿐만 아니라, 서비스 상태를 지속적으로 모니터링하고 문제가 발생했을 때 이를 효과적으로 해결하는 방법에도 중점을 둬야 한다. 일반적으로, 서비스를 운영하는 데 필요한 작업과 고려 사항은 비즈니스 로직을 구현하는 것보다 더 방대하고 복잡할 수 있다.
'스프링 마이크로서비스' 카테고리의 다른 글
6장. 서비스 디스커버리 (2) | 2024.12.12 |
---|---|
5장. 스프링 클라우드 컨피그 서버로 구성 관리 (0) | 2024.12.12 |
4장. 도커 (0) | 2024.12.11 |
2장. 스프링 클라우드와 함께 마이크로서비스 세계 탐험 (4) | 2024.12.10 |
1장. 스프링, 클라우드와 만나다 (4) | 2024.12.09 |