본문 바로가기

엔지니어링(TA, AA, SA)/아키텍처

[MSA] 마이크로서비스 심화(1)

고전적인 소프트웨어 엔지니어링 실무에서는 잘 설계된 시스템의 바람직한 특성으로 높은 응집력과 느슨한 결합력을 강조한다. 이런 특성을 가진 시스템은 유지 및 관리가 쉽고 변화에 대처하기 쉽다. 응집력은 특정 모듈의 내부 구성요소의 결속 정도를 나타내는 반면에, 결합도는 특정 모듈의 한 요소가 다른 모듈의 내부 동작을 아는 정도를 말한다.

 

"같은 이유로 변경되는 것은 함께 모아라. 다른 이유로 변경이 되는 것은 분리하라." - 로버트 마틴

 

모놀리식 애플리케이션에서는 이러한 특성을 클래스나 모듈 또는 라이브러리 수준에서 설계한다. 마이크로서비스 애플리케이션에서는 독립적으로 배포하는 단위의 기능 수준에서 이러한 특성을 반영하려고 한다. 단일 마이크로서비스는 높은 응집력을 가지는데, 애플리케이션 내에서 한 가지 기능을 담당해야하기 때문이다. 마찬가지로 각 서비스가 다른 서비스의 내부 동작을 모를수록 다른 서비스의 변경을 강요하지 않고도 서비스 변경이 쉬워지기 마련이다.

 

이에 따른, 마이크로서비스의 3가지 주요 특성은 아래와 같다.

1. 각 마이크로서비스는 단일 역량을 담당한다. 이는 비즈니스와 관련될 수도 있고 제삼자와의 연계와 같은 공유 기술 역량일 수도 있다.

2. 마이크로서비스는 자신의 데이터 저장소가 있는 경우 데이터 저장소에 대한 오너십을 갖는다. 이는 서비스간 결합력을 줄여주는데, 다른 서비스는 자신이 소유하지 않은 데이터에 접근할때 데이터를 소유한 서비스가 제공한 인터페이스를 통해서만 접근할수 있다.

3. 유용한 활동을 수행하기 위해 일련의 메시지와 동작을 자율적으로 구성하고 협업하는 것을 책임지는 것은 마이크로서비스를 연결하는 메시징 메커니즘이나 다른 소프트웨어가 아닌 마이크로서비스 자신이다.

 

위3가지 특성 외에도 마이크로서비스의 2가지 기본속성이 있다.

1. 각 마이크로서비스는 독립적으로 배포될 수 있다. 그렇지 않으면 마이크로서비스 애플리케이션은 배포 시점에 여전히 모놀리식이 된다.

2. 마이크로서비스는 교체할 수 있다. 이 역량은 자연스럽게 마이크로서비스의 크기를 제한한다. 마찬가지로 이는 서비스의 책임 또는 역할을 이해하기 쉽게 만든다.

 

시스템에서 작업을 조율하는 책임을 진다는 마이크로서비스의 개념은 전통적인 서비스 기반 아키텍처와 마이크로서비스의 결정적인 차이다. 서비스 기반 아키텍처 시스템은 애플리케이션에서 메시징과 조율을 외부로 떼어내기 위해 엔터프라이즈 서비스 버스 또는 더 복잡한 오케스트레이션 표준을 사용한다. 그러한 모델에서는 비즈니스 로직이 서비스 자체보다 서비스 버스에 점진적으로 추가돼 서비스가 대체로 응집력이 부족했다. 

 


분해를 통한 확장

"The Art of Scalability - Abbott/Fisher"에서 확장 큐브를 사용해 확장의 3가지 차원을 정의(규모 확장성 모델)했다. 모놀리식 애플리케이션은 일반적으로 수평적 복제를 통해 확장한다. 즉, 동일한 애플리케이션의 여러 인스턴스를 배포한다. 이것은 쿠키-커터 또는 X-축 확장으로도 알려져 있다. 마이크로서비스 애플리케이션은 다양한 기능의 확장 요건에 따라 시스템을 분해해 Y-축으로도 확장할 수 있다. 

 

Z축은 샤딩(sharding)과 같은 수평적 데이터 파티셔닝을 일컫는다. 마이크로서비스 또는 모놀리식 애플리케이션 접근법 모두에 적용할 수 있다.

 

규모 확장성 모델 - Abbott, Fisher

