Skip to content

필사 모드: 서비스 메시(Service Mesh) 실전 가이드: Istio·Envoy·Linkerd 기반 mTLS·트래픽 관리·가시성 확보

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

들어가며

마이크로서비스 아키텍처가 확산되면서 서비스 간 통신의 복잡성이 급격히 증가했다. 인증, 암호화, 트래픽 관리, 가시성, 장애 격리 등의 횡단 관심사(cross-cutting concerns)를 각 서비스에 직접 구현하면 코드 중복과 운영 부담이 기하급수적으로 늘어난다. 서비스 메시(Service Mesh)는 이러한 네트워킹 관심사를 인프라 계층으로 추출하여, 애플리케이션 코드 변경 없이 일관된 보안, 관측성, 트래픽 제어를 제공한다.

이 글에서는 서비스 메시의 핵심 개념인 데이터 플레인과 컨트롤 플레인을 설명하고, Istio(Envoy 사이드카)와 Linkerd를 비교하며, mTLS 설정, 트래픽 분할, 서킷 브레이커, 가시성 도구, 그리고 최신 Ambient Mesh까지 실전 예제와 함께 다룬다. 프로덕션에서 겪을 수 있는 대표적인 장애 시나리오와 대응 방안도 함께 정리한다.

서비스 메시 핵심 개념

데이터 플레인과 컨트롤 플레인

서비스 메시는 크게 두 계층으로 구성된다.

| 계층 | 역할 | 구현체 |

| ------------- | ------------------------------------------------ | ---------------------------------------------- |

| 데이터 플레인 | 서비스 간 모든 네트워크 트래픽을 가로채서 처리 | Envoy (Istio), linkerd2-proxy (Linkerd) |

| 컨트롤 플레인 | 프록시 설정 배포, 인증서 관리, 서비스 디스커버리 | Istiod (Istio), destination/identity (Linkerd) |

Istio vs Linkerd 비교

| 항목 | Istio | Linkerd |

| -------------------- | ---------------------------------- | ------------------------------------- |

| 프록시 | Envoy (C++) | linkerd2-proxy (Rust) |

| 컨트롤 플레인 | Istiod (통합) | destination, identity, proxy-injector |

| 프록시 메모리 | 약 50MB+ / 사이드카 | 약 20-30MB / 사이드카 |

| 컨트롤 플레인 메모리 | 1-2GB (프로덕션) | 200-300MB |

| L7 기능 | 매우 풍부 (헤더 라우팅, 미러링 등) | 핵심 기능 위주 |

| 학습 곡선 | 가파름 | 완만 |

| CRD 수 | 50+ | 약 10개 |

| Ambient 모드 | 지원 (ztunnel + waypoint) | 미지원 |

| 적합한 환경 | 대규모, 복잡한 트래픽 관리 | 중소규모, 빠른 도입 |

성능 오버헤드 비교

벤치마크 결과 (2000 RPS 기준)

P99 레이턴시 추가량:

No mesh: 기준값

Linkerd: +2.0ms

Istio Sidecar: +5.8ms

Istio Ambient: +2.4ms

리소스 사용량 (사이드카 당):

Envoy: ~50MB RAM, ~0.5 vCPU

linkerd2-proxy: ~20MB RAM, ~0.2 vCPU

대규모 환경 (12800 RPS) 벤치마크에서

Istio Ambient가 가장 낮은 레이턴시를 기록

Linkerd 대비 P99에서 약 11ms 차이

Istio 아키텍처와 설정

Istio 설치

istioctl 설치

curl -L https://istio.io/downloadIstio | sh -

cd istio-1.24.0

export PATH=$PWD/bin:$PATH

프로덕션 프로파일로 설치

istioctl install --set profile=default -y

네임스페이스에 사이드카 자동 인젝션 활성화

kubectl label namespace default istio-injection=enabled

설치 확인

istioctl verify-install

kubectl get pods -n istio-system

VirtualService와 DestinationRule

VirtualService: 트래픽 라우팅 규칙 정의

apiVersion: networking.istio.io/v1

kind: VirtualService

metadata:

name: reviews-route

namespace: default

spec:

hosts:

- reviews

http:

- match:

- headers:

end-user:

exact: beta-tester

route:

- destination:

host: reviews

subset: v2

weight: 100

- route:

- destination:

host: reviews

subset: v1

weight: 90

- destination:

host: reviews

subset: v2

weight: 10

DestinationRule: 서비스 서브셋과 정책 정의

apiVersion: networking.istio.io/v1

kind: DestinationRule

metadata:

name: reviews-destination

namespace: default

spec:

host: reviews

trafficPolicy:

connectionPool:

tcp:

maxConnections: 100

http:

h2UpgradePolicy: DEFAULT

http1MaxPendingRequests: 100

http2MaxRequests: 1000

loadBalancer:

simple: ROUND_ROBIN

subsets:

- name: v1

labels:

version: v1

- name: v2

labels:

version: v2

trafficPolicy:

loadBalancer:

simple: LEAST_REQUEST

트래픽 분할 (카나리 배포)

카나리 배포: v2로 5%씩 트래픽 증가

apiVersion: networking.istio.io/v1

kind: VirtualService

metadata:

name: my-service-canary

spec:

hosts:

- my-service

http:

- route:

- destination:

host: my-service

subset: stable

weight: 95

- destination:

host: my-service

subset: canary

weight: 5

카나리 트래픽 비율 점진적 증가 스크립트

5% → 10% → 25% → 50% → 100%

for weight in 10 25 50 100; do

stable_weight=$((100 - weight))

kubectl patch virtualservice my-service-canary --type=json \

-p="[

