ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [IHOS 1기] 3주차 - Traffic control, Resilience
    Kubernetes 2025. 4. 27. 00:16

    5. Traffic control: Fine-grained traffic routing

    • Traffic routing basics
    • Shifting traffic during a new release
    • Mirroring traffic to reduce the risk of a new release
    • Controlling traffic as it leaves a cluster
    들어가며 : 트래픽 세부 라우팅 필요 설명
    • 이전 장에서 우리는 트래픽을 클러스터로 가져오는 방법과 이를 위해 고려해야 할 사항들을 살펴보았습니다.
    • 요청이 클러스터에 들어오면, 요청을 처리하기 위해 적절한 서비스로 어떻게 라우팅되나요?
    • 클러스터 내에 있는 서비스는 같은 클러스터 내 또는 클러스터 외부에 있는 다른 서비스와 어떻게 통신하나요?
    • 마지막으로, 그리고 가장 중요한 것은 서비스를 변경하고 새로운 버전을 도입할 때, 고객과 고객이 이러한 변화에 안전하게 노출될 수 있도록 방해와 영향을 최소화하는 방법은 무엇일까요?
    • 앞서 살펴본 바와 같이, Istio 서비스 프록시는 서비스 메시 클러스터 내의 서비스 간 통신을 차단하고 트래픽을 제어할 수 있는 지점을 제공합니다.
    • Istio를 사용하면 애플리케이션 간의 트래픽을 개별 요청까지 세밀하게 제어할 수 있습니다.
    • 이 장에서는 왜 그렇게 하고 싶은지, 어떻게 해야 하는지, 그리고 이러한 기능을 활용할 때 어떤 이점을 얻어야 하는지 살펴봅니다.

    5.0. 실습환경 구성

    5.0.1. [실습 환경 구성] k8s(1.23.17) 배포 : NodePort(30000 HTTP, 30005 HTTPS)

    Copy
    #
    git clone https://github.com/AcornPublishing/istio-in-action
    cd istio-in-action/book-source-code-master
    pwd # 각자 자신의 pwd 경로
    code .
    
    # 아래 extramounts 생략 시, myk8s-control-plane 컨테이너 sh/bash 진입 후 직접 git clone 가능
    kind create cluster --name myk8s --image kindest/node:v1.23.17 --config - <https://geek-cookbook.github.io/charts/
    helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30007 --set env.TZ="Asia/Seoul" --namespace kube-system
    kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view
    
    ## kube-ops-view 접속 URL 확인
    open "http://localhost:30007/#scale=1.5"
    open "http://localhost:30007/#scale=1.3"
    
    # (옵션) metrics-server
    helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
    helm install metrics-server metrics-server/metrics-server --set 'args[0]=--kubelet-insecure-tls' -n kube-system
    kubectl get all -n kube-system -l app.kubernetes.io/instance=metrics-server
    

    5.0.2. [실습 환경 구성] istio 1.17.8 설치 (addon 필수) - Docs , Install , profile

    Copy
    # myk8s-control-plane 진입 후 설치 진행
    docker exec -it myk8s-control-plane bash
    -----------------------------------
    # (옵션) 코드 파일들 마운트 확인
    tree /istiobook/ -L 1
    혹은
    git clone ... /istiobook
    
    # istioctl 설치
    export ISTIOV=1.17.8
    echo 'export ISTIOV=1.17.8' >> /root/.bashrc
    
    curl -s -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIOV sh -
    cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl
    istioctl version --remote=false
    
    # default 프로파일 컨트롤 플레인 배포
    istioctl install --set profile=default -y
    
    # 설치 확인 : istiod, istio-ingressgateway, crd 등
    kubectl get istiooperators -n istio-system -o yaml
    kubectl get all,svc,ep,sa,cm,secret,pdb -n istio-system
    kubectl get cm -n istio-system istio -o yaml
    kubectl get crd | grep istio.io | sort
    
    # 보조 도구 설치
    kubectl apply -f istio-$ISTIOV/samples/addons
    kubectl get pod -n istio-system
    
    # 빠져나오기
    exit
    -----------------------------------
    
    # 실습을 위한 네임스페이스 설정
    kubectl create ns istioinaction
    kubectl label namespace istioinaction istio-injection=enabled
    kubectl get ns --show-labels
    
    # istio-ingressgateway 서비스 : NodePort 변경 및 nodeport 지정 변경 , externalTrafficPolicy 설정 (ClientIP 수집)
    kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 8080, "nodePort": 30000}]}}'
    kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "ports": [{"port": 443, "targetPort": 8443, "nodePort": 30005}]}}'
    kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec":{"externalTrafficPolicy": "Local"}}'
    kubectl describe svc -n istio-system istio-ingressgateway
    
    # NodePort 변경 및 nodeport 30001~30003으로 변경 : prometheus(30001), grafana(30002), kiali(30003), tracing(30004)
    kubectl patch svc -n istio-system prometheus -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "targetPort": 9090, "nodePort": 30001}]}}'
    kubectl patch svc -n istio-system grafana -p '{"spec": {"type": "NodePort", "ports": [{"port": 3000, "targetPort": 3000, "nodePort": 30002}]}}'
    kubectl patch svc -n istio-system kiali -p '{"spec": {"type": "NodePort", "ports": [{"port": 20001, "targetPort": 20001, "nodePort": 30003}]}}'
    kubectl patch svc -n istio-system tracing -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 16686, "nodePort": 30004}]}}'
    
    # Prometheus 접속 : envoy, istio 메트릭 확인
    open http://127.0.0.1:30001
    
    # Grafana 접속
    open http://127.0.0.1:30002
    
    # Kiali 접속 1 : NodePort
    open http://127.0.0.1:30003
    
    # (옵션) Kiali 접속 2 : Port forward
    kubectl port-forward deployment/kiali -n istio-system 20001:20001 &
    open http://127.0.0.1:20001
    
    # tracing 접속 : 예거 트레이싱 대시보드
    open http://127.0.0.1:30004
    
    
    # 내부 접속 테스트용 netshoot 파드 생성
    cat <

    5.1 Reducing the risk of deploying new code

    5.1.1. 들어가며
    • 1장에서는 ACME 회사의 시나리오를 소개했습니다.
    • 클라우드 플랫폼으로 전환하여 회사의 코드 배포 위험을 줄이는 데 도움이 되는 관행을 채택했습니다.
    • ACME가 시도한 패턴 중 하나는 애플리케이션에 변경 사항을 도입하기 위한 blue / green deployments였습니다.
    • 파란색/녹색 배포를 통해 ACME는 변경하고자 하는 서비스의 v2(녹색)를 가져와 v1(파란색) 옆에 프로덕션에 배포했습니다(그림 5.1 참조).
    • ACME는 고객에게 출시하고자 할 때 트래픽을 v2(녹색)로 줄였습니다.
    • 이 접근 방식은 문제가 발생하면 ACME가 서비스의 v1(파란색)으로 축소될 수 있기 때문에 배포 중 중단을 줄이는 데 도움이 되었습니다.
    • 파란색/녹색 배포가 도움이 되지만, v1에서 v2로 전환할 때는 여전히 모든 코드 변경 사항을 한 번에 해제하는 "빅뱅"을 경험합니다.
    • 배포 위험을 더욱 줄일 수 있는 방법을 살펴보겠습니다.
    • 먼저, 배포와 릴리스가 무엇을 의미하는지 명확히 해야 합니다.
    5.1.2. 배포 Deployment vs. 릴리스 release
    • 가상의 카탈로그 서비스를 사용하여 배포와 릴리스의 차이점을 설명해 보겠습니다.
    • catalog 서비스의 v1이 현재 운영 중이라고 가정해 보겠습니다.
    • catalog 서비스에 코드 변경 사항을 도입하려면, CI시스템으로 빌드하고, 새 버전(v1.1이라고 하자)으로 태그하고, 이를 스테이징 환경에 배포하고 테스트할 것이다.
    • 이 변화를 스테이징에서 검증하고 필요한 승인을 받으면 새 버전 v1.1을 운영 환경으로 가져올 수 있다.
    • 운영 환경에 배포할 때는 새 코드를 운영 환경 리소스(서버, 컨테이너 등)에 설치하지만, 트래픽은 보내지는 않는다.
    • 운영 환경에 배포하는 행위는 운영 환경에서 실행 중인 사용자에게 영향을 미치면 안 된다. (아직 어떤 사용자 요청도 받지 않기 때문이다.)
    • 이 시점에서는 운영 환경에서 실행 중인 새 배포에 테스트를 실행해 기대대로 작동하는지 검증할 수 있다. (그림 5.2 참조).
    • 메트릭과 로그 수집을 활성화했을 것이므로, 이 신호들을 보고 새 배포가 기대한대로 행동하다고 확신할 수 있다.
    • 일단 운영 환경에 코드를 배포하면, 사용자들에게 어떻게 릴리즈할지와 관련해 비즈니스 의사결정을 내릴 수 있다.
    • 코드를 릴리즈한다는 말은 실제 트래픽을 새 배포로 가져오는 것을 의미한다. 단, 릴리스가 꼭 한 번에 전부 가야 하는 것은 아니다.
    • 새로운 코드를 운영 환경에 도입하는 위험성을 줄이려면 배포와 릴리스를 분리하는 것이 중요하다.
    • 새로운 소프트웨어는 내부 직원에게만 먼저 공개하도록 결정할 수도 있다. (그림 5.3 참조).
    • 내부 직원들은 트래픽을 제어해 신 버전을 사용할 수 있다.
    • 내부 직원은 소프트웨어의 운영자로서 로그와 메트릭 수집을 활용해 코드 변경 효과가 의도대로인지 관찰하고 검증할 수 있다.
    • 이제 실 트래픽의 대부분은 소프트웨어의 구 버전이 받고, 신 버전은 일부만을 받고 있다.
    • 이 방식을 ‘카나리한다 canarying’ 고 말하거나 ‘카나리 릴리스 canary release’라고 부른다.
    • 기본적으로는 신 버전을 노출할 소수의 사용자 그룹을 선택하고 신 버전이 어떻게 작동하는지 지켜본다.
    • 신 버전이 의도치 않게 동작하면 릴리스를 철회하고 트래픽을 이전 버전으로 되돌릴 수 있다.
    • 코드 변경의 동작과 성능이 괜찮으면 릴리스의 범위를 더 넓힐 수 있다. (그림 5.4 참조).
    • 이제 무과금 고객이나 실버 등급 고객(즉, 골드나 플래티넘 대비 우선순위가 낮은 고객)에게 릴리스를 할 수 있다.
    • 새 코드를 모든 고객에게 노출할 때까지는 이렇게 릴리스하고 관찰하는 접근법을 계속 반복한다. (그림 5.5 참조).
    • 이 과정 중 어느 시점에서라도 실제 사용자 상호작용 중에 새 코드가 예상하고 검증했던 기능이나 동작, 성능을 전달하지 않고 있다는 것을 발견할 수 있다.
    • 이 경우 트래픽을 이전 버전으로 돌려 릴리스를 롤백할 수 있다.
    • 과거에 ACME는 배포와 릴리스라는 두 가지 개념을 결합했습니다.
    • 코드 변경을 운영 환경으로 가져오고자, 이 회사는 서비스 구 버전을 신 버전으로 효과적으로 교체하는 롤링 업그레이드를 시작했다.
    • 신 버전은 클러스터에 도입되자마자 운영 환경 트래픽을 받았다.
    • 이 방식은 코드 신 버전뿐 아니라 신 버전에 수반되는 버그나 이슈에도 사용자들을 노출했다.
    • 배포와 릴리스를 분리하면 새 변화를 어떤 사용자들에게, 어떻게 내보일지 정밀하게 제어할 수 있다.
    • 이를 통해 새 코드를 운영 환경에 가져오는 위험성을 줄일 수 있다.
    • 이스티오가 시스템에 들어오는 요청을 기반으로 트래픽을 제어함으로써 릴리스 위험성을 낮추는 데 어떻게 도움이 되는지 살펴보자.

    5.2 Routing requests with Istio (실습)

    5.2.1. 들어가며 : VirtualService
    • 2장에서는 Istio를 사용하여 카탈로그 서비스로의 트래픽을 제어했습니다.
    • Istio VirtualService 리소스를 사용하여 트래픽을 라우팅하는 방법을 지정했습니다.
    • 어떻게 작동하는지 자세히 살펴보겠습니다. 요청의 내용에 따라 헤더를 평가하여 요청의 경로를 제어합니다.
    • 이러한 방식으로 다크 런치라는 기술을 사용하여 특정 사용자에게 배포를 제공할 수 있습니다.
    • 어두운 실행에서는 많은 사용자가 알려진 작동 중인 서비스 버전으로 보내지며, 특정 클래스의 사용자는 최신 버전으로 보내집니다.
    • 따라서 우리는 다른 모든 사람에게 영향을 미치지 않고 특정 그룹에 통제된 방식으로 새로운 기능을 노출할 수 있습니다.
    • Cleaning up our workspace
    5.2.2 Deploying v1 of the catalog service
    Copy
    # Let’s deploy v1 of our catalog service. From the root of the book’s source code, run the following command
    kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction
    
    # 확인
    kubectl get pod -n istioinaction -owide
    NAME                     READY   STATUS    RESTARTS   AGE   IP           NODE                  NOMINATED NODE   READINESS GATES
    catalog-6cf4b97d-ftl77   2/2     Running   0          42s   10.10.0.14   myk8s-control-plane              
    
    # 도메인 질의를 위한 임시 설정 : 실습 완료 후에는 삭제 해둘 것
    echo "127.0.0.1       catalog.istioinaction.io" | sudo tee -a /etc/hosts
    cat /etc/hosts | tail -n 3
    
    # netshoot로 내부에서 catalog 접속 확인
    kubectl exec -it netshoot -- curl -s http://catalog.istioinaction/items | jq
    [
      {
        "id": 1,
        "color": "amber",
        "department": "Eyewear",
        "name": "Elinor Glasses",
        "price": "282.00"
      },
      {
        "id": 2,
        "color": "cyan",
        "department": "Clothing",
        "name": "Atlas Shirt",
        "price": "127.00"
      },
      {
        "id": 3,
        "color": "teal",
        "department": "Clothing",
        "name": "Small Metal Shoes",
        "price": "232.00"
      },
      {
        "id": 4,
        "color": "red",
        "department": "Watches",
        "name": "Red Dragon Watch",
        "price": "232.00"
      }
    ]
    
    # 외부 노출을 위해 Gateway 설정
    cat ch5/catalog-gateway.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: catalog-gateway
    spec:
      selector:
        istio: ingressgateway
      servers:
      - port:
          number: 80
          name: http
          protocol: HTTP
        hosts:
        - "catalog.istioinaction.io"
        
    kubectl apply -f ch5/catalog-gateway.yaml -n istioinaction
    
    # 트래픽을 catalog 서비스로 라우팅하는 VirtualService 리소스 설정
    cat ch5/catalog-vs.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: catalog-vs-from-gw
    spec:
      hosts:
      - "catalog.istioinaction.io"
      gateways:
      - catalog-gateway
      http:
      - route:
        - destination:
            host: catalog
    
    kubectl apply -f ch5/catalog-vs.yaml -n istioinaction
    
    # 확인
    kubectl get gw,vs -n istioinaction
    
    NAME                                          AGE
    gateway.networking.istio.io/catalog-gateway   14s
    
    NAME                                                    GATEWAYS              HOSTS                          AGE
    virtualservice.networking.istio.io/catalog-vs-from-gw   ["catalog-gateway"]   ["catalog.istioinaction.io"]   4s
    
    
    # istio-ingressgateway Service(NodePort)에 포트 정보 확인
    kubectl get svc -n istio-system istio-ingressgateway -o jsonpath="{.spec.ports}" | jq
    [
      {
        "name": "status-port",
        "nodePort": 31674,
        "port": 15021,
        "protocol": "TCP",
        "targetPort": 15021
      },
      {
        "name": "http2",
        "nodePort": 30000, # 순서1 nodePort service의 nodePort
        "port": 80,        # cluster 내부 접근시
        "protocol": "TCP",
        "targetPort": 8080 # 순서2 target istio-ingressgateway pod의 http용 port
      },
      {
        "name": "https",
        "nodePort": 30005,
        "port": 443,
        "protocol": "TCP",
        "targetPort": 8443
      }
    ]
    
    # 호스트에서 NodePort(Service)로 접속 확인
    curl -v -H "Host: catalog.istioinaction.io" http://localhost:30000
    
    ...
    
    kubectl stern -l app=catalog -n istioinaction
    
    open http://localhost:30000
    open http://catalog.istioinaction.io:30000
    open http://catalog.istioinaction.io:30000/items
    
    
    # 신규 터미널 : 반복 접속 실행 해두기
    while true; do curl -s http://catalog.istioinaction.io:30000/items/ ; sleep 1; echo; done
    while true; do curl -s http://catalog.istioinaction.io:30000/items/ -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
    while true; do curl -s http://catalog.istioinaction.io:30000/items/ -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 0.5; echo; done
    
    • 호스트 외부에서 호출 경로 : 아래 그림 맨 왼쪽(curl client)는 외부라고 생각하면 됨.
    • 상세 정보 확인
    • 특정 파드의 istio-proxy 에 Envoy 에 Admin 웹 접속
    5.2.3 Deploying v2 of the catalog service
    • istio 트래픽 제어 기능 동작을 알아보기 위해, catalog 서비스 v2 를 배포해보자. ⇒ 버전1,2 호출 된다. v2 호출되지 않게 할 수 없을까?
    Copy
    # catalog 서비스 v2 를 배포 : v2에서는 imageUrl 필드가 추가
    kubectl apply -f services/catalog/kubernetes/catalog-deployment-v2.yaml -n istioinaction
    
    #
    kubectl get deploy -n istioinaction --show-labels
    NAME         READY   UP-TO-DATE   AVAILABLE   AGE   LABELS
    catalog      1/1     1            1           30m   app=catalog,version=v1
    catalog-v2   1/1     1            1           34s   app=catalog,version=v2
    
    kubectl get pod -n istioinaction -o wide
    NAME                          READY   STATUS    RESTARTS   AGE   IP           NODE                  NOMINATED NODE   READINESS GATES
    catalog-6cf4b97d-xvwcj        2/2     Running   0          12m   10.10.0.12   myk8s-control-plane              
    catalog-v2-6df885b555-npg6c   2/2     Running   0          7s    10.10.0.13   myk8s-control-plane              
    
    # 같은 catalog service에 v1, v2가 같이 ep로 등록되어있고, vs는 해당 service를 바라보고 있기 때문에 v2도 RDS SYNCED 상태임
    docker exec -it myk8s-control-plane istioctl proxy-status
    NAME                                                  CLUSTER        CDS        LDS        EDS        RDS        ECDS         ISTIOD                      VERSION
    catalog-6cf4b97d-xvwcj.istioinaction                  Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-lf2hl     1.17.8
    catalog-v2-6df885b555-npg6c.istioinaction             Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-lf2hl     1.17.8
    istio-ingressgateway-996bc6bb6-h4dkf.istio-system     Kubernetes     SYNCED     SYNCED     SYNCED     SYNCED     NOT SENT     istiod-7df6ffc78d-lf2hl     1.17.8
    
    # 호출 테스트 : v1 , v2 호출 확인
    for i in {1..10}; do curl -s http://catalog.istioinaction.io:30000/items/ ; printf "\n\n"; done
    
    
    # istio-ingressgateway proxy-config 확인
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local -o json
    ...
            "name": "outbound|80||catalog.istioinaction.svc.cluster.local",
            "type": "EDS",
            "edsClusterConfig": {
                "edsConfig": {
                    "ads": {},
                    "initialFetchTimeout": "0s",
                    "resourceApiVersion": "V3"
                },
                "serviceName": "outbound|80||catalog.istioinaction.svc.cluster.local"
            },
            "connectTimeout": "10s",
            "lbPolicy": "LEAST_REQUEST", # default Envoy LB Policy
            "circuitBreakers": {
                "thresholds": [
                    {
                        "maxConnections": 4294967295,
                        "maxPendingRequests": 4294967295,
                        "maxRequests": 4294967295,
                        "maxRetries": 4294967295,
                        "trackRemaining": true
                    }
                ]
            },
            "commonLbConfig": {
                "localityWeightedLbConfig": {}
            },
    ...
    
    kubectl get po -n istioinaction -o wide
    NAME                          READY   STATUS    RESTARTS   AGE    IP           NODE                  NOMINATED NODE   READINESS GATES
    catalog-6cf4b97d-xvwcj        2/2     Running   0          16m    10.10.0.12   myk8s-control-plane              
    catalog-v2-6df885b555-npg6c   2/2     Running   0          4m8s   10.10.0.13   myk8s-control-plane              
    
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local'
    ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
    10.10.0.12:3000     HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local # catalog pod
    10.10.0.13:3000     HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local # catalog-v2 pod
    
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local' -o json
    ...
       {
            "name": "outbound|80||catalog.istioinaction.svc.cluster.local",
            "addedViaApi": true,
            "hostStatuses": [
                {
                    "address": {
                        "socketAddress": {
                            "address": "10.10.0.12",
                            "portValue": 3000
                        }
                    },
                    "stats": [
                        {
                            "name": "cx_connect_fail"
                        },
                        {
                            "value": "8",
                            "name": "cx_total"
                        },
                        {
                            "name": "rq_error"
                        },
                        {
                            "value": "315",
                            "name": "rq_success"
                        },
                        {
                            "name": "rq_timeout"
                        },
                        {
                            "value": "315",
                            "name": "rq_total"
                        },
                        {
                            "type": "GAUGE",
                            "value": "8",
                            "name": "cx_active"
                        },
                        {
                            "type": "GAUGE",
                            "name": "rq_active"
                        }
                    ],
                    "healthStatus": {
                        "edsHealthStatus": "HEALTHY"
                    },
                    "weight": 1,
                    "locality": {}
                },
                {
                    "address": {
                        "socketAddress": {
                            "address": "10.10.0.13",
                            "portValue": 3000
                        }
                    },
                    "stats": [
                        {
                            "name": "cx_connect_fail"
                        },
                        {
                            "value": "8",
                            "name": "cx_total"
                        },
                        {
                            "name": "rq_error"
                        },
                        {
                            "value": "308",
                            "name": "rq_success"
                        },
                        {
                            "name": "rq_timeout"
                        },
                        {
                            "value": "308",
                            "name": "rq_total"
                        },
                        {
                            "type": "GAUGE",
                            "value": "8",
                            "name": "cx_active"
                        },
                        {
                            "type": "GAUGE",
                            "name": "rq_active"
                        }
                    ],
                    "healthStatus": {
                        "edsHealthStatus": "HEALTHY"
                    },
                    "weight": 1,
                    "locality": {}
                }
            ],
            "circuitBreakers": {
                "thresholds": [
                    {
                        "maxConnections": 4294967295,
                        "maxPendingRequests": 4294967295,
                        "maxRequests": 4294967295,
                        "maxRetries": 4294967295
                    },
                    {
                        "priority": "HIGH",
                        "maxConnections": 1024,
                        "maxPendingRequests": 1024,
                        "maxRequests": 1024,
                        "maxRetries": 3
                    }
                ]
            },
            "observabilityName": "outbound|80||catalog.istioinaction.svc.cluster.local",
            "edsServiceName": "outbound|80||catalog.istioinaction.svc.cluster.local"
        },
    ...
    
    # catalog proxy-config 도 직접 확인해보자
    # 같은 service에 두개의 pod가 등록되어있고 vs가 해당 service를 보고있으므로 두개의 envoy endpoint가 보인다.
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog-v2.istioinaction --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local'
    
    ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
    10.10.0.12:3000     HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
    10.10.0.13:3000     HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
    Traffic Distribution, Traffic Animation 체크
    5.2.4 Routing all traffic to v1 of the catalog service
    • 모든 트래픽을 catalog v1 으로 라우팅. 이것이 다크런치를 시작하기 전의 일반적인 트래픽 패턴이다.
    • 어느 워크로드가 v1, v2 인지 이스티오에게 힌트를 줘야한다.
    • catalog v1은 deployment 리소스에서 레이블 app:catalog, version:v1 을 사용한다.
    • catalog v2은 deployment 리소스에서 레이블 app:catalog, version:v2 을 사용한다.
    • 이스티오에게는 이렇게 다른 버전들의 부분집합 subset 으로 지정하는 DestinationRule 을 만들어준다.
    Copy
    #
    kubectl get pod -l app=catalog -n istioinaction --show-labels
     
    NAME                          READY   STATUS    RESTARTS   AGE   LABELS
    catalog-6cf4b97d-xvwcj        2/2     Running   0          23m   app=catalog, ... , version=v1
    catalog-v2-6df885b555-npg6c   2/2     Running   0          10m   app=catalog, ... , version=v2
    
    # pod의 version label을 destinationRule에 설정해줘 특정 cluster subset이 어떤 pod로 routing 될지 결정!
    cat ch5/catalog-dest-rule.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: catalog
    spec:
      host: catalog.istioinaction.svc.cluster.local
      subsets:
      - name: version-v1
        labels:
          version: v1
      - name: version-v2
        labels:
          version: v2
    
    kubectl apply -f ch5/catalog-dest-rule.yaml -n istioinaction
    
    # 확인
    kubectl get destinationrule -n istioinaction
    NAME      HOST                                      AGE
    catalog   catalog.istioinaction.svc.cluster.local   8s
    
    
    # catalog proxy-config 확인 : SUBSET(v1, v2, -) 확인
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local
    SERVICE FQDN                                PORT     SUBSET         DIRECTION     TYPE     DESTINATION RULE
    catalog.istioinaction.svc.cluster.local     80       -              outbound      EDS      catalog.istioinaction
    catalog.istioinaction.svc.cluster.local     80       version-v1     outbound      EDS      catalog.istioinaction
    catalog.istioinaction.svc.cluster.local     80       version-v2     outbound      EDS      catalog.istioinaction
    
    # cluster name에 port뒤에 subset name이 붙게된다.
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --subset version-v1 -o json
    ...
            "name": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
            "type": "EDS",
            "edsClusterConfig": {
                "edsConfig": {
                    "ads": {},
                    "initialFetchTimeout": "0s",
                    "resourceApiVersion": "V3"
                },
                "serviceName": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local"
            },
            "connectTimeout": "10s",
            "lbPolicy": "LEAST_REQUEST",
    ...
    
    # cluster name에 port뒤에 subset name이 붙게된다.
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --subset version-v2 -o json
    ...
            "name": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
            "type": "EDS",
            "edsClusterConfig": {
                "edsConfig": {
                    "ads": {},
                    "initialFetchTimeout": "0s",
                    "resourceApiVersion": "V3"
                },
                "serviceName": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local"
            },
            "connectTimeout": "10s",
            "lbPolicy": "LEAST_REQUEST",
    ...
    
    #
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local -o json
    ...
            "name": "outbound|80||catalog.istioinaction.svc.cluster.local",
            "type": "EDS",
            "edsClusterConfig": {
                "edsConfig": {
                    "ads": {},
                    "initialFetchTimeout": "0s",
                    "resourceApiVersion": "V3"
                },
                "serviceName": "outbound|80||catalog.istioinaction.svc.cluster.local"
            },
    ...
    
    # subset마다 각각의 endpoint가 생겼고, 기존의 통합된 endpoint도 존재
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | egrep 'ENDPOINT|istioinaction'
    ENDPOINT                                                STATUS      OUTLIER CHECK     CLUSTER
    10.10.0.12:3000                                         HEALTHY     OK                outbound|80|version-v1|catalog.istioinaction.svc.cluster.local
    10.10.0.12:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
    10.10.0.13:3000                                         HEALTHY     OK                outbound|80|version-v2|catalog.istioinaction.svc.cluster.local
    10.10.0.13:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
    
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v1|catalog.istioinaction.svc.cluster.local'
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v1|catalog.istioinaction.svc.cluster.local' -o json
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v2|catalog.istioinaction.svc.cluster.local'
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v2|catalog.istioinaction.svc.cluster.local' -o json
    
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||catalog.istioinaction.svc.cluster.local'
    ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
    10.10.0.12:3000     HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
    10.10.0.13:3000     HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
    
    
    
    # VirtualService 수정 (subset 추가)
    # 해당 route 설정을 통해서 catalog라는 cluster로 route되는데, subset으로 version-v1을 설정해준다.
    # 이를 통해서 이제 catalog service에 mapping된 pod중 v1 pod로만 traffic이 routing 된다.
    cat ch5/catalog-vs-v1.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: catalog-vs-from-gw
    spec:
      hosts:
      - "catalog.istioinaction.io"
      gateways:
      - catalog-gateway
      http:
      - route:
        - destination:
            host: catalog
            subset: version-v1
    
    kubectl apply -f ch5/catalog-vs-v1.yaml -n istioinaction
    
    # 호출 테스트 : v1
    for i in {1..10}; do curl -s http://catalog.istioinaction.io:30000/items/ ; printf "\n\n"; done
    
    
    # 세부 정보 확인
    # routes 에 virtualHosts 항목에 routes.route 에 cluster 부분이 ...version-v1... 설정 확인
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system
    
    # 이제 routes 정보를 보면, istio-ingressgateway pod의 http.8080에 대해서 version-v1에 대한 route 정보만 출력된다.
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080 -o json
    ...
            "virtualHosts": [
                {
                    "name": "catalog.istioinaction.io:80",
                    "domains": [
                        "catalog.istioinaction.io"
                    ],
                    "routes": [
                        {
                            "match": {
                                "prefix": "/"
                            },
                            "route": {
                                "cluster": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
                                "timeout": "0s",
                                "retryPolicy": {
                                    "retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
                                    "numRetries": 2,
    ...
    
    # cluster 
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --subset version-v1
    SERVICE FQDN                                PORT     SUBSET         DIRECTION     TYPE     DESTINATION RULE
    catalog.istioinaction.svc.cluster.local     80       version-v1     outbound      EDS      catalog.istioinaction
    
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --subset version-v1 -o json
    ...
            "name": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
            "type": "EDS",
            "edsClusterConfig": {
                "edsConfig": {
                    "ads": {},
                    "initialFetchTimeout": "0s",
                    "resourceApiVersion": "V3"
                },
                "serviceName": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local"
            },
            ...
            "metadata": {
                "filterMetadata": {
                    "istio": {
                        "config": "/apis/networking.istio.io/v1alpha3/namespaces/istioinaction/destination-rule/catalog",
                        "default_original_port": 80,
                        "services": [
                            {
                                "host": "catalog.istioinaction.svc.cluster.local",
                                "name": "catalog",
                                "namespace": "istioinaction"
                            }
                        ],
                        "subset": "version-v1"
    ...
    
    # endpoint 
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | egrep 'ENDPOINT|istioinaction'
    
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v1|catalog.istioinaction.svc.cluster.local'
    ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
    10.10.0.12:3000     HEALTHY     OK                outbound|80|version-v1|catalog.istioinaction.svc.cluster.local
    
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80|version-v1|catalog.istioinaction.svc.cluster.local' -o json
    ...
    
    # istio-proxy (catalog)
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction --subset version-v1 -o json
    ...
            "metadata": {
                "filterMetadata": {
                    "istio": {
                        "config": "/apis/networking.istio.io/v1alpha3/namespaces/istioinaction/destination-rule/catalog",
                        "default_original_port": 80,
                        "services": [
                            {
                                "host": "catalog.istioinaction.svc.cluster.local",
                                "name": "catalog",
                                "namespace": "istioinaction"
                            }
                        ],
                        "subset": "version-v1"
                    }
                }
            },
    ...
    
    • 이 시점에서 모든 트래픽이 v1 라우팅 된다.
    • 이제 특정 요청들을 통제된 방식으로 v2 라우팅하고 싶다. 다음 절에서 알아보자.
    5.2.5 Routing specific requests to v2
    특정 콘텐츠가 담긴 요청 라우팅
    • HTTP 요청 헤더 x-istio-cohort: internal 을 포함한 트래픽(통제된 방식)은 catalog v2로 보내고 싶다.
    Copy
    #
    cat ch5/catalog-vs-v2-request.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: catalog-vs-from-gw
    spec:
      hosts:
      - "catalog.istioinaction.io"
      gateways:
      - catalog-gateway
      http:
      # match[].headers[]에 특정 http header값을 주어 match될 경우 routing을 하도록 한다.
      - match:
        - headers:
            x-istio-cohort:
              exact: "internal"
        route:
        - destination:
            host: catalog
            subset: version-v2
      - route:
        - destination:
            host: catalog
            subset: version-v1
    
    kubectl apply -f ch5/catalog-vs-v2-request.yaml -n istioinaction
    
    # 호출 테스트 : 여전히 v1 -> header가 없기때문에!!
    for i in {1..10}; do curl -s http://catalog.istioinaction.io:30000/items/ ; printf "\n\n"; done
    
    # 요청 헤더 포함 호출 테스트 : v2!
    curl http://catalog.istioinaction.io:30000/items -H "x-istio-cohort: internal"
    [
      {
        "id": 1,
        "color": "amber",
        "department": "Eyewear",
        "name": "Elinor Glasses",
        "price": "282.00",
        "imageUrl": "http://lorempixel.com/640/480"
      },
    ...
    ]
    
    # (옵션) 신규 터미널 : v2 반복 접속
    while true; do curl -s http://catalog.istioinaction.io:30000/items/ -H "x-istio-cohort: internal" -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 2; echo; done
    
    
    # 상세 확인
    # route 추가 : routes 에 2개의 route 확인 - 위에서 부터 적용되는 순서 중요!
    # envoy(istio)는 따로 우선순위에 대한 설정이 없고 위에 정의된 route rule부터 확인한다.
    # 그러므로, specific한 route rule부터 상단에 두고 가장 general한 default rule같은것들은 최하단에 두는것이 좋다!!
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080
    NAME          DOMAINS                      MATCH     VIRTUAL SERVICE
    http.8080     catalog.istioinaction.io     /*        catalog-vs-from-gw.istioinaction
    http.8080     catalog.istioinaction.io     /*        catalog-vs-from-gw.istioinaction
    
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080 -o json
    ...
            "virtualHosts": [
                {
                    "name": "catalog.istioinaction.io:80",
                    "domains": [
                        "catalog.istioinaction.io"
                    ],
                    "routes": [
                        {
                            "match": {
                                "prefix": "/",
                                "caseSensitive": true,
                                "headers": [
                                    {
                                        "name": "x-istio-cohort",
                                        "stringMatch": {
                                            "exact": "internal"
                                        }
                                    }
                                ]
                            },
                            "route": {
                                "cluster": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
                                "timeout": "0s",
                    ...
                       {
                            "match": {
                                "prefix": "/"
                            },
                            "route": {
                                "cluster": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
    ...
    
    #
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | egrep 'ENDPOINT|istioinaction'
    
    # istio-proxy (catalog)에는 routes 정보가 아래 cluster 로 보내는 1개만 있다. 즉 istio-proxy(istio-ingressgateway)가 routes 분기 처리하는 것을 알 수 있다.
    ## "cluster": "outbound|80||catalog.istioinaction.svc.cluster.local"
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction --name 80 -o json
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction | grep catalog
    80                                                            catalog, catalog.istioinaction + 1 more...          /*
    
    # deploy/catalog-v2.istioinaction에 대한 istio-proxy에도 역시 routes 정보가 하나
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog-v2.istioinaction | grep catalog   
    80                                                            catalog, catalog.istioinaction + 1 more...          /* 
    5.2.6 Routing deep within a call graph 호출 그래프 내 깊은 위치에서 라우팅 Mesh(Gateway)
    호출 그래프 내 깊은 위치에서 수행하는 특정 콘텐츠가 담긴 요청 라우팅
    • 지금까지 이스티오를 사용해 요청을 라우팅하는 방법을 살펴봤지만, 라우팅 수행 위치가 에지/게이트웨이뿐이었다.
    • 이런 트래픽 규칙은 호출 그래프 내 깊은 곳에서도 적용할 수 있습니다. (그림 5.9 참조).
    • 2장에서 이 작업을 수행했으므로, 프로세스를 다시 만들고 기대대로 동작하는지 확인해보자.
    Copy
    # 초기화
    kubectl delete gateway,virtualservice,destinationrule --all -n istioinaction
    
    # webapp 기동
    kubectl apply -n istioinaction -f services/webapp/kubernetes/webapp.yaml
    kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction # 이미 배포 상태
    kubectl apply -f services/catalog/kubernetes/catalog-deployment-v2.yaml -n istioinaction # 이미 배포 상태
    
    # 확인
    kubectl get deploy,pod,svc,ep -n istioinaction
    NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
    deployment.apps/catalog      1/1     1            1           50m
    deployment.apps/catalog-v2   1/1     1            1           38m
    deployment.apps/webapp       0/1     1            0           4s
    
    NAME                              READY   STATUS            RESTARTS   AGE
    pod/catalog-6cf4b97d-xvwcj        2/2     Running           0          50m
    pod/catalog-v2-6df885b555-npg6c   2/2     Running           0          38m
    pod/webapp-7685bcb84-42zqn        0/2     PodInitializing   0          4s
    
    NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
    service/catalog   ClusterIP   10.200.1.5     <none>        80/TCP    50m
    service/webapp    ClusterIP   10.200.1.111   <none>        80/TCP    4s
    
    NAME                ENDPOINTS                         AGE
    endpoints/catalog   10.10.0.12:3000,10.10.0.13:3000   50m
    endpoints/webapp                                      4s
    • GW, VS 설정 후 호출 테스트 : webapp → catalog 는 k8s service(clusterIP) 라우팅 사용
    Copy
    # Now, set up the Istio ingress gateway to route to the webapp service
    cat services/webapp/istio/webapp-catalog-gw-vs.yaml
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: coolstore-gateway
    spec:
      selector:
        istio: ingressgateway # use istio default controller
      servers:
      - port:
          number: 80
          name: http
          protocol: HTTP
        hosts:
        - "webapp.istioinaction.io"
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: webapp-virtualservice
    spec:
      hosts:
      - "webapp.istioinaction.io"
      gateways:
      - coolstore-gateway
      http:
      - route:
        - destination:
            host: webapp
            port:
              number: 80
    
    kubectl apply -f services/webapp/istio/webapp-catalog-gw-vs.yaml -n istioinaction
    
    # 확인
    kubectl get gw,vs -n istioinaction
    NAME                                            AGE
    gateway.networking.istio.io/coolstore-gateway   3s
    
    NAME                                                       GATEWAYS                HOSTS                         AGE
    virtualservice.networking.istio.io/webapp-virtualservice   ["coolstore-gateway"]   ["webapp.istioinaction.io"]   3s
    
    
    # 도메인 질의를 위한 임시 설정 : 실습 완료 후에는 삭제 해둘 것
    echo "127.0.0.1       webapp.istioinaction.io" | sudo tee -a /etc/hosts
    cat /etc/hosts | tail -n 3
    
    
    # 호출테스트 : 외부(web, curl) → ingressgw → webapp → catalog (v1, v2)
    curl -s http://webapp.istioinaction.io:30000/api/catalog | jq
    
    # 반복 호출테스트 : 신규터미널 2개에 아래 각각 실행 해두기
    while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
    while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -H "x-istio-cohort: internal" -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 2; echo; done
    
    
    # proxy-config : istio-ingressgateway
    # istio-ingressgateway의 http.8080은 이제 webapp.istioinaction.svc.cluster.local cluster로 routing 된다.
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/istio-ingressgateway.istio-system --name http.8080 
    NAME          DOMAINS                     MATCH     VIRTUAL SERVICE
    http.8080     webapp.istioinaction.io     /*        webapp-virtualservice.istioinaction
    => route."cluster": "outbound|80||webapp.istioinaction.svc.cluster.local"
    
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system | egrep 'webapp|catalog'
    catalog.istioinaction.svc.cluster.local                 80        -          outbound      EDS            
    webapp.istioinaction.svc.cluster.local                  80        -          outbound      EDS
    
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn webapp.istioinaction.svc.cluster.local -o json
    ...
            "name": "outbound|80||webapp.istioinaction.svc.cluster.local",
            "type": "EDS",
    ...
    
    # outbound|80||webapp.istioinaction.svc.cluster.local cluster에 대한 endpoint는 webapp pod이다.
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system --cluster 'outbound|80||webapp.istioinaction.svc.cluster.local'
    ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
    10.10.0.14:8080     HEALTHY     OK                outbound|80||webapp.istioinaction.svc.cluster.local
    
    kubectl get po -n istioinaction -o wide
    NAME                          READY   STATUS    RESTARTS   AGE    IP           NODE                  NOMINATED NODE   READINESS GATES
    catalog-6cf4b97d-xvwcj        2/2     Running   0          56m    10.10.0.12   myk8s-control-plane              
    catalog-v2-6df885b555-npg6c   2/2     Running   0          44m    10.10.0.13   myk8s-control-plane              
    webapp-7685bcb84-42zqn        2/2     Running   0          6m7s   10.10.0.14   myk8s-control-plane              
    
    # proxy-config : webapp
    docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/webapp.istioinaction
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction
    
    # proxy-config : catalog
    docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/catalog.istioinaction
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction
    
    
    # webapp istio-proxy 로그 활성화
    # 신규 터미널
    kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
    
    # webapp istio-proxy 로그 활성화 적용
    cat << EOF | kubectl apply -f -
    apiVersion: telemetry.istio.io/v1alpha1
    kind: Telemetry
    metadata:
      name: webapp
      namespace: istioinaction
    spec:
      selector:
        matchLabels:
          app: webapp
      accessLogging:
      - providers:
        - name: envoy #2 액세스 로그를 위한 프로바이더 설정
        disabled: false #3 disable 를 false 로 설정해 활성화한다
    EOF
    
    # webapp → catalog 는 k8s service(clusterIP) 라우팅 사용 확인!
    kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
    [2025-04-23T15:34:10.224Z] "HEAD /api/catalog HTTP/1.1" 200 - via_upstream - "-" 0 0 4 3 "192.168.65.1" "curl/8.7.1" "aa7d83a7-60ac-41aa-b4ca-71d7572cb9b0" "webapp.istioinaction.io:30000" "10.10.0.14:8080" inbound|8080|| 127.0.0.6:52231 10.10.0.14:8080 192.168.65.1:0 outbound_.80_._.webapp.istioinaction.svc.cluster.local default
    => 이 로그는 webapp 서비스의 사이드카 프록시가 클라이언트로부터 직접 HTTP 요청을 받은 장면이고, 이 요청을 10.10.0.14:8080 (즉, webapp 서비스의 실제 컨테이너)으로 보냄을 의미
    
    [2025-04-23T15:34:11.234Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 698 3 3 "192.168.65.1" "beegoServer" "7d9f5110-da1c-421f-b0eb-bca0d5b273c4" "catalog.istioinaction:80" "10.10.0.13:3000" outbound|80||catalog.istioinaction.svc.cluster.local 10.10.0.14:33550(src) 10.200.1.5:80(dst) 192.168.65.1:0 - default
    =>  이 로그는 webapp 서비스가 catalog 서비스로 HTTP 요청을 보낸 상황이에요. Envoy는 catalog.istioinaction이라는 Kubernetes catalog 서비스(clusterIp 10.200.1.5:80)로 라우팅하고, 실제 Pod IP는 10.10.0.13:3000으로 연결되었어요.
    ...
    

    Let’s create VirtualService and DestinationRule resources that route all traffic to v1 of the catalog service

    Copy
    #
    ch5/catalog-dest-rule.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: catalog
    spec:
      host: catalog.istioinaction.svc.cluster.local
      subsets:
      - name: version-v1
        labels:
          version: v1
      - name: version-v2
        labels:
          version: v2
          
    kubectl apply -f ch5/catalog-dest-rule.yaml -n istioinaction
    
    # istio-proxy
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/istio-ingressgateway.istio-system --fqdn catalog.istioinaction.svc.cluster.local
    SERVICE FQDN                                PORT     SUBSET         DIRECTION     TYPE     DESTINATION RULE
    catalog.istioinaction.svc.cluster.local     80       -              outbound      EDS      catalog.istioinaction
    catalog.istioinaction.svc.cluster.local     80       version-v1     outbound      EDS      catalog.istioinaction
    catalog.istioinaction.svc.cluster.local     80       version-v2     outbound      EDS      catalog.istioinaction
    
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/istio-ingressgateway.istio-system | egrep 'ENDPOINT|istioinaction'
    ENDPOINT                                                STATUS      OUTLIER CHECK     CLUSTER
    10.10.0.16:3000                                         HEALTHY     OK                outbound|80|version-v1|catalog.istioinaction.svc.cluster.local
    10.10.0.16:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
    10.10.0.17:3000                                         HEALTHY     OK                outbound|80|version-v2|catalog.istioinaction.svc.cluster.local
    10.10.0.17:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
    10.10.0.18:8080                                         HEALTHY     OK                outbound|80||webapp.istioinaction.svc.cluster.local
    
    #
    cat ch5/catalog-vs-v1-mesh.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: catalog
    spec:
      hosts:
      - catalog
      gateways: # 만약, gateways 부분을 제외하고 배포하면 암묵적으로 mesh gateways가 적용됨.
        - mesh  # VirtualService는 istio-proxy가 적용된 서비스 메시 내의 모든 사이드카(현재 webapp, catalog pod에 sidecar로 istio-proxy가 존재)에 적용된다. edge는 제외.
      http:
      - route:
        - destination:
            host: catalog
            subset: version-v1
    
    kubectl apply -f ch5/catalog-vs-v1-mesh.yaml -n istioinaction
    
    # VirtualService 확인 : GATEWAYS 에 mesh 확인
    kubectl get vs -n istioinaction
    NAME                    GATEWAYS                HOSTS                         AGE
    catalog                 ["mesh"]                ["catalog"]                   12s
    webapp-virtualservice   ["coolstore-gateway"]   ["webapp.istioinaction.io"]   28s
    
    
    # 반복 호출테스트 : 신규터미널 2개에 아래 각각 실행 해두기 >> 현재는 v1만 라우팅 처리
    while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
    while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -H "x-istio-cohort: internal" -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 2; echo; done
    
    
    # webapp → catalog 호출도 istio 의 DestinationRule 라우팅 전달 처리! : 신규터미널
    # mesh gateways가 적용됨!
    kubectl logs -n istioinaction -l app=webapp -c istio-proxy -f
    [2025-04-23T15:45:08.769Z] "GET /items HTTP/1.1" 200 - via_upstream - "-" 0 502 3 3 "192.168.65.1" "beegoServer" "05743f3d-4f03-4d83-a036-636f77bea0fc" "catalog.istioinaction:80" "10.10.0.12:3000" outbound|80|version-v1|catalog.istioinaction.svc.cluster.local 10.10.0.14:45682 10.200.1.5:80 192.168.65.1:0 - -
    => 이 로그는 webapp이 내부적으로 catalog 서비스를 호출하는 로그이고, version-v1이라는 **서브셋(subset)**으로 요청이 라우팅되었어요. 이는 DestinationRule에서 subset: version-v1으로 정의된 엔드포인트로 라우팅이 잘 되었다는 뜻이에요. 
    
    
    # proxy-config (webapp) : 기존에 webapp 에서 catalog 로 VirtualService 정보는 없었는데, 추가됨을 확인
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction | egrep 'NAME|catalog'
    NAME                                                          DOMAINS                                             MATCH                  VIRTUAL SERVICE
    80                                                            catalog, catalog.istioinaction + 1 more...          /*                     catalog.istioinaction
    
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 -o json > webapp-routes.json
    cat webapp-routes.json | jq
    ...
        "virtualHosts": [
          {
            "name": "catalog.istioinaction.svc.cluster.local:80",
            "domains": [
              "catalog.istioinaction.svc.cluster.local",
              "catalog",
              "catalog.istioinaction.svc",
              "catalog.istioinaction",
              "10.200.1.5" # 해당 IP는 catalog service(clusterIP)
            ],
            "routes": [
              {
                "match": {
                  "prefix": "/"
                },
                "route": {
                  "cluster": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
                  "timeout": "0s",
    ...
    
    
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn catalog.istioinaction.svc.cluster.local
    SERVICE FQDN                                PORT     SUBSET         DIRECTION     TYPE     DESTINATION RULE
    catalog.istioinaction.svc.cluster.local     80       -              outbound      EDS      catalog.istioinaction
    catalog.istioinaction.svc.cluster.local     80       version-v1     outbound      EDS      catalog.istioinaction
    catalog.istioinaction.svc.cluster.local     80       version-v2     outbound      EDS      catalog.istioinaction
    
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --subset version-v1 -o json
    ...
            "name": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
            "type": "EDS",
    ...
    
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction | egrep 'ENDPOINT|catalog'
    ENDPOINT                                                STATUS      OUTLIER CHECK     CLUSTER
    10.10.0.16:3000                                         HEALTHY     OK                outbound|80|version-v1|catalog.istioinaction.svc.cluster.local
    10.10.0.16:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
    10.10.0.17:3000                                         HEALTHY     OK                outbound|80|version-v2|catalog.istioinaction.svc.cluster.local
    10.10.0.17:3000                                         HEALTHY     OK                outbound|80||catalog.istioinaction.svc.cluster.local
    
    # proxy-config (catalog) : gateway.mesh 이므로, 메시 내에 모든 사이드카에 VirtualService 적용됨을 확인. 아래 routes 부분
    docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/catalog.istioinaction
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction | egrep 'NAME|catalog'
    NAME                                                          DOMAINS                                             MATCH                  VIRTUAL SERVICE
    80                                                            catalog, catalog.istioinaction + 1 more...          /*                     catalog.istioinaction
    
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction
    
    • DR은 DestinationRule이고, VS는 VirtualService이다.
    Copy
    #
    cat ch5/catalog-vs-v2-request-mesh.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: catalog
    spec:
      hosts:
      - catalog
      gateways:
        - mesh
      http:
      - match:
        - headers:
            x-istio-cohort:
              exact: "internal"
        route:
        - destination:
            host: catalog
            subset: version-v2
      - route:
        - destination:
            host: catalog
            subset: version-v1
    
    kubectl apply -f ch5/catalog-vs-v2-request-mesh.yaml -n istioinaction
    
    
    # 반복 호출테스트 : 신규터미널 2개에 아래 각각 실행 >> v1, v2 각기 라우팅 확인
    while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
    while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -H "x-istio-cohort: internal" -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 2; echo; done
    
    
    # proxy-config (webapp) : 기존에 webapp 에서 catalog 로 VirtualService 추가된 2개 항목 확인(v1, v2 두개의 route를 위에서 생성함!)
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction | egrep 'NAME|catalog'
    NAME                                                          DOMAINS                                             MATCH                  VIRTUAL SERVICE
    80                                                            catalog, catalog.istioinaction + 1 more...          /*                     catalog.istioinaction
    80                                                            catalog, catalog.istioinaction + 1 more...          /*                     catalog.istioinaction
    
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 -o json > webapp-routes.json
    cat webapp-routes.json | jq
    ...
            "virtualHosts": [
                {
                    "name": "catalog.istioinaction.svc.cluster.local:80",
                    "domains": [
                        "catalog.istioinaction.svc.cluster.local",
                        "catalog",
                        "catalog.istioinaction.svc",
                        "catalog.istioinaction",
                        "10.200.1.5"
                    ],
                    "routes": [
                        {
                            "match": {
                                "prefix": "/",
                                "caseSensitive": true,
                                "headers": [
                                    {
                                        "name": "x-istio-cohort",
                                        "stringMatch": {
                                            "exact": "internal"
                                        }
                                    }
                                ]
                            },
                            "route": {
                                "cluster": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
                         ....
                        {
                            "match": {
                                "prefix": "/"
                            },
                            "route": {
                                "cluster": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
    ...
    
    
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn catalog.istioinaction.svc.cluster.local
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction | egrep 'ENDPOINT|catalog'
    
    # proxy-config (catalog) : mesh 이므로 VS가 아래 routes(catalog)에도 적용됨
    docker exec -it myk8s-control-plane istioctl proxy-config listener deploy/catalog.istioinaction
    
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction | egrep 'NAME|catalog'
    NAME                                                          DOMAINS                                             MATCH                  VIRTUAL SERVICE
    80                                                            catalog, catalog.istioinaction + 1 more...          /*                     catalog.istioinaction
    80                                                            catalog, catalog.istioinaction + 1 more...          /*                     catalog.istioinaction
    
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/catalog.istioinaction --fqdn 'catalog.istioinaction.svc.cluster.local'
    SERVICE FQDN                                PORT     SUBSET         DIRECTION     TYPE     DESTINATION RULE
    catalog.istioinaction.svc.cluster.local     80       -              outbound      EDS      catalog.istioinaction
    catalog.istioinaction.svc.cluster.local     80       version-v1     outbound      EDS      catalog.istioinaction
    catalog.istioinaction.svc.cluster.local     80       version-v2     outbound      EDS      catalog.istioinaction
    
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/catalog.istioinaction
    

    5.3 Traffic shifting (실습)

    Manual Canary release
    • 이 절에서는 배포를 ‘카나리 canary’하거나 점진적으로 릴리스 incrementally release 하는 또 다른 방법을 살펴본다.
    • 이전 절에서는 헤더 비교를 기반해 특정 사용자 그룹에 다크 런치 dark launch 를 수행하는 라우팅을 살펴봤다.
    • 이 절에서는 가중치를 기반으로 특정 서비스의 여러 버전에 라이브 트래픽을 분배해본다.
    • 예를 들어 catalog 서비스 v2를 내부 직원에게 다크 런치했고 이 버전을 모두에게 천천히 릴리스하고 싶다면, v2의 라우팅 가중치를 10%로 지정할 수 있다.
    • catalog로 향하는 전체 트래픽의 10%는 v2로 가고 90%는 여전히 v1으로 갈 것이다.
    • 이렇게 하면, 전체 트래픽 중 얼마만큼이 v2 코드에 부정적 영향을 받을지 제어함으로써 릴리스의 위험성을 더욱 줄일 수 있다.
    • 다크 런치와 마찬가지로 서비스에 오류가 있는지 모니터링하고 관찰하려고 하며, 문제가 있으면 릴리스를 롤백하려고 한다.
    • 이 경우, 롤백은 catalog v2 서비스로 가는 트래픽 가중치를 줄이는 것만큼(필요한 경우 다시 0%까지) 간단하다.
    • 이스티오로 가중치 기반의 트래픽 전환을 수행하는 방법을 살펴보자.
    Copy
    # 이전 절부터 다음 서비스가 실행 중이다 : 파드에 labels 에 버전 정보 없을 경우 latest 설정되며, kiali 에 Workloads Details 에 'Missing Version' 표기됨
    kubectl apply -f services/webapp/kubernetes/webapp.yaml -n istioinaction # 이미 배포 상태
    kubectl apply -f services/catalog/kubernetes/catalog.yaml -n istioinaction # 이미 배포 상태
    kubectl apply -f services/catalog/kubernetes/catalog-deployment-v2.yaml -n istioinaction # 이미 배포 상태
    kubectl get deploy,rs,pod -n istioinaction --show-labels
    NAME                         READY   UP-TO-DATE   AVAILABLE   AGE   LABELS
    deployment.apps/catalog      1/1     1            1           23h   app=catalog,version=v1
    deployment.apps/catalog-v2   1/1     1            1           23h   app=catalog,version=v2
    deployment.apps/webapp       1/1     1            1           23h   app=webapp
    
    # 반복 호출테스트 : 신규터미널
    while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
    
    # 모든 트래픽을 catalog service v1 으로 재설정하자
    cat ch5/catalog-vs-v1-mesh.yaml
    ...
      http:
      - route:
        - destination:
            host: catalog
            subset: version-v1
    
    kubectl apply -f ch5/catalog-vs-v1-mesh.yaml -n istioinaction
    
    # 호출테스트
    curl -s http://webapp.istioinaction.io:30000/api/catalog | jq
    
    • 트래픽 중 10%를 catalog v2 로 라우팅해보자
    Copy
    #
    cat ch5/catalog-vs-v2-10-90-mesh.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: catalog
    spec:
      hosts:
      - catalog
      gateways:
      - mesh
      http:
      - route:
        - destination:
            host: catalog
            subset: version-v1
          weight: 90
        - destination:
            host: catalog
            subset: version-v2
          weight: 10 
    kubectl apply -f ch5/catalog-vs-v2-10-90-mesh.yaml -n istioinaction
    
    #
    kubectl get vs -n istioinaction catalog
    NAME      GATEWAYS   HOSTS         AGE
    catalog   ["mesh"]   ["catalog"]   112s
    
    # 호출 테스트 : v2 호출 비중 확인 10:90의 비율로 출력됨
    for i in {1..10};  do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l
    1
    
    for i in {1..100}; do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l
    7
    
    # proxy-config(webapp) : mesh 이므로 메시 내 모든 사이드카에 VS 적용
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 -o json
    ...
                            "route": {
                                "weightedClusters": {
                                    "clusters": [
                                        {
                                            "name": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
                                            "weight": 90
                                        },
                                        {
                                            "name": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
                                            "weight": 10
                                        }
                                    ],
                                    "totalWeight": 100
    ...
    
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/webapp.istioinaction --fqdn catalog.istioinaction.svc.cluster.local
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/webapp.istioinaction | grep catalog
    
    
    # proxy-config(catalog) : mesh 이므로 메시 내 모든 사이드카에 VS 적용
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/catalog.istioinaction --name 80 -o json
    ...
    
    • 트래픽을 50:50 반반으로 라우팅 해보자
    Copy
    #
    cat ch5/catalog-vs-v2-50-50-mesh.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: catalog
    spec:
      hosts:
      - catalog
      gateways:
      - mesh
      http:
      - route:
        - destination:
            host: catalog
            subset: version-v1
          weight: 50
        - destination:
            host: catalog
            subset: version-v2
          weight: 50
    kubectl apply -f ch5/catalog-vs-v2-50-50-mesh.yaml -n istioinaction
    
    # 호출 테스트 : v2 호출 비중 확인
    for i in {1..10};  do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l
    5
    
    for i in {1..100}; do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l
    57
    
    # proxy-config(webapp) 
    docker exec -it myk8s-control-plane istioctl proxy-config routes deploy/webapp.istioinaction --name 80 -o json
    ...
                          "route": {
                                "weightedClusters": {
                                    "clusters": [
                                        {
                                            "name": "outbound|80|version-v1|catalog.istioinaction.svc.cluster.local",
                                            "weight": 50
                                        },
                                        {
                                            "name": "outbound|80|version-v2|catalog.istioinaction.svc.cluster.local",
                                            "weight": 50
                                        }
                                    ],
                                    "totalWeight": 100
    ...
    • 각 서비스 버전의 트래픽 가중치를 1에서 100 사이로 바꿀 수 있지만, 가중치 총합은 반드시 100이어야 한다.
    • 그렇지 않으면 예기치 못한 트래픽 라우팅이 발생할 수 있다.
    • v1, v2 외 다른 버전이 있을 경우, DestinationRule 에서 subnet 으로 선언해야 한다는 것도 유념하자.
    • 예시로는 ch5/catalog-dest-rule.yaml 을 참조하자.

    이 절에서는 여러 버전 사이에서 트래픽을 단계적으로 옮기는 작업을 수작업으로 수행했다.

    이상적으로는 이 트래픽 전환을 어떤 도구나 CI/CD 파이프라인의 배포 파이프라인에서 자동화하고 싶다.

    다음 절에서는 이런 카나리 릴리스 프로세스 자동화를 돕는 도구를 살펴본다.

    👉🏻

    소프트웨어 새 버전을 천천히 출시할 때는 구 버전과 신 버전을 모두 모니터링하고 관찰해 안정성, 성능, 정확성 등을 확인해야 한다. 문제를 발견하면 가중치를 변경해 구 버전으로 쉽게 롤백할 수 있다. 이 방식을 사용할 때는 여러 버전을 동시에 실행할 수 있도록 서비스를 구축해야 한다는 점도 명심하자. 서비스가 더 많은 상태를 갖고 있을 수록(심지어 외부 상태에 의존한다 하더라고) 이런 작업은 더 어려워질 수 있다. 이 주제에 대한 자세한 내용은 우리의 블로그 포스트를 참조하자. https://blog.christianposta.com/microservices/traffic-shadowing-with-istio-reduce-the-risk-of-code-release/ https://blog.christianposta.com/microservices/advanced-traffic-shadowing-patterns-for-microservices-with-istio-service-mesh/

    5.3.1 (Automating) Canary releasing with Flagger (Progressive Delivery Operator for Kubernetes) - Link
    • 이전 절 에서 본 것처럼 이스티오는 트래픽 라우팅을 제어하는 강력한 기능을 운영자에게 제공하지만, 라우팅 변경과 새로운 설정 적용은 CLI에서 수동으로 해야 한다. 또한 여러 버전의 설정을 만들었기 때문에 작업도 더 많았고 설정이 잘못될 가능성도 있었다. 수백 개의 릴리스가 동시에 진행될 수도 있으므로, 카나리를 수행할 때 사람에 의한 이와 같은 수작업을 피하길 원하며 실수 가능성도 줄이고 싶다.
    • Flagger 같은 것을 사용하면 서비스 릴리스를 자동화할 수 있다. Flagger는 스테판 프로단 Stefan Prodan 이 만든 카나리 자동화 도구로, 릴리스를 어떻게 수행할지, 언제 더 많은 사용자에게 릴리스를 개방할지, 릴리스가 문제를 일으킬 경우 언제 롤백할지 등에 관련된 파라미터를 지정할 수 있다. Flagger는 릴리스를 수행하는 데 필요한 작절한 설정을 모두 만든다.
    • 이스티오와 Flagger를 함께 사용하는 방법을 살펴보자.
    • 이전 절에서는 catalog-v2 와 트래픽 라우팅을 명시적으로 제어하는 VirtualService를 배포했다.
    • 이 둘을 제거하고 Flagger가 라우팅과 배포 변경을 처리하게 하자.
    • Flagger는 서비스 상태를 판단할 때 메트릭에 의존하며, 카나리 릴리스를 사용할 때 특히 그렇다.
    • Flagger가 성공 메트릭을 사용하려면 프로메테우스를 설치해 이스티오 데이터 플레인수집해야 한다.
    • 이 책의 예제를 따라오고 있다면, 프로메테우스 샘플이 이미 설치돼 있을 것이다.
    • 다음으로는 Flagger를 설치하려고 한다. helm 사용. https://docs.flagger.app/install/flagger-install-on-kubernetes
    • 아래 코드 처럼 Flagger Canara 리소스를 사용해 카나리 릴리스의 파라미터를 지정하고, Flagger가 적절한 리로스를 만들어 이 릴리스를 주관하게 할 것이다. - Docs
    • 위 설정을 적용하고 catalog 서비스를 자동으로 v2로 카나리하는 절차를 시작해보자
    • 지금까지는 기본 설정만 준비 했을 뿐, 실제 카나리는 수행하지 않았다.
    • Flagger는 원본 디폴로이먼트 대상(여기서는 catalog 디플로이먼트 ?catalog-primary가 아닌가?)의 변경 사항을 지켜보고, 카나리 디폴로이먼트(catalog-canary) 및 서비스(catalog-canary)를 생성하고, VirtualService 의 가중치를 조정한다.
    • 이제 catalog v2 를 도입하고 Flagger가 어떻게 릴리스에서 이를 자동화하는지, 어떻게 메트릭에 기반해 의사결정을 내리는지 살펴보자.
    • 또한 Flagger가 정상 메트릭 기준선을 가져올 수 있도록 이스티오를 통해 서비스에 대한 부하를 만들어보자.
    • 카나리 과정 중 kiali 확인
    • 프로메테우스에서 확인 : flagger_canary_weight - Link
    • Flagger를 사용해 이스티오의 API로 카나리 릴리스를 자동 제어함으로써, 리소스를 직접 설정하는 등 설정 오류를 일으킬 수 있는 수작업의 필요성을 없앴다.
    • 또한 Flagger는 다크 런치 테스트, 트래픽 미러링(다음 절에서 설명한다) 등도 할 수 있다.
    • 관련 내용은 웹 사이트를 참고하자. https://docs.flagger.app/usage/deployment-strategies
    • 실습을 정리하고 이 장을 계속 진행할 수 있는 상태로 만들기 위해 Flagger Canary 리소스를 제거한다.

    5.4 Reducing risk even further: Traffic mirroring

    5.4.1. 트래픽 미러링 소개
    • 앞서 살펴본 요청 수준 라우팅과 트래픽 전환이라는 두 가지 기술을 사용하면 릴리스의 위험성을 낮출 수 있다.
    • 두 기술 모두 라이브 트래픽과 요청을 사용하므로, 영향의 파급 범위를 아무리 제어하더라도 사용자에게 영향을 줄 수 있었다.
    • 또 다른 접근법은 운영 환경 트래픽을 새 디플로이먼트로 미러링하는 것으로, 그림 5.10처럼 운영 환경 트래픽을 복사해 고객 트래픽 범위 외부의 새 디플로이먼트로 보내는 것이다.
    • 미러링 방식을 사용하면, 실제 운영 환경 트래픽을 배포로 보냄으로써 사용자에게 영향을 주지 않고 새 코드가 어떻게 동작할지에 대한 실제 피드백을 얻을 수 있다.
    • 이스티오는 트래픽 미러링을 지원하며, 이는 배포 및 릴리스 수행의 위험성을 다른 두 방식보다 휠씬 더 줄일 수 있다. 한번 살펴보자.

    미러링 Mirroring vs. 복제 Replication 의 용어의 차이? 동작의 차이는?

    5.4.2. 실습 환경 초기화 (실습)
    Copy
    # catalog 디플로이먼트를 초기 상태로 돌리고, catalog-v2 를 별도의 디플로이먼트로 배포
    kubectl apply -f services/catalog/kubernetes/catalog-svc.yaml -n istioinaction
    kubectl apply -f services/catalog/kubernetes/catalog-deployment.yaml -n istioinaction
    kubectl apply -f services/catalog/kubernetes/catalog-deployment-v2.yaml -n istioinaction
    kubectl apply -f ch5/catalog-dest-rule.yaml -n istioinaction
    kubectl apply -f ch5/catalog-vs-v1-mesh.yaml -n istioinaction
    
    # 확인
    kubectl get deploy -n istioinaction -o wide
    NAME         READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES                         SELECTOR
    catalog      1/1     1            1           8s    catalog      istioinaction/catalog:latest   app=catalog,version=v1
    catalog-v2   1/1     1            1           8s    catalog      istioinaction/catalog:latest   app=catalog,version=v2
    webapp       1/1     1            1           24h   webapp       istioinaction/webapp:latest    app=webapp
    
    kubectl get svc,ep -n istioinaction -owide
    NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE   SELECTOR
    service/catalog   ClusterIP   10.200.1.89            80/TCP    16s   app=catalog
    service/webapp    ClusterIP   10.200.1.111           80/TCP    24h   app=webapp
    
    NAME                ENDPOINTS                         AGE
    endpoints/catalog   10.10.0.20:3000,10.10.0.21:3000   16s
    endpoints/webapp    10.10.0.14:8080                   24h
    
    kubectl get gw,vs -n istioinaction
    NAME                                            AGE
    gateway.networking.istio.io/coolstore-gateway   24h
    
    NAME                                                       GATEWAYS                HOSTS                         AGE
    virtualservice.networking.istio.io/catalog                 ["mesh"]                ["catalog"]                   22s
    virtualservice.networking.istio.io/webapp-virtualservice   ["coolstore-gateway"]   ["webapp.istioinaction.io"]   24h
    
    # 반복 접속
    while true; do curl -s http://webapp.istioinaction.io:30000/api/catalog -I | head -n 1 ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; echo; done
    
    # catalog v1 으로만 접속 확인
    for i in {1..100}; do curl -s http://webapp.istioinaction.io:30000/api/catalog | grep -i imageUrl ; done | wc -l
    0
    5.4.3. 트래픽 미러링 (실습)
    • 미러링 수행이 필요한 VS 확인
    • VS 리소스를 만들어보자
    • 트래픽 미러링은 릴리스의 위험성을 낮추는 방법 중 하나다.
    • 요청 라우팅 및 트래픽 전환과 마찬가지로 애플리케이션은 상황을 인지해 실제와 미러링 모드 모두로 동작하거나, 여러 버전으로 동작하거나, 혹은 둘 다 모두 할 수 있어야 한다.
    • 자세한 내용은 우리의 블로그 게시물을 참조하자.
    5.4.4. Istio 트러블 슈팅
    • 503 upstream connect error or disconnect/reset before headers. reset reason: connection termination Error

    5.5 Routing to services outside your cluster by using Istio’s service discovery

    5.5.1. 들어가며
    5.5.2. 외부 트래픽을 차단하도록 이스티오를 설정해 메시에 간단한 보호 계층을 더해보자. (그림 5.11참조). (실습)
    ServiceEntry 소개 : 이스티오의 서비스 디스커버리 기능을 사용해 클러스터 외부의 서비스로 라우팅하기
    ServiceEntry (실습)
    (추가 실습) 외부에 web 서버(https://httpbin.org/ or docker container nginx 1대 기동 후)에 Istio 에 ServiceEntry 등록 후 통신 허용 설정 해보자

    5.6. Summary

    • DestinationRule 로 워크로드를 v1, v2 버전과 같이 더 작은 부분집합들로 분리할 수 있다.
    • VirtualService는 이런 부분집합들을 사용해 트래픽을 세밀하게 라우팅한다.
    • VirtualService는 HTTP 헤더 같은 애플리케이션 계층 정보를 기반으로 라우팅 결정을 설정한다.
    • 가중치 라우팅(VirtualService 리소스로 설정)을 사용하는 서비스 프록시는 트래픽을 점진적으로 새 배포로 라우팅할 수 있는데, 덕분에 카나리 배포(트래픽 전환이라고도 함) 같은 방법을 사용할 수 있다.
    • 트래픽 전환은 Flagger를 사용해 자동화할 수 있다. Flagger는 수집한 메트릭을 사용해 새 배포로 라우팅되는 트래픽을 점진적으로 늘리는 오픈소스 솔루션이다.
    • outboundTrafficPolicyREGISTER_ONLY로 설정하면 어떤 트래픽도 클러스터를 떠나지 못하게 함으로써 악의적인 사용자가 외부로 정보를 전송하는 것을 방지할 수 있다.
    • outboundTrafficPolicyREGISTER_ONLY로 설정했을 때는 ServiceEntry로 외부로 향하는 트래픽을 허용할 수 있다.
    • 실습 후 삭제 : kind delete cluster --name myk8s

    6. Resilience: Solving application networking challenges

    • 회복탄력성의 중요성 이해 Understanding the importance of resilience
    • 클라이언트 측 로드 밸런싱 활용 Leveraging client-side load balancing
    • 요청 시간 초과 및 재시도 구현 Implementing request timeouts and retries
    • 회로 차단 및 연결 풀링 Circuit breaking and connection pooling
    • 마이그레이션 Migrating from application libraries used for resilience
    들어가며
    • 4장에서 다룬 이스티오 인그레스 게이트웨이를 거쳐 클러스터 내부로 트래픽이 들어오게 되면, 트래픽을 요청 수준에서 조작할 수 있고 요청을 정확히 어디로 라우팅할지 제어할 수 있다.
    • 앞 장에서는 가중치 라우팅, 요청 비교 기반 라우팅과 이를 통해 가능해진 릴리스 패턴 유형을 다륐다.
    • 또한 애플리케이션 오류, 네트워크 파티션, 기타 주요 문제가 발생한 경우에 문제를 우회하는 데도 이 트래픽 제어를 사용할 수 있다.
    • 분산 시스템의 문제는 시스템이 이따금 예측할 수 없는 방식으로 실패하며 수작업으로 트래픽 전환 조치를 할 수 없다는 것이다.
    • 따라서 문제가 발생했을 때 애플리케이션이 스스로 대응할 수 있도록 애플리케이션에 합리적인 동작을 구축할 수 있는 방법이 필요하다.
    • 이스티오를 사용하면 애플리케이션 코드를 변경하지 않고도 타임아웃, 재시도, 서킷 브레이커를 추가할 수 있다.
    • 이번 장에서는 그 방법과 나머지 시스템에 미치는 영향을 살펴본다.

    6.0. 실습환경 준비

    6.0.1. [실습 환경 구성] k8s(1.23.17) 배포 : NodePort(30000 HTTP, 30005 HTTPS)
    Copy
    #
    git clone https://github.com/AcornPublishing/istio-in-action
    cd istio-in-action/book-source-code-master
    pwd # 각자 자신의 pwd 경로
    code .
    
    # 아래 extramounts 생략 시, myk8s-control-plane 컨테이너 sh/bash 진입 후 직접 git clone 가능
    kind create cluster --name myk8s --image kindest/node:v1.23.17 --config - <https://geek-cookbook.github.io/charts/
    helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30007 --set env.TZ="Asia/Seoul" --namespace kube-system
    kubectl get deploy,pod,svc,ep -n kube-system -l app.kubernetes.io/instance=kube-ops-view
    
    ## kube-ops-view 접속 URL 확인
    open "http://localhost:30007/#scale=1.5"
    open "http://localhost:30007/#scale=1.3"
    
    # (옵션) metrics-server
    helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
    helm install metrics-server metrics-server/metrics-server --set 'args[0]=--kubelet-insecure-tls' -n kube-system
    kubectl get all -n kube-system -l app.kubernetes.io/instance=metrics-server
    
    6.0.2. [실습 환경 구성] istio 1.17.8 설치 (addon 필수, istio-proxy log 설정) - Docs , Install , profile
    Copy
    # myk8s-control-plane 진입 후 설치 진행
    docker exec -it myk8s-control-plane bash
    -----------------------------------
    # (옵션) 코드 파일들 마운트 확인
    tree /istiobook/ -L 1
    혹은
    git clone ... /istiobook
    
    # istioctl 설치
    export ISTIOV=1.17.8
    echo 'export ISTIOV=1.17.8' >> /root/.bashrc
    
    curl -s -L https://istio.io/downloadIstio | ISTIO_VERSION=$ISTIOV sh -
    cp istio-$ISTIOV/bin/istioctl /usr/local/bin/istioctl
    istioctl version --remote=false
    
    # default 프로파일 컨트롤 플레인 배포
    istioctl install --set profile=default -y
    
    # 설치 확인 : istiod, istio-ingressgateway, crd 등
    kubectl get istiooperators -n istio-system -o yaml
    kubectl get all,svc,ep,sa,cm,secret,pdb -n istio-system
    kubectl get cm -n istio-system istio -o yaml
    kubectl get crd | grep istio.io | sort
    
    # 보조 도구 설치
    kubectl apply -f istio-$ISTIOV/samples/addons
    kubectl get pod -n istio-system
    
    # 빠져나오기
    exit
    -----------------------------------
    
    # istio-proxy 로그 출력 설정 : configmap 에 mesh 바로 아래에 accessLogFile 부분 추가
    KUBE_EDITOR="nano"  kubectl edit cm -n istio-system istio
    ...
      mesh: |-
        accessLogFile: /dev/stdout
    ...
    
    # 실습을 위한 네임스페이스 설정
    kubectl create ns istioinaction
    kubectl label namespace istioinaction istio-injection=enabled
    kubectl get ns --show-labels
    
    # istio-ingressgateway 서비스 : NodePort 변경 및 nodeport 지정 변경 , externalTrafficPolicy 설정 (ClientIP 수집)
    kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 8080, "nodePort": 30000}]}}'
    kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec": {"type": "NodePort", "ports": [{"port": 443, "targetPort": 8443, "nodePort": 30005}]}}'
    kubectl patch svc -n istio-system istio-ingressgateway -p '{"spec":{"externalTrafficPolicy": "Local"}}'
    kubectl describe svc -n istio-system istio-ingressgateway
    
    # NodePort 변경 및 nodeport 30001~30003으로 변경 : prometheus(30001), grafana(30002), kiali(30003), tracing(30004)
    kubectl patch svc -n istio-system prometheus -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "targetPort": 9090, "nodePort": 30001}]}}'
    kubectl patch svc -n istio-system grafana -p '{"spec": {"type": "NodePort", "ports": [{"port": 3000, "targetPort": 3000, "nodePort": 30002}]}}'
    kubectl patch svc -n istio-system kiali -p '{"spec": {"type": "NodePort", "ports": [{"port": 20001, "targetPort": 20001, "nodePort": 30003}]}}'
    kubectl patch svc -n istio-system tracing -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "targetPort": 16686, "nodePort": 30004}]}}'
    
    # Prometheus 접속 : envoy, istio 메트릭 확인
    open http://127.0.0.1:30001
    
    # Grafana 접속
    open http://127.0.0.1:30002
    
    # Kiali 접속 1 : NodePort
    open http://127.0.0.1:30003
    
    # (옵션) Kiali 접속 2 : Port forward
    kubectl port-forward deployment/kiali -n istio-system 20001:20001 &
    open http://127.0.0.1:20001
    
    # tracing 접속 : 예거 트레이싱 대시보드
    open http://127.0.0.1:30004
    
    
    # 접속 테스트용 netshoot 파드 생성
    cat <

    6.1 Building resilience into the application

    6.1.0. 들어가며 : 복원력 패턴 필요
    • 마이크로서비스는 복원력을 최우선으로 고려해 구축해야 한다.
    • ‘장애가 발생하지 않도록 구축하면 된다’고 말하는 세상은 현실이 아니다. 장애가 발생하면 모든 서비스를 중단시킬 위험이 있다.
    • 네트워크로 통신하는 서비스로 분산 시스템을 구축할 때는 장애 지점을 더 많이 많들어낼 위험이 있으며, 치명적인 장애가 발생할 가능성을 마주하게 된다.
    • 따라서 서비스 소유자는 애플리케이션 및 서비스 전반에 걸쳐 몇 가지 복원력 패턴을 일관되게 채택해야 한다.
    • 그림 6.1 처럼 서비스 A가 서비스 B를 호출했는데 서비스 B의 특정 엔드포인트로 전송이된 요청에서 지연이 발생했다면, 우리는 서비스 A가 이를 사전에 식별하고 다른 엔드포인트, 다른 가용 영역, 심지어 다른 리전으로 라우팅하길 원한다.
    • 만약 서비스 B에 간간히 오류가 발생한다면 실패한 요청을 재시도할 수 있다.
    • 마찬가지로 서비스 B를 호출하는 데 문제가 생긴다면, 그것이 무슨 문제든 서비스 B가 회복할 때까지 물러나야 할 수 있다.
    • 계속 서비스 B로 부하를 가한다면(경우에 따라 요청을 재시도하면서 부하가 증폭되는 경우도 있음) 서비스를 과부하 상태로 만들 위험이 있다.
    • 이런 과부하는 서비스 A와 이러한 서비스에 의존하는 모든 서비스에 파급돼 심각한 연쇄 오류를 일으킬 수 있다.
    • 해결책은 애플리케이션이 장애를 예상해 요청을 처리할 때 자동으로 복원을 시도하거나 대체 경로로 돌아갈 수 있도록 구축하는 것이다.
    • 예를 들어 서비스 A가 서비스 B를 호출할 때 문제가 발생한다면, 요청을 재시도하거나 타임아웃시키거나 서킷 브레이킹 패턴을 사용해 더 이상의 발신 요청을 취소할 수 있다.
    • 이번 장에서는 애플리케이션의 프로그래밍 언어와 상관없이 애플리케이션에 복원력을 올바르고 일관되게 구현할 수 있도록, 이스티오를 사용해 이런 문제들을 투명하게 해결하는 방법을 살펴본다.
    6.1.1 Building resilience into application libraries : 언어 마다 구현 상이, 운영 부담
    • 서비스 메시 기술이 널리 사용되기 전까지 서비스 개발자인 우리가 애플리케이션 코드에 이런 복원력 패턴을 많이 작성해야 했다.
    • 오픈소스 커뮤니티에서는 이런 문제를 해결하는 데 도움이 되는 몇 가지 프레임워크가 등장했다.
    • 트위터는 2011년에 자사의 복원력 프레임워크 Finagle을 오픈소스화 했다.
    • 트위터 Finagle은 ‘스칼라/자바/JVM’ 애플리케이션 라이브러리 타임아웃, 재시도, 서킷 브레이킹 등 다양한 RPC 복원력 패턴을 구현하는 데 사용한다.
    • 얼마 지나지 않아 넷플릭스는 자사의 복원력 프레임워크 구성 요소를 오픈소스화했다.
    • 여기에는 HystrixRibbon이 포함되는데, 각기 서킷 브레이커클라이언트 측 로드 밸런싱 기능을 제공한다.
    • 두 라이브러리 모두 자바 커뮤니티에서 매우 인기가 많았으며, 스프링 프레임워크는 NetfilxOSS 스택을 스프링 클라우드 프레임워크에 채택하기도 했다.
    • 이런 프레임워크의 한 가지 문제점은 언어, 프레임워크, 인프라 조합마다 구현 방식이 상이하다는 것이다.
    • 트위터 FinagleNetfilxOSS는 자바 개발자에게는 훌륭하지만 Node.js, Go 언어, 파이썬 개발자는 이런 패턴의 변형을 찾거나 직접 구현해야 했다.
    • 때에 따라서는 이런 라이브러리가 애플리케이션 코드에 침입해 네트워킹 코드가 여거저기 흩어지고 실제 비즈니스 로직을 가려버리는 상황이 발생하기도 했다.
    • 나아가 여러 언어와 프레임워크에 걸쳐 이런 라이브러리들을 유지 관리하는 것은 마이크로서비스 운영 측면에서 부담이 된다.
    • 모든 조합을 동시에 패치하고 기능을 동일하게 유지해야 하기 때문이다.
    6.1.2 Using Istio to solve these problems : 이스티오 서비스 프록시가 복원력 기능 제공
    • 앞 장에서 봤듯이, 이스티오의 서비스 프록시는 애플리케이션 옆에 위치하며 애플리케이션을 드나드는 모든 네트워크 트래픽을 처리한다. (그림 6.2)
    • 이스티오에서 서비스 프록시는 애플리케이션 수준 요청과 메시지(HTTP 요청 등)를 이해하므로 프록시 안에서 복원력 기능을 구현할 수 있다.
    • 예를 들어 서비스 호출 시 HTTP 503 오류가 발생하면 세 번까지 재시도하도록 설정할 수 있다.
    • 재시도할 실패, 재시도 횟수, 재시도별 타임아웃을 정확히 설정할 수 있는데, 서비스 프록시가 서비스 인스턴스마다 배포되므로 애플리케이션마다 필요한 대로 재시도 동작을 세밀하게 제어할 수 있다.
    • 이스티오의 다른 복원력 설정도 모두 마찬가지다.
    • 이스티오의 서비스 프록시는 기본적으로 다음과 같은 복원력 패턴을 구현한다.
    6.1.3 Decentralized implementation of resilience : 복원력 패턴을 분산 구현
    • 이스티오를 사용하면, 애플리케이션 요청이 통과하는 데이터 플레인 프록시가 애플리케이션 인스턴스와 같은 위치에 있고 중앙집중식 게이트웨이가 필요하지 않음을 알 수 있다.
    • 이런 복원력 패턴 처리를 코드에 함께 두는 애플리케이션 라이브러리를 사용하면 동일한 아키텍처를 얻는다.
    • 예전에는 이런 분산 시스템의 공통 문제 중 일부를 해결하기 위해 비싸고 변경하기 어려운 중앙집중식 하드웨어 장치와 기타 소프트웨어 미들웨어를 요청 경로에 배치헀다. (하드웨어 로드 밸런싱, 메시징 시스템, 엔터프라이즈 서비스 버스, API 관리 등).
    • 이런 초기 구현들은 더 정적인 환경에 맞춰 만들어져서, 고도로 동적이고 탄력적인 클라우드 아키텍처와 인프라에 맞게 확장되거나 잘 대응하지 못한다.
    • 따라서 이런 일부 복원력 패턴을 해결할 때는 분산 구현을 선택해야 한다.
    • 다음 절에서는 이스티오가 도움이 될 수 있는 복원력 패턴을 살펴볼 것이다.
    • 서비스 작동 방식을 좀 더 세밀하게 제어하기 위해 다른 샘플 애플리케이션 셋을 사용하는데, 이 프로젝트는 좀 더 현실적인 운영 환경에서 서비스가 어떻게 작동할 수 있는지 설명하기 위해 만든 닉 잭슨 Nic Jackson의 Fake Service 라는 프로젝트다.
    • 다음 예제에서는 그림 6.3과 같이 simple-web 서비스가 simple-backend 백엔드를 호출하는 것을 볼 수 있다.

    6.2 Client-side load balancing 클라이언트 측 로드 밸런싱 (실습)

    6.2.0. 들어가며 : EDS, DestinationRule(Client LoadBalancer)
    • 클라이언트 측 로드 밸런싱이란 엔드포인트 간에 요청을 최적으로 분산시키기 위해 클라이언트에게 서비스에서 사용할 수 있는 여러 엔드포인트를 알려주고 클라이언트가 특정 로드 밸런싱 알고리듬을 선택하게 하는 방식을 말한다.
    • 이렇게 하면 병목 현상과 장애 지점을 만들 수 있는 중앙집중식 로드 밸런싱에 의존할 필요성이 줄어들고, 클라이언트가 군더더기 홉을 거칠 필요 없이 특정 엔드포인트로 직접적이면서 의도적으로 요청을 보낼 수 있다.
    • 그러므로 클라이언트와 서비스는 더 잘 확장돼 변화하는 토폴로지에 대응할 수 있다.
    • 이스티오는 서비스 엔드포인트 디스커버리를 사용해 그림 6.4처럼 서비스 간 통신의 클라이언트 측 프록시에 올바른 최신 정보를 제공한다.
    • 그러면 서비스의 개발자와 운영자는 이스티오 설정으로 클라이언트 측 로드 밸런싱 동작을 설정할 수 있다.
    • 서비스 운영자와 개발자는 DestinationRule 리소스로 클라이언트가 어떤 로드 밸런싱 알고리듬을 사용할지 설정할 수 있다.
    • 이스티오의 서비스 프록시는 기반이 엔보이이므로 엔보이의 로드 밸런싱 알고리듬을 지원하며 그 중 일부는 다음과 같다.
    👉🏻

    Server side load balancing vs. Client side load balancing 용어 정리와 비교(장/단점)를 해보시기 바랍니다.

    Server side LB
    Client side LB
    6.2.1 Getting started with client-side load balancing : DestinationRule 실습
    • 실습 전 초기화
    • 예제 서비스 2개와 gw,vs 배포
    • 이스티오 DestinationRule 리소스로 simple-backend 서비스를 호출하는 모든 클라이언트의 로드 밸런싱을 ROUND_ROBIN으로 설정하자.
    • DestinationRule특정 목적지를 호출하는 메시 내 클라이언트들에 정책을 지정한다.
    • simple-backend 용 첫 DestinationRule는 다음과 같다.
    • 적용 및 확인해보자
    • simple-web 과 simple-backend 간 호출이 여러 simple-backend 엔드포인트로 효과적으로 분산되는 것을 확인할 수 있다.
    • 우리는 simple-web 과 simple-backend 간의 클라이언트 측 로드 밸런싱을 보고 있는데, simple-web 과 함께 배포된 서비스 프록시가 모든 simple-backend 엔드포인트를 알고 있고 기본 알고리듬을 사용해 요청을 받을 엔드포인트를 결정하고 있기 때문이다.
    • ROUND_ROBIN 로드 밸런싱을 사용하도록 DestinationRule 을 설정하기는 했지만, 사실 이스티오 서비스 프록시의 기본 설정도 ROUND_ROBIN 로드 밸런싱 전략을 사용하는 것이다.
    • 클라이언트 측 로드 밸런싱은 어떻게 서비스의 복원력을 기여할 수 있을까?
    • 부하 생성기를 사용해 simple-backend 서비스 지연 시간을 변화시키는 어느 정도 현실적인 시나리오를 살펴보자.
    • 그러면 이런 상황에서 어떤 이스티오의 로드 밸런싱 전략이 가장 적합한지 선택하는 데 도움이 될 것이다.
    6.2.2 Setting up our scenario : Fortio 설치
    • 현실적인 환경에서 서비스가 요청을 처리하는 데 시간이 걸린다. 소요 시간은 여러 이유로 달라질 수 있다.
    • 서비스 외적인 이유도 응답 시간에 영향을 줄 수 있다.
    • 예제 서비스에서는 이를 모방하고자 응답 시간에 지연 delays편차(변인) variance 를 도입해본다.
    • 서비스를 다시 호출하고 최초 설정한 서비스 응답 시간의 차이를 관찰하자.
    • 우리는 Fortio 라는 CLI 부하 생성 도구를 사용해 서비스를 실행하고 클라이언트 측 로드 밸런싱의 차이를 관찰할 것이다.
    6.2.3 Testing various client-side load-balancing strategies* : LB 알고리즘에 따란 지연 시간 성능 측정

    테스트 환경

    • 이제 Fortio 로드 테스트 클라이언트를 사용할 준비가 됐으므로 사용 사례를 살펴보자.
    • Fortio를 사용해서 60초 동안 10개의 커넥션을 통해 초당 1000개의 요청을 보낼 것이다.
    • Fortio는 각 호출의 지연 시간을 추적하고 지연 시간 백분위수 분석과 함께 히스토그램에 표시한다.
    • 테스트를 하기 전에 지연 시간을 1초까지 늘린 simple-backend-1 서비스를 도입할 것이다.
    • 이는 엔드포인트 중 하나에 긴 가비지 컬렉션 이벤트 또는 기타 애플리케이션 지연 시간이 발생한 상황을 시뮬레이션한다.
    • 우리는 로드 밸런싱 전략을 라운드 로빈, 랜덤, 최소 커넥션으로 바꿔가면서 차이점을 관찰할 것이다.
    • 지연된 simple-backend-1 서비스를 배포해보자 : fake-service - Dockerhub , Github
    • Fortio 를 서버 모드로 실행 후 웹 대시보드에 접근하자. fortio server
    • 웹 대시보드에서는 테스트에 인자를 입력하고, 테스트를 실행하고, 결과를 시각화 할 수 있다. http://127.0.0.1:8080/fortio/
    • 테스트가 완료되면 결과 파일이 파일시스템이 저장된다. 또한 결과 그래프도 표시된다.
    • 로드 밸런싱 알고리듬을 RANDOM 으로 변경하고 다시 로드 테스트를 해보자
    • 로드 밸런싱 알고리듬을 Least connection 으로 변경하고 다시 로드 테스트를 해보자
    • 실습 완료 후 Ctrl+CFortio 서버를 종료하자.
    6.2.4 Understanding the different load-balancing algorithms
    • 로드 테스트 종합해보면,
    • 라운드 로빈과 랜덤 모두 간단한 로드 밸런싱 알고리듬이다. 따라서 구현하기도 이해하기도 쉽다.
    • 라운드 로빈(또는 next-in-loop)은 엔드포인트에 차례대로 요청을 전달한다. 랜덤은 엔드포인트를 무작위로 균일하게 고른다.
    • 둘 다 비슷한 분포를 기대할 수 있는데, 이 두 전략의 과제는 로드 밸런서 풀의 엔드포인트가 일반적으로 균일하지 않다는 것이다.
    • 서비스의 리소스가 동일하더라도 그렇다.
    • 테스트에서 시뮬레이션했던 것처럼, 엔드포인트에는 지연 시간을 늘리는 가비지 컬렉션이나 리소스 경합이 일어날 수 있고 라운드 로빈과 랜덤은 런타임 동작을 고려하지 않는다.
    • 최소 커넥션 least-connection 로드 밸런서(엔보이에서는 최소 요청 least request으로 구현)는 특정 엔드포인트의 지연 시간을 고려한다.
    • 요청을 엔드포인트로 보낼 때 대기열 깊이 queue depth 를 살펴 활성 요청 개수를 파악하고, 활성 요청이 가장 적은 엔드포인트를 고른다.
    • 이런 알고리듬 유형을 사용하면, 형편없이 동작하는 엔드포인트로 요청을 보내는 것을 피하고 좀 더 빠르게 응답하는 엔드포인트를 선호할 수 있다.
    👉🏻

    엔보이 최소 요청 로드 밸런싱 Envoy least-request load balancing - 이스티오 설정에서 최소 요청 로드 밸런싱 least-request load balancingLEAST_CONN이라고 지칭하지만, 엔보이가 엔드포인트마다 파악하는 것은 요청 깊이커넥션이 아니다. Envoy is tracking request depths for endpoints, not connections. - 로드 밸런서는 무작위 엔드포인트를 둘 고르고, 어떤 엔드포인트에 활성 요청이 더 적은지 확인하고, 더 적은 것을 고른다. The load balancer picks two random endpoints, checks which has the fewest active requests, and chooses the one with the fewest active requests. - 연속적인 로드 밸런싱 시도에서도 마찬가지로 동작한다. It does the same thing for successive load-balancing tries - 이런 방식을 ‘두 가지 선택의 힘(power of two choices)’ 이라고 한다. This is known as the power of two choices - 로그 밸런서를 이렇게 구현하는 것이 전체를 확인하는 것에 비해 좋은 타협점이라고 알려져 있으며 좋은 결과를 보인다. it has been shown to be a good trade-off (versus a full scan) when implementing a load balancer like this, and it achieves good results - 이 로드 밸런서에 대한 자세한 정보는 엔보이 문서를 참고하자. - Docs

    6.3 Locality-aware load balancing 지역 인식 로드 밸런싱 (실습)

    6.3.0. 들어가며 : 배경 소개
    • 이스티오 같은 컨트롤 플레인의 역할 중 하나는 서비스 토폴로지를 이해하고 그 토폴로지가 어떻게 발전할 수 있는지 파악하는 것이다.
    • 서비스 메시에서 전체 서비스 토폴로지를 이해할 때의 이점은 서비스와 피어 서비스의 위치 같은 휴리스틱 heuristic 을 바탕으로 라우팅과 로드 밸런싱을 자동으로 결정할 수 있다는 점이다.
    • 이스티오가 지원하는 로드 밸런싱 유형에는 워크로드의 위치에 따라 루트에 가중치를 부여하고 라우팅 결정을 내리는 것이다.
    • 예를 들어 이스티오는 특정 서비스를 배포한 리전과 가용 영역을 식별하고, 더 가까운 서비스에 우선순위를 부여할 수 있다.
    • 만약 simple-backend 서비스를 여러 리전에 배포했다면(us-west, us-east, europe-west), 호출하는 방법은 여러 가지가 있다.
    • simple-web을 us-west 리전에 배포했다면, 우리는 simple-web이 하는 simple-backend 호출이 us-west 로컬이길 바랄 것이다. (그림 참고)
    • 모든 엔드포인트를 동등하게 취급한다면 리전이나 영역을 넘나들면서 지연 시간이 길어지고 비용이 발생할 가능성이 크다.
    6.3.1 Hands-on with locality load balancing*
    • 지역 인식 로드 밸성싱이 잘 동작하는지 살펴보자. 쿠버네티스에 배포할 때, 리전과 영역 정보를 노드 레이블에 추가할 수 있다.
    • 예를 들어 failure-domain.beta.kubernetes.io/region 레이블 및 failure-domain.beta.kubernetes.io/zone 은 각각 리전과 영역을 지정 할 수 있게 해준다.
    • 최근에는 쿠버네티스 API 정식 버전에서는 이 레이블들을 topology.kubernetes.io/region 과 topology.kubernetes.io/zone 으로 대체했다.
    • 이런 레이블은 구글 클라우드나 AWS 같은 클라우드 프로바이더가 자동으로 추가하는 경우가 많다.
    • 이스티오는 이런 노드 레이블을 가져와 엔보이 로드 밸런싱 엔드포인트에 지역 정보로 보강한다.
    • 현재 노드 1대(?)로 실습 하고 있으니, 다른 방식으로 지역 노드(?)로 실습을 하자.
    • 이스티오에는 워크로드에 지역을 명시적으로 설정할 수 있는 방법이 있다.
    • 파드에 istio-locality 라는 레이블을 달아 리전/영역을 지정할 수 있다.
    • 이러면 지역 인식 라우팅 및 로드 밸런싱을 시연하기에 충분하다.
    • 예를 들어 simple-web 디플로이먼트는 다음과 같을 수 있다.
    • simple-backend 서비스를 배포할 때 지역 레이블을 다양하게 지정할 것이다.
    • simple-web과 같은 지역인 us-west1-a 에 simple-backend-1을 배포한다.
    • 그리고 us-west1-b 에 simple-backend-2 를 배포한다. 이 경우, 리전은 동일하지만 영역이 다르다.
    • 지역 간에 로드 밸런싱을 수행할 수 있는 이스티오의 기능에는 리전, 영역, 심지어는 더 세밀한 하위 영역 subzone 도 포함된다.
    • 다음 서비스를 배포하자.
    • 이스티오의 지역 인식 로드 밸런싱은 기본적으로 활성화돼 있다. https://istio.io/v1.17/docs/reference/config/istio.mesh.v1alpha1/
    • 비활성화 하고 싶다면, meshConfig.localityLbSetting.enabled: false 로 설정하면 된다.
    • 책 저자가 추천하는 이스티오의 지역 인식 로드 밸런싱 블로깅 https://karlstoney.com/locality-aware-routing/
    • 지역 정보가 준비되면, us-west1-a 에 있는 simple-web 호출이 같은 영역인 us-west1-a 에 배포된 simple-backend 서비스로 갈 것으로 기대할 수 있다.
    • 우린 예제에서는 simple-web의 모든 트래픽이 us-west1-a 에 있는 simple-backend-1 로 향한다.
    • simple-backend-2 서비스는 simple-web 과 다른 영역인 us-west-1b에 배포돼 있으므로, us-west1-a 에 있는 서비스가 실패하기 시작할 때만 simple-backend-2 로 향할 것으로 기대할 수 있다.
    • 호출 테스트 1지역 정보를 고려하지 않고 simple-backend 모든 엔드포인트로 트래픽이 로드 밸런싱 됨, 아직 지역 기반 load balancing 적용이안됨
    • 이스티오에서 지역 인식 로드밸런싱이 작동하려면 헬스 체크를 설정해야 한다.
    • 헬스 체크가 없으면 이스티오가 로드 밸런싱 풀의 어느 엔드포인트가 비정상 인지, 다음 지역으로 넘기는 판단 기준은 무엇인지를 알 수 없다.
    • 이상값 감지는 엔드포인트의 동작과, 엔드포인트가 정상적으로 보이는지 여부를 수동적으로 감시한다.
    • 엔드포인트가 오류를 반환하는지 지켜보다가, 오류가 반환되면 엔드포인트를 비정상으로 표시한다.
    • 이상값 감지는 다음 절에서 더 자세히 다룬다.
    • simple-backend 서비스에 이상값 감지를 설정해 수동적인 헬스 체크 설정을 추가해보자. 아래는 설정값에 대한 자세한 설명이다.
    Copy
    #
    cat ch6/simple-backend-dr-outlier.yaml
    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: simple-backend-dr
    spec:
      host: simple-backend.istioinaction.svc.cluster.local
      trafficPolicy:
        outlierDetection:             # 이상값 감지 설정
          consecutive5xxErrors: 1     # 5xx error가 연속 1개 있으면 unhealthy
          interval: 5s                # 5초 간격으로 요청
          baseEjectionTime: 30s       # 30초 동안 트래픽에서 제외
          maxEjectionPercent: 100     # 100%는 모든 인스턴스가 비정상으로 간주될 경우 전부 제외될 수 있음
    
    kubectl apply -f ch6/simple-backend-dr-outlier.yaml -n istioinaction
    
    # 확인
    kubectl get dr -n istioinaction simple-backend-dr -o jsonpath='{.spec}' | jq
    
    
    # 반복 호출 확인 : 파드 비중 확인
    for in in {1..10}; do curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body"; done
    for in in {1..50}; do curl -s http://simple-web.istioinaction.io:30000 | jq ".upstream_calls[0].body"; done | sort | uniq -c | sort -nr
    
    
    # proxy-config : simple-web 에서 simple-backend 정보 확인
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local        
    docker exec -it myk8s-control-plane istioctl proxy-config cluster deploy/simple-web.istioinaction --fqdn simple-backend.istioinaction.svc.cluster.local -o json
    ...
            },
            "outlierDetection": {
                "consecutive5xx": 1,
                "interval": "5s",
                "baseEjectionTime": "30s",
                "maxEjectionPercent": 100,
                "enforcingConsecutive5xx": 100,
                "enforcingSuccessRate": 0
            },
    ...
    
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local'
    docker exec -it myk8s-control-plane istioctl proxy-config endpoint deploy/simple-web.istioinaction --cluster 'outbound|80||simple-backend.istioinaction.svc.cluster.local' -o json
    ...
                    "healthStatus": {
                        "edsHealthStatus": "HEALTHY"
                    },
                    "weight": 1,
                    "priority": 1,
                    "locality": {
                        "region": "us-west1",
                        "zone": "us-west1-b"
                    }
                
    ...
    
    # 로그 확인
    kubectl logs -n istioinaction -l app=simple-backend -c istio-proxy -f
    
    kubectl stern -l app=simple-backend -n istioinaction
    simple-backend-1-687cff7785-2qrbw istio-proxy [2025-04-25T16:07:56.973Z] "GET // HTTP/1.1" 301 - via_upstream - "-" 0 36 1 0 "192.168.65.1" "curl/8.7.1" "7f8ee51b-0da7-4289-8a06-85522dc4bac7" "simple-backend:80" "10.10.0.20:8080" inbound|8080|| 127.0.0.6:56199 10.10.0.20:8080 192.168.65.1:0 outbound_.80_._.simple-backend.istioinaction.svc.cluster.local default
    simple-backend-1-687cff7785-2qrbw istio-proxy [2025-04-25T16:07:56.977Z] "GET / HTTP/1.1" 200 - via_upstream - "-" 0 277 152 152 "192.168.65.1" "curl/8.7.1" "7f8ee51b-0da7-4289-8a06-85522dc4bac7" "simple-backend:80" "10.10.0.20:8080" inbound|8080|| 127.0.0.6:40131 10.10.0.20:8080 192.168.65.1:0 outbound_.80_._.simple-backend.istioinaction.svc.cluster.local default
    ...
    • 모든 트래픽이 simple-web과 동일한 영역에 있는 simple-backend-1 서비스로 가고 있다.
    • 호출 테스트 2 ⇒ 오동작 주입 후 확인
    • 다음 실습을 위해 simple-backend-1 을 정상화
    6.3.2 More control over locality load balancing with weighted distribution : 가중치 분포로 지역 인식 LB 제어 강화
    • 앞 절에서는 지역 인식 로드 밸런싱이 실제로 작동하는 모습을 살펴봤다.
    • 지역 인식 로드밸런싱에서 마지막으로 알아둬야 하는 내용은 동작 방식 일부를 제어할 수 있다는 것이다.
    • 이스티오의 기본 설정에서 서비스 프록시는 모든 트래픽을 동일 지역의 서비스로 보내고, 장애나 비정상 엔드포인트가 있을 때만 다른 지역으로 넘긴다.
    • 하지만 트래픽 일부를 여러 지역에 분산하고 싶다면 이 동작에 영향을 줄 수 있는데, 이를 지역 가중 분포 locality weighted distribution 라고 한다.
    • 특정 지역의 서비스가 피크 peak 시간이나 계절성 트래픽으로 인해 과부하될 것으로 예상될 경우 이런 방법을 사용할 수 있다.
    • 특정 영역에서 리전이 처리 할 수 없는 부하가 들어온다고 해보자.
    • 트래픽의 70%가 최인접 지역으로 가고, 30%가 인접 지역으로 가길 원한다.
    • 앞선 예제를 따라 simple-backend 서비스로 가는 트래픽 70%를 us-west1-a로, 30%를 us-west1-b로 보낼 것이다.
    • 실습해보자 : lb에 가중치 적용하기!
    • 일부 요청이 분산됐다. 대부분은 가장 가까운 지역으로 향했지만, 다음 최인접 지역으로 넘어갈 수 있는 여지는 있었다.
    • 이는 5장에서 트래픽을 명시적으로 제어했던 것과 동일하지 않다는 점에 유의하자.
    • 트래픽 라우팅에서는 서비스의 부분집합 간에 트래픽 비중을 제어할 수 있었고, 보통 전체 서비스 그룹 내에서 종류나 버전이 여럿일 때 사용한다.
    • 위 예제에서는 서비스의 배포 토폴로지를 바탕으로 트래픽에 가중치를 부여했고, 부분집합과는 무관한다.
    • 부분집합 라우팅과 가중치 부여는 상호 배타적인 개념이 아니다.
    • 이 둘은 중첩될 수 있으며, 5장에서 봤던 세밀한 트래픽 제어 및 라우팅을 이번 절에서 살펴본 지역 인식 로드 밸런서 위에 적용 할 수 있다.

    6.4 Transparent timeouts and retries (실습)

    6.4.0. 들어가며 : 네트워크 신뢰성 문제 극복 필요
    • 네트워크에 분산된 구성 요소에 의존하는 시스템을 구축할 때 가장 큰 문제는 지연 시간실패다.
    • 앞선 절에서는 이스티오에서 로드 밸런싱과 지역을 사용해 이런 문제를 완화하는 방법을 살펴봤다.
    • 이 네트워크 호출이 너무 길면 어떻게 되는가?
    • 또는 지연 시간이나 다른 네트워크 요인 때문에 간간이 실패한다면?
    • 이스티오는 이런 문제를 해결하는 데 어떻게 도움이 될 수 있을까?
    • 이스티오를 사용하면 다양한 종류의 타임아웃과 재시도를 설정해 네트워크에 내재된 신뢰성 문제를 극복할 수 있다.
    6.4.1 Timeouts : 지연 시간
    • 분산 환경에서 가장 다루기 어려운 시나리오 중 하나가 지연 시간이다.
    • 처리 속도가 느려지면 리소스를 오래 들고 있을 테고, 서비스에서는 처리해야 할 작업이 적체될 수 있으며, 상황은 연쇄 장애로까지 이어질 수 있다.
    • 이런 예기치 못한 시나리오를 방지하려면 커넥션이나 요청, 혹은 둘 다에서 타임아웃을 구현해야 한다.
    • 중요한 점은 서비스 호출 사이의 타임아웃이 서로 상호작용하는 방법이다.
    • 예를 들어 서비스 A가 서비스 B를 호출할 때 타임아웃은 1초이지만 서비스 B가 서비스 C를 호출할 때 타임이웃은 2초라면, 어떤 타임아웃이 먼저 작동하는가?
    • 가장 제한적인 타임아웃이 먼저 동작하므로, 서비스 B에서 서비스 C로의 타임아웃은 발동하지 않을 것이다.
    • 일반적으로 아키텍처의 가장자리(트래픽이 들어오는 곳)에 가까울수록 타임아웃이 길고 호출 그래프의 계층이 깊을수록 타임아웃이 짧은(혹은 더 제한적인)것이 합리적이다.
    • 어떻게 이스티오를 사용해 타임아웃 정책을 제어하는지 살펴보자.
    • 환경을 재설정하자
    • 호출 테스트 : simple-backend-1 1초 delay로 응답
    • 1초 정도는 괜찮을 수 있지만, simple-backend의 지연 시간이 5초, 혹은 100초로 급증한다면 어떻게 될까?
    • 이스티오를 사용해 simple-backend 서비스 호출에 타임아웃을 적용해보자.
    • 이스티오는 VirtualService 리소스로 요청별로 타임아웃을 지정할 수 있다.
    • 예를 들어 메시 내 클라이언트에서 simple-backend 로 향하는 호출의 타임아웃을 0.5초로 지정하려면 다음과 같이 하면 된다.
    • 다음 절에서 타임아웃 같은 실패를 해결하기 위한 다른 방법을 논의한다.
    6.4.2 Retries* : 재시도
    • 설정 초기화
    • 서비스를 호출할 때 간간이 네트워크 실패를 겪는다면, 애플리케이션이 요청을 재시도하길 원할 수 있다.
    • 요청을 재시도하지 않으면, 서비스가 흔히 발새하고 예견할 수 있는 실패에 취약해져 사용자에게 좋지 않은 경험을 제공할 수 있다.
    • 한편으로 무분별한 재시도는 연쇄 장애를 야기하는 등 시스템 상태를 저하시킬 수 있으므로 적절히 균형을 맞춰야 한다.
    • 서비스가 실제로 과부화된 경우에는 재시도해봐야 문제가 악화시킬 뿐이다.
    • 이스티오의 재시도 옵션을 살펴보자.
    • 이스티오에서는 재시도가 기본적으로 활성화돼 있고, 두 번까지 재시도한다.
    • 동작을 세밀하게 튜닝하기에 앞서 기본 동작을 이해해야 한다.
    • 시작하기 위해 예제 애플리케이션에서 기본적인 재시도를 비활성화하자.
    • VirtualService 리소스에서 최대 재시도를 0으로 설정하면 된다.

    에러 발생 시 재시도 실습

    • 이제 주기적으로(75%) 실패하는 simple-backend 서비스 버전을 배포해보자.
    • 이 경우 엔드포인트 셋 중 하나(simple-backend-1)는 그림 6.12처럼 호출 중 75%에 HTTP 503 반환한다.
    • 기본적으로, 이스티오는 호출이 실패하면 두 번 더 시도한다. 이 기본 재시도는 특정 상황에서만 적용된다.
    • 일반적으로는 이들 기본 상황에서는 재시도해도 안전한다.
    • 이 상황들은 네트워크 커넥션이 수립되지 않아 첫 시도에서 요청이 전송될 수 없음을 의미하기 때문이다.
    • 우리는 앞서 살펴본 설정으로 기본 재시도 정책을 비활성화했다.
    • 다음 VirtualService 리소스를 사용해 simple-backend 로 향하는 호출에 재시도를 2회로 명시적으로 설정해보자.
    • 앞에서 봤듯이, 실패는 있지만 호출자에게는 드러나지 않는다. 이스티오의 재시도 정책을 활성화해 이런 오류를 우회하게끔 했기 때문이다.
    • HTTP 503은 기본적으로 재시도할 수 있는 상태 코드 중 하나다.
    • 다음 VirtualService 재시도 정책은 설정할 수 있는 재시도 파라미터를 보여준다.
    • 다양한 재시도 설정으로 재시도 동작(얼마나 많이, 얼마나 깊게, 어느 엔드포인트로 재시도할 지)과 어떤 상태 코드일 때 재시도할지를 어느 정도 제어할 수 있다. 상술했듯이 모든 요청을 재시도할 수 있거나 해야 하는 것은 아니다.
    • 예를 들어 HTTP 500 코드를 반환하는 simple-backend 서비스를 배포하면, 기본 재시도 동작은 실패를 잡아내지 않는다.
    • 503 이외의 다른 에러 발생 시에도 retry 가 동작하는지 확인해보자.
    • HTTP 500은 재시도하는 상태 코드에 포함되지 않는다.
    • 모든 HTTP 500 코드(커넥션 수립 실패 및 스트림 거부 포함)를 재시도하는 VirtualService 재시도 정책을 사용해보자.
    • 사용할 수 있는 retryOn 설정은 엔보이 문서를 참조하자.

    타임아웃에 따른 재시도

    • 재시도에는 자체적인 제한 시간(perTryTimeout) 이 있다.
    • 이 설정에서 주의할 점은 perTryTimeout총 시도 횟수곱한 값 전체 요청 제한 시간(= app에 설정된 timeout)보다 작아야 한다는 것이다.
    • 예를 들어, 총 제한 시간이 1초이고 시도별 제한 시간이 500ms에 3회까지 재시도하는 재시도 정책은 의도대로 동작하지 않는다. → 2번까지 재시도 하거나 2번째 재시도도 못할 수 있음(backoff 지연에 의해)
    • 재시도를 하기 전에 전체 요청 타임아웃이 발생할 것이다.
    • 또 재시도 사이에는 백오프 backoff 지연이 있다는 점도 유념하자.
    • 이 백오프 시간도 전체 요청 제한 시간 계산에 포함된다.
    • 백오프는 다음 절에서 더 자세히 다룬다.

    작동 방식

    • 요청이 이스티오 서비스를 서비스 프록시를 거쳐 흐를 때, 업스트림으로 전달되는 데 실패하면 요청을 ‘실패 failed’로 표시하고 VirtualService 리소스에 정의한 최대 재시도 횟수까지 재시도한다.
    • 재시도 횟수가 2이면 실제로는 요청이 3회까지 전달되는데, 한 번은 원래 요청이고 두 번은 재시도다.
    • 재시도 사이에 이스티오는 25ms 를 베이스로 재시도를 ‘백오프’ 한다.
    • 재시도 및 백오프 동작을 설명하는 그림 6.13을 참조하자.
    요청 실패 시 재시도 요청 흐름
    • 즉, 이스티오는 재시도에 시차를 주고자 연속적인 재시도에서 (25ms x 재시도 횟수)까지 백오프한다. (기다린다)
    • 현재 재시도 베이스는 고정돼 있다.
    • 그러나 다음 절에서 언급하겠지만, 이스티오가 노출하지 않는 엔보이는 API를 바꿀 수 있다.
    • 상술했듯이 이스티오의 기본 재시도 횟수는 2회다.
    • 시스템 내의 계층이 다르면 재시도 횟수도 다르도록 이 값을 재정의하고 싶을 수도 있다.
    • 기본값과 같이 재시도 횟수를 무턱대고 설정하면, 심각한 재시도천둥 무리 thundering herd’ 문제가 발생할 수 있다. (그림 6.14 참조)
    • 에를 들어 서비스 체인이 5단계 깊이로 연결돼 있고 각 단계가 두 번씩 재시도할 수 있다면, 들어오는 요청 하나에 대해 최대 32회의 요청이 발생할 수 있다.
    • 체인 끝부분의 리소스에 과부하가 걸린 상태에서는 이 추가적인 부하가 해당 리소스를 감당할 수 없게 만들어 쓰러뜨릴수 있다.
    • 이 상황을해결하는 한 가지 방법은 아키텍처 가장자리에서는 재시도 횟수를 1회 내지 0회로 제한하고, 중간 요소는 0회로 하며, 호출 스택 깊숙한 곳에서만 재시도하게 하는 것이다. 하지만 이 방법도 잘 작동하지는 않는다.
    • 또 다른 전략은 전체 재시도 횟수에 상한을 두는 것이다.
    • 재시도 예산 budget 를 이용해 조절할 수 있는데, 이 기능은 아직 이스티오 API에서 노출되지 않고 있다.
    • 이스티오에 이런 문제에 대한 우회로가 있기는 하지만, 이 책의 범위를 벗어나는 내용이다.
    • 마지막으로 재시도는 기본적으로 자기 지역의 엔드포인트에 시도한다. retryRemoteLocalities 설정은 이 동작에 영향을 준다.
    • true로 설정하면, 이스티오는 재시도가 다른 지역으로 넘어갈 수 있도록 허용한다.
    • 이상값 감지가 같은 지역의 엔드포인트가 오작동하고 있음을 알아내기 전에 이 설정이 유용할 수 있다.
    6.4.3 Advanced retries : Istio Extension API (EnvoyFilter)
    • 앞 절에서는 서비스가 간헐적인 네트워크 실패에 복원력을 갖추는 데 이스티오의 자동 재시도가 어떻게 도움이 되는지 살펴봤다.
    • 재시도 동작을 조정할 수 있는 파라미터도 다뤘다.
    • 재시도 기능 일부는 백오프 시간이나 재시도할 수 있는 상태 코드처럼 바꾸기 어려운 기본값들을 고려한다.
    • 기본적으로 백오프 시간은 25ms이고, 재시도할 수 있는 상태 코드는 HTTP 503뿐이다.
    • 이 책을 저술하는 시점에 이스티오 API가 이 설정을 노출하고 있지는 않지만, 이스티오 확장 API를 사용해 엔보이 설정에서 이 값들을 직접 바꿀수 있다.
    • 이때에는 EnvoyFilter API를 사용한다.
    • 여기서는 엔보이 API를 직접 사용해 재시도 정책 설정값을 설정하고 재정의한다. 적용해보자.

    요청 헤징 REQUEST HEDGING

    • 재시도에 대한 마지막 이야기는 이스티오 API에서도 직접 노출하지 않는 고급 주제를 중심으로 한다.
    • 요청이 임계값에 도달해 시간을 초과하면 요청 헤징을 수행하도록 선택적으로 엔보이를 설정할 수 있다.
    • 요청 헤징 request hedging 이란, 요청이 타임아웃되면 다른 호스트로도 요청을 보내 원래의 타임아웃된 요청과 ‘경쟁 race’ 시키는 것을 말한다.
    • 경쟁한 요청이 성공적으로 반환되면, 그 응답을 원래 다운스트림 호출자에게 보낸다.
    • 만약 경쟁한 요청보다 원본 요청이 먼저 반환되면 원본 요청이 다운스트림 호출자에게 반환된다.
    • 요청 헤징을 설정하려면 다음 EnvoyFilter 리소스를 사용한다.
    • 이번 절에서 봤듯이, 타임아웃과 재시도에 대한 주제는 그리 간단하지 않다.
    • 서비스에 적절한 타임아웃 및 재시도 정책을 설정하는 것은 어려운 일이며, 둘이 어떻게 연결될 수 있을지를 고려하면 더욱 그렇다.
    • 타임아웃과 재시도를 잘못 설정하면 시스템 아키텍처에서 의도치 않은 동작을 증폭시켜 시스템을 과부하시키고 연쇄 장애를 일으킬 수도 있다.
    • 복원력 있는 아키텍처를 구축하는 과정에서 마지막 퍼즐 조작 재시도를 모두 건너뛰는 것이다.
    • 즉, 재시도하는 대신 빠르게 실패한다.
    • 부하를 더 늘리는 대신에 업스트림 시스템이 복구될 수 있도록(회복 시간 벌기) 부하를 잠시 제한할 수 있으며, 이를 위해 서킷 브레이커를 사용할 수 있다.

    6.5 Circuit breaking with Istio

    6.5.0. 들어가며 : 서킷 브레이커 소개
    • 서킷 브레이커 기능을 사용하면 부분적이거나 연쇄적인 장애를 방지할 수 있다.
    • 비정상 시스템을 계속 과부하시켜 회복을 방해하지 않도록 비정상 시스템으로 향하는 트래픽을 줄이고 싶다.
    • 예를 들어 simple-web 서비스가 simple-backend 서비스를 호출하고 simple-backend 는 연속된 호출에서 오류를 반환하면, 계속 재시도해 시스템에 스트레스를 더 주는 대신 simple-backend로의 호출을 모두 멈추고 싶을 수 있다.
    • 이 방식은 집의 전기 시스템에서 회로 차단기가 동작하는 방식과 의도가 비슷하다.
    • 시스템에 단락이 있거나 고장이 반복되면, 회로 차단기는 회로를 개방해 나머지 시스템을 보호하도록 설계된다.
    • 서킷 브레이커 패턴은 네트워크 호출이 실패할 수 있고 실제로 실패한다는 사실을 애플리케이션이 처리하게함으로써 전체 시스템을 연쇄 실패로부터 보호하는 데 도움이 된다.
    • 이스티오에 ‘서킷 브레이커’라는 명시적인 설정은 없지만, 백엔드 서비스, 특히 문제가 있는 서비스로의 부하를 제한할 수 있는 방법이 두 가지 있어 서킷 브레이커를 효과적으로 시행할 수 있다.
    • 첫 번째는 특정 서비스로의 커넥션미해결 요청 개수를 얼마나 허용할지 관리하는 것이다. The first is to manage how many connections and outstanding requests are allowed to a specific service.
    • 그림 6.15처럼 이 방법을 사용해 slow down 시켜서, 클라이언트를 적체시키는 서비스에 대비할 수 있다. We use this control to guard against services that slow down and thus back up the client, as illustrated in figure 6.15.
    • 어떤 서비스에 진행 중인 요청이 10개이고 수신 부하가 동일한데 그 수가 계속 증가하고 있다면, 요청을 더 보내는 것은 의미가 없다.
    • 요청을 더 보내면 업스트림 서비스가 압도 될 수 있다.
    • 이스티오에서는 DestinationRuleconnectionPool 설정을 사용해 서비스 호출 시 누적될 수 있는 커넥션 요청 개수를 제한할 수 있다.
    • 요청이 너무 많이 쌓이면, 요청을 단락(빠르게 실패)시키고 클라이언트에 반환할 수 있다. If too many requests pile up, we can short-circuit them (fail fast) and return to the client.
    • 두 번째 방법은 로드 밸런싱 풀의 엔드포인트에 상태를 관찰해 오동작하는 엔드포인트를 잠시 퇴출시키는 것이다.
    • 서비스 풀의 특정 호스트에 문제가 발생하면 그 호스트로의 트래픽 전송을 건너뛸 수 있다.
    • 모든 호스트를 소진하면 회로는 한동안 사실상 ‘개방’된다. If we exhaust all hosts, the circuit is effectively 'open' for a while.
    • 이스티오로 이 서킷 브레이커 제어 각각을 어떻게 구현하는지 살펴보자.
    6.5.1 Guarding against slow services wih connection-pool control* : 커넥션 풀 제어로 느린 서비스에 대응하기
    • (옵션) tracing 샘플링을 기본 1% → 100% 늘려두기
    • 실습 환경 구성
    • 이스티오의 커넥션 제한 서킷 브레이커 테스트 시작해볼 수 있다. 아주 간단한 로드 테스트를 실행해보자.
    • 커넥션 및 요청 제한을 도입하고 어떤 일이 일어나는지 살펴보자. 아주 간단한 제한으로 시작한다 - Docs
    • 테스트를 다시 실행해 이 설정을 검증하다. 커넥션 하나에 초당 요청을 하나 보낼 때 제대로 동작해야 한다.
    • 정확한 확인을 위해서 이스티오 서비스 프록시에서 더 많은 통계 수집을 활성화하자. 서킷 브레이크 영향인지 vs 업스트림의 장애인지 확인
    • (참고) 혹시 istio-proxy 상세 로그 확인 필요 시
    • Envoy 메트릭 - Docs
    • 커넥션 개수와 초당 요청 수를 2로 늘리면 어떨까? 2개의 커넥션에서 요청을 초당 하나씩 보내기 시작해보자.
    • 병렬로 발생하는 요청(현재 로드테스트 동시 요청 2)을 더 처리하고자 http2MaxRequests(parallel requests)를 늘려보자
    • 보류 대기열 깊이를 2로 늘리고 다시 실행해보자. Let’s increase the pending queue depth to 2 and re-run
    • 서킷 브레이커가 발동되면 통계를 보고 무슨 일이 일어났는지 확인 할 수 있다. 그런데 런타임은 어떤가?
    • 우리 예제에서는 simple-web → simple-backend 를 호출한다. 그런데 서킷 브레이커 때문에 요청이 실패한다면, simple-web 은 그 사실을 어떻게 알고 애플리케이션이나 네트워크 장애 문제와 구별할 수 있는가?
    • 요청 서킷 브레이커 임계값을 넘겨 실패하면, 이스티오 서비스 프록시는 x-envoy-overloaded 헤더를 추가한다.
    • 이를 테스트하는 한 가지 방법은 커넥션 제한을 가장 엄격한 수준으로 설정하고(커넥션, 보류 요청, 최대 요청을 1로 설정함 1 for connections, pending requests, and maximum requests) 로드 테스트를 다시 수행해보는 것이다.
    • 로드 테스트를 실행하는 도중에 단일 curl 명령도 실행하면 서킷 브레이커 때문에 실패할 가능성이 높다.
    • 일반적으로 네트워크가 실패할 수 있다는 점을 감안해 애플리케이션 코드를 작성해야 한다.
    • 애플리케이션 코드가 이 헤더를 확인하면 호출한 클라이언트에게 응답을 보내기 위해 대체 전략 fallback 을 사용하는 결정을 내릴 수 있다.
    6.5.2 Guarding against unhealthy services with outlier detection* : 이상값 감지로 비정상 서비스에 대응하기
    • 앞 절에서는 서비스에 예기치 못한 지연 시간이 있을 때 오동작하는 서비스로의 요청을 이스티오가 어떻게 제한할 수 있는지 살펴봤다.
    • 이번 절에서는 오동작 misbehaving 하는 특정 호스트를 서비스에서 제거하는 이스티오의 접근법을 다룬다.
    • 이스티오는 이를 위해 엔보이의 이상값 감지 기능을 사용한다. Istio uses Envoy’s outlier-detection functionality for this.
    • 실습 환경 초기화
    • 정기적으로 실패하는 서비스에 요청을 보내고 있는데 서비스의 다른 엔드포인트들은 실패하지 않고 있다면, 해당 엔드포인트가 과부하됐거나 어떤 이유로든 성능이 저하된 상태일 수 있으므로 당분간 그 엔드포인트로 트래픽을 전송하는 것을 멈춰야 한다.
    • 이상값 감지를 설정해보자 : 기존 오류율 대비 극적으로 감소 → 오동작하는 엔드포인트를 잠시 제거했기 때문이다. - Docs
    • 오류률을 더 개선해보자. 기본 재시도 설정을 추가해보자. VirtuslService 에 명시적으로 설정 가능. (현재 mesh 기본 재시도 0 상태)
    • 이번 장 이전에는 이스티오의 기능과 API를 사용해, 인그레스 게이트웨이를 사용한 예지에서부터 클러스터 내 통신에 이르기까지 네트워크의 동작을 바꾸는 방법을 살펴봤다.
    • 그러나 이번 장의 서두에서 언급했듯이, 끊임없이 변화하는 대규모 시스템에서 예상치 못한 네트워크 오류에 대응하기 위한 수동적인 개입은 불가능에 가까운 것이다.
    • 이번 장에서의 이스티오의 다양한 클라이언트 측 복원력 기능을 깊이 들여다봤다.
    • 이 기능들은 서비스가 간헐적인 네트워크 문제나 토폴로지 변화로부터 투명하게 복구될 수 있도록 돕는다.
    • 다음 장에서는 이런 기능을 더해 네트워크 동작을 관찰하는 방법을 살펴볼 것이다.
    6.6. Summary
    • 로드 밸런싱은 DestinationRule 리소스로 설정한다. 지원하는 알고리듬은 다음과 같다.
    • 이스티오는 노드의 영역 및 리전 정보를 엔드포인트 상태 정보(outlierDetection 이 설정돼 있어야 함)와 함께 활용해 트래픽을 동일 영역 내의 워크로드로 라우팅한다. (가능한 경우 그렇게 하고, 그렇지 않을 경우 다음 영역으로 넘긴다)
    • DestinationRule 를 사용하면 클라이언트가 여러 지역에 가중치를 부여해 트래픽을 분배하도록 설정할 수 있다.
    • 재시도와 타임아웃은 VirtualService 리소스에서 설정한다.
    • EnvoyFilter 리소스를 사용하면 이스티오 API가 노출하지 않은 엔보이의 기능을 구현할 수 있다. 요청 헤징으로 이를 보여줬다.
    • 서킷 브레이커는 DestinationRule 리소스에서 설정하는데, 이 기능은 트래픽을 더 전송하기 전에 업스트림 서비스가 회복할 시간을 벌어준다.
Designed by Tistory.