Istio advanced

Istio를 통해 요청 라우팅, A/B 테스트, 트래픽 관리 규칙을 사용하여 Kubernetes 환경에서 효율적인 서비스 배포가 가능하다.

Istio advanced
Photo by Vamshi Vangapally / Unsplash

개요

Istio 트래픽 라우팅 규칙을 명확하게 이해하기 위해선 트래픽 관리 모델을 이해해야합니다. 이를 위해 기능의 작동 방식과 아키텍쳐 개요를 통해 트래픽 관리에 대해 읽어보실 것을 권장드립니다.

Request Routing

DEMO 앱을 확인해본다면 Istio Bookinfo 샘플은 각각 여러 버전이 있는 네 개의 개별 마이크로서비스로 구성되어 있습니다. 그 중 하나인 리뷰 서비스의 세 가지 다른 버전이 배포되어 동시에 실행되고 있습니다. 이로 인해 발생하는 문제를 설명하기 위해, 브라우저에서 Bookinfo 앱의 /productpage에 접근하고 여러 번 새로 고침을 해보세요. URL은 http://$GATEWAY_URL/productpage이며, 여기서 $GATEWAY_URL은 Bookinfo 문서에 설명된 대로 인그레스의 외부 IP 주소입니다.

새로 고침할 때마다 책 리뷰 출력에 별점이 포함되기도 하고 포함되지 않기도 하는 것을 알 수 있습니다. 이는 명시적인 기본 서비스 버전이 라우팅되지 않기 때문에 Istio가 요청을 모든 사용 가능한 버전으로 라운드 로빈 방식으로 라우팅하기 때문입니다.

이 작업의 초기 목표는 모든 트래픽을 마이크로서비스의 v1(버전 1)으로 라우팅하는 규칙을 적용하는 것입니다. 이후에는 HTTP 요청 헤더의 값에 따라 트래픽을 라우팅하는 규칙을 적용할 것입니다.

확인해보면 총 3개의 버전이 존재합니다. 목표는 별점이 포함된 v1으로 라우팅 규칙을 적용하는 것입니다.

Notion Image
Notion Image
Notion Image

Route to v1

virtual-service 설정을 통해 v1으로 라우팅합니다.

kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml

virtual-service를 설정하는 yaml 파일은 다음과 같습니다.

모든 서비스 구성을 v1으로 라우트하는 것을 확인할 수 있습니다.

apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: productpage
spec:
  hosts:
  - productpage
  http:
  - route:
    - destination:
        host: productpage
        subset: v1
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
  - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: v1
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: ratings
spec:
  hosts:
  - ratings
  http:
  - route:
    - destination:
        host: ratings
        subset: v1
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: details
spec:
  hosts:
  - details
  http:
  - route:
    - destination:
        host: details
        subset: v1
---

이때 subsetDestinationRule 에 저장되어있으며 해당 파일에서 살펴보면 각 라벨 중 키 값에 따라 version을 구분하는 것을 확인할 수 있습니다.

각 서브셋에 맞춰 RANOM하게 트래픽을 분배합니다.

apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: reviews
spec:
  host: reviews
  trafficPolicy:
    loadBalancer:
      simple: RANDOM
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
  - name: v3
    labels:
      version: v3

---------------------------------
...
Pod Template:
  Labels:           app=reviews
                    version=v1
...

VirtualService와 DestinationRule(참고)

VirtualService와 Destination은 Istio traffic routing의 핵심이 됩니다. 이를 이해하기 위해선 아래 포스트를 참고합니다.

  1. k8s service가 바라보는 트래픽이 다수인 경우
Notion Image

서로 다른 두 개의 파드를 같은 label로 설정하여, endpoints가 다수로 설정된 경우엔 트래픽들을 자동으로 round robin으로 배포시키도록 동작합니다.

  1. k8s service에 selector로 지정하는 경우
Notion Image

해당 트래픽은 일치하는 label로 이동합니다.

  1. Istio VirtualService를 정의한 경우
Notion Image

Virtual Service는 반드시 하나의 서비스에 연결되어야 합니다. Virtual Service는 클라이언트가 요청을 보낼 때 사용하는 주소(호스트)를 정의하고, 이 주소에 대한 트래픽을 어떻게 라우팅할지를 설정하는 역할을 합니다.

구성은 다음과 같습니다.