{\"op\":\"replace\",\"path\":\"/spec/http/0/route/0/weight\",\"value\":${stable_weight}},

{\"op\":\"replace\",\"path\":\"/spec/http/0/route/1/weight\",\"value\":${weight}}

]"

echo "Canary weight: ${weight}%, Stable weight: ${stable_weight}%"

echo "Monitoring for 5 minutes..."

sleep 300

done

서킷 브레이커 설정

DestinationRule을 이용한 서킷 브레이커

apiVersion: networking.istio.io/v1

kind: DestinationRule

metadata:

name: payment-service-cb

spec:

host: payment-service

trafficPolicy:

connectionPool:

tcp:

maxConnections: 50

http:

http1MaxPendingRequests: 50

http2MaxRequests: 100

maxRequestsPerConnection: 10

maxRetries: 3

outlierDetection:

consecutive5xxErrors: 5

interval: 30s

baseEjectionTime: 30s

maxEjectionPercent: 50

minHealthPercent: 30

서킷 브레이커 상태 확인

istioctl proxy-config cluster <pod-name> --fqdn payment-service.default.svc.cluster.local -o json | grep -A 20 "outlierDetection"

Envoy 통계에서 서킷 브레이커 동작 확인

kubectl exec <pod-name> -c istio-proxy -- pilot-agent request GET stats | grep "circuit_breakers"

mTLS 설정과 보안

Strict mTLS 적용

네임스페이스 전체에 Strict mTLS 적용

apiVersion: security.istio.io/v1

kind: PeerAuthentication

metadata:

name: default

namespace: default

spec:

mtls:

mode: STRICT

메시 전체에 Strict mTLS 적용 (istio-system 네임스페이스에 생성)

apiVersion: security.istio.io/v1

kind: PeerAuthentication

metadata:

name: default

namespace: istio-system

spec:

mtls:

mode: STRICT

특정 포트 제외 (레거시 서비스 연동)

특정 서비스에서 일부 포트만 PERMISSIVE 모드

apiVersion: security.istio.io/v1

kind: PeerAuthentication

metadata:

name: legacy-integration

namespace: default

spec:

selector:

matchLabels:

app: legacy-adapter

mtls:

mode: STRICT

portLevelMtls:

8080:

mode: PERMISSIVE

인증서 관리와 SPIFFE

현재 mTLS 상태 확인

istioctl authn tls-check <pod-name>

인증서 정보 확인

istioctl proxy-config secret <pod-name> -o json

SPIFFE ID 형식: spiffe://cluster.local/ns/NAMESPACE/sa/SERVICE_ACCOUNT

Istio는 Kubernetes 서비스 어카운트를 기반으로 SPIFFE ID를 자동 할당

인증서 만료 시간 확인 (기본 24시간, 자동 갱신)

kubectl exec <pod-name> -c istio-proxy -- \

openssl x509 -noout -dates -in /var/run/secrets/istio/tls/cert-chain.pem

인증서 로테이션 강제 실행 (디버깅용)

kubectl delete secret istio-ca-root-cert -n default

Istiod가 자동으로 새 인증서 발급

Authorization Policy (접근 제어)

특정 서비스만 접근 허용

apiVersion: security.istio.io/v1

kind: AuthorizationPolicy

metadata:

name: payment-access

namespace: default

spec:

selector:

matchLabels:

app: payment-service

rules:

- from:

- source:

principals:

- cluster.local/ns/default/sa/order-service

- cluster.local/ns/default/sa/checkout-service

to:

- operation:

methods: ['POST', 'GET']

paths: ['/api/v1/payments/*']

모든 접근 차단 (기본 거부 정책)

apiVersion: security.istio.io/v1

kind: AuthorizationPolicy

metadata:

name: deny-all

namespace: default

spec: {}

Ambient Mesh (사이드카리스 모드)

Ambient Mesh 아키텍처

Ambient Mesh는 사이드카를 없애고 두 계층으로 메시 기능을 제공한다.

| 계층 | 컴포넌트 | 기능 |

| ------------------- | -------------------------------- | ----------------------------------- |

| L4 (Secure Overlay) | ztunnel (노드 당 DaemonSet) | mTLS, L4 인가, L4 텔레메트리 |

| L7 (Waypoint) | waypoint proxy (네임스페이스 당) | HTTP 라우팅, L7 인가, L7 텔레메트리 |

Ambient 모드로 Istio 설치

istioctl install --set profile=ambient -y

네임스페이스를 Ambient 메시에 추가

kubectl label namespace default istio.io/dataplane-mode=ambient

ztunnel DaemonSet 확인

kubectl get pods -n istio-system -l app=ztunnel

L7 기능이 필요한 경우 Waypoint 프록시 배포

istioctl waypoint apply --namespace default --name default-waypoint

Waypoint 프록시 확인

kubectl get pods -n default -l istio.io/gateway-name=default-waypoint

Ambient vs Sidecar 비교

Sidecar 모드:

장점: 완전한 L7 제어, 성숙한 생태계

단점: 파드당 프록시 오버헤드, 재시작 필요

리소스: ~50MB RAM + ~0.5 vCPU / 파드

Ambient 모드:

장점: 파드 재시작 불필요, 낮은 리소스 오버헤드

단점: L7은 waypoint 필요, 상대적으로 새로운 기술

리소스: ztunnel ~30MB RAM / 노드 + waypoint 공유

선택 기준:

기존 워크로드 마이그레이션 → Ambient 우선 검토

세밀한 L7 제어 필요 → Sidecar 유지

리소스 절약 우선 → Ambient

안정성 우선 → Sidecar (더 성숙)

가시성(Observability) 확보

Kiali 대시보드

Kiali 설치 (Istio 애드온)

kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/kiali.yaml

Kiali 대시보드 접근

istioctl dashboard kiali

Kiali가 제공하는 정보:

- 서비스 간 트래픽 흐름 그래프

- 요청 성공률 / 에러율

- P50/P90/P99 레이턴시

- mTLS 상태 (잠금 아이콘)

- Istio 설정 검증 (오류 하이라이트)

분산 추적 (Jaeger/Zipkin)

Jaeger 설치

kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/jaeger.yaml

Jaeger 대시보드 접근

istioctl dashboard jaeger

애플리케이션에서 추적 헤더 전파 필수

다음 헤더를 upstream으로 전달해야 함:

x-request-id

x-b3-traceid

x-b3-spanid

x-b3-parentspanid

x-b3-sampled

x-b3-flags

traceparent

tracestate

Python Flask에서 추적 헤더 전파 예시

from flask import Flask, request

app = Flask(__name__)

TRACE_HEADERS = [

'x-request-id',

'x-b3-traceid',

'x-b3-spanid',

'x-b3-parentspanid',

'x-b3-sampled',

'x-b3-flags',

'traceparent',

'tracestate',

]

def propagate_headers():

headers = {}

for header in TRACE_HEADERS:

value = request.headers.get(header)

if value:

headers[header] = value

return headers

@app.route('/api/orders')

def get_orders():

다운스트림 서비스 호출 시 추적 헤더 전파

headers = propagate_headers()

response = requests.get(

'http://payment-service:8080/api/payments',

headers=headers

)

return response.json()

Prometheus + Grafana 메트릭

Prometheus와 Grafana 설치

kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/prometheus.yaml

kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/grafana.yaml

Grafana 대시보드 접근

istioctl dashboard grafana

Istio가 자동 수집하는 핵심 메트릭:

istio_requests_total - 총 요청 수

istio_request_duration_milliseconds - 요청 레이턴시

istio_request_bytes - 요청 크기

istio_response_bytes - 응답 크기

istio_tcp_connections_opened_total - TCP 연결 수

Prometheus에서 주요 쿼리 예시

서비스별 에러율 (5xx)

rate(istio_requests_total{response_code=~"5.."}[5m])

/

rate(istio_requests_total[5m])

P99 레이턴시

histogram_quantile(0.99,

sum(rate(istio_request_duration_milliseconds_bucket[5m]))

by (le, destination_service_name))

장애 시나리오와 대응

시나리오 1: 사이드카 인젝션 실패

증상: 파드는 Running이지만 사이드카(istio-proxy)가 없음

1. 네임스페이스 라벨 확인

kubectl get namespace default --show-labels

istio-injection=enabled 라벨이 있는지 확인

2. 파드의 컨테이너 목록 확인

kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].name}'