이러한 이유로 마이크로서비스로 개발할때 도구를 억지로 꿰맞추는 대신 각각의 문제를 해결할 최상의 도구를 선택할 수 있다. 마찬가지로 자율적이고 독립적인 배포는 마이크로서비스의 하위 리소스를 분리할 수 있다는 뜻이다. Y축 확장의 경우, 장애 확산을 제한하는 자연스러운 방법으로 금융 예측 서버에 장애가 발생할 경우 시장 거래 또는 투자 계정 서비스에 확산되지 않는다.

 

마이크로서비스 애플리케이션은 다음과 같은 흥미로운 기술적 속성이 있다.

1. 단일 역량에 따라 서비스를 개발하면 자연스럽게 크기와 책임을 제한한다.

2. 자율성은 서비스를 독립적으로 "개발/배포/확장" 하도록 한다. 

 


핵심 원칙

마이크로서비스 개발을 지원하는 5가지 문화와 아키텍처 원칙( 자율성/회복성/투명성/자동화/정렬성 )이 존재한다. 이런 원칙은 마이크로서비스 애플리케이션을 개발하고 운영할 때 기술 및 조직에 관한 의사결정을 도와준다.

자율성(Autonomy)

마이크로서비스는 자율적이다. 각 서비스는 다른 서비스와 독립적으로 변경되고 운영된다. 자율성을 확실히 하기 위해 다음과 같은 기준으로 설계한다.

 

1) 느슨한 결합

명확하게 정의된 인터페이스를 통해 협업하거나 메시징 시스템을 통해 각 마이크로서비스는 협업하는 다른 마이크로서비스의 내부 구현과 독립적으로 유지된다.

 

2) 독립적으로 배포 가능

서비스는 여러 팀에 의해 종종 동시에 배포된다. 엄격한 규칙에 따라 강제로 배포하거나 배포를 조율하면 위험하고 불안한 배포가 된다. 이상적인 배포는 소규모 서비스로 만들어 신속하게 자주 작게 출시하는 것이다.

 

비즈니스 영향력의 전달을 담당하는 팀에게 서비스의 소유권과 책임을 위임하는 것이 중요하다. 조직의 구성은 시스템 설계에 영향을 준다. 서비스의 소유권이 명화하면 팀이 반복해서 개발하고 자치적인 의사결정을 내릴 수 있다. 정의된 프로토콜을 통해 커뮤니케이션하게 하여 상세 구현을 숨김으로써 서비스를 느슨하게 결합할 수 있다.

 

서비스간 메시지는 언어 중립적이어야 한다. 이를 위해 gRPC, Thrift, JSON+HTTP 등을 사용한다.

회복성 (Resilience)

마이크로서비스는 장애를 격리하는 자연스러운 메커니즘이다. 다시 말해, 마이크로서비스를 독립적으로 배포하면 애플리케이션 또는 인프라스트럭처의 장애는 시스템의 일부에만 영향을 미친다. 새로운 기능을 빅뱅 방식으로 출시하는 것보다 작은 기능으로 배포하는 것이 시스템을 점진적으로 변화시키는데 도움을 준다.

 

애플리케이션을 여러 서비스로 분리해 장애를 격리시킬 수는 있지만, 그럴 경우 장애 지점이 늘어나게 된다. 게다가 장애가 발생할 때 확산을 막으려면 발생한 일을 처리해야 한다. 즉, 가능한 부분에 비동기 처리를 하고 적절한 회로 차단기와 타임아웃을 사용하도록 설계한다. 그리고 입증할 수 있는 지속 전달 기술을 적용하고 시스템 활동을 모니터링한다.

 

투명성(Transparency)

가장 중요한 것은 언제 장애가 발생했는지 아는 것이다. 그리고 마이크로서비스 애플리케이션은 시스템 하나가 아니라 다른 팀이 개발했을 수도 있는 여러 서비스에 의존하고 상호작용한다. 시스템이 어느지점에서나 투명하고 관찰 가능해야 문제를 관찰하고 진단할 수 있다. 애플리케이션의 모든 서비스는 비즈니스, 운영, 인프라스트럭처 메트릭과 애플리케이션 로그, 그리고 요청 추적을 생성할 것이다. 결국 대량의 데이터를 이해해야 한다. 

 

자동화(Automation)

수많은 서비스를 개발하면서 이로 인해 증가하는 애플리케이션의 어려운 점을 완화하려는 것은 다소 직관적이지 않을 것이다. 사실 마이크로서비스는 단일 애플리케이션을 개발하는 것보다 훨씬 복잡한 아키텍처를 가진다. 자동화를 도입하고 서비스간 일관된 인프라스트럭처를 만들면 부가적인 복잡성을 관리하기 위한 비용을 획기적으로 줄일 수 있다. 정확한 배포와 운영을 보장하려면 자동화를 적용해야 한다.

 