$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: svc-hello
  labels:
    app: hello
spec:
  selector:
    app: hello
  ports:
  - name: http
    protocol: TCP
    port: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: svc-hello-v1
  labels:
    app: hello
spec:
  selector:
    app: hello
    version: v1
  ports:
  - name: http
    protocol: TCP
    port: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: svc-hello-v2
  labels:
    app: hello
spec:
  selector:
    app: hello
    version: v2
  ports:
  - name: http
    protocol: TCP
    port: 8080
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: vs-hello
spec:
  hosts:
  - "svc-hello.default.svc.cluster.local"
  http:
  - match:
    - uri:
        prefix: /v2
    route:
    - destination:
        host: "svc-hello-v2.default.svc.cluster.local"
  - route:
    - destination:
        host: "svc-hello-v1.default.svc.cluster.local"
EOF

HTTP Request URI에 따라 각 pod 들로 라우팅 되도록 정의할 수 있습니다.

공식 문서의 예시를 추가로 살펴보면 아래 단계와 같이 동작합니다.

  1. 트래픽 수신:
    • 클라이언트가 hosts(reviews.prod.svc.cluster.local)로 HTTP 요청을 보냅니다.
  2. VirtualService 매칭:
    • Istio는 수신한 요청의 호스트가 reviews.prod.svc.cluster.local인지 확인합니다.
    • 요청의 URI가 /wpcatalog 또는 /consumercatalog로 시작하는지 검사합니다.
  3. URI 재작성 및 라우팅:
    • 만약 요청 URI가 /wpcatalog 또는 /consumercatalog인 경우:
      • reviews.prod.svc.cluster.local 로 라우팅 됩니다.
    • 만약 요청 URI가 위의 두 패턴과 일치하지 않는 경우:
      • 기본적으로 reviews-v1-route 규칙이 적용됩니다.
      • 이후 reviews.prod.svc.cluster.local 로 라우팅 됩니다.
hosts:
- reviews.prod.svc.cluster.local
http:
- name: "reviews-v2-routes"
  match:
  - uri:
      prefix: "/wpcatalog"
  - uri:
      prefix: "/consumercatalog"
  route:
  - destination:
      host: reviews.prod.svc.cluster.local
- name: "reviews-v1-route"
  route:
  - destination:
      host: reviews.prod.svc.cluster.local
  1. Istio VirtualService 를 활용하여 트래픽을 조절할 수 있습니다.
Notion Image

yaml 파일은 다음과 같습니다.

hosts:
- "svc-hello.default.svc.cluster.local"
http:
- route:
  - destination:
      host: "svc-hello-v1.default.svc.cluster.local"
    weight: 90
  - destination:
      host: "svc-hello-v2.default.svc.cluster.local"
    weight: 10

rewrite, redirection 모두 동작합니다.

hosts:
- reviews.prod.svc.cluster.local
http:
- name: "reviews-v2-routes"
  match:
  - uri:
      prefix: "/wpcatalog"
  - uri:
      prefix: "/consumercatalog"
  rewrite:
    uri: "/newcatalog"
  route:
  - destination:
      host: reviews.prod.svc.cluster.local
- name: "reviews-v1-route"
  route:
  - destination:
      host: reviews.prod.svc.cluster.local
  1. Istio DestinationRule을 구성하여 Service 목적지를 정의할 수 있습니다.
Notion Image

Selector를 통한 Service 정의가 없어도 DestinationRule을 정의하여 labels를 지정할 수 있고 해당 규칙을 VirtualService에서 subset으로 참고하여 정의할 수 있습니다.

