Istio 공식문서를 참고하면, Istio를 활용해 무엇을 할수 있는지 리스트업이 되어있다.
https://istio.io/latest/docs/tasks/
Tasks
How to do single specific targeted activities with the Istio system.
istio.io
크게 Traffic management, Security, Policy Enforcement, Observability, Extensibility 5가지로 나뉜다.
아직 다른건 자세히 모르겠고,
지원하는 기능 하나하나 씩 파보려고한다.
이번 글에서 다룰 내용은 Istio의 본연 기능 Traffic Management이다.
Traffic management
Request Routing, Fault Injection, Traffic Shifting(+TCP), Request timeout, Circuit Breaking,
Mirror, Locality Load Balacing, In/Egress 를 지원한다.
지원하는 기능들을 통해 고급 라우팅 + 고급 배포 기술 지원까지 가능 하다고 보인다.
Ingress Controller에서 지원하지않는 기능인 Request Routing, Fault Injection, Circuit Breaking,
Mirror 위주로 살펴보려고한다.
- Request Routing: 특정 조건(예: URL, 헤더 등)에 따라 트래픽을 원하는 서비스 또는 서비스 버전으로 라우팅하는 기능
- Fault Injection: 네트워크 지연이나 오류를 인위적으로 주입하여 애플리케이션의 복원력을 테스트하는 기능
- Circuit Breaking: 과부하된 서비스로의 요청을 차단하여 시스템 안정성을 유지하고 연쇄 실패(cascading failure)를 방지하는 기능
- Mirror: 실제 요청을 처리하는 동시에 동일한 요청을 다른 서비스로 복사하여 테스트나 모니터링에 활용하는 기능
Request Routing
url, 헤더 기반으로 트래픽을 원하는 서비스로 보낼수 있는데, URL 기반으로 가중치를 주어 원하는 서비스로 트래픽을 전달한다.
- review verison1 - 3까지 3개의 app을 배포하고
- 한개의 Service로 3개의 app을 묶는다.
- v1 앱에 70% 가중치를 주고, v3 앱에 30% 가중치를 주고, v2는 0으로 설정한다.
기대하는 결과: 설정 대롤면 10개의 패킷중 7개의 패킷은 v1으로 가야하고, v2로는 가지 말아야한다.
실습에 필요한 YAML
apiVersion: v1
kind: Namespace
metadata:
labels:
istio-injection: enabled
kubernetes.io/metadata.name: istio-sample-1
name: istio-sample-1
apiVersion: v1
kind: Service
metadata:
labels:
app: reviews
service: reviews
name: reviews
namespace: istio-sample-1
spec:
ports:
- name: http
port: 9080
selector:
app: reviews
# Deployment : review v1, v2, v3
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: reviews
version: v1
name: reviews-v1
namespace: istio-sample-1
spec:
replicas: 1
selector:
matchLabels:
app: reviews
version: v1
template:
spec:
containers:
- image: docker.io/istio/examples-bookinfo-reviews-v1:1.20.2
imagePullPolicy: IfNotPresent
name: reviews
ports:
- containerPort: 9080
protocol: TCP
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: reviews
version: v2
name: reviews-v2
namespace: istio-sample-1
spec:
replicas: 1
selector:
matchLabels:
app: reviews
version: v2
template:
spec:
containers:
- image: docker.io/istio/examples-bookinfo-reviews-v2:1.20.2
imagePullPolicy: IfNotPresent
name: reviews
ports:
- containerPort: 9080
protocol: TCP
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: reviews
version: v3
name: reviews-v3
namespace: istio-sample-1
spec:
replicas: 1
selector:
matchLabels:
app: reviews
version: v3
template:
spec:
containers:
- image: docker.io/istio/examples-bookinfo-reviews-v3:1.20.2
imagePullPolicy: IfNotPresent
name: reviews
ports:
- containerPort: 9080
protocol: TCP
# Gateway
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: bookinfo-gateway
namespace: istio-sample-1
spec:
selector:
istio: ingressgateway
servers:
- hosts:
- hj-book.com
port:
name: http
number: 8080
protocol: HTTP
# Virtual Service
# 가중치 정보 추가
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
weight: 70
- destination:
host: reviews
subset: v2
weight: 0
- destination:
host: reviews
subset: v3
weight: 30
# Destination rule
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: reviews
spec:
host: reviews
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
- name: v3
labels:
version: v3
실습에 필요한 YAML을 배포하고, 별도의 Pod를 생성하여 review 서비스로 요청을 보낸다.
# Packet Generator
kubectl run test-client --image=curlimages/curl -- /bin/sh -c \
"while true; do curl -s http://reviews:9080/reviews/4; echo; echo '---'; sleep 1; done"
당연히 잘 적용된다.
너무 심플했던 테스트였고, Destination <-> VirtualService 간의 상관관계를 파악해보려고 실습해보긴 했다.
이 실습에서 다루진 않았지만 header 기반으로도 traffic을 분산시킬수 있다.
(조금 더 응용하면 서브도메인 단위로 Service를 매핑할 수 있을것 같다)
Fault Injection
Injecting an HTTP delay fault
네트워크 지연이나 오류를 인위적으로 주입하여 애플리케이션의 복원력을 테스트하는 기능
시나리오 : 7초의 네트워크 지연시간이 서비스에 영향을 미칠까?
- reviews:v2와 ratings 마이크로서비스 간에 사용자 jason(header :: end-user.: json)에 대해 7초의 지연을 주입
- reviews:v2 서비스는 ratings 서비스에 대한 호출 시 10초의 하드코딩된 timeout 을 가지고 있음.
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- match:
- headers:
end-user:
exact: jason
fault:
delay:
percentage:
value: 100.0
fixedDelay: 2s # 7초로 세팅 후 -> 2초로 변경해보고, fault 처리까지 얼마나 걸리는지 세팅.
route:
- destination:
host: ratings
subset: v1
- route:
- destination:
host: ratings
subset: v1
결론 : 영향을 미친다.
위 패킷을 보면 응답까지 약 6.5초가 걸린다. ( timeout - 3초 + 1회 재시도(총 6초) )
- 서비스 간 타임아웃 불일치: reviews:v2와 ratings 서비스 사이의 10초 타임아웃은 7초 지연을 허용하지만,
productpage와 reviews 서비스 사이의 6초 타임아웃(3초 + 1회 재시도)은 이를 허용하지 않는다. - fixed leday를 6초 미만으로 설정하면 서비스에 영향을 미치지 않는다는 것을 확인할 수 있고,
- 코드 레벨에서 10초로 timeout을 설정했다 한들, 서비스간 타임아웃 불일치로 기본 설정인 6초가 넘어가면 timeout이 발생한다는 의미이다.
Injecting an HTTP abort fault
- 'jason'이라는 end-user에 대해 적용
- 이 사용자의 요청에 대해 100% 확률로 HTTP 500 에러를 발생
- 정상적인 경우, 트래픽은 'ratings' 서비스의 'v1' 서브셋으로 라우팅
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: ratings
spec:
hosts:
- ratings
http:
- fault:
abort:
httpStatus: 500
percentage:
value: 100
match:
- headers:
end-user:
exact: jason
route:
- destination:
host: ratings
subset: v1
- route:
- destination:
host: ratings
subset: v1
결론: jason 유저가 요청하면 그냥 drop 됨..
header 기반으로 악성 유저들 필터링하고, 테스트 하는데 요긴하게 쓰일 것 같다.
Mirroring
미러링 언제쓸까 싶지만 생각이 짧았다...........비용만 감당할 수 있다면, 신규기능 배포 오류감소 + DR 구성까지 가능해보인다.
- 안전한 기능 테스트
- 새 버전 배포 전 실제 트래픽으로 시스템 검증
- 사용자 영향 없이 버그 탐지 및 성능 비교
- A/B 테스트 대비 리스크 감소
- 문제 진단 및 디버깅
- 재현 어려운 프로덕션 이슈 분석
- 실제 트래픽 패턴 기반 테스트 케이스 생성
- 성능 모니터링
- 새 인프라 구성(예: 다른 리전/클러스터)의 부하 테스트
- 트래픽 폭증 시 시스템 반응 예측
참고자료 : https://tech.trivago.com/post/2020-06-10-crossclustertrafficmirroringwithistio
Cross-Cluster Traffic Mirroring with Istio · trivago tech blog
The price of reliability is the pursuit of the utmost simplicity.— C.A.R. Hoare, Turing Award lectureHave you ever enthusiastically released a new, delightful version to product...
tech.trivago.com
시나리오
- cluster를 2개 생성하긴 쉽지 않으니, httpbin, httpbin-mirror 2개의 네임스페이스를 생성한다.
- httpbin에 ServiceEntry로 mirroring될 서비스의 도메인 주소를 등록한다
- dummy 도메인으로 등록하고 coreDNS에 등록하여 cluster 밖으로 query가 나가진 않는다.
- curl 요청을 보낼 pod를 생성한뒤 httpbin에 배포된 앱에 패킷을 보낸다.
- httpbin-mirror 앱에도 요청이 잘 전달되었는지 확인한다.
테스트에 필요한 YAML
# Dockerfile
FROM python:3.9-slim-buster
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 8080
CMD ["python", "app.py"]
# app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return "blue-mirror" # blue, blue-mirror 이런식으로 바꿔가면서 이미지 생성
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)
# requirements.txt
Flask
# ServiceEntry에 mirror.example.com으로
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: mirror-service
namespace: httpbin
spec:
hosts:
- mirror.example.com
ports:
- number: 80
name: http
protocol: HTTP
location: MESH_EXTERNAL
resolution: DNS
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: httpbin
namespace: httpbin
spec:
hosts:
- httpbin.httpbin.svc.cluster.local
http:
- route:
- destination:
host: httpbin.httpbin.svc.cluster.local
port:
number: 80
mirror:
host: mirror.example.com
port:
number: 80
mirrorPercentage:
value: 100.0
# 테스트를 위한 App 배포: Deployment, Service
apiVersion: v1
kind: Namespace
metadata:
name: httpbin
labels:
istio-injection: enabled
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
namespace: httpbin
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
template:
metadata:
labels:
app: httpbin
spec:
containers:
- name: httpbin
image: kennethreitz/httpbin
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
namespace: httpbin
spec:
selector:
app: httpbin
ports:
- port: 80
targetPort: 80
# mirroring 환경
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: mirror-gateway
namespace: httpbin-mirror
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- mirror.example.com
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: mirror-redirect
namespace: httpbin-mirror
spec:
hosts:
- mirror.example.com
gateways:
- httpbin-mirror/mirror-gateway
http:
- route:
- destination:
host: httpbin-mirror.httpbin-mirror.svc.cluster.local
port:
number: 80
# mirroring 환경
apiVersion: v1
kind: Namespace
metadata:
name: httpbin-mirror
labels:
istio-injection: enabled
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin-mirror
namespace: httpbin-mirror
spec:
replicas: 1
selector:
matchLabels:
app: httpbin-mirror
template:
metadata:
labels:
app: httpbin-mirror
spec:
containers:
- name: httpbin-mirror
image: kennethreitz/httpbin
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: httpbin-mirror
namespace: httpbin-mirror
spec:
selector:
app: httpbin-mirror
ports:
- port: 80
targetPort: 80
coreDNS 설정부분은 약간 복잡해서 별도로 뺏고,
모든 pod의 /etc/host파일을 수정할순 없으니... cluster 내부 dns인 coredns 설정에 아래 host 정보를 추가해준다.
# coredns 설정 수정
apiVersion: v1
data:
Corefile: |
... # 기존 설정 아래 추가
mirror.example.com:53 {
errors
cache 30
hosts {
10.104.91.38 mirror.example.com
}
}
.. 생략
curl 요청 전달용 테스트용 파드 생성
kubectl run testpod --image=nginx
curl httpbin.httpbin
실제로 패킷이 어떻게 전달되는지 살펴보자면,,,
httpbin app과 httpbin-mirror app에서의 패킷은 특이사항 없이 하나씩 잘 도착했다.
패킷 헤더정보가 완전히 동일하진 다(미러링을 구분하는듯한 플래그가 있음).
요청을 보낸 packet에서 살펴보면
istio-inject된 namespace에서의 pod와 그렇지 않은 pod에서의 요청이 다르다.
istio-inject되지 않은 pod에서의 요청은 미러링 되지 않는다.
virtual service 설정에 따라 받는 쪽에서 처리해서 넘겨주는줄 알앗는데 ...
그것이 아니라 보내는 쪽에서 2번 요청을 보낸다.
envoy(istio-proxy)가 응답까지 받고 폐기처분 하는 것으로 보인다.
패킷 열어보면 httpbin.httpbin-shadow 이런식으로 미러링된 패킷 host에는 postfix로 -shadow가 붙는다.
Istio의 VirtualService 설정을 따르지 않고, DNS를 통해 직접 httpbin.httpbin 서비스의 IP 주소로 요청을 보냅니다. 따라서 httpbin-mirror로 트래픽이 미러링되지 않습니다.
보내는쪽과 받는 쪽 모두 Envoy proxy가 있어야 한다...
이러면 k8s가 아닌 환경이랑 엮일땐 어떻게 되는건지 찾아봐야겠다.