Yunseok's Dev Blog

배운 것을 적는 블로그입니다.

마이크로 서비스에서 신뢰할 수 있는 서비스 설계하기

마이크로 서비스 인 액션 6장을 읽고 정리해보았습니다.

신뢰성 정의하기

  • 가동시간(uptime): 서비스가 성공적으로 동작한 시간
  • 중단시간(downtime): 서비스가 작업을 못한 시간
  • 가용성: 동작한 가동 시간의 백분율

가용성은 신뢰성의 척도가 된다.

마이크로 서비스에서 전체 애플리케이션은 각각 구성요소보다 신뢰성이 떨어진다. 예를들면 하나의 서비스가 의존하고 있는 체인이 6개이고 각각의 신뢰성은 99.99%라고 하자. 그러면 전체의 신뢰성은 0.9999^6은 0.94가되어 94%의 신뢰성을 가지게 된다. 즉 체인에 참여하는 모든 가용성 수치를 곱하면 전체 시스템의 실패율을 예측할 수 있다.

가용성을 극대화하거나 낮은 신뢰성의 영향을 고립시켜서 가용성을 보장하는 것은 매우 중요하다. 가용성을 측정하는 것이 신뢰할 수 있는 서비스를 만드는 방법을 알려주지는 않지만 목표를 제시해 준다. 서비스 개발과 그 서비스를 사용하는 서비스와 엔지니어의 기대치 모두에 목표를 제시한다.

다음 3가지 목표를 방어적으로 설계할 필요가 있다.

  • 피할 수 있는 장애의 발생률 줄이기
  • 예측할 수 없는 장애가 전파되거나 시스템 전체에 영향을 주는 것을 제한하기
  • 장애가 발생하면 즉시 복구하고, 이상적으로는 자동으로 복구하도록하기

잠재 위험 찾아내기

복잡한 시스템에서 장애는 불가피하다. 애플리케이션이 감당할 수 있는 다양한 장애의 유형을 이해하여 적절한 완화 전략을 설계하고 장애가 발생했을 때 즉각 대응할 수 있도록 해야한다.

실용적인 것이 중요하다. 모든 가능한 장애의 원인을 예측하거나 제거할 수는 없다. 회복성을 설계할 때 다음을 고려하여 균형을 맞출 필요가 있다.

  • 주어진 시간과 제약조건
  • 보호 솔루션의 설계, 개발, 배포, 운영을 위한 비용
  • 비즈니스와 고객 기대치의 속성

책임 있는 서비스 설계자라면 가능한 장애 유형을 식별하고 기대하는 주기와 영향도에 따라 순위를 매긴 후 어떻게 그 영향에 대처할지 결정해야 한다.

장애의 원인

서비스와 다른 구성요소 간에 상호작용하는 모든 지점이 장애가 발생할 수 있는 영역이다. 장애는 네가지 주요 영역에서 발생할 수 있다.

  • 하드웨어: 서비스가 동작하는 하부의 물리적 또는 가상의 인프라스트럭처
    • 서비스가 공개 클라우드, 사설 클라우드, PaaS 상에서 실행되는지 아닌지에 상관없이 서비스의 신뢰성은 이들을 지탱하는 물리적 및 가상의 인프라스트럭처에 의존할 것이다.
    • 하드웨어 구성요소의 장애는 조직 내에서 여러 서비스의 운영에 영향을 준다.
    • 일반적으로 시스템에 적절한 수준의 다중화를 설계해 하드웨어 장애 영향에 대처할 수 있다.
    • 애플리케이션을 위한 중복의 올바른 수준을 선택하려면 장애의 빈도 및 영향도 대비 잠재적으로 드믄 이벤트에 대처하는 비용을 비교해 신중하게 판단해야 한다.
  • 커뮤니케이션: 서비스 간 또는 제삼자 간의 협업
    • 네트워크, DNS, 메시징, 방화벽 모두 장애를 유발할 수 있다.
    • 구성 변경을 철저히 테스트하고 에러가 발생하면 쉽게 롤백할 수 있어야 한다.
  • 의존성: 의존하는 서비스 내에서의 장애
    • 의존하는 다른 서비스에서 발생하거나 데이터베이스 같은 내부 의존성에서 발생할 수 있다.
    • 설계와 빌드의 실패로 인해 의존성에러가 발생하기 쉽다.
  • 내부: 엔지니어에 의해 발생하는 결함과 서비스 자체의 코드에서 발생하는 에러
    • 개발과 배포 과정에서 부적절하거나 제한된 엔지니어링 실무로 인해 운영환경에서 장애가 발생할 수 있다.