공식 문서의 예제를 참고하면 다음과 같습니다.

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: dr-hello
spec:
  host: reviews.prod.svc.cluster.local
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: reviews-route
spec:
  hosts:
  - reviews.prod.svc.cluster.local
  http:
  - name: "reviews-v2-routes"
    match:
    - uri:
        prefix: "/wpcatalog"
    - uri:
        prefix: "/consumercatalog"
    rewrite:
      uri: "/newcatalog"
    route:
    - destination:
        host: reviews.prod.svc.cluster.local
        subset: v2
  - name: "reviews-v1-route"
    route:
    - destination:
        host: reviews.prod.svc.cluster.local
        subset: v1
  1. 트래픽 수신:
    • 클라이언트가 hosts(reviews.prod.svc.cluster.local)로 HTTP 요청을 보냅니다.
  2. VirtualService 매칭:
    • Istio는 수신한 요청의 호스트가 reviews.prod.svc.cluster.local인지 확인합니다.
    • 요청의 URI가 /wpcatalog 또는 /consumercatalog로 시작하는지 검사합니다.
  3. URI 재작성 및 라우팅:
    • 만약 요청 URI가 /wpcatalog 또는 /consumercatalog인 경우:
      • 요청 URI는 /newcatalog로 rewrite됩니다.
      • 이 요청은 reviews-v2-routes 규칙에 따라 v2 서브셋으로 라우팅됩니다.
    • 만약 요청 URI가 위의 두 패턴과 일치하지 않는 경우:
      • 기본적으로 reviews-v1-route 규칙이 적용되어 v1 서브셋으로 라우팅됩니다.
  4. DestinationRule 적용:
    • 요청이 v2 또는 v1 서브셋으로 라우팅되면, 해당 서브셋에 정의된 서비스 인스턴스 중 하나로 트래픽이 전달됩니다.
    • DestinationRule에 설정된 로드 밸런싱 정책(예: RANDOM)이 적용되어, 여러 인스턴스 중 하나로 요청이 분배됩니다.

따라서, Service를 직접 참조하는 대신 DestinationRule을 통해 더 세부적인 트래픽 관리와 정책 적용이 가능합니다. 이를 통해 A/B 테스트, 카나리 배포 등 다양한 트래픽 관리 시나리오를 구현할 수 있습니다.

참고

v1으로 트래픽을 라우팅 하기 위해 VirtualService와 DestinationRule을 확인합니다.

  • DestinationRule

트래픽이 각 host로 들어온다면 DestinationRule을 VirtualService로 라우팅되며 이를 백엔드로 전달하게 됩니다.

apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: productpage
spec:
  host: productpage
  subsets:
  - name: v1
    labels:
      version: v1
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: reviews
spec:
  host: reviews
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
  - name: v3
    labels:
      version: v3
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: ratings
spec:
  host: ratings
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
  - name: v2-mysql
    labels:
      version: v2-mysql
  - name: v2-mysql-vm
    labels:
      version: v2-mysql-vm
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: details
spec:
  host: details
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
---
  • VirtualService

규칙에 따라 destination을 판단하며, 해당 트래픽을 subset에 따라 전달합니다.

apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: productpage
spec:
  hosts:
  - productpage
  http:
  - route:
    - destination:
        host: productpage
        subset: v1
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
  - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: v1
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: ratings
spec:
  hosts:
  - ratings
  http:
  - route:
    - destination:
        host: ratings
        subset: v1
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: details
spec:
  hosts:
  - details
  http:
  - route:
    - destination:
        host: details
        subset: v1
---

확인해보았다면 아래 코드를 통해 리소스를 생성한 뒤 실제로 트래픽이 라우팅 되는지 확인합니다.

kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml
kubectl apply -f samples/bookinfo/networking/destination-rule-all.yaml

# 생성된 리소스 확인
kubectl get destinationrules -o yaml
kubectl get virtualservices.networking.istio.io
Notion Image

Route based on user identity

헤더를 통해 사용자가 일치하는 경우 v2로 트래픽을 전환할 수 있습니다.

kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-test-v2.yaml

해당 yaml 파일은 다음과 같습니다.

hosts:
- reviews
http:
- match:
  - headers:
      end-user:
        exact: jason
  route:
  - destination:
      host: reviews
      subset: v2
- route:
  - destination:
      host: reviews
      subset: v1
jason / jason

해당 유저로 로그인해보면 reviews가 정상적으로 보이는 것을 확인할 수 있고, 이는 트래픽이 v2로 간다는 것을 의미합니다.

Notion Image

Fault Injection

kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-test-v2.yaml
kubectl apply -f samples/bookinfo/networking/destination-rule-all.yaml

위의 구성을 사용하면 요청이 다음과 같이 흐릅니다.

  1. productpagereviews:v2ratings(사용자 전용 - jason)
  2. productpagereviews:v1 (default)

테스트를 위해 의도적인 HTTP delay 주입

의도적인 지연을 위해 Delay를 설정합니다.

