사전 서비스 메시
패턴

서비스 메시

gabury1고친 사람 github-actions[bot]

서비스 메시는 서비스끼리 주고받는 통신을 애플리케이션 코드 밖으로 빼내 따로 맡깁니다. 서비스마다 옆에 작은 프록시를 하나씩 세우고, 그 서비스가 보내고 받는 요청을 전부 이 프록시로 지나가게 합니다. 실패하면 다시 걸고, 암호로 감싸고, 오간 기록을 남기는 일을 프록시가 대신합니다.

쉽고 빠른 이해

서비스 메시는 서비스끼리의 통신을 대신 맡는 한 겹입니다. 주문 서비스가 결제 서비스를 부르면 그 요청은 주문 서비스 옆에 붙은 프록시를 먼저 지나갑니다.

같은 통신 규칙을 서비스마다 따로 넣으면 언어가 갈릴 때마다 다시 만들어야 합니다. 규칙 하나를 고치려고 서비스를 전부 다시 배포해야 합니다.

  1. 서비스마다 프록시를 하나씩 붙이고 그 서비스의 모든 통신을 프록시로 돌립니다
  2. 프록시가 다시 걸기·암호로 감싸기·기록 남기기를 요청이 지나갈 때 처리합니다
  3. 관리자가 정한 규칙을 가운데서 프록시들에 내려보냅니다

대가는 요청마다 프록시를 두 번 더 지난다는 것입니다. 그만큼 늦어집니다. 서비스 수만큼 프록시가 늘어 메모리를 먹습니다. 장애가 나면 서비스와 프록시 중 어디가 문제인지부터 가려야 합니다.

서비스가 수십 개이고 언어가 갈릴 때 값이 커집니다. 서넛에 한 언어면 라이브러리 하나로 끝나므로 과합니다.

상세

서비스 사이의 통신을 맡는 한 겹

서비스 메시는 서비스끼리 주고받는 통신을 애플리케이션 코드 밖으로 옮기기로 한 결정입니다. 옮겨 간 일은 서비스마다 옆에 세운 작은 프록시가 맡습니다. 주문 서비스가 결제 서비스를 부르면 그 요청은 주문 쪽 프록시와 결제 쪽 프록시를 차례로 지나 결제 서비스에 닿습니다.

이렇게 한 서비스 옆에 붙어 그 서비스의 통신을 대신 받는 프록시를 사이드카라고 부릅니다. 오토바이 옆에 붙이는 좌석에서 온 이름입니다. 사이드카는 자기 서비스와 같은 기계에서 돌고, 자기 서비스가 보내고 받는 것만 다룹니다.

메시는 그물이라는 뜻입니다. 프록시가 서비스마다 하나씩 붙고 그 프록시들이 서로 이어지면, 서비스들 밑에 통신만 다루는 그물이 한 겹 깔립니다. 그 그물을 하나의 물건으로 보고 한꺼번에 다루는 것이 이 패턴입니다.

flowchart TD
    subgraph M1["주문 서비스가 도는 기계"]
        S1["주문 서비스"] --- P1["주문 쪽 프록시"]
    end
    subgraph M2["결제 서비스가 도는 기계"]
        S2["결제 서비스"] --- P2["결제 쪽 프록시"]
    end
    subgraph M3["배송 서비스가 도는 기계"]
        S3["배송 서비스"] --- P3["배송 쪽 프록시"]
    end
    P1 <--> P2
    P2 <--> P3
    P1 <--> P3

서비스와 그 서비스의 프록시는 같은 기계에 묶여 있습니다. 서비스끼리는 서로 직접 닿지 않고 프록시들이 그 사이를 전부 덮습니다.

통신 규칙이 서비스마다 흩어지면 생기는 일

마이크로서비스로 쪼개면 함수 호출이던 것이 네트워크 호출이 됩니다. 네트워크는 끊기고 늦어지므로 부르는 쪽마다 대비가 필요합니다. 응답이 없으면 얼마나 기다렸다 포기할지, 실패하면 몇 번 다시 걸지, 상대가 계속 실패하면 언제부터 아예 안 부를지를 정해야 합니다.

