81~84일차) 2025-04-24 ~ 2025-04-29 [3. k8s 팀 프로젝트] - 이스티오 실습(mTLS, authentication 구성, 시나리오)
🛡️ 1. mTLS 통신 암호화 (Mutual TLS)
✅ 목적
- 파드 간 통신을 자동으로 암호화 및 인증
- 클러스터 내부에서도 패킷 스니핑 공격 방지
- 서비스 A → B 통신 시, 양쪽 모두 신뢰할 수 있는 인증서로 자신을 증명함
✅ Istio에서의 구성 방법
- Istio 설치 시 자동으로 Citadel 컴포넌트 생성
- istiod가 CA 역할 수행
- 각 사이드카 Envoy에 자동으로 TLS 인증서 주입
✅ 왜 중요한가?
보안 요소 mTLS 적용 전 mTLS 적용 후
트래픽 노출 | 암호화되지 않음 | 암호화됨 |
인증 확인 | 없음 | 양방향 인증 |
내부 위조 요청 | 가능 | 불가능 (인증서 검증 실패) |
🛂 2. Policy 기반 접근 제어 (AuthorizationPolicy)
✅ 목적
- 네임스페이스/서비스/사용자/경로/메서드 기반으로 접근 허용 또는 차단
- 제로 트러스트 보안 모델의 실현
🎯 mTLS + Policy 통합 운영
요소 역할
mTLS | 모든 통신을 암호화 + 신원 인증 |
AuthorizationPolicy | 신원이 확인된 요청 중 허용된 것만 통과 |
이 둘을 함께 쓰면 통신 경로 보호 + 사용자/서비스 수준 제어까지 모두 가능합니다.
🧠 결론: 왜 이걸 쓰는가?
목적 설명
보안 강제 | 전 구간 암호화 (Zero Trust), 패킷 도청 방지 |
신원 기반 접근 제어 | 기존 IP 기반 ACL보다 정교한 정책 구성 |
DevOps 친화적 | YAML 기반 정책으로 GitOps 가능 |
인프라와 무관 | 클러스터 네트워크 구조와 독립적으로 적용 가능 |
1. mTLS 전면 활성화 (제로 트러스트의 핵심)
vi peer-authentication.yml
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: dev
spec:
mtls:
mode: STRICT
→ STRICT 모드는 해당 네임스페이스 내 모든 파드 간 통신이 mTLS를 필수로 사용해야 함을 의미.
2. AuthorizationPolicy 구성
vi deny-all.yml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: dev
spec:
{}
→ dev 네임스페이스에 대한 모든 요청을 기본 차단합니다
3. 서비스 계정 정의
vi front-end-service-account.yml
apiVersion: v1
kind: ServiceAccount
metadata:
name: frontend-sa
namespace: dev
→ frontend 서비스가 가지는 고유한 SA 생성
4. frontend 서비스 배포 ( SA 포함 )
vi frontend-service.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-service
namespace: dev
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
serviceAccountName: frontend-sa
containers:
- name: frontend
image: 61.254.18.30:5000/frontend:1
ports:
- containerPort: 5000
---
apiVersion: v1
kind: Service
metadata:
name: frontend-service
namespace: dev
spec:
selector:
app: frontend
ports:
- port: 80
targetPort: 5000
type: LoadBalancer
5. 이외 다른 서비스들 모두 배포
vi book-service.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: book-service
namespace: dev
spec:
replicas: 1
selector:
matchLabels:
app: book
template:
metadata:
labels:
app: book
spec:
containers:
- name: book
image: 61.254.18.30:5000/book:1
ports:
- containerPort: 5001
---
apiVersion: v1
kind: Service
metadata:
name: book-service
namespace: dev
spec:
selector:
app: book
ports:
- port: 5001
targetPort: 5001
type: ClusterIP
vi review-service.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: review-service
namespace: dev
spec:
replicas: 1
selector:
matchLabels:
app: review
template:
metadata:
labels:
app: review
spec:
containers:
- name: review
image: 61.254.18.30:5000/review:1
ports:
- containerPort: 5002
---
apiVersion: v1
kind: Service
metadata:
name: review-service
namespace: dev
spec:
selector:
app: review
ports:
- port: 5002
targetPort: 5002
type: ClusterIP
→ 현재 허용 정책이 없기 때문에 접속이 안된다.
6. 허용 정책 설정
6-1. 외부에서 ingressgateway를 통해 접근 허용
vi allow-ingress.yml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-ingress-to-nginx
namespace: dev
spec:
selector:
matchLabels:
app: frontend # frontend 서비스로 들어오는 트래픽 허용
rules:
- from:
- source:
namespaces: ["istio-system"] # istio-system 네임스페이스에서 오는 트래픽 허용
→ ingress를 통해 들어온 트래픽이 frontend의 페이지를 보여주고있지만, frontend가 book,review를 불러오는 정책이 없기 때문에 위와 같음 페이지만 보여짐.
6-2. frontend가 book을 호출할 수 있는 허용 정책
vi allow-frontend-book.yml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-frontend-to-book
namespace: dev
spec:
selector:
matchLabels:
app: book
rules:
- from:
- source:
principals: ["cluster.local/ns/dev/sa/frontend-sa"]
→ frontend 서비스가 book 서비스를 호출할 수 있는 정책이 생기자, book정보는 가져오지만, review 서비스는 가져오지 못하는 것을 확인할 수 있음.
6-3. frontend가 review 서비스 호출 허용 정책
vi allow-frontend-review.yml
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-frontend-to-review
namespace: dev
spec:
selector:
matchLabels:
app: review
rules:
- from:
- source:
principals: ["cluster.local/ns/dev/sa/frontend-sa"]
시나리오
🔶 1. 기존 방식의 문제점 (비교 대상)
🧱 전통적인 배포 방식
- 새 버전(v2)을 배포하면 → 모든 사용자가 동시에 받음
- 문제 발생 시 → 서비스 전체 장애
- 되돌리려면? → 긴급 롤백 (시간 오래 걸림, 다운타임 발생)
❌ 문제 발생 시 대처가 늦고,
❌ 실제 트래픽에서 오류 테스트는 거의 불가능함
❌ 특정 사용자만 대상으로 실험 불가능함
✅ 2. Istio가 제공하는 해결책
🧪 [1] Fault Injection – “일부러 망가뜨려보기”
시나리오:
우리는 v2를 배포했지만, DB 연동이 불안정할 수 있어.
→ Istio를 통해 v2로 가는 요청에 대해 500 Internal Server Error를 의도적으로 유발
fault:
abort:
percentage: 100
httpStatus: 500
활용 포인트:
- 실제 장애를 발생시키지 않고도 시뮬레이션 가능
- 장애 상황에 대한 모니터링/알림 시스템을 미리 점검
🚦 [2] Canary Deployment – “조심스럽게 조금씩 배포하기”
시나리오:
- 전체 유저 중 20%만 v2로 보내고 싶다
- 트래픽 분산:
route:
- subset: v1
weight: 80
- subset: v2
weight: 20
활용 포인트:
- 장애 발생 시 피해 규모 최소화
- 실제 사용자 반응을 실시간 확인 가능
- A/B 테스트도 간단히 구현
🔄 [3] Circuit Breaker – “망가지면 자동으로 차단하기”
시나리오:
- v2로 보냈더니 500이 계속 난다?
- Istio가 감지해서 자동으로 해당 엔드포인트를 차단함
outlierDetection:
consecutive5xxErrors: 1
interval: 1s
baseEjectionTime: 30s
활용 포인트:
- 문제 있는 인스턴스를 자동으로 격리
- 사용자 입장에서 무중단 대응 가능
📊 3. 비교 표 정리
항목 | 전통적 방식 | Istio 도입 시 |
배포 방식 | 전체 사용자에 즉시 적용 | 일부 트래픽만 v2에 적용 가능 (Canary) |
장애 테스트 | 실제 서버 망가뜨려야 함 | Fault Injection으로 시뮬레이션 가능 |
문제 대응 | 수동 롤백 / 다운타임 발생 | Circuit Breaker로 자동 차단 / 회복 |
A/B 테스트 | 별도 로직 필요 | Route 조건만 바꾸면 가능 |
트래픽 제어 | 불가능 | 가중치 기반 정밀 분산 가능 |
💡 4. 결론 - 왜 Istio를 써야 하나?
"Istio는 단순히 ‘트래픽을 보내는 도구’가 아니라,
트래픽을 이해하고, 제어하고, 실험하고, 보호할 수 있게 해주는
‘서비스 메쉬 기반 운영 전략의 핵심’입니다."
→ 장애가 터지기 전에 감지하고,
→ 사용자에게 피해를 최소화하고,
→ 자동으로 회복하게 만들 수 있다면
→ 우리는 롤백이 아니라, 롤업이 가능한 팀이 되는 거죠.
🎯 핵심 비교 포인트
항목 | ① 레플리카 방식 (디플로이먼트 2개) | ② Istio 트래픽 조절 방식 |
트래픽 제어 단위 | Pod 개수 비례 | 실제 트래픽 비율 기반 🎯 |
정밀한 조절 가능? | ❌ 불가능 (1:4 → 20%) | ✅ 가능 (weight 17%, 23% 등 아주 정밀) |
배포 전략 | step-wise rollout (수동 조절) | Canary / A/B / Header-based 다양하게 |
롤백 | 수동 scale 조절 or 롤백 | Route만 변경하면 즉시 롤백 가능 |
운영 중 실험 분리 | ❌ 어려움 | ✅ 특정 유저만 v2 (헤더, URI 조건 등) |
코드 변경 없이 실험? | ❌ 어려움 | ✅ 가능 (서비스 라우팅만 조절하면 끝) |
관찰 + 대응 | Metrics로는 가능, 자동은 아님 | Prometheus + CircuitBreaker + Rollouts 연동 가능 |
이스티오 - 안전하게 환경 확인 후에 v2를 100%, 얘는 중단될 일이 없음
→ 중단되도록 오류 주입 → 복구 설정을 하면 알아서 재시도 → 안되면 다른 곳으로 트래픽 보냄 → 서비스 중 어디가 문제인지 확인 가능
기존꺼에 다른배포법을 쓴다면 - 서비스를 아예 이용하지 못함
Istio가 '필수’인 환경
- 버전별 사용자 트래픽 제어가 필요할때
- 10%의 사용자만 테스트
- Istio의 VirtualService로 트래픽을 세분화해서 컨트롤
- 백엔드가 여러 서비스로 확장됐을때
- 서비스 간 호출이 많아질수록 서비스 디스커버리, 로드밸런싱, 장애 복구(retry/failover)가 필요
- Istio는 서비스 간 호출을 사이드카(proxy)로 통제하면서 이걸 처리함
- 비정상 상황 테스트(fault injection)가 필요할 때
- 일부러 review-service를 느리게 응답하게 하거나, 에러를 반환하게 해보면서 UI나 다른 서비스가 어떻게 반응하는지 실험
- 이런 실험은 Istio의 fault 기능으로 손쉽게 설정 가능
- 서비스 간 보안 통신이 필요할 때 (mTLS)
- 회사 보안 정책상 서비스 간 통신을 암호화(mTLS)해야 한다면, Istio가 mTLS를 기본 제공
- 일반적인 K8s 환경에서는 직접 구현해야 함
- 서비스 호출의 모니터링 및 트레이싱이 필요할 때
- 여러 개의 마이크로서비스 간 복잡한 통신, 트래픽 제어, 장애 시뮬레이션, 보안 통신, 관찰성(모니터링/트레이싱)을 제어 및 실습
시나리오 자료
replica 수를 이용한 카나리 배포 방법
v1
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-v1
namespace: default
spec:
replicas: 4
selector:
matchLabels:
app: frontend
version: v1
template:
metadata:
labels:
app: frontend
version: v1
spec:
containers:
- name: frontend
image: choiseungyoung/frontend:21
ports:
- containerPort: 5000
---
apiVersion: v1
kind: Service
metadata:
name: frontend-service
namespace: default
spec:
selector:
app: frontend
ports:
- protocol: TCP
port: 80
targetPort: 5000
v2
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-v2
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: frontend
version: v2
template:
metadata:
labels:
app: frontend
version: v2
spec:
containers:
- name: frontend
image: choiseungyoung/frontend:8
ports:
- containerPort: 5000
- v1은 4개 v2는 1개로 80% 20% 비율이다.
이후 점진적으로 v1을 20퍼 v2를 80퍼로 하고싶다!
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-v1
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: frontend
version: v1
template:
metadata:
labels:
app: frontend
version: v1
spec:
containers:
- name: frontend
image: choiseungyoung/frontend:21
ports:
- containerPort: 5000
---
apiVersion: v1
kind: Service
metadata:
name: frontend-service
namespace: default
spec:
selector:
app: frontend
ports:
- protocol: TCP
port: 80
targetPort: 5000
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-v2
namespace: default
spec:
replicas: 4
selector:
matchLabels:
app: frontend
version: v2
template:
metadata:
labels:
app: frontend
version: v2
spec:
containers:
- name: frontend
image: choiseungyoung/frontend:8
ports:
- containerPort: 5000
이렇게 replica를 복제하는 방법으로 카나리 배포를 하면 파드를 지우고 생성하고를 반복해야 되기 때문에 서비스 운영 중단 시간이 생긴다. 서비스가 많아 질 수록 운영 중단 시간은 더욱 더 길어질 것이다!
✅ 1. book-service에 503 오류 주입
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: book-service
namespace: msa-demo
spec:
hosts:
- book-service
http:
- fault:
abort:
percentage:
value: 100
httpStatus: 503
route:
- destination:
host: book-service
확인 후, 아래대로 꼭 지워주기
kubectl delete virtualservice book-service -n msa-demo
✅ 2. frontend-v2에만 장애 주기
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: frontend
namespace: msa-demo
spec:
hosts:
- "*"
gateways:
- frontend-gateway
http:
- match:
- headers:
cookie:
regex: ".*ver=2.*"
fault:
abort:
httpStatus: 500
percentage:
value: 100.0
route:
- destination:
host: frontend-service
subset: v2
- route:
- destination:
host: frontend-service
subset: v1
- 요청 헤더에 cookie: ver=2 가 들어있으면
- frontend-service의 v2로 라우팅되긴 하지만
- 무조건 HTTP 500 에러로 응답
→ 테스트/실험 용도로 일부러 쿠키를 가진 요청만 망가뜨려서 v2 버전 사용자들이 에러를 경험할때 UI가 어떻게 동작하는지와 관련된 시나리오 실험
💡 쿠키 없이 v1로, ver=2 쿠키 있으면 장애!
조건 | 도달하는 Pod | 결과 |
쿠키 없음 | frontend-v1 | 정상 응답 |
쿠키 있음 (ver=2) | frontend-v2 | HTTP 500 강제 |
curl --header "Cookie: ver=2" , 브라우저에서 쿠키 설정해 접속하면 v2에만 장애
- *x-version: v2*는 버전 라우팅을 위해 직접 요청하는 헤더이고,
- *Cookie: ver=2*는 세션 또는 상태 정보를 서버에 전달하는 쿠키입니다.
따라서 두 헤더는 서로 다른 목적을 가지고 있으며, 쿠키는 자동으로 관리되는 세션 정보인 반면, x-version은 버전 라우팅을 위한 수동 설정인 거죠.
- fault injection이 100% 확률로 걸려서 HTTP 500 뜨도록 확인
curl -i --header "Cookie: ver=2" http://211.183.3.200/
curl -i --header "x-version: v2" http://211.183.3.200/
//fault-service2-v2 영상
✅ 3. review-service에만 장애 주기
이 설정은 review-service로의 요청을 받았을 때 100%의 확률로 HTTP 500 오류를 반환하도록 합니다.
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: review-vs
namespace: msa-demo
spec:
hosts:
- review-service
http:
- fault:
abort:
httpStatus: 500
percentage:
value: 100 # 장애를 100% 확률로 주기
route:
- destination:
host: review-service
port:
number: 5002
확인 후, 아래대로 꼭 지워주기
kubectl delete virtualservice review-service -n msa-demo
//fault-reviewErr-v2 영상
원래 계획:
Pod 로그 확인:
Kubernetes에서 직접 서비스의 로그를 확인하려면 kubectl logs 명령어를 사용해 해당 Pod에서 발생하는 오류나 로그를 확인할 수 있다는데…
kubectl logs -n msa-demo <review-service-pod-name>
Retry 확인:
Istio가 요청을 보내고 실패(예: 5xx 응답)했을 때 재시도하는 방식을 설정
- attempts: 3 → 최대 3번까지 재시도
- perTryTimeout: 2s → 각 시도당 최대 2초 기다리고
- retryOn: 5xx → 5xx 오류가 나면 재시도 트리거되어야 하는디…
설정대로면 리뷰 서비스가 500을 주면 Istio는 총 3번까지 재요청을 보내야하는데…
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: review-vs
namespace: msa-demo
spec:
hosts:
- review-service
http:
- fault:
abort:
httpStatus: 500
percentage:
value: 100
retries:
attempts: 3
perTryTimeout: 2s
retryOn: 5xx
route:
- destination:
host: review-service
port:
number: 5002