kubectl apply -f samples/bookinfo/networking/virtual-service-ratings-test-delay.yaml
hosts:
- ratings
http:
- fault:
    delay:
      fixedDelay: 7s # fault를 사용한 딜레이 설정
      percentage:
        value: 100
  match:
  - headers:
      end-user:
        exact: jason
  route:
  - destination:
      host: ratings
      subset: v1
- route:
  - destination:
      host: ratings
      subset: v1

페이지를 불러오기까지 약 6초 가량이 소요되는 것을 확인할 수 있습니다.

Notion Image

하지만 페이지를 확인해보면 에러가 발생하였는데 이는 각 서비스별로 타임아웃이 다르게 동작하기 때문입니다.

  • 제품(Product) 페이지와 리뷰(Review) 서비스 간의 타임아웃은 3초 + 1회 재시도로 총 6초입니다.
  • 리뷰(Review) 서비스와 평점(Rating) 서비스 간의 지연 시간은 10초로 하드코딩되어 있습니다.

따라서 리뷰와 평점의 통신은 정상적으로 수행될 수 있지만, 수행 되기 전 제품과 리뷰간 통신에서 타임 아웃 에러가 발생하는 것입니다.

버그 수정하는 방법

버그를 해결하는 방법으로는 여러가지가 있지만 일반적으로 문제를 해결하는 방법은 다음과 같습니다.

  1. productpagereviews 사이의 타임아웃 시간을 증가시키거나 reviewsratings 사이의 타임아웃 시간을 감소시키기
    1. 재시작이 필요합니다.
  2. HTTP abort fault 도입

HTTP abort fault

kubectl apply -f samples/bookinfo/networking/virtual-service-ratings-test-abort.yaml
hosts:
- ratings
http:
- m atch:
  - headers:
      end-user:
        exact: jason
  fault:
    abort:
      percentage:
        value: 100.0
      httpStatus: 500
  route:
  - destination:
      host: ratings
      subset: v2
- route:
  - destination:
      host: ratings
      subset: v1

jason으로 로그인했을 시 Ratings 서비스를 조회해본다면 100%로 에러가 발생하게 됩니다. 이를 통해 ratings는 조회되지 않지만 정상적으로 리뷰는 조회되는 것을 확인할 수 있습니다.

Notion Image

여기서 로그아웃하고 테스트해보면 에러없이 리뷰가 출력되는 것을 확인할 수 있습니다.

Notion Image

Traffic shifting(다른 버전으로 트래픽 전환)

reviews의 모든 트래픽을 v1과 v3의 가중치로 라우팅 할 수 있도록 설정합니다

hosts:
- reviews
http:
- route:
  - destination:
      host: reviews
      subset: v1
    weight: 50
  - destination:
      host: reviews
      subset: v3
    weight: 50

새로고침 시 트래픽이 가중치에 따라 라우팅되는 것을 확인할 수 있습니다.

Notion Image
Notion Image

Prometheus에서 메트릭 쿼리

이번엔 Prometheus를 사용하여 Istio Metrics를 쿼리하는 방법을 알아봅니다. metric 쿼리 값을 사용하기 위해 웹 인터페이스를 사용할 수 있습니다.

Prometheus가 정상 동작하는 것을 확인합니다.

kubectl -n istio-system get svc prometheus

NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
prometheus   ClusterIP   10.102.220.222   <none>        9090/TCP   23h
istioctl dashboard prometheus # 프로메테우스 실행

Query 사용이 가능합니다.

Notion Image

마찬가지로 Graph 로 확인 가능합니다.

Notion Image

쿼리가 어떤 특성을 가지는지, 각 요청 전체를통해 조회 가능합니다.

Notion Image
Notion Image

다양한 쿼리가 존재합니다. 또한 쿼리 자체는 Prometheus의 PromQL을 따르므로 고급 쿼리는 아래 문서를 참고하여 조합합니다.

Grafana를 활용한 Metric 시각화

[Grafana] → [Dashboards] → [Istio] → [Istio Mesh Dashboard]

for i in $(seq 1 1000); do
    echo "요청 #$i 실행 중..."
    curl -s -o /dev/null "http://localhost:8080/productpage"
done

트래픽이 정상적으로 감지되는 것을 확인할 수 있습니다.

Notion Image

Istio Service Dashboard를 확인해본다면 다음과 같이 출력되는 것을 확인할 수 있습니다.

Notion Image

Accessing External Services

