왜 모든 앱에 "트래픽 관리 코드"를 중복해서 짜야 할까 — 서비스 메시가 푸는 문제

마이크로서비스가 30개로 늘었다. 각 서비스마다 같은 코드가 반복됐다: 재시도, 타임아웃, 서킷 브레이커, 메트릭, 분산 추적, mTLS 인증. 개발자는 비즈니스 로직보다 이 인프라 코드*를 더 짜고 있었다. 그리고 언어마다(Java/Go/Node) 각자 다시 구현했다. 한 팀이 물었다: "이걸 *앱 밖으로 뺄 수 없나?" 서비스 메시(service mesh)가 바로 그 답이다.

이 글이 푸는 것은: 서비스 메시가 어떤 문제를 풀고, 사이드카 패턴으로 어떻게 앱 코드에서 인프라 로직을 빼내는가다. 이해하면 11장(Istio)/12장(Cilium 메시)의 설계가 보인다.

서비스 간 통신에 필요한 것 — 그리고 그것이 앱마다 중복되는 문제

마이크로서비스 환경에서 서비스 간 통신에 필요한 것들을 나열해 보자:

  • 신뢰성: 재시도, 타임아웃, 서킷 브레이커.
  • 보안: 서비스 간 인증(mTLS), 권한.
  • 관측성: 분산 추적(trace), 메트릭, 로그 상관.
  • 트래픽 제어: 카나리, 가중치 분배, A/B.

이것들을 각 앱이 직접 구현하면 어떻게 되나:

  • 모든 언어/프레임워크마다 다시 짠다(Java 팀, Go 팀, Node 팀이 각자 재시도 로직을 구현).
  • 한 서비스가 잘못 구현하면(재시도 누락 등) 전체 연쇄 장애.
  • 비즈니스 로직과 인프라 로직이 뒤섞여 변경이 어렵다 — "주문 로직 고치려다 재시도 코드까지 건드린다."

이 중복이 도입의 사례다. 30개 서비스, 3개 언어, 각자 인프라 코드. 서비스 메시는 이 인프라 로직을 앱 밖으로 빼낸다 — 앱은 비즈니스 로직만, 통신 인프라는 메시가 담당한다.

사이드카 패턴 — 옆에 앉아 투명하게 가로채기

"인프라 로직을 앱 밖으로"가 어떻게 가능한가? 앱 코드를 고치지 않으면서 트래픽을 통제하려면, 트래픽이 나가고 들어오는 경로에 무언가를 끼워넣어야 한다. 그것이 사이드카 프록시다. (Phil Calçado - Pattern: Service Mesh)

flowchart LR
    APP1["앱1<br/>(비즈니스만)"] -->|"localhost"| PROXY1["프록시 사이드카<br/>(Envoy)"]
    PROXY1 -. mTLS,재시도,메트릭 .-> PROXY2["프록시 사이드카"]
    PROXY2 -->|"localhost"| APP2["앱2"]

전통적 서비스 메시의 구현: 사이드카 프록시(Istio는 Envoy)를 각 Pod에 앱 옆에 넣는다. 모든 인바운드/아웃바운드 트래픽이 사이드카를 거치게 해, 사이드카가 재시도/메트릭/mTLS를 처리한다.

  • 앱은 localhost로 다른 서비스를 부른다(사이드카로 감).
  • 사이드카가 실제 원격 서비스의 사이드카로 mTLS 통신.
  • 앱 코드는 한 줄도 안 바뀜. 04장의 "Pod 안 컨테이너 공유"가 사이드카를 가능하게 하는 기반이다.

사이드카가 "투명"하게 가로채는 법 — iptables 인터셉션

사이드카가 "앱 코드를 안 고친다"는 게 어떻게 가능한가? 핵심은 iptables 투명 인터셉션이다. 사이드카 주입 시(보통 namespace 라벨 + MutatingAdmissionWebhook), 다음이 일어난다:

  1. Pod에 istio-proxy(Envoy) 컨테이너가 추가된다.
  2. 동시에 Pod의 initContainersistio-init이 추가 — 이 init 컨테이너가 Pod 네트워크 네임스페이스의 iptables 규칙을 재작성.
  3. 규칙의 효과: 이 Pod의 모든 아웃바운드/인바운드 트래픽이 강제로 istio-proxy(15001 포트)를 경유.