마이크로서비스 아키텍처가 데브옵스 기술, 인프라스트럭처 코드화의 도입 증가와 API를 통해 프로그램을 짤 수 있는 AWS/Azure와 같은 인프라스트럭처 환경의 부상과 함께한다는 것은 우연이 아니다.

 

동기화(Alignment)

개발팀의 노력을 올바른 방향으로 동기화하는 것은 매우 중요하다. 서비스가 비즈니스 컨셉과 일치하도록 노력하면 궁극적으로 팀도 동기화되기 마련이다. 많은 전통적인 SOA는 애플리케이션의 기술 계층을 UI, 비즈니스 로직, 통합, 데이터로 분리해서 배포했다. SOA의 수평적 분해(horizontal decomposition)가 문제다. 하나로 묶어야할 기능이 여러 시스템에 퍼져있기 때문이다. 반면에 마이크로서비스 아키텍처는 수직적 분해(vertical decomposition)한다. 즉, 각 서비스가 모든 관련된 기술 레이어를 내포하면서 하나의 비즈니스 역량과 역량된다.

 

또한, 서비스의 사용자를 염두에 둬야 한다. 안정적인 시스템을 보장하기 위해서(명확하게 또는 여러 버전을 유지하면서) 하위호환성을 유지해 다른 팀이 업그레이드하도록 강제하거나 서비스 간의 복잡한 협업을 깨지 않도록 해야 한다.

 


왜 마이크로서비스가 올바른 선택인가?

마이크로서비스 접근법에 대한 확신이 없더라도 마이크로서비스 원칙을 적용하면 비즈니스 문제를 해결할 수 있는 다양한 기술을 선택할 수 있다.

 

시스템의 복잡성이 커지면서 개발의 마찰이 증가한다

이것은 시스템의 복잡성에 관한 것이다. 소프트웨어 개발자는 복잡한 문제를 해결하기 위한 방법을 효과적으로 적시에 만들어내기 위해 노력한다. 그러나 소프트웨어 시스템은 태생적으로 복잡하다. 어떤 방법론이나 아키텍처도 그런 시스템의 근본적인 복잡성을 없앨 수 없다.

 

"소프트웨어 개발의 목적은 긍정적인 비즈니스 영향력을 전달하는데 소요되는 시간을 최소화하는 것이다." - 댄 노스

 

복잡한 소프트웨어 시스템에서 어려운 부분은 변경 요건이 있을때 지속 가능한 방식으로 비즈니스 가치를 전달하는 것이다. 그리고 시스템의 규모가 커지고 복잡해지더라도 계속해서 기민하게 반복적이고 안전하게 요구사항을 반영하는 것이다. 그러므로 잘 설계된 복잡한 시스템이란 마찰과 위험의 2가지 요소가 시스템 전반에 걸쳐 최소화되는 것일 것이다. 마찰과 위험은 개발 속도를 제한하고 고객에게 비즈니스 영향을 전달하는 역량을 제한한다.

 

모놀리식 시스템은 규모가 커지면서 다음과 같은 요소로 인해 마찰이 발생할 수 있다.

 - 여러 변경 사이클이 함께 결합되어 고도의 조율을 요구하고 회귀 테스트의 위험이 증가한다.

 - 유연한 모듈과 컨텍스트 경계는 훈련되지 않은 팀에게는 혼란을 유발해 구성요소 간의 예상치 못한 결합을 만들어낸다.

 - 규모만으로도 고통이 될 수 있다. 지속적인 통합을 위한 작업과 출시는 점점 느려진다.

 

팀 간이든 기존 코드에서든 개발할 때 의존성을 격리시키고 최소화하면 개발자가 빠르게 이동할 수 있다. 모놀리식 애플리케이션에서 과거에 결정된 장기적인 종속성이 줄어들면 개발은 병렬로 움직일 수 있다. 또한 기술적 부채는 자연스럽게 서비스 경계로 국한된다.

 

마이크로서비스는 모놀리식 애플리케이션보다 개별적으로 쉽게 개발하고 추론할 수 있다. 이는 성장하는 조직에서 개발 생산성에 도움이 된다. 또한 규모의 증가에 대처하거나 새로운 기술을 원활하게 도입할 수 있는 강력하고 유연한 패러다임을 제공한다.

 