모든 아웃바운드 트래픽이 기본적으로 Istio가 활성화된 파드에서 사이드카 프록시로 리디렉션되기 때문에, 클러스터 외부의 URL 접근성은 프록시의 구성에 따라 달라집니다.

기본적으로 Istio는 Envoy 프록시를 구성하여 알려지지 않은 서비스에 대한 요청을 통과시키도록 설정합니다. 이는 Istio를 시작하는 데 편리한 방법이지만, 보다 엄격한 제어를 구성하는 것이 일반적으로 더 바람직합니다.

이 작업에서는 외부 서비스에 접근하는 세 가지 방법을 보여줍니다:

  1. Envoy 프록시가 메시에 구성되지 않은 서비스에 대한 요청을 통과하도록 허용합니다.
  2. 서비스 항목을 구성하여 외부 서비스에 대한 제어된 접근을 제공합니다.
  3. 특정 IP 범위에 대해 Envoy 프록시를 완전히 우회합니다.
kubectl apply -f samples/curl/curl.yaml # 샘플 앱 배포

# 테스트 파드
export SOURCE_POD=$(kubectl get pod -l app=curl -o jsonpath='{.items..metadata.name}')

Envoy의 외부 서비스에 대한 패스스루 설정

Istio에는 외부 서비스(즉, Istio의 내부 서비스 레지스트리에 정의되지 않은 서비스)의 사이드카 처리를 구성하는 설치 옵션인 meshConfig.outboundTrafficPolicy.mode가 있습니다.

이 옵션이 ALLOWANY로 설정되면, Istio 프록시는 알려지지 않은 서비스에 대한 호출을 통과시킵니다. 이 옵션이 REGISTRYONLY로 설정되면, Istio 프록시는 메시에 정의된 HTTP 서비스나 서비스 항목이 없는 호스트에 대한 요청을 차단합니다. ALLOW_ANY는 기본값으로, 외부 서비스에 대한 접근을 제어하지 않고 Istio를 빠르게 평가할 수 있도록 해줍니다. 이후에 외부 서비스에 대한 접근을 구성할 수 있습니다.

이 접근 방식을 실행하려면 Istio 설치가 meshConfig.outboundTrafficPolicy.mode 옵션이 ALLOWANY로 설정되어 있는지 확인해야 합니다. Istio를 설치할 때 명시적으로 REGISTRYONLY 모드로 설정하지 않았다면, 기본적으로 이 옵션이 활성화되어 있을 것입니다.

확실하지 않은 경우, 다음 명령어를 실행하여 메쉬 구성을 표시할 수 있습니다:

$ kubectl get configmap istio -n istio-system -o yaml

meshConfig.outboundTrafficPolicy.mode의 값이 REGISTRYONLY로 명시적으로 설정되어 있지 않다면, 이 옵션이 ALLOWANY로 설정되어 있다고 볼 수 있습니다. ALLOW_ANY는 유일한 다른 가능한 값이며 기본값입니다.

apiVersion: v1
data:
  mesh: |-
    accessLogFile: /dev/stdout
    defaultConfig:
      discoveryAddress: istiod.istio-system.svc:15012
    defaultProviders:
      metrics:
      - prometheus
    enablePrometheusMerge: true
    extensionProviders:
    - envoyOtelAls:
        port: 4317
        service: opentelemetry-collector.observability.svc.cluster.local
      name: otel
    - name: skywalking
      skywalking:
        port: 11800
        service: tracing.istio-system.svc.cluster.local
    - name: otel-tracing
      opentelemetry:
        port: 4317
        service: opentelemetry-collector.observability.svc.cluster.local
    rootNamespace: istio-system
    trustDomain: cluster.local
  meshNetworks: 'networks: {}'
kind: ConfigMap
metadata:
  creationTimestamp: "2024-12-30T04:49:25Z"
  labels:
    app.kubernetes.io/instance: istio
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: istiod
    app.kubernetes.io/part-of: istio
    app.kubernetes.io/version: 1.24.2
    helm.sh/chart: istiod-1.24.2
    install.operator.istio.io/owning-resource: unknown
    install.operator.istio.io/owning-resource-namespace: istio-system
    istio.io/rev: default
    operator.istio.io/component: Pilot
    operator.istio.io/managed: Reconcile
    operator.istio.io/version: 1.24.2
    release: istio
  name: istio
  namespace: istio-system
  resourceVersion: "81624"
  uid: 2caea856-1ae2-411b-aefb-cf5e99eddd25