flowchart LR
    APP["앱 컨테이너"] -->|"connect 외부:8080"| IPT["iptables REDIRECT"]
    IPT -->|"15001로 강제"| PROXY["istio-proxy (Envoy)"]
    PROXY -->|"mTLS/재시도/메트릭 적용 후"| OUT["외부 목적지"]

앱은 connect("외부:8080")을 호출하지만, iptables가 그것을 가로채 15001(프록시)로 보낸다. 프록시가 실제 연결을 맺고, mTLS/재시도/메트릭을 적용. 앱은 자기가 직접 연결했다고 착각 — 사이드카의 존재를 모른다.

이것이 "투명(transparent)"의 의미다. 그리고 왜 "Pod의 컨테이너 공유"가 사이드카의 전제인지 — iptables 규칙이 Pod 네트워크 네임스페이스 단위로 적용되므로, 같은 Pod의 컨테이너들이 같은 규칙을 공유한다.

투명성의 역설 — 사이드카가 문제를 일으킬 때

이 투명성이 이점이지만 함정이기도 하다. 앱이 사이드카를 모르면 — 사이드카가 문제를 일으킬 때 원인이 보이지 않는다:

  • 프록시 지연: 모든 트래픽이 한 단계 더 거치므로 RTT 증가. 앱이 "네트워크가 느리다"고 호소하지만 실제론 사이드카.
  • 프록시 장애: istio-proxy가 크래시되면 Pod의 모든 통신이 막힘. 앱은 정상인데 통신 불가 — "앱 문제인 줄 알았는데 사이드카였다."
  • 설정 오류: VirtualService 규칙이 트래픽을 의도치 않게 라우팅. 앱 로직엔 문제 없는데 통신이 이상.

이것이 메시 도입 후 디버깅 날이도가 올라가는 이유다. 문제의 이 하나 더 생겼으니. Hubble/Istio의 proxy-status가 이 "보이지 않는 층"을 관측하게 해 준다.

데이터플레인과 컨트롤플레인 — 결정과 실행의 분리

500개의 사이드카에 정책을 일관되게 배포하려면, 누군가 중앙에서 결정하고 사이드카가 그것을 받아야 한다. 메시는 이를 두 층으로 나눈다:

  • 데이터플레인: 사이드카 프록시들. 실제 트래픽을 처리.
  • 컨트롤플레인: 사이드카들에게 "이런 정책을 적용해라"(라우팅 규칙, 인증서, 메트릭)를 배포. Istio는 Istiod, Linkerd는 자체 컨트롤러. (Istio - What is Istio?)
flowchart TD
    CP["컨트롤플레인<br/>(Istiod 등)"] -. 설정 배포 .-> S1["사이드카1"]
    CP -. 설정 배포 .-> S2["사이드카2"]
    CP -. 인증서 발급 .-> S1
    CP -. 인증서 발급 .-> S2
    S1 == mTLS 트래픽 ==> S2

이 구조가 Kubernetes의 컨트롤플레인/노드와 비슷하다는 걸 눈치챌 것이다 — "결정은 중앙(컨트롤플레인), 실행은 분산(사이드카)"이라는 같은 패턴. 사이드카들은 컨트롤플레인을 watch해 자기 설정을 갱신한다.

mTLS — 코드 없는 서비스 간 암호화

서비스 간 통신을 암호화하려면(mTLS) 전통적으로 언어별 라이브러리와 인증서 관리가 필요했다. 메시는 이 전부를 mTLS 자동화로 흡수한다 — 사이드카들이 서로를 인증하고 트래픽을 암호화하고, 인증서는 컨트롤플레인이 발급/갱신한다. 앱은 한 줄도 안 건드린다.

이것이 03-k8s-security 영역의 mTLS와 직결된다 — "서비스 간 전송 구간 암호화"를 앱 코드 없이 달성. 전통적으로 mTLS를 앱에 넣으려면 언어별 라이브러리 + 인증서 관리가 필요했는데, 메시가 그 전부를 사이드카로 흡수.

관측의 세 기둥 — 메시가 주는 가시성

메시가 앱 코드 밖으로 빼는 건 통신 제어만이 아니다. 관측(observability)의 세 기둥을 메시가 수집:

기둥 메시가 수집하는 것 앱이 직접 안 해도 되는 이유
메트릭 요청 수/지연/에러율(Red)/연결 수 사이드카가 모든 트래픽을 지나치므로 자동 계산
분산 추적(trace) 요청이 여러 서비스를 거치는 경로 + span 사이드카가 헤더를 주입/전파
로그 접근 로그(누가 누구에게) 사이드카가 연결 단위로 기록

이 세 가지를 각 앱이 언어마다 다시 구현하는 고통(앞 절의 중복 문제)을 메시가 한 번에 해결. 분산 추적의 경우 — 사이드카가 HTTP 헤더(B3, W3C Trace Context)를 자동 주입/전파하므로, 앱이 추적 헤더를 코드로 다루지 않아도 spans가 연결된다. 이것이 메시의 숨겨진 큰 가치.

트래픽 제어 패턴 — 메시가 여는 능력

메시가 주는 통신 제어는 단순 로드밸런싱을 넘어 여러 패턴을 가능하게:

  • 카나리(canary): 신규 버전에 X% 트래픽. 11장 VirtualService의 weight.
  • 미러링(mirror): 프로덕션 트래픽을 복사해 신규 버전에 보냄(응답 무시). 실전 부하로 신규 검증.
  • 서킷 브레이커: 실패 임계 초과 시 자동 차단(연쇄 장애 방지).
  • 재시도/타임아웃: 앱 대신 메시가 통신 신뢰성 담당.
  • A/B 라우팅: 헤더/가중치 기반 사용자 분기.

이 패턴들을 코드 없이 메시 규칙만으로. 08장 Ingress가 외부 진입의 카나리를 다뤘다면, 메시는 내부 서비스 간의 트래픽 분배를 다룬다. "서비스A→서비스B 호출의 10%만 새 버전으로"를 메시 규칙으로. 이것이 카나리 배포의 핵심 도구.

사이드카 메시의 비용 — 만능이 아니다

사이드카 메시엔 비용이 있다:

  • 자원 오버헤드: Pod마다 프록시 컨테이너 추가(CPU/메모리). Pod 1000개면 사이드카 1000개.
  • 지연: 모든 트래픽이 사이드카를 두 번 거치므로 RTT 증가(~1-2ms 추가). 대부분 무시 가능하나 극단 지연 민감 서비스엔 의미.
  • 디버깅 난이도: "보이지 않는 층"이 추가돼, 통신 문제의 원인이 앱/사이드카/메시 정책 어디인지 분리 어려움.
  • 버전 업그레이드: 메시 자체(Istiod 등)의 업그레이드가 모든 사이드카에 영향. 잘못하면 전체 통신에 영향.

그래서 메시 도입은 트레이드오프다. 모든 클러스터가 메시를 필요로 하진 않는다.

언제 메시를 쓰지 말까 — 비용이 이익을 넘는 순간

flowchart LR
    Q1{"서비스 > 20개?"} -->|아니오| NOPE["메시 과잉 가능성"]
    Q1 -->|예| Q2{"다언어/다프레임워크?"}
    Q2 -->|아니오| MAYBE["라이브러리로 충분할 수 있음"]
    Q2 -->|예| Q3{"mTLS/분산추적/카나리 필요?"}
    Q3 -->|예| MESH["메시 가치 높음"]
  • 서비스 수가 적다(5-10개): 메시 오버헤드(자원/복잡성)가 이익(mTLS/관측)을 넘음.
  • 한 언어/프레임워크: 라이브러리로 통일 가능하면, 사이드카보다 라이브러리가 가벼움.
  • 단일 클러스터/단일 리전: cross-cluster/메시 통제가 과제가 아니면, 메시의 가치가 좁음.
  • 팀이 메시 운영 숙련도 부족: 메시 디버깅이 어려움. 숙련도 없으면 메시가 장애 원인이 됨.

이 결정 트리가 "메시 도입이 가치 있는가"의 합리적 기준이다. "메시가 최신 트렌드라서"가 아니라 구체적 필요로. 잘못된 도입은 복잡성만 더하고 장애 디버깅을 어렵게 한다.

사이드카 vs 라이브러리 vs 사이드카리스 — 메시 기술의 흐름

메시 이전엔 이 기능들을 라이브러리*로 각 언어에 (Netflix Hystrix, Twitter Finagle 등). 문제: 언어/프레임워크마다 다시 구현, 버전 업데이트 부담, 다언어 환경에서 파편화. 메시가 이것을 *플랫폼 층(사이드카)으로 올려 — 언어 무관, 일관된 업데이트.