- 소규모의 자율적인 서비스를 구축하면 수명이 긴 복잡한 시스템 개발 과정에서 마찰을 줄여준다.

- 응집되고 독립적인 기능 조각을 전달하면 변경이 쉽고 유연한 시스템을 구축할 수 있고 위험 감소와 함께 지속 가능한 비즈니스 영향을 제공할 수 있다.

 

가장 성공적인 마이크로서비스 마이그레이션은 비즈니스 요구사항, 우선순위, 자원의 제약에 따라 균형있게 아키텍처 비전을 세워 조금씩 이동하는 것이다. 이 방식이 시간이 오래 걸리고 더 많은 엔지니어링 노력이 들겠지만, 마이크로서비스 실패 사례에 이르게 하는 가능성을 줄여줄 것이다.

 


무엇이 마이크로서비스를 어렵게 만드는가?

마이크로서비스가 분해와 분산을 통한 유일한 아키텍처는 아니지만, SOA와 같은 것은 전반적으로 성공적이지 못하다고 받아들여진다. SOA의 원칙이 마이크로서비스와 유사하기는 하지만, SOA의 정의는 ESB와 같은 무거운 엔터프라이즈 벤더 도구와 밀접하게 관련되어 있다.

 

마이크로서비스는 시스템에서 움직이는 부품의 수를 대폭 늘린다. 그것은 기능과 데이터의 소유권을 자율적인 여러 서비스에 분산해 애플리케이션의 안정성과 정상적인 운영의 책임도 마찬가지로 분산시킨다.   

 

마이크로서비스 애플리케이션을 설계하고 운영하다보면 다음과 같은 문제에 직면할 수 있다.

 - 마이크로서비스의 범위를 정하고 식별하는 데는 대상 도메인에 대한 상당한 지식이 필요하다.

 - 서비스 간의 올바른 경계와 계약을 식별하는 것이 어렵고, 한번 정한 후 변경하는데 많은 시간이 소요된다.

 - 마이크로서비스는 분산 시스템이므로 시스템의 상태, 일관성, 네트워크 신뢰성에 대해 다른 가정을 전제해야 한다.

 - 시스템 구성요소를 여러 네트워크에 분산하고 혼재된 기술의 수가 늘어나면 마이크로서비스에는 새로운 형태의 장애가 생긴다.

 - 정상 상태에서는 무엇이 발생해야 하는지 이해하고 검증하는 것이 더욱더 어렵다.

 

설계상의 어려움

서비스가 자율적으로 되려면 전체적으로는 느슨하게 연결되고 개발적으로는 기능 요소들이 높은 응집도를 가지도록 설계해야 한다. 이는 점진적으로 진화하는 과정이다. 서비스 범위는 시간이 지남에 따라 변화할 수 있고, 기존 서비스에서 새로운 기능을 떼어내거나 제거할지를 선택해야 한다. 서비스간 경계는 느슨한 결합에서 가장 중요하다. 이것이 잘못되면 서비스는 변화에 저항하게 되고, 전체적으로 애플리케이션의 순응성과 유연성이 떨어지게 된다.

 

마이크로서비스의 범위를 정하는 것은 도메인 지식을 요구한다.

각 마이크로서비스는 단일 역량을 담당한다. 이런 역량을 도출하는 것은 애플리케이션에 대한 비즈니스 도메인 지식을 요구한다. 애플리케이션 수명의 초기에 설계자가 갖고 있는 도메인 지식은 노력을 해도 불완전하고 최악의 경우 부정확할 수 있다.

 

대상 도메인에 대한 부적절한 이해는 나쁜 디자인을 선택하게 만든다. 마이크로서비스 애플리케이션에서는 모놀리식 애플리케이션 내의 모듈에 비해 서비스 경계가 더욱 견고해진다. 이는 범위가 잘못 결정되면 후속 공정 비용이 증가할 가능성이 높아진다는 뜻이다.

 - 여러 코드 베이스에 걸쳐서 리팩토링을 해야할 수 있다.

 - 서비스의 데이터베이스에서 다른 서비스로 데이터를 이관해야 할 수 있다.

 - 서비스 간에 내포된 의존성을 식별하지 못했을 수 있다. 이것은 배포할 때 에러와 호환성의 문제를 야기할 수 있다.

 

서비스간 계약 유지하기 