장애의 전파

애플리케이션은 서로 끊임없이 상호작용하는 여러 서비스로 이루어지기 때문에 서비스 하나의 장애는 전체 시스템으로 퍼질 수 있다. 장애 전파는 분산 애플리케이션에서 일반적인 장애의 형태다.

과부하는 도미노 현상을 발생시킬 수 있다. 장애 전파는 부하가 가장 일반적인 원인의 하나지만 부하에 의해서만 유발되지는 않는다. 일반적으로 증가된 오류율 또는 느린 응답 시간이 서비스를 건강하지 못하게 만들 수 있고 서로 의존하는 여러 서비스에 장애를 증가시킬 수 있다. 장애를 제한하기위해 회로 차단기, 폴백, 부하 검사와 용량 계획, 백오프가 재시도, 적절한 타임아웃등이 있다.

신뢰할 수 있는 커뮤니케이션 설계하기

실패를 피할 수 없다면 실패가 발생했을 때 가용성을 극대화하고 정확하게 동작하며 신속히 복구하는 서비스를 설계하고 구축해야 한다.

재시도

장애는 격리되거나 지속될 수 있지만, 각각 서비스는 이 하나의 요청이 어떤 경우인지 알 수 없다. 장애가 격리되고 과도기적 현상이라면 재시도가 합리적인 선택이다. 하지만 재시도 예산을 고려하는 것이 중요하다. 장애가 지속되어 시장 데이터 서비스의 용량이 감소된다면 이후의 호출은 문제를 악화시키고 시스템을 더욱 불안하게 할 것이다.

기하급수적 백-오프 전략을 사용하여 가변적인 시간 간격을 두고 성공적인 재시도를 하여 재시도가 고르게 퍼지도록 하고 재시도 부하를 빈도를 줄일 수 있다.

재시도는 간헐적인 의존성 오류를 견디는 데 효과적이지만, 하위 이슈를 악화시키거나 불필요하게 자원을 소모하지 않도록 조심해야 한다.

  • 항상 최대 재시도 횟수를 제한한다.
  • 기합급수적 백-오프 지터를 사용해 재시도 요청을 부드럽게 분산시키고 부하가 몰리는 것을 회피한다.
  • 여러 에러 조건에서 재시도를 해야 할지, 그래서 어떤 재시도가 실패할 것 같은지를 고려한다.

서비스가 재시도 제한에 걸리거나 재시도를 할 수 없으면 실패를 받아들이거나 요청을 처리할 다른 방법을 찾아야 한다.

폴백

의존하는 서비스가 실패할 경우 4가지 폴백 옵션이 있다.

  • 우아한 서비스의 저하
    • 수용할 만한 서비스의 저하를 설계할 수 있다.
  • 캐싱
    • 전담 캐시에 저장해놓을 수 있다.
  • 기능 중복
    • 다른 서비스로 폴백하여 동일한 기능을 달성할 수 있다.
    • 지리적으로 분산해 배포한 경우 다른 리전에 배포된 서비스로 폴백할 수도 있다.
  • 대체 데이터
    • 대체 데이터를 폴백에 사용할 수 있다.

타임아웃

상호작용에 적절한 데드라인을 설정하면 자원 소모 시간을 제한할 수 있다.

