트래픽 관리 모범 사례
트래픽 관리 모범 사례 (Traffic Management Best Practices)
네트워킹 또는 트래픽 관리 문제를 피하기 위한 구체적인 배포·구성 지침을 다뤄요. 서비스 기본 라우트 설정, 네임스페이스 간 구성 공유 제어, 가상 서비스 분할, 503 오류 방지 등을 배워요.
출처: Istio 문서
본문
이 섹션은 네트워킹 또는 트래픽 관리 문제를 피하기 위한 구체적인 배포·구성 지침을 제공해요.
서비스에 대한 기본 라우트 설정하기
기본 Istio 동작이 편리하게 어떤 규칙 설정 없이도 임의 소스에서 목적지 서비스의 모든 버전으로 트래픽을 보내지만, 처음부터 모든 서비스에 대해 기본 라우트가 있는 VirtualService를 만드는 것이 Istio에서 일반적으로 모범 사례로 간주돼요.
처음에 서비스의 버전이 하나뿐이더라도, 두 번째 버전을 배포하기로 결정하는 순간 새 버전이 통제되지 않은 방식으로 즉시 트래픽을 받지 않도록 새 버전이 시작되기 전에 라우팅 규칙이 자리 잡고 있어야 해요.
Istio의 기본 라운드로빈 라우팅에 의존할 때 발생할 수 있는 또 다른 잠재적 문제는 Istio의 destination rule 평가 알고리즘의 미묘함 때문이에요. 요청을 라우팅할 때 Envoy는 먼저 virtual service의 라우트 규칙을 평가해서 특정 subset으로 라우팅되는지 결정해요. 그렇다면 그때서야 해당 subset에 대응하는 destination rule 정책을 활성화해요. 결과적으로 Istio는 특정 subset에 대해 정의한 정책을 해당 subset으로 트래픽을 명시적으로 라우팅한 경우에만 적용해요.
예를 들어 reviews 서비스에 대해 정의된 유일한 구성으로 다음 destination rule을 고려해 보세요. 즉 해당 VirtualService 정의에 라우트 규칙이 없는 경우예요.
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: reviews
spec:
host: reviews
subsets:
- name: v1
labels:
version: v1
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
Istio의 기본 라운드로빈 라우팅이 때때로 "v1" 인스턴스를 호출하더라도, "v1"이 유일하게 실행 중인 버전이라 항상 그럴지라도, 위 트래픽 정책은 결코 호출되지 않을 거예요.
위 예제는 두 가지 방법 중 하나로 수정할 수 있어요. 트래픽 정책을 DestinationRule에서 한 단계 위로 올려 모든 버전에 적용되게 하거나:
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: reviews
spec:
host: reviews
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
subsets:
- name: v1
labels:
version: v1
더 좋은 방법은 VirtualService 정의에서 서비스에 대한 적절한 라우트 규칙을 정의하는 것이에요. 예를 들어 "reviews:v1"에 대한 간단한 라우트 규칙을 추가하세요.
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
네임스페이스 간 구성 공유 제어하기
virtual service, destination rule 또는 service entry를 한 네임스페이스에서 정의하고, 다른 네임스페이스로 내보내면 그 네임스페이스들에서 재사용할 수 있어요. Istio는 기본적으로 모든 트래픽 관리 리소스를 모든 네임스페이스로 내보내지만, exportTo 필드로 가시성을 재정의할 수 있어요.
예를 들어 다음 virtual service는 같은 네임스페이스의 워크로드의 요청에만 영향을 줄 수 있어요.
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: myservice
spec:
hosts:
- myservice.com
exportTo:
- "."
http:
- route:
- destination:
host: myservice
특정 네임스페이스에서 destination rule의 가시성을 설정하는 것이 그 규칙이 사용된다는 것을 보장하지는 않아요. destination rule을 다른 네임스페이스로 내보내면 그 네임스페이스들에서 사용할 수 있게 되지만, 실제로 요청 중에 적용되려면 그 네임스페이스도 destination rule 조회 경로에 있어야 해요.
- client 네임스페이스
- service 네임스페이스
- 구성된
meshconfig.rootNamespace네임스페이스(기본적으로istio-system)
예를 들어 다음 destination rule을 고려해 보세요.
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: myservice
spec:
host: myservice.default.svc.cluster.local
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
ns1 네임스페이스에 이 destination rule을 만든다고 가정해 보세요.
ns1의 클라이언트에서 myservice 서비스로 요청을 보내면 destination rule이 적용돼요. 조회 경로의 첫 번째 네임스페이스, 즉 client 네임스페이스에 있기 때문이에요.
이제 다른 네임스페이스, 예를 들어 ns2에서 요청을 보내면 클라이언트는 더 이상 destination rule과 같은 네임스페이스(ns1)에 있지 않아요. 해당 서비스인 myservice.default.svc.cluster.local도 ns1이 아니라 default 네임스페이스에 있기 때문에, destination rule은 조회 경로의 두 번째 네임스페이스인 service 네임스페이스에서도 발견되지 않을 거예요.
myservice 서비스가 모든 네임스페이스로 내보내져 ns2에서 보이고, destination rule도 ns2를 포함한 모든 네임스페이스로 내보내져도, 조회 경로의 어떤 네임스페이스에도 없기 때문에 ns2에서의 요청 중에는 적용되지 않아요.
이 문제는 해당 서비스와 같은 네임스페이스(이 예제에서는 default)에 destination rule을 만들어 피할 수 있어요. 그러면 어떤 네임스페이스의 클라이언트 요청에도 적용될 거예요. 조회 경로의 세 번째 네임스페이스인 istio-system 네임스페이스로 destination rule을 옮길 수도 있지만, 이는 destination rule이 정말 모든 네임스페이스에 적용되는 전역 구성이고 관리자 권한이 필요하지 않는 한 권장되지 않아요.
Istio는 이 제한된 destination rule 조회 경로를 두 가지 이유로 사용해요.
- 완전히 관련 없는 네임스페이스의 서비스 동작을 재정의할 수 있는 destination rule이 정의되는 것을 방지하기 위해서.
- 같은 호스트에 대해 destination rule이 둘 이상 있을 때 명확한 조회 순서를 가지기 위해서.
큰 virtual service와 destination rule을 여러 리소스로 분할하기
특정 호스트에 대한 완전한 라우트 규칙 또는 정책 집합을 단일 VirtualService나 DestinationRule 리소스에 정의하는 것이 불편한 상황에서는, 호스트에 대한 구성을 여러 리소스에서 점진적으로 지정하는 것이 더 나을 수 있어요. 컨트롤 플레인은 게이트웨이에 바인딩된 경우 그러한 destination rule들을 병합하고 그러한 virtual service들을 병합해요.
여러 구현 서비스에 경로 기반 위임을 사용하는 애플리케이션 호스트를 노출하는 ingress 게이트웨이에 바인딩된 VirtualService의 경우를 고려해 보세요. 대략 이렇게요.
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: myapp
spec:
hosts:
- myapp.com
gateways:
- myapp-gateway
http:
- match:
- uri:
prefix: /service1
route:
- destination:
host: service1.default.svc.cluster.local
- match:
- uri:
prefix: /service2
route:
- destination:
host: service2.default.svc.cluster.local
- match:
...
이런 구성의 단점은 기본이 되는 마이크로서비스 중 어느 것에 대한 다른 구성(예: 라우트 규칙)도 이 단일 구성 파일에 포함해야 한다는 것이에요. 개별 서비스 팀이 소유할 수 있는 별도 리소스에 두는 대신 말이에요. 자세한 내용은 Route rules have no effect on ingress gateway requests를 참고하세요.
이 문제를 피하려면 myapp.com의 구성을 백엔드 서비스당 하나씩 여러 VirtualService 조각으로 나누는 것이 더 나을 수 있어요. 예를 들어:
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: myapp-service1
spec:
hosts:
- myapp.com
gateways:
- myapp-gateway
http:
- match:
- uri:
prefix: /service1
route:
- destination:
host: service1.default.svc.cluster.local
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: myapp-service2
spec:
hosts:
- myapp.com
gateways:
- myapp-gateway
http:
- match:
- uri:
prefix: /service2
route:
- destination:
host: service2.default.svc.cluster.local
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: myapp-...
기존 호스트에 대한 두 번째 및 이후 VirtualService가 적용되면 istiod는 추가 라우트 규칙을 호스트의 기존 구성에 병합해요. 하지만 이 기능에는 사용할 때 신중히 고려해야 하는 몇 가지 주의사항이 있어요.
- 주어진 소스
VirtualService내 규칙의 평가 순서는 유지되지만, 리소스 간 순서는 UNDEFINED(unbound)예요. 즉 조각 구성에 걸친 규칙의 평가 순서는 보장되지 않아서, 조각 간에 충돌하는 규칙이나 순서 의존성이 없다면 예측 가능한 동작만 해요. - 조각에는 "catch-all" 규칙(즉
match필드가 없는 규칙)이 하나만 있어야 해요. 그러한 모든 "catch-all" 규칙은 병합된 구성에서 목록 끝으로 이동하지만, 모든 요청을 잡기 때문에 먼저 적용되는 것이 본질적으로 다른 것을 덮어쓰고 무력화해요. VirtualService는 게이트웨이에 바인딩된 경우에만 이렇게 분할할 수 있어요. sidecar에서는 호스트 병합이 지원되지 않아요.
DestinationRule도 비슷한 병합 의미론과 제약으로 분할할 수 있어요.
- 같은 호스트에 대한 여러 destination rule에 걸쳐 주어진 subset의 정의는 하나만 있어야 해요. 같은 이름이 둘 이상 있으면 첫 번째 정의가 사용되고 이후의 중복은 버려져요. subset 내용의 병합은 지원되지 않아요.
- 같은 호스트에 대한 최상위
trafficPolicy는 하나만 있어야 해요. 여러 destination rule에 최상위 트래픽 정책이 정의되면 먼저 처리된 것이 사용돼요. 이후의 최상위trafficPolicy구성은 버려져요. - virtual service 병합과 달리 destination rule 병합은 sidecar와 게이트웨이 모두에서 작동해요.
서비스 라우트 재구성 중 503 오류 피하기
트래픽을 서비스의 특정 버전(subset)으로 보내는 라우트 규칙을 설정할 때, subset들이 라우트에서 사용되기 전에 사용 가능한지 확인하도록 주의해야 해요. 그렇지 않으면 재구성 기간 동안 서비스 호출이 503 오류를 반환할 수 있어요.
해당 subset을 정의하는 VirtualServices와 DestinationRules를 모두 단일 kubectl 호출로 만들면(예: kubectl apply -f myVirtualServiceAndDestinationRule.yaml) 충분하지 않아요. 리소스가(구성 서버, 즉 쿠버네티스 API 서버에서) istiod 인스턴스로 결과적으로 일관성(eventually consistent) 있는 방식으로 전파되기 때문이에요. subset을 사용하는 VirtualService가 subset이 정의된 DestinationRule보다 먼저 도착하면, istiod가 생성하는 Envoy 구성은 존재하지 않는 업스트림 풀을 참조하게 돼요. 이는 모든 구성 객체가 istiod에 사용 가능해질 때까지 HTTP 503 오류를 초래해요.
subset으로 라우트를 구성할 때 서비스에 제로 다운타임이 보장되도록, 아래 설명된 "make-before-break" 프로세스를 따르세요.
- 새 subset을 추가할 때:
- 그것을 사용하는 VirtualService를 업데이트하기 전에 먼저 DestinationRules를 업데이트해 새 subset을 추가하세요. kubectl 또는 플랫폼별 도구를 사용해 규칙을 적용하세요.
- DestinationRule 구성이 Envoy sidecar로 전파될 때까지 몇 초 기다리세요.
- 새로 추가된 subset을 참조하도록 VirtualService를 업데이트하세요.
- subset을 제거할 때:
- DestinationRule에서 subset을 제거하기 전에 먼저 VirtualServices를 업데이트해 subset에 대한 참조를 제거하세요.
- VirtualService 구성이 Envoy sidecar로 전파될 때까지 몇 초 기다리세요.
- 사용되지 않는 subset을 제거하도록 DestinationRule을 업데이트하세요.