istio-proxy가 목록에 없으면 인젝션 실패

3. 원인 진단

a. 네임스페이스 라벨 누락

kubectl label namespace default istio-injection=enabled

b. 파드에 인젝션 비활성화 어노테이션이 있는 경우

kubectl get pod <pod-name> -o jsonpath='{.metadata.annotations.sidecar\.istio\.io/inject}'

"false"면 인젝션이 비활성화된 상태

c. Webhook 설정 확인

kubectl get mutatingwebhookconfiguration istio-sidecar-injector -o yaml

4. 수동 인젝션 (긴급 시)

istioctl kube-inject -f deployment.yaml | kubectl apply -f -

5. 파드 재시작 (인젝션 적용을 위해)

kubectl rollout restart deployment <deployment-name>

시나리오 2: 인증서 로테이션 실패

증상: 서비스 간 통신 실패, TLS 핸드셰이크 에러

1. 인증서 상태 확인

istioctl proxy-config secret <pod-name>

VALID 상태와 만료 시간 확인

2. Istiod 로그에서 인증서 관련 에러 확인

kubectl logs -n istio-system deployment/istiod | grep -i "certificate\|cert\|error"

3. CA 인증서 확인

kubectl get secret istio-ca-secret -n istio-system -o jsonpath='{.data.ca-cert\.pem}' | base64 -d | openssl x509 -noout -dates

4. 인증서 강제 갱신

파드의 istio-proxy 재시작

kubectl delete pod <pod-name>

5. Istiod 재시작 (CA 문제인 경우)

kubectl rollout restart deployment istiod -n istio-system

6. Root CA 로테이션 (계획된 작업)

새 Root CA 생성 후 중간 CA를 통한 점진적 전환

공식 문서의 CA rotation guide 참조

시나리오 3: 과도한 메모리 사용 (Envoy OOM)

증상: istio-proxy 컨테이너가 OOMKilled로 재시작

1. 현재 리소스 사용량 확인

kubectl top pod <pod-name> --containers

2. Envoy 통계 확인

kubectl exec <pod-name> -c istio-proxy -- pilot-agent request GET stats/memory

3. 리소스 제한 조정

kubectl patch deployment <deployment-name> --type=json \

-p='[{"op":"replace","path":"/spec/template/metadata/annotations/sidecar.istio.io~1proxyMemoryLimit","value":"512Mi"}]'

4. 전역 프록시 리소스 설정 (IstioOperator)

istio-operator.yaml에서 아래와 같이 설정