회로 차단기

장애 서비스로의 요청을 멈춰 장애가 확산되는 것을 방지한다.

  • 원격 커뮤니케이션은 오지 않을 응답을 기다리는 데 자원을 소모하지 말고 문제가 발생하면 빠르게 실패해야 한다.
  • 의존 서비스가 계속해서 실패하면 복구될 때까지 더이상 요청하지 않는 것이 낫다.

목표 서비스의 예상 신뢰도와 서비스 간의 상호작용 규모를 고려해 조심스럽게 고려해서 시간 간격과 임계치를 설정해야 한다. 회로가 열리고 닫힐 때를 모니터링해야 할 뿐만 아니라 특히 회로가 자주 열릴 경우 담당팀에 알림을 보내야 한다. 연결이 정상 상태인지 결정하기 위해 회로 차단기는 시험 요청을 보내야 한다. 시험 상태에서 회로는 반만 열린다. 시험이 성공하면 회로는 닫힌다.

비동기 커뮤니케이션

즉각적이고 일정한 응답이 필요하지 않은 경우라면 더욱 복잡해지는 비즈니스 로직의 비용을 감수하고서라도 이 기법을 사용해 서비스 간 직접 커뮤니케이션을 줄여서 전체적인 가용성을 증가시킬 수 있다. 커뮤니케이션 브로커 자체가 단일 장애 지점이므로 확장과 모니터링, 효율적인 운영을 위한 조심스러운 관심이 필요할 것이다.

서비스 신뢰성 극대화 하기

부하 분산기

  • 하위의 어느 인스턴스가 정상 상태이고 요청을 처리할 수 있을지 식별
  • 요청을 서비스의 다양한 인스턴스로 전달

부하 분산기는 상태 점검을 실행하고 결과를 사용하는 것을 담당한다. 실제 질의하는 순간이 아니라 항상 하위 애플리케이션의 상태를 이해할 수 있는 방법이 있어야 한다. 모든 서비스에 적절한 상태 점검 방법을 구현해야 한다. 비동기식 서비스에서는 큐와 컨슈머간의 연결을 테스트하기 위해 하트비트 메커니즘을 사용할 수 있다.

상태 점검은 2가지 기준으로 분류해야 한다.

  • 생존 여부
    • 애플리케이션이 시작되어 정상적으로 운영 중인지 간단히 점검하는 것
  • 서비스 가능 여부
    • 서비스가 요청을 처리할 준비가 됐는지 점검하는 것
    • 생존 여부가 서비스가 성공하리라는 것을 나타내지 않는다.

비율 제한

상위서비스가 단일 일괄 처리 호출이 더 적절한 곳에 여러 호출을 하거나 가용 리소스가 호출자에게 고르게 분산되지 않았을 수 있다. 제 3자 의존 서비스가 의존하는 다른 서비스가 부여한 제한에 걸릴 수 있다. 적절한 해결 방법은 협업 서비스가 일정 시간 범위 내에서 사용할 수 있는 요청의 비율 또는 전체 요청 수를 명시적으로 제한하는 것이다. 이렇게 하면 특히 수많은 협업 서비스를 가지고 있는 서비스가 부하를 받지 않도록 도움을 준다. 비율 제한은 서비스의 클라이언트에 설계할 때 공유하거나 운영 중에 공유하면 더 좋다. 서비스가 요청을 반환할 때 남아 있는 사용 가능한 요청의 수를 헤더에 담아 컨슈머에게 제공할 수 있다. 호출자는 이 정보를 이용해 요청의 비율을 조정한다. 이 기술을 백프레셔라고 한다.

신뢰성 검증과 장애 내성