이 셋에는 이름이 붙어 있습니다. 기다림을 끊는 것이 타임아웃, 다시 걸어 보는 것이 재시도, 계속 실패하는 상대를 한동안 건너뛰는 것이 서킷 브레이커입니다. 어느 것도 업무 규칙이 아니라 네트워크를 건너는 데 드는 품입니다.

예전에는 이 품을 라이브러리로 만들어 서비스마다 넣었습니다. 그러면 같은 규칙이 서비스 수만큼 복사됩니다. 팀마다 언어가 다르면 언어 수만큼 같은 라이브러리를 또 만들어야 합니다.

고칠 때 더 아픕니다. 다시 거는 횟수 하나를 바꾸려 해도 라이브러리 판을 올리고, 그것을 쓰는 서비스를 전부 다시 빌드해 배포해야 합니다. 서비스 수십 개가 저마다 배포 일정을 가진 곳에서는 이 갱신이 몇 달을 끕니다.

서비스 메시는 이 품을 서비스 밖 프록시로 옮깁니다. 규칙이 한 벌만 있고, 서비스가 어떤 언어로 쓰였든 같은 규칙을 받습니다.

요청 하나가 프록시 둘을 지나는 길

요청은 부르는 쪽 프록시와 받는 쪽 프록시를 차례로 지납니다. 두 프록시 사이만 네트워크를 건너고, 서비스와 자기 프록시 사이는 같은 기계 안에서 끝납니다.

sequenceDiagram
    participant A as 주문 서비스
    participant PA as 주문 쪽 프록시
    participant PB as 결제 쪽 프록시
    participant B as 결제 서비스
    A->>PA: 결제 서비스를 부른다 · 같은 기계 안
    Note over PA: 어느 기계로 보낼지 고르고 암호로 감싼다
    PA->>PB: 네트워크를 건넌다
    Note over PB: 암호를 풀고 받아도 되는 요청인지 본다
    PB->>B: 결제 서비스에 넘긴다 · 같은 기계 안
    B-->>PB: 결과를 돌려준다
    PB-->>PA: 결과가 같은 길을 되짚는다
    PA-->>A: 결과를 돌려준다

주문 서비스의 코드에는 결제 서비스를 부른다는 것만 있습니다. 상대가 몇 대인지, 그중 어디로 갈지, 암호로 감쌀지는 프록시가 정합니다.

요청을 프록시로 돌리는 일도 서비스가 하지 않습니다. 메시를 깔 때 그 기계의 네트워크 설정을 고쳐, 서비스가 내보내는 요청이 프록시를 거치게 만듭니다. 그래서 서비스 코드를 한 줄도 안 고치고 메시에 넣을 수 있습니다.

요청을 나르는 쪽과 규칙을 내려보내는 쪽

프록시들이 요청을 나르는 동안, 그 프록시들에게 무엇을 하라고 알려 주는 쪽이 따로 있습니다. 앞을 데이터 플레인, 뒤를 컨트롤 플레인이라고 부릅니다.

flowchart TD
    subgraph CP["컨트롤 플레인"]
        C["규칙과 주소 목록을 들고 있다"]
    end
    subgraph DP["데이터 플레인"]
        P1["주문 쪽 프록시"]
        P2["결제 쪽 프록시"]
        P3["배송 쪽 프록시"]
    end
    C -->|규칙을 내려보낸다| P1
    C -->|규칙을 내려보낸다| P2
    C -->|규칙을 내려보낸다| P3
    P1 -->|요청| P2

데이터 플레인은 요청이 실제로 지나는 사이드카 프록시들입니다. 서비스마다 하나씩 붙으므로 주문·결제·배송 서비스가 있으면 프록시도 셋입니다. 이 프록시들이 요청 하나하나를 받아 넘기고, 실패하면 다시 걸고, 걸린 시간을 셉니다.

컨트롤 플레인은 요청을 만지지 않습니다. 어떤 서비스가 어느 기계에 몇 대 떠 있는지를 모아 두었다가 프록시에게 알려 줍니다. 이렇게 상대의 주소를 찾아 주는 일이 서비스 디스커버리입니다. 관리자가 정한 규칙을 프록시들에 내려보내는 것도 이쪽 몫입니다.