각 마이크로서비스는 다른 서비스와 독립적으로 구현하고, 이는 기술의 혼재와 자율성을 가능하게 한다. 이를 위해 각 마이크로서비스는 계약을 노출한다. 이것은 서비스가 수신하고 응답하기를 기대하는 메시지를 정의하는 것으로 객체 지향 설계에서 인터페이스와 유사하다.

 - 완전성: 상호작용의 완전한 범위를 정의한다.

 - 충분성: 필요 이상의 정보를 제거해 메시지 소비자가 적절한 범위에서 메시지를 구성할 수 있게 한다.

 - 예측성: 모든 구현의 실제 행동을 정확하게 반영한다.

 

서비스는 계약으로 엮인다. 시간이 지나면서 계약은 기존의 API를 사용하는 협력자와의 하위 호환성을 유지하면서 진화해야 한다. 이러한 안정성과 변화 간의 긴장을 처리하기가 쉽지 않다.

 

마이크로서비스 애플리케이션은 분산 시스템이다.

마이크로서비스 애플리케이션을 설계하는 것은 분산 시스템을 설계하는 것을 의미한다. 분산시스템을 설계할때는 다음과 같은 오해를 하기 쉽다. ( 네트워크를 신뢰할 수 있다 / 대기시간이 없다 / 대역폭이 무한하다 / 전송 비용이 없다 )

 

비분산시스템에서 했던 메소드 호출의 속도와 신뢰성 같은 가정은 더이상 적절치 않으며 나쁘고 불안정한 구현을 야기할 수 있다. 즉, 애플리케이션 간의 상태에 대한 대기시간, 신뢰성, 일관성을 고려해야 한다.

 

일단 애플리케이션이 분산되면 하위의 상태 데이터가 곳곳에 분산되어 일관성 문제에 직면한다. 시스템에서 동작의 순서를 보장하기 어려울 수 있다. 동작이 여러 서비스에 걸쳐 수행되면 ACID와 같은 트랜잭션 보장을 유지하기가 어려울 수 있다. 이는 애플리케이션 수준의 설계에 영향을 준다. 일관적이지 않은 상태에서 서비스가 동작하는 방법과 트랜잭션이 실패할 때 롤백하는 방법을 고려해야 한다.

 

운영상의 도전과제

마이크로서비스 접근 방식은 본질적으로 시스템에 장애가 발생할 수 있는 지점을 증가시킨다. 애플리케이션이 운영 중이라면 다음 질문에 대한 답변을 생각해보자.

 - 문제가 발생해서 사용자의 주문이 제출되지 않으면 어디에서 오류가 발생했는지 어떻게 결정할 것인가?

 - 주문에 영향을 주지 않고 새로운 버전의 서비스를 어떻게 배포할 것인가?

 - 어떤 서비스가 호출돼야 했는지를 어떻게 알 것인가?

 - 어떤 동작이 여러 서비스에 걸쳐 올바르게 작동하는지 어떻게 테스트할 것인가?

 - 서비스를 사용할 수 없으면 무슨 일이 발생하는가?

 

마이크로서비스는 위험을 제거하는 대신 이런 비용을 시스템의 라이프사이클 후반부로 미룬다. 그래서 개발할때는 마찰이 줄지만, 운영 중인 애플리케이션을 배포/검증/모니터링하는 방법이 더 복잡해진다.

 - 네트워크/라우터 장애: 네트워크 문제가 사용자/서비스/의존성 사이에 요청 전달이 실패하는 문제를 일으킨다.

 - 과부하: 서비스 인스턴스가 요청을 너무 많이 받아 응답하지 못하거나 시간제한에 걸린다.

 - 하드웨어 장애: 데이터베이스 또는 서비스 인스턴스를 실행하는 하드웨어에 장애가 발생한다.

 - 하위 의존성 장애: 의존하는 서비스가 실패하거나 응답이 느려질 수 있다.

 - 제삼자 장애: 의존하는 제삼자로의 요청이 실패할 수 있다.

 

마이크로서비스 접근방식은 시스템을 설계할 때 점진적인 방식을 권장한다. 기존 서비스를 변경하지 않고 새로운 기능을 독립적으로 추가할 수 있다. 이것은 변경의 비용과 위험을 줄여준다.

 

그러나 계속해서 변화하는 시스템을 느슨하게 결합하면 전체 그림을 추적하는 것이 극도로 어려워져서 문제를 진단하고 지원하기가 더욱 어려워진다. 뭔가 잘못되면 시스템이 어떻게 행동했는지(무슨 서비스를 호출했고 어떤 순서로, 무슨 결과가 나왔는지)에 대해 추절할 방법이 필요하다. 그리고 시스템이 어떻게 반응해야 했는지 알 방법이 필요하다. 결국, 마이크로서비스에서는 관측 가능성과 장애 지점의 증가라는 운영상의 어려움에 직면하게 된다.

 