REGISTRY_ONLY 모드를 명시적으로 구성한 경우, 원래의 istioctl 설치 명령을 변경된 설정으로 다시 실행하여 변경할 수 있습니다.

$ istioctl install <flags-you-used-to-install-Istio> --set meshConfig.outboundTrafficPolicy.mode=ALLOW_ANY

SOURCE_POD에서 외부 HTTPS 서비스에 몇 번 요청을 보내 200 응답을 확인합니다:

$ kubectl exec "$SOURCE_POD" -c curl -- curl -sSI <https://www.google.com> | grep "HTTP/"; kubectl exec "$SOURCE_POD" -c curl -- curl -sI <https://edition.cnn.com> | grep "HTTP/"

HTTP/2 200
HTTP/2 200

이러한 방식이 Mesh에서 이그레스 트래픽을 전송하는 방법입니다. 외부 서비스에 접근하는 이 간단한 방법은 외부 서비스에 대한 트래픽에 대한 Istio 모니터링 및 제어 기능을 잃는 단점이 있습니다.

다음 섹션에서는 메쉬의 외부 서비스 접근을 모니터링하고 제어하는 방법을 보여줍니다.

외부 서비스 엑세스 통제

외부 서비스에 대한 접근을 제어된 방식으로 활성화하려면, meshConfig.outboundTrafficPolicy.mode 옵션을 ALLOWANY 모드에서 REGISTRYONLY 모드로 변경해야 합니다.

ALLOWANY 모드에서 이미 접근 가능한 서비스에 대해 제어된 접근을 추가할 수 있습니다. 이렇게 하면 다른 서비스의 접근을 차단하지 않고 일부 외부 서비스에서 Istio 기능을 사용할 수 있습니다. 모든 서비스를 구성한 후에는 모드를 REGISTRYONLY로 전환하여 의도하지 않은 접근을 차단할 수 있습니다.

meshConfig.outboundTrafficPolicy.mode 옵션을 REGISTRY_ONLY로 변경합니다.

IstioOperator 구성을 사용하여 Istio를 설치한 경우, 다음 필드를 구성에 추가합니다:

spec:
  meshConfig:
    outboundTrafficPolicy:
      mode: REGISTRY_ONLY

그렇지 않은 경우, 원래의 istioctl install 명령어에 해당 설정을 추가합니다. 예를 들어:

$ istioctl install <flags-you-used-to-install-Istio> \\
--set meshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLY

SOURCE_POD에서 외부 HTTPS 서비스에 몇 번 요청을 보내 이제 차단되었는지 확인합니다:

$ kubectl exec "$SOURCE_POD" -c curl -- curl -sI <https://www.google.com> | grep "HTTP/"; kubectl exec "$SOURCE_POD" -c curl -- curl -sI <https://edition.cnn.com> | grep "HTTP/"


command terminated with exit code 35
command terminated with exit code 35

REGISTRY_ONLY에서 외부 HTTP 서비스에 엑세스하는 방법

DNS resolution은 아래의 서비스 항목에서 보안 조치로 사용됩니다. resolution을 NONE으로 설정하면 공격의 가능성이 열립니다.

악의적인 클라이언트는 HOST 헤더에 httpbin.org를 설정하여 자신이 httpbin.org에 접근하고 있다고 가장할 수 있지만, 실제로는 httpbin.org와 연결되지 않은 다른 IP에 연결될 수 있습니다. Istio 사이드카 프록시는 HOST 헤더를 신뢰하고, 다른 호스트의 IP 주소로 전달되고 있음에도 불구하고 잘못된 트래픽을 허용합니다. 그 호스트는 악의적인 사이트일 수도 있고, 메쉬 보안 정책에 의해 금지된 합법적인 사이트일 수도 있습니다.

DNS resolution을 사용하면 사이드카 프록시는 원래의 목적지 IP 주소를 무시하고 트래픽을 httpbin.org로 직접 전달하며, httpbin.org의 IP 주소를 얻기 위해 DNS 쿼리를 수행합니다.


정리하면 사이드카 프록시는 DNS 요청에 대해 목적지 IP를 무시하고 직접 IP로 변환합니다.