컨트롤 플레인이 멈춰도 프록시는 이미 받아 둔 규칙으로 요청을 계속 나릅니다. 대신 그동안의 변경은 프록시에 닿지 않습니다.

이렇게 둘로 가른 덕에 규칙을 한 곳에서 바꾸면 프록시 수백 개가 함께 바뀝니다. 서비스를 다시 배포하지 않습니다.

메시가 대신 맡는 일

모든 요청이 프록시를 지나게 해 두면, 통신에 관한 일은 무엇이든 그 프록시에 얹을 수 있습니다. 메시가 흔히 맡는 일은 네 갈래입니다.

갈래 프록시가 하는 일
끊김 다루기 응답이 늦으면 끊고, 실패하면 다시 걸고, 계속 실패하는 상대는 한동안 건너뛴다
길 고르기 같은 서비스가 여러 대면 어디로 보낼지 고르고(부하 분산), 새 판으로 보내는 비율을 조금씩 올린다(카나리 배포)
신원 확인 양쪽이 서로 인증서를 내보이게 해서 누가 누구를 부르는지 확인한다
기록 남기기 요청 수와 걸린 시간을 세고, 한 요청이 서비스 몇 개를 거쳤는지 이어 붙인다

신원을 확인할 때 쓰는 것이 TLS(Transport Layer Security, 전송 계층 보안)입니다. 웹 브라우저가 서버의 인증서를 확인하듯 통신을 암호로 감싸는 규약입니다. 메시에서는 양쪽이 서로 인증서를 내보입니다. 이렇게 서로 확인하는 꼴을 상호 TLS라고 부릅니다.

기록 쪽에서 얻는 것은 한 요청이 서비스들을 거쳐 간 길입니다. 프록시가 요청마다 표시를 붙여 넘기면 그 표시를 따라 길을 되짚을 수 있습니다. 이것이 분산 추적입니다. 어느 서비스에서 시간이 가장 많이 들었는지가 여기서 드러납니다.

이 둘은 메시가 아니면 빠뜨리기 쉬운 일입니다. 서비스마다 따로 붙이면 한 팀이 잊어도 다른 팀은 모릅니다. 메시에서는 모든 요청이 프록시를 지나므로 빠진 서비스가 곧바로 눈에 띕니다.

프록시 하나가 더 늘어난 대가

메시를 깔면 요청 하나가 프록시를 두 번 더 지납니다. 한 번 지날 때마다 지연 시간이 조금씩 붙습니다. 화면 하나를 그리려고 서비스를 여럿 거치면 그 조금이 겹쳐 쌓입니다.

flowchart TD
    A["주문 서비스"] --> AP["주문 쪽 프록시"]
    AP --> BP["결제 쪽 프록시"]
    BP --> B["결제 서비스"]
    B --> BP2["결제 쪽 프록시"]
    BP2 --> CP["배송 쪽 프록시"]
    CP --> C["배송 서비스"]

주문이 결제를 부르고 결제가 배송을 부른다고 해 봅시다. 서비스를 두 번 거치는 동안 프록시를 네 번 지납니다.

메모리와 계산 자원도 더 듭니다. 프록시가 서비스마다 하나씩 붙으므로 서비스를 한 대 늘릴 때마다 프로세스가 두 개씩 늘어납니다.

장애를 가리는 일도 어려워집니다. 요청이 실패했을 때 서비스가 틀렸는지, 프록시가 규칙대로 끊은 것인지, 컨트롤 플레인이 어긋난 규칙을 내려보낸 것인지를 차례로 봐야 합니다. 서비스 로그만 보면 되던 것이 프록시 로그까지 함께 봐야 하는 일이 됩니다.

메시 자체가 배워야 할 물건이라는 것도 대가입니다. 규칙을 적는 방법과 프록시가 그 규칙을 받는 구조를 팀이 알아야 합니다. 아는 사람이 한둘뿐이면 통신에 얽힌 장애가 전부 그 사람에게 몰립니다.

그리고 메시는 서비스들을 서로에게서 떼어 놓지 않습니다. 모두가 같은 컨트롤 플레인의 규칙을 받으므로 그 규칙을 잘못 바꾸면 전부가 한꺼번에 흔들립니다. 따로 배포하려고 쪼갠 서비스들이 설정 하나로 다시 묶이면 분산 모놀리스 쪽으로 기웁니다.