왜 마이크로서비스 애플리케이션에서는 투명성을 확보하기가 더 어려운 것일까?

마이크로서비스의 경우, 전체 그림을 이해하기가 더 어렵기 때문이다. 비즈니스 성과를 내기 위한 광범위한 컨텍스트에서 각 서비스가 무엇을 하는지 확실히 이해하기 위해 각 서비스가 발생시키는 데이터의 상관관계를 분석하고 여러 퍼즐 조각을 맞춰 전체 그림을 조립할 필요가 있다. 개별 서비스의 로그는 시스템 운영의 일부를 보여주는데 도움이 되지만, 시스템 전체를 이해하려면 현미경과 광각렌즈 모두를 이용해야 한다.

 

마찬가지로 배포 정책에 따라 여러 애플리케이션을 운영하기 때문에 애플리케이션과 인프라의 메모리, CPU사용량과 같은 메트릭의 상관관계가 덜 명확할 수 있다. 그래서 이러한 메트릭은 여전히 유용하지만, 모놀리식 시스템에서보다는 덜 집중하게 된다.

 

서비스가 많아지면 장애 지점도 증가한다. 

시스템을 구성하는 여러 서비스가 약하고 깨지기 쉽다고 생각하면 문제가 발생했을때 너무 놀라지 않고 시스템을 설계하고 배포하고 모니터링하는 더 나은 방법을 배울 수 있다. 개별 구성요소에 장애가 발생해도 어떻게 하면 시스템이 계속해서 동작할지에 대해 생각해 봐야 한다. 이것은 개별 서비스가 에러 검사, 장애 조치, 복구 등에서 좀 더 견고해질 필요가 있다는 의미다. 또한 개별 구성요소는 100% 신뢰할 수 없어도 전체 시스템은 신뢰성이 있어야 한다.

 


마이크로서비스 개발 라이프사이클

마이크로서비스를 개발할때도 일반 애플리케이션을 개발할때 적용하는 것과 비슷한 프레임워크 기법을 사용한다. 그러나 시스템 수준에서는 마이크로서비스 아키텍처를 선택하는 것이 애플리케이션을 설계하고 운영하는 방법에 상당한 영향을 준다. 마이크로서비스 애플리케이션의 개발 라이프사이클에서 3가지 핵심단계( 서비스 설계하기 / 운영으로 배포하기 / 시스템 관측하기 )를 집중해서 살펴보는 것은 중요하다.

 

각 단계에서 타당한 의사결정은 요구사항이 변경되고 복잡도가 증가해도 회복력있는 시스템을 개발하는데 도움을 준다. 각 단계를 살피고 마이크로서비스 애플리케이션을 구축할 때 필요한 절차를 생각해보자.

 

마이크로서비스 설계하기

마이크로서비스 애플리케이션을 개발할 때는 모놀리식 애플리케이션을 개발할때 마주치지 않았을 몇가지 설계에 관한 의사결정을 해야 한다. 모놀리식 애플리케이션에서는 종종 3계층 아키텍처 또는 MVC와 같은 유명한 패턴이나 프레임워크를 따른다. 그러나 마이크로서비스를 설계하는 기법은 여전히 초기단계에 있다.

 

마이크로서비스를 설계할 때는 다음과 같은 것을 생각해야 한다.

 - 모놀리식으로 시작할 것인지, 아니면 마이크로서비스로 시작할지

 - 애플리케이션과 외부 사용자에게 노출할 관문(facade)에 대한 전반적인 아키텍처

 - 서비스 경계를 식별하고 규정하는 방법.

 - 동기식 또는 비동식과 같은 방식으로 서비스가 서로 커뮤니케이션하는 방법.

 - 서비스의 회복성을 달성하는 방법.

 

다뤄야할 범위가 상당히 넓다. 마이크로서비스 애프리케이션을 잘 설계하려면 모든 항목에 관심을 둬야 한다.

 

모놀리식 애플리케이션으로 시작할 것인가?

모놀리식으로 개발을 시작해야 하는 이유는 개발 초기에 시스템의 경계를 이해하기 어렵고 마이크로서비스 애플리케이션에서 잘못 설계한 경우 비용이 증가하기 때문이다. 반면에 모놀리식에서 설정한 경계는 잘 설계된 마이크로서비스 애프리케이션에서의 경계와 같지는 않다.

 