서비스가 장애를 견딜 수 있고 우아하게 회복할 수 있는지 검증해야 한다. 철저한 테스트는 예측할 수 있거나 예측할 수 없는 장애가 발생했을 때 선택한 설계가 효과적인지를 확신하도록 도와준다.

  • 부하 테스팅
    • 예상 증가량과 트래픽의 형태를 모델링해서 버시의 예상 사용량을 이해한다.
    • 그 트래픽을 위한 용량을 산정한다.
    • 이런 제한에 따라 부하 테스팅을 수행해 서비스의 용량을 검증한다.
    • 용량을 다시 선정할 때 적절한 비즈니스와 서비스 메트릭을 사용한다.
    • 개별 서비스 수준에서는 전달 파이프라인의 일부로 각 서비스의 부하 테스팅을 자동화할 수 있다.
    • 서비스도 포함해 부하 테스트를 한다. 이것은 서비스와의 상호작용에서 오는 특이한 부하 패턴과 병목 지점을 식별하는 데 도움을 준다.
  • 카오스 테스팅
    • 많은 장애는 마이크로 서비스 내부에서 발생하지 않는다.
    • 네트워크 장애, 가상 머신 장애, 응답 없는 데이터베이스처럼 장애는 도처에 있다.
    • 운영환경에서 장애가 발생하도록 한다.
    • 불안정과 장애를 유발해 실제 시스템 장애를 정확하게 흉내 낼 뿐만 아니라 엔지니어링팀이 이런 장애에 대응할 수 있도록 훈련시킨다. 이는 궁극적으로 시스템이 실제 혼란을 견딜 수 있다는 확신을 갖게 하는데, 카오스 테스팅을 통해 시스템의 회복성을 꾸준히 개선하고 운영에 영향을 줄 수 있는 요인을 지속적으로 줄여나가기 때문이다.
    • 정상적인 시스템 운영 시에 측정 가능한 일정한 상태를 정의한다.
    • 실험군과 대조군에서 행동이 유지될 것이라고 가정한다. 시스템은 유발된 장애로부터 회복될 것이다.
    • 실제 장애를 유발한다. 예를들어 서버 또는 서버의 네트워크 연결을 제거하거나 높은 대기 시간을 유발한다.
    • 세운 가설이 틀렸다는 것을 증명하려고 시도한다.
    • 트랜잭션 서비스가 실행되는 노드를 예고 없이 내린다.
    • 무작위로 보유 인스턴스를 내려 용량을 줄인다.
    • 네트워크 연결을 끊는다.

정기적이고 조직적으로 카소트 이벤트에 대해 시스템을 검증하고 발견된 이슈를 해결하면 애플리케이션의 장애 회복성에 대한 상당한 수준의 확신을 얻게 될 것이다.

기본적으로 안전하게

장애가 발생한 후에야 중요한 기능이 그 장애를 견디지 못한다는 사실을 발견하는 것을 원치 않을 것이다. 프레임워크와 프락시는 여러 서비스 간의 커뮤니케이션 표준에 적용하는 2가지 기술적 접근 방법으로 서비스 커뮤니케이션의 회복성과 기본적으로 인접한 것을 보장해 엔지니어가 올바른 것을 하기 쉽게 한다.

프레임워크

라이브러리를 사용해 서비스 간의 상호작용을 표준화하는 것은 다음의 장점들이 있다.

  • 서비스 상호작용에 개별적인 방식을 피함으로써 애플리케이션의 전반적인 신뢰성을 개선한다.
  • 수많은 서비스 커뮤니케이션의 개선과 최적화의 과정을 간단하게 한다.
  • 코드 내에서 내부 호출과 네트워크 호출을 명확하고 일관되게 구분한다.
  • 서비스 상호작용에서 메트릭을 수집하는 것과 같이 기능 확장을 지원한다.

서비스 메시

대안으로 서비스 메시(service mesh)를 도입할 수 있다. 다양한 기술이 혼합된 애플리케이션에서 방어적 커뮤니케이션을 간단하게 해준다.

Sources

  • 마이크로 서비스 인 액션 - 모건 부르스, 파울로 페레이라