마이크로서비스가 늘어나자 어느 호출이 느린지, 어떤 서비스 간 통신을 암호화해야 하는지 파악하기 어려워졌습니다. 애플리케이션 코드마다 재시도와 인증을 넣으면 일관성이 깨집니다. Service Mesh는 트래픽 제어, 관측성, mTLS를 인프라 레이어에서 다루게 해줍니다.
Istio 서비스 메시
마이크로서비스 아키텍처를 운영하다 보면 서비스 간 통신 보안, 트래픽 관찰, 장애 격리라는 세 가지 문제가 동시에 찾아옵니다. 보안팀은 "서비스 간 통신도 암호화해야 한다"고 요구하는데 수십 개 서비스 각각에 TLS 코드를 추가하는 것은 현실적이지 않습니다. 트래픽 분배는 L7 라우팅 없이는 헤더 기반 카나리를 구현할 수 없고, 특정 서비스 장애가 연쇄 장애로 번지는 것을 막을 서킷브레이커가 없습니다. Istio는 애플리케이션 코드를 전혀 수정하지 않고 이 세 가지를 모두 해결합니다. 각 파드에 Envoy 프록시를 사이드카로 자동 주입해서 모든 트래픽이 Envoy를 통하도록 만들고, mTLS 인증서 발급과 갱신, 트래픽 라우팅, 메트릭 수집을 대신 처리합니다. 이 모듈을 마치면 Istio를 클러스터에 설치하고, 서비스 간 mTLS를 자동 적용하며, 트래픽 10%를 신규 버전으로 보내는 카나리 배포를 구성할 수 있습니다.
- 1Istio 아키텍처(istiod Control Plane과 Envoy 사이드카 Data Plane)를 설명할 수 있다
- 2iptables, istio-init, istio-proxy로 이뤄지는 사이드카 인젝션 메커니즘을 설명할 수 있다
- 3PeerAuthentication으로 mTLS 모드를 PERMISSIVE에서 STRICT로 설정할 수 있다
- 4VirtualService로 트래픽 가중치를 분배해 카나리 배포를 수행할 수 있다
- 5DestinationRule로 subset을 정의하고 로드밸런싱 정책을 설정할 수 있다
- 6mTLS STRICT 모드 전환 후 통신 실패를 진단할 수 있다
공식 릴리스 파일을 다운로드한 뒤 체크섬·서명을 검증하고 설치합니다(curl | sh 방식은 사용하지 않음).istioctl x precheckistioctl install --set profile=demo -ykubectl get pods -n istio-systemkubectl label namespace default istio-injection=enabledIstio 아키텍처
확대
istiod는 세 가지를 담당합니다:
- Pilot: VirtualService, DestinationRule 등 트래픽 규칙을 Envoy에 xDS API로 배포
- Citadel: 워크로드 인증서(SVID)를 자동 발급하고 24시간마다 갱신
- Galley: Istio 설정 유효성 검증 및 istiod에 전달
사이드카 인젝션 이해
iptables로 트래픽을 Envoy로 투명하게 리다이렉트하는 원리
Istio를 도입했는데 특정 파드의 트래픽이 Envoy를 통하지 않아 mTLS가 적용되지 않습니다. istioctl x check-inject를 실행해도 원인이 명확하지 않고, 애플리케이션 코드는 아무것도 바꾸지 않았는데 왜 어떤 파드는 메시에 포함되고 어떤 파드는 제외되는지 이해하기 어렵습니다. 사이드카 인젝션 메커니즘을 이해하지 못하면 인젝션 실패 원인을 찾지 못하고 무작정 파드를 재시작하게 됩니다. 이 CB에서는 istio-init init container가 iptables 규칙을 설정하는 원리와, 그 결과 앱 컨테이너가 투명하게 Envoy를 경유하는 구조를 다룹니다. 이것이 가능한 이유는 istio-init init container가 Pod 시작 전에 iptables 규칙을 설정하기 때문입니다.
확대
# 사이드카가 주입된 Pod 내부 컨테이너 확인
kubectl get pod payment-7d9b4c8f6-xkp2n -o jsonpath='{.spec.containers[*].name}'
# 출력: payment istio-proxy ← 두 컨테이너
# istio-proxy 컨테이너 상세 정보
kubectl describe pod payment-7d9b4c8f6-xkp2n | grep -A20 "istio-proxy"
# iptables 규칙 확인 (nsenter로 Pod 네트워크 네임스페이스 진입)
# 실제 운영에서는 kubectl debug 사용
kubectl debug -n production \
-it payment-7d9b4c8f6-xkp2n \
--image=nicolaka/netshoot \
-- iptables -t nat -L ISTIO_REDIRECT
iptables 규칙 구조:
Chain ISTIO_REDIRECT (인바운드)
REDIRECT → TCP 15006 # 모든 인바운드 트래픽 → Envoy 15006
Chain ISTIO_OUTPUT (아웃바운드)
RETURN → uid 1337 # Envoy 자신의 트래픽은 제외 (루프 방지)
REDIRECT → TCP 15001 # 나머지 아웃바운드 → Envoy 15001
결과: 앱 컨테이너는 localhost:8080으로 요청을 수신하고 http://other-service:80으로 요청을 보내지만, 실제로는 모두 Envoy를 경유합니다. Envoy가 mTLS 핸드쉐이크, 메트릭 수집, 트래픽 라우팅을 처리하고 실제 목적지로 전달합니다.
# 사이드카 인젝션 활성화된 네임스페이스 확인
kubectl get namespace -L istio-injection
# 특정 Pod에 사이드카 인젝션 비활성화 (모니터링 에이전트 등)
# Pod annotations에 추가:
# sidecar.istio.io/inject: "false"
요청 한 번이 두 파드의 Envoy를 거쳐 가는 길 — 인터셉트부터 전달까지 6단계
앞에서 iptables가 트래픽을 Envoy로 우회시킨다는 것까지 봤습니다. 그런데 앱이 http://payment:8080 한 줄을 호출했을 때, 그 요청이 실제로 어느 지점들을 거쳐 상대 앱에 도착하는지를 단계로 그려두면 통신 실패가 났을 때 "어느 단계가 끊겼나"를 바로 좁힐 수 있습니다. 요청 하나는 보내는 파드의 Envoy와 받는 파드의 Envoy를 모두 거칩니다 — 즉 사이드카는 둘이 한 쌍으로 일합니다.
[Pod A: app] http://payment:8080 로 요청
│
① iptables OUTPUT 가로채기 (istio-init 규칙이 app 아웃바운드를 → A의 Envoy 15001)
│
② A Envoy: 라우팅 결정 (VirtualService·DestinationRule로 목적지 subset 선택)
│
③ A Envoy: mTLS 클라이언트 (Citadel 발급 인증서로 상대 Envoy와 상호 TLS로 암호화)
│ (파드 경계를 넘는 이 구간만 암호화됨)
▼
[Pod B]
④ iptables PREROUTING 가로채기 (인바운드를 → B의 Envoy 15006)
│
⑤ B Envoy: mTLS 검증 + 인가 + 메트릭 (AuthorizationPolicy 확인, 요청 기록)
│
⑥ B Envoy → 로컬 app (127.0.0.1:8080 의 실제 컨테이너로 전달)
▼
[응답] 같은 경로를 역방향 — 양쪽 Envoy에 메트릭·트레이스가 남음
각 단계에서 무슨 일이 일어나고, 막히면 어떤 증상인가:
| 단계 | 하는 일 | 여기서 막히면 |
|---|---|---|
| ① OUTPUT 가로채기 | istio-init이 심은 iptables가 app의 모든 아웃바운드를 같은 파드 Envoy(15001)로 우회 | 사이드카 미주입이면 우회가 없어 평문 직행 → STRICT 상대가 거부 |
| ② A Envoy 라우팅 | VirtualService(어디로·가중치)·DestinationRule(subset·정책)로 목적지 결정 | subset 레이블이 Pod 레이블과 불일치면 NR(No Route) → 503 |
| ③ A Envoy mTLS | Citadel 발급 워크로드 인증서로 상대 Envoy와 상호 TLS 수립 | 한쪽만 mTLS면 CONFLICT·URX(핸드셰이크 실패)로 연결 리셋 |
| ④ PREROUTING 가로채기 | 목적지 파드의 인바운드를 그 파드 Envoy(15006)로 우회 | 목적지에 사이드카 없으면 인증서를 못 줘 STRICT에서 거부 |
| ⑤ B Envoy 검증·인가 | mTLS 검증 + AuthorizationPolicy 평가 + 요청 메트릭·트레이스 기록 | 정책이 거부하면 403·UAEX(RBAC access denied) |
| ⑥ 로컬 전달 | 통과분만 127.0.0.1:8080의 실제 app 컨테이너로 넘김 | app이 아직 기동 전이면 UF(upstream failure)·503 |
즉 앱은 http://payment:8080 한 줄만 아는데, 실제로는 두 파드의 Envoy를 ①~⑥으로 거치며 라우팅·암호화·인가·관측이 얹힙니다. 통신이 실패하면 kubectl logs -c istio-proxy의 Envoy 코드(NR/URX/UAEX/UF)로 어느 단계인지 좁히고, 그 전에 양쪽 파드에 사이드카가 주입됐는지(①④)부터 확인합니다 — 사이드카 쌍 중 하나만 빠져도 mTLS는 성립하지 않습니다.
네임스페이스 레이블로 자동 인젝션 설정
운영 Deployment 재시작
안전한 실행 조건: 무중단 배포 설정과 모니터링을 확인한 뒤 변경 창구에서 실행하세요.
실행 전 반드시 확인
- 현재 컨텍스트와 Namespace가 의도한 대상인지 확인했는가
- 운영 트래픽이나 상태 저장 데이터에 미치는 영향을 확인했는가
- 되돌릴 매니페스트, 백업, 또는 복구 절차가 준비되어 있는가
kubectl rollout restart deployment -n production위 항목을 모두 확인한 후 복사할 수 있습니다
# 네임스페이스에 자동 인젝션 활성화
kubectl label namespace production istio-injection=enabled
# 기존 Pod에 적용하려면 재배포 필요
kubectl rollout restart deployment -n production
# 사이드카 주입 확인
kubectl get pod -n production -o yaml | grep -c "istio-proxy"
# 숫자가 Pod 수와 일치해야 함
mTLS 설정
PeerAuthentication으로 mTLS 모드 설정
# PERMISSIVE 모드: mTLS와 평문 트래픽 모두 허용 (기본값, 마이그레이션 단계)
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: production
spec:
mtls:
mode: PERMISSIVE
# STRICT 모드: mTLS 인증서 없는 트래픽은 거부
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: production
spec:
mtls:
mode: STRICT
# 적용
kubectl apply -f peer-auth-strict.yaml
# mTLS 상태 확인
istioctl x check-inject -n production
istioctl authn tls-check payment.production.svc.cluster.local
# 출력 예시:
# HOST:PORT STATUS SERVER CLIENT
# payment.production.svc.cluster.local:8080 OK STRICT STRICT
DestinationRule로 아웃바운드 mTLS 설정
# mTLS를 클라이언트 측에서도 강제
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: payment-mtls
namespace: production
spec:
host: payment.production.svc.cluster.local
trafficPolicy:
tls:
mode: ISTIO_MUTUAL # Istio 발급 인증서로 mTLS
connectionPool:
tcp:
maxConnections: 100
http:
h2UpgradePolicy: UPGRADE
outlierDetection: # 서킷브레이커
consecutiveErrors: 5
interval: 30s
baseEjectionTime: 30s
maxEjectionPercent: 50
kubectl apply -f destination-rule.yaml
# DestinationRule 적용 확인
kubectl get destinationrule -n production
istioctl proxy-config cluster payment-7d9b4c8f6-xkp2n.production | grep payment
트래픽 가중치 기반 카나리 배포
확대
실습: v1 → v2 점진적 트래픽 전환
# 1. 두 버전의 Deployment 준비
# v1 Deployment (기존)
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-v1
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: payment
version: v1
template:
metadata:
labels:
app: payment
version: v1 # 버전 레이블 필수
spec:
containers:
- name: payment
image: payment:1.0.0
ports:
- containerPort: 8080
---
# v2 Deployment (신규)
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-v2
namespace: production
spec:
replicas: 1 # 처음엔 적게
selector:
matchLabels:
app: payment
version: v2
template:
metadata:
labels:
app: payment
version: v2
spec:
containers:
- name: payment
image: payment:2.0.0
ports:
- containerPort: 8080
# 2. DestinationRule로 v1/v2 subset 정의
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: payment-dr
namespace: production
spec:
host: payment # Service 이름
subsets:
- name: v1
labels:
version: v1 # 이 레이블을 가진 Pod를 v1 subset으로 그룹화
- name: v2
labels:
version: v2
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
# 3. VirtualService로 트래픽 분배 (처음: v1 90% / v2 10%)
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: payment-vs
namespace: production
spec:
hosts:
- payment # Service 이름과 일치
http:
- route:
- destination:
host: payment
subset: v1
weight: 90 # v1으로 90%
- destination:
host: payment
subset: v2
weight: 10 # v2으로 10%
timeout: 5s # 타임아웃 설정
retries:
attempts: 3
perTryTimeout: 2s
retryOn: gateway-error,connect-failure,retriable-4xx
# 적용
kubectl apply -f destination-rule.yaml
kubectl apply -f virtual-service.yaml
# 트래픽 분배 확인 (부하 테스트)
for i in $(seq 1 100); do
kubectl exec -n production \
$(kubectl get pod -n production -l app=frontend -o name | head -1) \
-- curl -s http://payment:8080/version
done | sort | uniq -c
# 약 90개: "version: v1"
# 약 10개: "version: v2"
# v2 에러율 모니터링 (Grafana 또는 istioctl)
istioctl dashboard kiali # Kiali 트래픽 시각화 대시보드
헤더 기반 카나리 (특정 사용자만 v2 체험)
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: payment-vs
namespace: production
spec:
hosts:
- payment
http:
# QA 팀은 X-Canary: true 헤더로 항상 v2로 라우팅
- match:
- headers:
x-canary:
exact: "true"
route:
- destination:
host: payment
subset: v2
weight: 100
# 나머지는 v1
- route:
- destination:
host: payment
subset: v1
weight: 100
운영 리소스 직접 패치
안전한 실행 조건: 변경 내용을 코드에 반영할 계획이 있고 영향 범위를 검토했을 때만 실행하세요.
실행 전 반드시 확인
- 현재 컨텍스트와 Namespace가 의도한 대상인지 확인했는가
- 운영 트래픽이나 상태 저장 데이터에 미치는 영향을 확인했는가
- 되돌릴 매니페스트, 백업, 또는 복구 절차가 준비되어 있는가
kubectl patch virtualservice payment-vs -n production위 항목을 모두 확인한 후 복사할 수 있습니다
# v2 테스트 (헤더 포함)
curl -H "x-canary: true" http://payment.production.svc.cluster.local/api/pay
# 단계별 트래픽 전환 스크립트
for weight in 10 25 50 75 100; do
echo "v2 트래픽: $weight%"
kubectl patch virtualservice payment-vs -n production \
--type=json \
-p="[
{\"op\": \"replace\", \"path\": \"/spec/http/0/route/0/weight\", \"value\": $((100-weight))},
{\"op\": \"replace\", \"path\": \"/spec/http/0/route/1/weight\", \"value\": $weight}
]"
echo "에러율 모니터링 중... (5분 대기)"
sleep 300
done
트러블슈팅
PeerAuthentication을 STRICT 모드로 변경한 직후 일부 서비스에서 RBAC: access denied 또는 upstream connect error 오류가 발생합니다.
1단계: 사이드카 인젝션 여부 확인
# 통신 실패 Pod에 istio-proxy 사이드카가 있는지 확인
kubectl get pod -n production -o json | \
jq '.items[] | select(.spec.containers[].name == "istio-proxy") | .metadata.name'
# 사이드카가 없는 Pod 찾기
kubectl get pod -n production -o yaml | \
grep -B5 "istio-injection: false"
# 특정 Pod의 사이드카 상태 한 번에 확인
kubectl describe pod <pod-name> -n production | \
grep -E "istio-proxy|istio-init|Containers:"
2단계: mTLS 연결 상태 진단
# 서비스 간 mTLS 상태 확인
istioctl authn tls-check <pod-name>.<namespace> <service-name>.<namespace>.svc.cluster.local
# 예시:
istioctl authn tls-check payment-7d9b4c8f6-xkp2n.production \
order.production.svc.cluster.local
# 출력:
# HOST:PORT STATUS SERVER CLIENT
# order.production.svc.cluster.local:8080 OK STRICT STRICT ← 정상
# order.production.svc.cluster.local:8080 CONFLICT STRICT DISABLE ← 문제!
# CONFLICT: 서버는 STRICT를 요구하지만 클라이언트는 mTLS 없이 연결 시도
3단계: Envoy 접근 로그로 실제 에러 확인
# Envoy 접근 로그 활성화
kubectl apply -f - << 'EOF'
apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
name: enable-access-log
namespace: production
spec:
accessLogging:
- providers:
- name: envoy
EOF
# 실패하는 Pod의 Envoy 로그 확인
kubectl logs -n production <pod-name> -c istio-proxy | \
grep -E "response_code=403|UF|URX|NR"
# 주요 에러 코드 해석:
# UF: Upstream connection failure
# URX: Upstream connection reset (mTLS 핸드쉐이크 실패)
# NR: No route found
# UAEX: Upstream application exception (AuthorizationPolicy 거부)
4단계: 단계적 해결
# 해결 1: 사이드카가 없는 네임스페이스에 인젝션 활성화
kubectl label namespace <namespace> istio-injection=enabled
kubectl rollout restart deployment -n <namespace>
# 해결 2: 특정 Pod만 PERMISSIVE 예외 처리 (임시)
kubectl apply -f - << 'EOF'
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: legacy-service-exception
namespace: production
spec:
selector:
matchLabels:
app: legacy-batch # 사이드카 없는 레거시 앱
mtls:
mode: PERMISSIVE # 이 Pod만 예외
EOF
# 해결 3: DestinationRule의 tls 모드 확인
kubectl get destinationrule -n production -o yaml | \
grep -A3 "tls:"
# mode: DISABLE 라면 ISTIO_MUTUAL로 변경
# 해결 4: AuthorizationPolicy 규칙 확인 (403 에러인 경우)
kubectl get authorizationpolicy -n production
kubectl describe authorizationpolicy <name> -n production
5단계: 프록시 설정 덤프로 최종 확인
# Envoy가 실제로 받은 라우팅 설정 확인
istioctl proxy-config route <pod-name>.production
istioctl proxy-config cluster <pod-name>.production | grep payment
istioctl proxy-config endpoint <pod-name>.production | grep payment
# 전체 설정 덤프 (고급 분석용)
istioctl proxy-config all <pod-name>.production -o json > /tmp/proxy-config.json
심화 — 사이드카는 앱과 '동시에' 뜨지 않는다
심화: 사이드카의 시작 순서와 종료 — 설정이 맞아도 타이밍이 어긋나면 실패한다
인젝션이 Envoy를 앱 옆에 놓아준다는 것까지는 배웠지만, 두 컨테이너의 시작·종료 타이밍이 어긋나면 mTLS 설정이 완벽해도 요청이 실패합니다.
- 시작은 레이스입니다: 파드가 뜰 때 앱 컨테이너와 istio-proxy(Envoy)는 병렬로 시작됩니다. iptables는 istio-init이 미리 심어 앱의 아웃바운드를 전부 Envoy로 우회시키는데, 앱이 Envoy보다 먼저 요청을 쏘면 아직 리스닝하지 않는 Envoy로 리다이렉트돼 connection refused나 503이 납니다. DB 마이그레이션·초기화 API 호출을 기동 즉시 하는 앱에서 잘 터집니다. holdApplicationUntilProxyStarts를 켜면 Envoy가 준비될 때까지 앱 시작을 지연시켜 막을 수 있습니다.
- istio-proxy는 스스로 종료하지 않습니다: Envoy는 파드가 사는 동안 계속 떠 있는 상주 프로세스입니다. 그래서 Job/CronJob 파드는 앱이 끝나도 istio-proxy가 계속 Running이라 파드가 Completed로 전이하지 못하고 영원히 남습니다. 그런 배치 파드는 사이드카 주입을 제외(sidecar.istio.io/inject: false)하거나, 앱 종료 후 Envoy를 종료시키는 방식(K8s 네이티브 사이드카 컨테이너 등)으로 처리합니다.
정리하면 사이드카는 공짜가 아니라 파드 생애주기에 새 참여자를 더한 것입니다. mTLS·라우팅 설정이 맞아도 컨테이너 수명주기가 어긋나면 장애가 나므로, 사이드카 파드는 시작·종료 순서까지 설계해야 합니다.
상황: payment 앱이 부팅 직후 DB와 다른 서비스로 초기화 호출을 하는데, 파드가 새로 뜬 직후 그 호출들이 connection refused나 503으로 실패합니다. 앱 로그엔 기동 시점에만 에러가 몰리고, 5~10초 뒤 요청은 멀쩡합니다. mTLS·VirtualService·DestinationRule은 모두 맞게 설정돼 있습니다.
원인: 앱 컨테이너와 istio-proxy가 병렬로 시작되는데, istio-init이 심은 iptables가 앱의 모든 아웃바운드를 Envoy로 우회시킵니다. 앱이 Envoy가 리스닝을 시작하기 전에 요청을 보내면, 갈 곳(Envoy 15001)이 아직 안 열려 connection refused가 납니다. Envoy가 준비되면 정상화됩니다. 설정 오류가 아니라 시작 순서 레이스입니다.
진단: 실패가 파드 기동 직후에만 몰리는 패턴인지 확인합니다(정상 운영 중엔 없음). kubectl logs <pod> -c istio-proxy로 Envoy가 언제 준비됐는지와 앱 로그의 첫 요청 시각을 비교합니다. istioctl proxy-config로 라우팅·mTLS 설정 자체는 정상임을 확인해 설정 문제를 배제합니다.
해결: 앱 시작을 Envoy 준비까지 미루도록 holdApplicationUntilProxyStarts를 켭니다(전역 MeshConfig 또는 파드 어노테이션 proxy.istio.io/config). 그러면 Envoy가 준비된 뒤 앱이 시작돼 초기 요청 실패가 사라집니다. 아울러 배치성 Job 파드는 istio-proxy가 끝나지 않아 Completed가 안 되므로, 그런 파드는 사이드카 주입에서 제외하거나 네이티브 사이드카 방식을 씁니다.
시나리오: v2 결제 모듈 카나리 배포 — 에러 감지 시 즉시 롤백
새로운 결제 로직이 포함된 payment:2.0.0을 프로덕션에 배포합니다. 장애 없이 점진적으로 트래픽을 전환하고, 문제 감지 시 즉시 롤백하는 절차를 준비했습니다.
운영 리소스 직접 패치
안전한 실행 조건: 변경 내용을 코드에 반영할 계획이 있고 영향 범위를 검토했을 때만 실행하세요.
실행 전 반드시 확인
- 현재 컨텍스트와 Namespace가 의도한 대상인지 확인했는가
- 운영 트래픽이나 상태 저장 데이터에 미치는 영향을 확인했는가
- 되돌릴 매니페스트, 백업, 또는 복구 절차가 준비되어 있는가
kubectl patch virtualservice payment-vs -n production위 항목을 모두 확인한 후 복사할 수 있습니다
# 배포 전: 기준선 에러율 측정
istioctl dashboard prometheus
# PromQL: sum(rate(istio_requests_total{destination_service="payment.production.svc.cluster.local", response_code=~"5.."}[5m])) / sum(rate(istio_requests_total{destination_service="payment.production.svc.cluster.local"}[5m]))
# 기준값: 0.2% (0.002)
# Step 1: v2 Deployment 배포 (0% 트래픽)
kubectl apply -f payment-v2-deployment.yaml
# Step 2: 10% 트래픽 전환
kubectl apply -f - << 'EOF'
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: payment-vs
namespace: production
spec:
hosts:
- payment
http:
- route:
- destination:
host: payment
subset: v1
weight: 90
- destination:
host: payment
subset: v2
weight: 10
EOF
# Step 3: 5분간 v2 에러율 모니터링
watch -n 10 'kubectl exec -n monitoring \
$(kubectl get pod -n monitoring -l app=prometheus -o name | head -1) \
-- curl -sg "http://localhost:9090/api/v1/query" \
--data-urlencode "query=sum(rate(istio_requests_total{destination_workload=\"payment-v2\",response_code=~\"5..\"}[2m])) / sum(rate(istio_requests_total{destination_workload=\"payment-v2\"}[2m]))" \
| jq ".data.result[0].value[1]"'
# v2 에러율이 1%를 초과하면 즉시 롤백
V2_ERROR_RATE=$(kubectl exec -n monitoring ... | jq -r ...)
if (( $(echo "$V2_ERROR_RATE > 0.01" | bc -l) )); then
echo "에러율 임계값 초과, 롤백 시작"
# 롤백: 100% v1으로 되돌리기
kubectl patch virtualservice payment-vs -n production \
--type=json \
-p='[{"op":"replace","path":"/spec/http/0/route/0/weight","value":100},
{"op":"replace","path":"/spec/http/0/route/1/weight","value":0}]'
fi
# 안정적이면 25% → 50% → 100% 순으로 단계적 전환
# 각 단계 5분 관찰 후 이상 없으면 다음 단계 진행
Istio 없이 카나리 배포를 구현하려면 Deployment 레플리카 수로 트래픽을 조절해야 합니다(10%를 위해 v1 9개, v2 1개 유지). Istio의 weight 기반 라우팅은 각 Deployment의 레플리카 수와 무관하게 정확한 비율을 유지할 수 있어, 적은 v2 레플리카로도 세밀한 트래픽 분배가 가능합니다.
핵심 요약
| 리소스 | 역할 | 주요 설정 |
|---|---|---|
| PeerAuthentication | 인바운드 mTLS 정책 | PERMISSIVE / STRICT |
| DestinationRule | 아웃바운드 정책 + subset 정의 | tls.mode, subsets, outlierDetection |
| VirtualService | 라우팅 규칙 | weight, match, timeout, retries |
| AuthorizationPolicy | L7 접근 제어 | principals, methods, paths |
마이그레이션 안전 순서: 전체 PERMISSIVE → 각 서비스 사이드카 확인 → 서비스별 STRICT 전환 → 네임스페이스 STRICT 전환
Istio 설치 및 istiod 상태 확인
istioctl install --set profile=demo -y
kubectl get pods -n istio-system예상 출력
NAME READY STATUS RESTARTS istiod-xxx 1/1 Running 0 istio-ingressgateway-xxx 1/1 Running 0 istio-egressgateway-xxx 1/1 Running 0
네임스페이스에 사이드카 인젝션 활성화 및 검증
kubectl label namespace default istio-injection=enabled
kubectl get namespace default --show-labels | grep istio-injection예상 출력
default Active 10d istio-injection=enabled,kubernetes.io/metadata.name=default
파드 배포 후 사이드카 주입 확인
kubectl run nginx --image=nginx:1.25
kubectl get pod nginx -o jsonpath='{.spec.containers[*].name}'예상 출력
nginx istio-proxy
PeerAuthentication STRICT 모드 적용
kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: default
spec:
mtls:
mode: STRICT
EOF
kubectl get peerauthentication -n default예상 출력
NAME MODE AGE default STRICT 5s
mTLS 연결 상태 확인
istioctl authn tls-check nginx.default.svc.cluster.local 2>/dev/null | head -5예상 출력
HOST:PORT STATUS SERVER CLIENT nginx.default.svc.cluster.local:80 OK STRICT STRICT
- kubectl get pods -n istio-system 에서 istiod Pod이 Running 상태인지 먼저 확인 — istiod가 CrashLoopBackOff 이면 이후 사이드카 인젝션 및 mTLS 설정이 모두 실패함
- kubectl get pod -n <namespace> -o yaml | grep -c "istio-proxy" 결과가 Pod 수와 일치하면 모든 Pod에 사이드카 인젝션 성공 — 숫자가 적으면 레이블 또는 어노테이션 설정 확인
- istioctl authn tls-check <pod>.<ns> <service>.<ns>.svc.cluster.local 출력에서 STATUS=OK + SERVER=STRICT + CLIENT=STRICT 조합이면 mTLS 정상 동작 — CONFLICT 이면 한쪽에 사이드카 없음
- VirtualService weight 합산이 100이어야 함: kubectl get virtualservice -o yaml 에서 route weight 합계가 100이 아니면 503 오류 발생. 0%일 때도 weight: 0으로 명시 필요
- 카나리 트래픽 분배 확인: for i in $(seq 1 20); do curl -s http://svc/version; done | sort | uniq -c 결과에서 v1/v2 분포가 설정한 weight(90/10)에 근사하면 정상. 편차가 크면 DestinationRule subset 레이블과 Pod 레이블 불일치 확인
- Envoy 에러코드 해석: kubectl logs <pod> -c istio-proxy | grep -E "NR|UF|URX" — NR(No Route)이면 VirtualService 미적용, UF(Upstream Failure)이면 서비스 자체 장애, URX이면 mTLS 핸드쉐이크 실패
명령어·단축키 빠른 참조
이 모듈에서 다룬 istioctl과 사이드카·mTLS·카나리 관련 kubectl 명령을 실전 옵션과 함께 모았습니다.
| 명령어/단축키 | 용도 | 자주 쓰는 예 |
|---|---|---|
istioctl install | 컨트롤 플레인 설치(프로파일) | istioctl install --set profile=demo -y |
istioctl x precheck | 설치 전 클러스터 호환성 점검 | istioctl x precheck |
istioctl authn tls-check | 서비스 간 mTLS 상태(STRICT/CONFLICT) | istioctl authn tls-check payment.prod svc.prod.svc.cluster.local |
istioctl proxy-config | Envoy 실제 설정 덤프(route/cluster/endpoint) | istioctl proxy-config route <pod>.<ns>, istioctl proxy-config cluster <pod>.<ns> |
istioctl dashboard | Kiali·Prometheus 대시보드 열기 | istioctl dashboard kiali |
kubectl label namespace | 네임스페이스 자동 사이드카 주입 | kubectl label namespace default istio-injection=enabled |
kubectl get pod -o jsonpath | 사이드카(istio-proxy) 주입 확인 | kubectl get pod X -o jsonpath='{.spec.containers[*].name}' → app istio-proxy |
kubectl logs -c istio-proxy | Envoy 접근 로그·에러코드 확인 | kubectl logs <pod> -c istio-proxy | grep -E 'UF|URX|NR' |
kubectl apply -f (PeerAuth/DR/VS) | mTLS·subset·가중치 라우팅 적용 | kubectl apply -f peer-auth-strict.yaml |
kubectl patch virtualservice | 카나리 트래픽 가중치 조정 | kubectl patch virtualservice payment-vs --type=json -p='[...]' |
관련 모듈로 더 깊이:
- NetworkPolicy를 활용한 특정 파드 간 통신 차단 및 제한 — 사이드카 mTLS 이전에 L3/L4 레벨에서 파드 간 통신을 차단하는 기본 방어선
- Ingress Controller 경로 기반 포워딩과 SSL/TLS 설정 — 메시 외부에서 들어오는 트래픽을 받는 진입점, Istio Gateway와의 역할 구분
- Prometheus Operator와 Grafana 연동 대시보드 구축 — Envoy 사이드카가 노출하는 메트릭을 수집해 에러율·레이턴시를 가시화하는 법
다음 모듈 monitoring-prometheus에서는 Istio의 사이드카가 자동으로 노출하는 Envoy 메트릭을 Prometheus로 수집하고, 서비스 메시 레이어의 에러율과 레이턴시를 Grafana 대시보드로 가시화하는 방법을 다룹니다.