개발 초기의 개발 속도는 느리지만, 마이크로서비스는 향후 개발에서 마찰과 위험을 줄여준다. 마찬가지로, 도구와 프레임워크가 성숙하면서 마이크로서비스 모범 사례를 채택하는 데 부담이 점점 줄어들고 있다.

 

서비스 범위 정하기

각 서비스가 담당하는 범위를 올바르게 정하는 것은 마이크로서비스 애플리케이션을 설계하는데 있어 어려운 일 중 하나다. 이를 위해 서비스가 조직에 제공하는 비즈니스 역량에 근거해 서비스를 모델링해야 한다.

 

새롭고 특별한 유형의 주문을 다루려면 서비스를 어떻게 변경해야 할까?

 1. 기존 서비스 인터페이스를 확장하기

 2. 새로운 서비스 종단점(endpoint)를 추가하기

 3. 새로운 서비스를 추가하기.

 

선택지마다 장단점이 있는데, 이것이 애플리케이션의 서비스 간의 응집력과 결합력에 영향을 미친다. 기능의 범위를 정할때 기존 서비스에 포함할지 또는 새로운 서비스를 설계할지 결정해야 한다.

 

커뮤니케이션

서비스 간의 커뮤니케이션에는 동기식 또는 비동기식이 있다. 동기식 시스템이 추론하기 쉽기는 하지만, 비동기식 시스템은 결합도가 상당히 낮아서 변경의 위험을 줄여주고 잠재적으로 회복성이 더욱 뛰어나다. 하지만 이런 시스템은 복잡도가 높다. 마이크로서비스 애플리케이션에서는 동기식과 비동기식 메시징 사이에 균형을 맞춰서 여러 마이크로서비스 사이에 효과적으로 작업을 조율해야 한다.

 

회복성

분산 시스템에서 서비스가 협업하는 다른 서비스를 신뢰할 수 없는 이유는 서비스 간 네트워크 또는 서비스의 행동이 신뢰할 수 있거나 예측 가능하다고 안전하게 가정할 수 없기 때문이다. 이것을 달성하기 위해서는 에러가 발생했을 때 백오프를 하거나 품질이 낮은 서비스로부터의 요청을 제한하거나 정상 서비스를 동적으로 탐색하는 등의 방어적 설계를 해야 한다.


마이크로서비스 배포하기

마이크로서비스를 구축할때는 개발/운영이 밀접하게 엮여 있다. 수많은 자율적인 서비스로 구성된 시스템에서는 개발한 사람이 운영도 해야 한다. 서비스가 운영에서 어떻게 동작하는지 이해하면 시스템의 성장에 따라 더 좋은 설계 결정을 내리는데 도움이 된다.

 

애플리케이션을 특별하게 하는 것은 시스템이 전달하는 비즈니스 영향임을 명심해야 한다. 그것은 여러 서비스 협업을 통해서 나온다. 사실 각 서비스가 제공하는 독특한 기능 이외의 것들을 모두 표준화하거나 추상화하면 팀이 비즈니스 가치에 더욱 집중할 수 있다. 궁극적으로 새로운 서비스를 배포하는 것이 더이상 특별한 이벤트가 아닌 단계에 도달하게 된다. 그렇지 않으면, 고객에게 가치를 전달하는 대신 시스템을 구성하는데 모든 에너지를 쏟게 된다. 

 

혁신을 가속화하기 위해 새로운 서비스를 배포하는 비용은 작아야만 한다. 마찬가지로 시스템 운영르 간단하게 하고 서비스 간 일관성을 유지하기 위해 프로세스를 표준화해야 한다. 이를 위해 다음 사항을 고려해야 한다.

(마이크로서비스 배포 산출물 표준화 / 지속적인 전달 파이프라인 구현)

 

마이크로서비스 배포 산출물 표준화

모든 언어와 프레임워크는 전용 개발 도구가 있다. 예를 들어, 파이썬에는 Fabric, 루비에는 Caspistrano, 엘릭서에는 Exrm이 있다. 그리고 개발환경 자체가 복잡하다. 실행 중에 애플리케이션의 의존성은 광범위하며 라이브러리, 바이너리, OS 패키지, OS 프로세스를 포함할 수 있다.

 

기술적으로 혼재성은 서비스 자율성이 주는 혜택이다. 그러나 배포는 쉽지 않다. 일관성이 없으면 운영 환경으로 서비스를 이관하는 방식을 표준화할 수 없어서 배포를 관리하고 새로운 기술을 도입하는 비용을 증가시킨다. 애플리케이션은 운영 API를 노출하고 라이브러리/바이너리 의존성/프로세스 지원 등 다양한 유형의 의존성을 가진다.

 