이 "라이브러리에서 사이드카로"의 이동이 서비스 메시의 본질적 가치. 단 사이드카 비용(자원/지연/복잡성)이 새로 생기므로, 12장의 Cilium(사이드카 없는)과 11장의 Ambient가 그 비용을 다시 줄이려는 다음 진화. 메시 기술은 라이브러리→사이드카→사이드카리스로 흐르는 중.

직접 확인하기 (개념 중심)

서비스 메시는 설치 없이 개념으로 이해해도 충분. Istio/Cilium 실측은 11/12장.

# Kubernetes 1.36 — 사이드카 주입 전후 Pod 컨테이너 수 비교 (개념)
# 일반 Pod: 앱 컨테이너 1개
kubectl get pod <normal-app> -o jsonpath='{.spec.containers[*].name}{"\n"}'
# Istio 주입 Pod: 앱 + istio-proxy(Envoy 사이드카) 2개
kubectl get pod <meshed-app> -o jsonpath='{.spec.containers[*].name}{"\n"}'

확인할 것: 메시 주입 Pod에 istio-proxy 사이드카가 추가.

흔히 묻는 것, 흔히 틀리는 것

오해 정정
"서비스 메시는 쿠버네티스 필수다" 아니다. 트레이드오프. 서비스 적으면 과잉
"사이드카는 앱 코드를 고쳐야 한다" 안 고침. iptables로 투명하게 가로챔
"메시의 mTLS는 외부 사용자 대상이다" 서비스 . 외부 HTTPS는 Ingress/Gateway
"사이드카가 없으면 메시가 아니다" Cilium은 eBPF로 사이드카 없이 메시
"데이터플레인과 컨트롤플레인은 쿠버네티스 것과 같다" 패턴은 비슷(결정-실행 분리)이나 별개 객체/구현
"메시는 트래픽만 제어한다" 보안(mTLS)/관측(추적)/신뢰성(재시도) 포괄

요약 — 이 글의 결론

  • 서비스 메시가 푸는 문제: 마이크로서비스의 인프라 로직(재시도/인증/추적/트래픽 제어)을 앱 밖으로. 언어마다 중복 구현하는 고통 제거.
  • 사이드카 패턴이 전통적 구현 — Pod에 프록시(Envoy 등)를 앱 옆에 넣어 iptables 투명 인터셉션으로 트래픽을 가로챔. 앱 코드 한 줄 안 바뀜.
  • 투명성의 역설: 사이드카가 보이지 않아 디버깅이 어려워짐. "보이지 않는 층"이 추가.
  • 데이터플레인(사이드카) + 컨트롤플레인(설정/인증서 배포)의 2층. 컨트롤플레인을 watch하는 구조.
  • mTLS 자동화가 가장 가시적 이점 — 서비스 간 암호화를 코드 없이.
  • 관측의 세 기둥(메트릭/추적/로그)도 메시가 수집. 분산 추적 헤더 자동 전파가 숨은 가치.
  • 비용(자원/지연/복잡성)이 있어 만능 아님. 서비스 많고 세밀한 통제 필요할 때 가치. Cilium이 사이드카 없이 비용을 줄이는 대안.
  • 메시 기술은 라이브러리→사이드카→사이드카리스로 진화 중. 이 철학을 알면 11(Istio)/12(Cilium) 설계가 보인다.

생각해 볼 문제

  1. 사이드카가 iptables로 트래픽을 가로챈다고 했다. 앱이 외부로 직접 소켓을 열려 하면 어떻게 되나?
  2. mTLS 인증서가 만료된다. 컨트롤플레인 없이 사이드카끼리만 통신하면 어떻게 되나?
  3. 사이드카 메시의 자원 오버헤드를, Pod 1000개 클러스터에서 정량적으로 생각해 보라.
  4. 메시 없이 재시도/서킷브레이커를 구현하면 언어별로 다시 짜야 한다. 이것이 왜 장애를 키우는가?
  5. Cilium이 "사이드카 없는 메시"를 가능케 하는 기술과의 연결을 설명하라.
  6. 서비스가 5개인 소규모 클러스터에 메시를 도입하면 얻는 것보다 잃는 게 큰 이유를 비용 항목별로 말하라.

참고