해당 사이드카 프록시의 Resolution을 사용하지 않고 None으로 설정한 경우 클라이어트가 HOST 헤더를 조작하여 다른 IP로 유도할 수 있으며 이는 악의적인 사이트에 연결될 위험이 존재합니다. DNS Resolution을 통해 사이드카 프록시는 요청의 헤더를 신뢰하지 않고 실제 도메인에 대한 DNS 쿼리를 사용함으로써 신뢰있는 연결을 보장합니다.

  1. 도메인 이름 요청: 클라이언트가 특정 도메인 이름(예: httpbin.org)에 대한 요청을 보냅니다.
  2. 사이드카 프록시의 역할 : 사이드카 프록시는 이 요청을 가로채고, 요청의 HOST 헤더를 확인합니다.3.
  3. DNS 쿼리 수행 : 사이드카 프록시는 네임서버에 DNS 쿼리를 보내어 해당 도메인 이름에 대한 IP 주소를 조회합니다. 이 과정에서 사이드카 프록시는 원래의 목적지 IP 주소를 무시하고, DNS 쿼리를 통해 얻은 IP 주소를 사용합니다.
  4. 트래픽 전달: DNS 해상도를 통해 확인된 IP 주소로 트래픽을 전달합니다. 이를 통해 사이드카 프록시는 요청이 올바른 서비스로 향하도록 보장합니다.

실제 코드로 확인해보겠습니다.

  1. httpbin.org라는 주소로 Mesh external 요청을 허용합니다.
kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
  name: httpbin-ext
spec:
  hosts:
  - httpbin.org
  ports:
  - number: 80
    name: http
    protocol: HTTP
  resolution: DNS
  location: MESH_EXTERNAL
EOF
  1. 실제 요청을 전송합니다.

Istio sidecar proxy에 의해 X-Envoy-Decorator-Operation 이 추가되었다는 것에 주목해야합니다.

kubectl exec "$SOURCE_POD" -c curl -- curl -sS http://httpbin.org/headers

{
  "headers": {
    "Accept": "*/*", 
    "Host": "httpbin.org", 
    "User-Agent": "curl/8.11.1", 
    "X-Amzn-Trace-Id": "Root=1-6777952e-757f264e3554bc305d81b5d8", 
    "X-Envoy-Attempt-Count": "1", 
    "X-Envoy-Decorator-Operation": "httpbin.org:80/*", 
    "X-Envoy-Peer-Metadata": "ChoKCkNMVVNURVJfSUQSDBoKS3ViZXJuZXRlcwp3CgZMQUJFTFMSbSprCg0KA2FwcBIGGgRjdXJsCikKH3NlcnZpY2UuaXN0aW8uaW8vY2Fub25pY2FsLW5hbWUSBhoEY3VybAovCiNzZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1yZXZpc2lvbhIIGgZsYXRlc3QKHwoETkFNRRIXGhVjdXJsLTdjZDY0YmI2YzUtZno4eGcKFgoJTkFNRVNQQUNFEgkaB2RlZmF1bHQKSAoFT1dORVISPxo9a3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2RlZmF1bHQvZGVwbG95bWVudHMvY3VybAoXCg1XT1JLTE9BRF9OQU1FEgYaBGN1cmw=", 
    "X-Envoy-Peer-Metadata-Id": "sidecar~10.244.0.79~curl-7cd64bb6c5-fz8xg.default~default.svc.cluster.local"
  }
}

[2025-01-03T07:43:42.212Z] "GET /headers HTTP/1.1" 200 - via_upstream - "-" 0 818 412 412 "-" "curl/8.11.1" "db4a7573-01e5-9ae9-8bd8-19b3a0737928" "httpbin.org" "34.200.57.114:80" outbound|80||httpbin.org 10.244.0.79:50896 34.197.122.172:80 10.244.0.79:36258 - default

HTTPS로 접근하는 방식 또한 비슷합니다.

  1. ServiceEntry 추가
  2. 요청 보내기
  3. 로그 확인하기

정상적으로 443번으로 나간 것을 확인할 수 있습니다.

kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
  name: google
spec:
  hosts:
  - www.google.com
  ports:
  - number: 443
    name: https
    protocol: HTTPS
  resolution: DNS
  location: MESH_EXTERNAL
EOF

kubectl exec "$SOURCE_POD" -c curl -- curl -sSI https://www.google.com | grep  "HTTP/"

kubectl logs "$SOURCE_POD" -c istio-proxy | tail