이 작업에 적합한 도구는 컨테이너다. 컨테이너는 호스트 상에 격리된 시스템을 실행하는 것을 지원하는 운영체제 수준의 가상화 도구로 각 컨테이너는 자신의 네트워크와 프로세스 공간을 가지고 다른 컨테이너와 동일한 커널을 공유한다. 컨테이너는 가상 머신에 비해 빠르게 구축하고 실행할 수 있다. 하나의 머신에 여러 컨테이너를 실행할 수 있어 로컬 개발을 간단하게 하고 클라우드 환경에서 자원 사용을 최적화하도록 돕는다.

 

컨테이너는 애플리케이션의 패키징과 애플리케이션의 실행 환경 인터페이스를 표준화한다. 그리고 운영 환경과 코드 모두에 대해 불변성을 제공한다. 이것이 컨테이너를 고수준의 구성을 위한 강력한 빌딩 블록으로 만들어 준다. 이를 활용하면 모든 서비스의 실행 환경을 완전하게 정의하고 격리할 수 있다.

 


마이크로서비스 관찰하기

운영환경에서는 시스템에 무슨일이 일어나고 있는지를 알아야 한다.

 - 시스템에서 깨지기 쉬운 구현을 능동적으로 식별하고 리팩터하기를 원한다.

 - 시스템이 어떻게 행동하는지를 이해할 필요가 있다.

 

마이크로서비스 애플리케이션을 완전히 모니터링하는 것은 상당히 어렵다. 단일 트랜잭션이 여러 다른 서비스에 걸쳐 있기 때문이다. 기술적으로 혼재된 서비스는 혼란스러운 형식으로 데이터를 생성할 수 있다. 운영 데이터는 단일 모놀리식 애플리케이션에 비해 훨씬 클 가능성이 있다. 그러나 시스템의 동작 방식을 이해하고 가까이 관찰하면 이런 복잡성에도 불구하고 시스템을 효과적으로 변경할 수 있게 된다.

 

잠재적으로 깨지기 쉬운 구현을 식별하고 리팩터하기.

시스템은 버그나 실행 환경 에러, 네트워크 장애, 하드웨어 문제 등으로 실패하게 돼 있다. 시간이 지나면서 알려지지 않은 버그와 에러를 제거하는 비용이 에러가 발생했을 때 신속하고 효과적으로 대응하는 비용보다 높아진다.

 

모니터링과 알림 시스템은 문제를 진단하고 무엇이 문제를 일으켰는지 결정할 수 있게 도와준다. 경보를 받으면 다른 데이터 센터에 컨테이너를 띄우거나 서비스에 더 많은 컨테이너를 실행해서 부하 문제를 조치하는 등의 자동 조치 메커니즘을 구성할 수 있다.

 

이런 장애의 영향을 최소화하고 시스템에 전파되는 것을 방지하기 위해 장애를 국소화하는 방향으로 서비스간 의존성을 설계할 수 있어야 한다. 서비스 하나가 중다노디더라도 전체 애플리케이션이 중단되지 않도록 해야 한다. 장애는 언제든 발생할 수 있다는 것을 인식하고 애플리케잇녀에서 장애가 발생할 수 있는 지점에 대해 적절한 대응을 준비하는 것이 중요하다.

 

수백 개의 서비스에 걸친 동작 이해하기.

서비스의 동작을 이해하려면 설계와 구현의 투명성에 우선순위를 둬야 한다. 로그와 메트릭을 수집하고 분석과 경보를 위해 한곳에 저장하면 시스템의 행동을 모니터링하고 조사할 때 정보를 한곳에서 얻을 수 있는 시스템을 구축할 수 있다. 

 

서비스 주변으로 여러 층의 도구가 있는데, 비즈니스 메트릭/애플리케이션 로그/운영 메트릭/인프라스트럭처 메트릭 등은 관측 가능성을 제공하는 기능이다. 시스템으로 들어오는 각 요청은 이 계층을 통해 추적할 수 있다. 이 과정에서 수집된 데이터는 분석과 경보를 위해 운영 데이터 저장소에 저장할 수 있다.

 

로그와 메트릭에 관련된 여러 층의 도구들이 비즈니스 역량을 담당하는 마이크로서비스를 둘러싸고 요청을 마이크로서비스로 전달하고 응답을 반환하는 과정에서 데이터가 수집되어 운영 데이터 저장소에 저장된다.