spec:

meshConfig:

defaultConfig:

proxyMetadata: {}

values:

global:

proxy:

resources:

requests:

cpu: 100m

memory: 128Mi

limits:

cpu: 500m

memory: 512Mi

운영 시 주의사항

1. **점진적 도입**: 서비스 메시를 한 번에 전체 클러스터에 적용하지 말고, 비핵심 워크로드부터 단계적으로 확장하라. PERMISSIVE mTLS 모드로 시작하여 STRICT로 전환한다.

2. **리소스 예산 확보**: Envoy 사이드카 기준 파드당 약 50MB RAM + 0.5 vCPU를 추가로 확보하라. 대규모 클러스터에서는 이 오버헤드가 상당해질 수 있다.

3. **추적 헤더 전파**: 분산 추적이 제대로 작동하려면 애플리케이션에서 추적 헤더(x-b3-traceid 등)를 반드시 전파해야 한다. 서비스 메시가 자동으로 해주지 않는 부분이다.

4. **CRD 관리**: Istio는 50개 이상의 CRD를 사용한다. 업그레이드 시 CRD 호환성을 반드시 확인하고, canary 업그레이드를 권장한다.

5. **Ambient Mesh 고려**: 신규 도입이라면 Ambient Mesh를 적극 검토하라. 사이드카 오버헤드 없이 L4 보안을 즉시 확보할 수 있고, L7 기능은 필요한 서비스에만 waypoint를 배포하면 된다.

6. **Istiod 고가용성**: 프로덕션에서는 Istiod를 최소 2개 이상의 레플리카로 운영하고, Pod Disruption Budget을 설정하라.

Istiod 레플리카 확장

kubectl scale deployment istiod -n istio-system --replicas=3

PDB 설정

kubectl apply -f - <<ENDF

apiVersion: policy/v1

kind: PodDisruptionBudget

metadata:

name: istiod-pdb

namespace: istio-system

spec:

minAvailable: 1

selector:

matchLabels:

app: istiod

ENDF

마치며

서비스 메시는 마이크로서비스 환경에서 보안, 가시성, 트래픽 관리라는 세 가지 핵심 과제를 인프라 수준에서 해결한다. Istio는 풍부한 기능과 세밀한 제어를, Linkerd는 경량화와 빠른 도입을 강점으로 한다. 최신 Ambient Mesh는 사이드카 오버헤드를 제거하면서도 핵심 보안 기능을 제공하여 서비스 메시 도입의 장벽을 크게 낮추고 있다.

프로덕션에서 가장 중요한 것은 점진적 도입이다. PERMISSIVE mTLS에서 시작하여 가시성을 확보하고, 안정성을 확인한 후 STRICT 모드로 전환하는 단계적 접근이 성공의 핵심이다. 서비스 메시가 제공하는 일관된 가시성과 보안은 마이크로서비스 운영의 복잡성을 크게 줄여줄 것이다.

참고자료

- [Istio Architecture - Official Documentation](https://istio.io/latest/docs/ops/deployment/architecture/)

- [Istio Ambient Mesh Overview](https://istio.io/latest/docs/ambient/overview/)

- [Istio Performance and Scalability](https://istio.io/latest/docs/ops/deployment/performance-and-scalability/)

- [Linkerd vs Istio - Buoyant](https://www.buoyant.io/linkerd-vs-istio)

- [Mutual TLS: Securing Microservices in Service Mesh - The New Stack](https://thenewstack.io/mutual-tls-microservices-encryption-for-service-mesh/)

- [Service Mesh Architecture: Istio and Envoy in Production - Java Code Geeks](https://www.javacodegeeks.com/2025/11/service-mesh-architecture-istio-and-envoy-in-production.html)

- [Performance Comparison of Service Mesh Frameworks - arXiv](https://arxiv.org/html/2411.02267v1)

현재 단락 (1/278)

마이크로서비스 아키텍처가 확산되면서 서비스 간 통신의 복잡성이 급격히 증가했다. 인증, 암호화, 트래픽 관리, 가시성, 장애 격리 등의 횡단 관심사(cross-cutting con...

작성 글자: 0원문 글자: 11,644작성 단락: 0/278