[2025-01-03T07:47:00.918Z] "- - -" 0 - - - "-" 870 5628 462 - "-" "-" "-" "-" "142.250.197.68:443" outbound|443||www.google.com 10.244.0.79:47298 142.250.197.68:443 10.244.0.79:47286 www.google.com -

클러스터 간 요청과 유사하게, ServiceEntry 구성을 사용하여 접근하는 외부 서비스에 대해서도 라우팅 규칙을 설정할 수 있습니다. 클러스터 내부 또는 외부의 서비스에 대한 호출에 대해 동일한 Istio 서비스 메시 기능을 모두 사용할 수 있게 해줍니다.

이번 예제에서는 httpbin.org 서비스에 대한 호출에 타임아웃 규칙을 설정합니다.

kubectl exec "$SOURCE_POD" -c curl -- time curl -o /dev/null -sS -w "%{http_code}\n" http://httpbin.org/delay/5

200
real    0m 5.42s
user    0m 0.00s
sys     0m 0.00s

httpbin.org에서 timeout을 설정합니다.

kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: httpbin-ext
spec:
  hosts:
  - httpbin.org
  http:
  - timeout: 3s
    route:
    - destination:
        host: httpbin.org
      weight: 100
EOF

timeout이 정상적으로 동작하는 것을 확인할 수 있습니다.

kubectl exec "$SOURCE_POD" -c curl -- time curl -o /dev/null -sS -w "%{http_code}\n" http://httpbin.org/delay/5

504
real    0m 3.02s
user    0m 0.00s
sys     0m 0.00s

우회하여 외부 서비스에 접근하기

istio-sidecar-injector configmap을 업데이트하고 애플리케이션을 다시 배포한 후 curl 요청을 보냅니. 이때 Istio 사이드카는 클러스터 내의 내부 요청만 가로채서 관리하게 되며 모든 외부 요청은 사이드카를 우회하여 의도한 목적지로 바로 이동합니다.

내부 10.0.0.1/24 만 관리합니다.

istioctl install <flags-you-used-to-install-Istio> --set values.global.proxy.includeIPRanges="10.0.0.1/24"

다음과 같습니다.

kubectl exec "$SOURCE_POD" -c curl -- curl -sS http://httpbin.org/headers

{
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.org",
    ...
  }
}

HTTP 또는 HTTPS를 통해 외부 서비스에 액세스하는 것과 달리 Istio 사이드카와 관련된 헤더(X-Envoy-Decorator-Operation)가 표시되지 않으며 외부 서비스로 전송된 요청은 사이드카 로그에 나타나지 않습니다. 또한 Istio 사이드카를 우회하면 더 이상 외부 서비스에 대한 액세스를 모니터링할 수 없게되며 이는 보안적인 문제가 발생할 수 있습니다.

Istio 메시에서 외부 서비스를 호출하는 세 가지 방법을 살펴보았습니다.

  1. Envoy를 구성하여 외부 서비스에 대한 접근을 허용합니다.
    1. Istio 사이드카 프록시를 통해 트래픽을 찾아갑니다.
    2. ALLOW_ANY 기본 값으로 인해 서비스에 대한 엑세스를 모니터링 하거나 Istio가 트래픽 제어 기능을 활용할 수 없습니다.
  2. 서비스 엔트리를 사용하여 메시 내부에 접근 가능한 외부 서비스를 등록합니다. (Best Practice)
    1. 클러스터 내부 또는 외부의 서비스에 대한 호출에 대해 동일한 Istio 서비스 메시 기능을 모두 사용할 수 있게 해줍니다.
  3. 외부 IP를 다시 매핑된 IP 테이블에서 제외하도록 Istio 사이드카를 구성합니다.
    1. Istio 사이드카 프록시를 우회하여 외부 서버에 접속합니다.
    2. 클러스터 공급자를 사용하는 경우 공급자별 지식과 구성이 필요합니다.
    3. 액세스 모니터링 손실, 외부 서비스 트래픽 제한을 위한 Istio 기능을 활용할 수 없습니다.

Visualizing Mesh

kiali를 설치합니다.

kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/kiali.yaml
kubectl -n istio-system get svc kiali
istioctl dashboard kiali

# http://localhost:20001/kiali
Notion Image
Notion Image

가중치와 애니메이션이 동작하는 것을 확인할 수 있습니다.

Notion Image

Reference