세울 때와 안 세울 때

서비스가 서넛이고 전부 같은 언어로 쓰였다면 메시는 과합니다. 라이브러리 하나를 같이 쓰면 끝나는 일에 프록시를 붙이는 품만 더해집니다.

값이 커지는 쪽은 서비스가 수십 개이고 언어가 갈릴 때입니다. 라이브러리를 언어마다 만들어 관리하는 수고가 프록시를 붙이는 품보다 커지는 지점이 있습니다. 그 지점을 넘었는지가 잣대입니다.

쿠버네티스처럼 컨테이너를 묶어 굴리는 바탕이 있으면 프록시를 자동으로 끼워 넣을 수 있어 세우는 품이 줄어듭니다. 그런 바탕이 없으면 프록시를 어떻게 붙이고 갱신할지부터 직접 만들어야 합니다.

바깥 요청을 받는 API 게이트웨이

메시는 안쪽 서비스끼리의 통신만 맡습니다. 바깥에서 들어오는 요청은 다른 곳이 받습니다.

프로그램이 다른 프로그램을 부르는 약속을 API(Application Programming Interface)라고 합니다. 바깥 클라이언트가 보내는 요청을 한 곳에서 받아 넘기는 것이 API 게이트웨이입니다.

flowchart TD
    C["바깥 클라이언트"]
    G["API 게이트웨이"]
    subgraph MESH["메시 안쪽"]
        P1["주문 쪽 프록시"]
        S1["주문 서비스"]
        P2["결제 쪽 프록시"]
        S2["결제 서비스"]
        P1 --- S1
        P2 --- S2
        P1 -->|요청| P2
    end
    C --> G
    G --> P1

둘은 하는 일이 겹쳐 보이지만 서는 곳이 다릅니다. 그래서 대개 함께 씁니다.

프록시를 안 세우고 같은 일을 하는 길

gRPC(gRPC Remote Procedure Calls, 원격 프로시저 호출) 같은 통신 라이브러리가 다시 걸기와 부하 나누기를 이미 갖고 있으면 그것을 쓰면 됩니다. 부르는 쪽 코드가 상대를 직접 고르는 이 방식이 클라이언트 측 로드 밸런싱입니다. 언어가 하나로 통일된 곳에서는 이 길이 품이 덜 듭니다.

관련 항목

서비스 메시를 이루는 구성 요소

사이드카 · 프록시 · 데이터 플레인 · 컨트롤 플레인 · 서비스 디스커버리 · 사이드카 주입

서비스 메시가 대신 맡는 통신 기능

재시도 · 타임아웃 · 서킷 브레이커 · 부하 분산 · 속도 제한 · 요청 라우팅 · 트래픽 분할 · 벌크헤드

서비스 메시가 통신을 감싸는 보안 수단

TLS · 상호 TLS · 인증 · 인가 · 제로 트러스트 · 인증서

서비스 메시가 걷어 가는 관측 데이터

관측성 · 분산 추적 · 트레이스 · 메트릭 · 로그 · 골든 시그널

서비스 메시 노릇을 맡는 소프트웨어

Istio · Linkerd · Envoy · Consul Connect · Cilium · Kuma

서비스 메시를 세우는 아키텍처와 바탕

마이크로서비스 · 쿠버네티스 · 컨테이너 · 분산 시스템 · 클라우드 네이티브 · 컨테이너 오케스트레이션

서비스 메시와 같은 몫을 두고 겨루는 수단

API 게이트웨이 · 리버스 프록시 · 로드 밸런서 · 인그레스 · 클라이언트 측 로드 밸런싱 · gRPC · 프록시리스 메시

서비스 메시를 세우면 따라오는 위험

지연 시간 · 단일 장애점 · 분산 모놀리스 · 구성 드리프트 · 운영 복잡도

서비스 메시가 트래픽을 옮기며 돕는 배포 방식

카나리 배포 · 블루-그린 배포 · 롤아웃 · 롤링 업데이트 · A-B 테스트

다른 이름: service mesh · Service Mesh · 서비스메시